v2.0.0
This commit is contained in:
+66
-65
@@ -1,42 +1,40 @@
|
||||
<template lang="pug">
|
||||
#container
|
||||
core-aside#left
|
||||
#right
|
||||
core-toolbar#toolbar
|
||||
#view
|
||||
core-view#view-container
|
||||
core-play-bar#player
|
||||
core-icons
|
||||
core-version-modal
|
||||
core-pact-modal
|
||||
core-sync-mode-modal
|
||||
core-play-detail
|
||||
<template>
|
||||
<div id="container" class="view-container">
|
||||
<layout-aside id="left" />
|
||||
<div id="right">
|
||||
<layout-toolbar id="toolbar" />
|
||||
<layout-view id="view" />
|
||||
<layout-play-bar id="player" />
|
||||
</div>
|
||||
<layout-icons />
|
||||
<layout-version-modal />
|
||||
<layout-pact-modal />
|
||||
<layout-sync-mode-modal />
|
||||
<layout-play-detail />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { onMounted } from '@renderer/utils/vueTools'
|
||||
<script setup>
|
||||
import { onMounted } from '@common/utils/vueTools'
|
||||
// import BubbleCursor from '@common/utils/cursor-effects/bubbleCursor'
|
||||
import useApp from '@renderer/core/useApp'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const dom_root = document.getElementById('root')
|
||||
useApp()
|
||||
|
||||
useApp()
|
||||
onMounted(() => {
|
||||
document.getElementById('root').style.display = 'block'
|
||||
|
||||
// const styles = getComputedStyle(document.documentElement)
|
||||
// window.lxData.bubbleCursor = new BubbleCursor({
|
||||
// fillStyle: styles.getPropertyValue('--color-primary-alpha-900'),
|
||||
// strokeStyle: styles.getPropertyValue('--color-primary-alpha-700'),
|
||||
// })
|
||||
})
|
||||
|
||||
// onBeforeUnmount(() => {
|
||||
// window.lxData.bubbleCursor?.destroy()
|
||||
// })
|
||||
|
||||
onMounted(() => {
|
||||
// 隐藏等待界面
|
||||
/* inited.value = true
|
||||
const dom_mask = document.getElementById('waiting-mask')
|
||||
if (dom_mask) {
|
||||
dom_mask.addEventListener('transitionend', () => {
|
||||
dom_mask.parentNode.removeChild(dom_mask)
|
||||
})
|
||||
dom_mask.classList.add('hide')
|
||||
} */
|
||||
dom_root.style.display = 'block'
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -44,24 +42,28 @@ export default {
|
||||
@import './assets/styles/index.less';
|
||||
@import './assets/styles/layout.less';
|
||||
|
||||
html {
|
||||
height: 100vh;
|
||||
}
|
||||
html, body {
|
||||
overflow: hidden;
|
||||
// overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
user-select: none;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
}
|
||||
#root {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: @color-theme_2-font;
|
||||
background: @color-theme-bgimg @color-theme-bgposition no-repeat;
|
||||
background-size: @color-theme-bgsize;
|
||||
transition: background-color @transition-theme;
|
||||
background-color: @color-theme;
|
||||
color: var(--color-font);
|
||||
background: var(--background-image) var(--background-image-position) no-repeat;
|
||||
background-size: var(--background-image-size);
|
||||
transition: background-color @transition-normal;
|
||||
background-color: var(--color-content-background);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.disableAnimation * {
|
||||
@@ -70,6 +72,7 @@ body {
|
||||
}
|
||||
|
||||
.transparent {
|
||||
background: transparent;
|
||||
padding: @shadow-app;
|
||||
// #waiting-mask {
|
||||
// border-radius: @radius-border;
|
||||
@@ -78,18 +81,20 @@ body {
|
||||
// top: @shadow-app;
|
||||
// bottom: @shadow-app;
|
||||
// }
|
||||
#body {
|
||||
border-radius: @radius-border;
|
||||
}
|
||||
#root {
|
||||
box-shadow: 0 0 @shadow-app rgba(0, 0, 0, 0.5);
|
||||
border-radius: @radius-border;
|
||||
background-color: transparent;
|
||||
}
|
||||
#container {
|
||||
border-radius: @radius-border;
|
||||
background-color: transparent;
|
||||
}
|
||||
// #container {
|
||||
// border-radius: @radius-border;
|
||||
// background-color: transparent;
|
||||
// }
|
||||
}
|
||||
.disableTransparent {
|
||||
background-color: #fff;
|
||||
background-color: var(--color-content-background);
|
||||
|
||||
#right {
|
||||
border-top-left-radius: 0;
|
||||
@@ -101,7 +106,7 @@ body {
|
||||
// }
|
||||
}
|
||||
.fullscreen {
|
||||
background-color: #fff;
|
||||
background-color: var(--color-content-background);
|
||||
|
||||
#right {
|
||||
border-top-left-radius: 0;
|
||||
@@ -113,6 +118,7 @@ body {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background-color: var(--color-app-background);
|
||||
}
|
||||
|
||||
#left {
|
||||
@@ -123,8 +129,8 @@ body {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
transition: background-color @transition-theme;
|
||||
background-color: @color-theme_2;
|
||||
transition: background-color @transition-normal;
|
||||
background-color: var(--color-main-background);
|
||||
|
||||
border-top-left-radius: @radius-border;
|
||||
border-bottom-left-radius: @radius-border;
|
||||
@@ -137,24 +143,19 @@ body {
|
||||
#view {
|
||||
position: relative;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
// display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
#view-container {
|
||||
flex: auto;
|
||||
|
||||
.view-container {
|
||||
transition: opacity @transition-normal;
|
||||
}
|
||||
#root.show-modal > .view-container {
|
||||
opacity: .9;
|
||||
}
|
||||
#view.show-modal > .view-container {
|
||||
opacity: .2;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
#root.@{value} {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
background-color: ~'@{color-@{value}-theme}';
|
||||
background-image: ~'@{color-@{value}-theme-bgimg}';
|
||||
background-size: ~'@{color-@{value}-theme-bgsize}';
|
||||
background-position: ~'@{color-@{value}-theme-bgposition}';
|
||||
#right {
|
||||
background-color: ~'@{color-@{value}-theme_2}';
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 353 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 997 KiB |
@@ -6,6 +6,280 @@
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-primary: rgb(77, 175, 124);
|
||||
--color-primary-alpha-100: rgba(77, 175, 124, 0.90);
|
||||
--color-primary-alpha-200: rgba(77, 175, 124, 0.80);
|
||||
--color-primary-alpha-300: rgba(77, 175, 124, 0.70);
|
||||
--color-primary-alpha-400: rgba(77, 175, 124, 0.60);
|
||||
--color-primary-alpha-500: rgba(77, 175, 124, 0.50);
|
||||
--color-primary-alpha-600: rgba(77, 175, 124, 0.40);
|
||||
--color-primary-alpha-700: rgba(77, 175, 124, 0.30);
|
||||
--color-primary-alpha-800: rgba(77, 175, 124, 0.20);
|
||||
--color-primary-alpha-900: rgba(77, 175, 124, 0.10);
|
||||
--color-primary-dark-100: rgb(69,158,112);
|
||||
--color-primary-dark-100-alpha-100: rgba(69, 158, 112, 0.90);
|
||||
--color-primary-dark-100-alpha-200: rgba(69, 158, 112, 0.80);
|
||||
--color-primary-dark-100-alpha-300: rgba(69, 158, 112, 0.70);
|
||||
--color-primary-dark-100-alpha-400: rgba(69, 158, 112, 0.60);
|
||||
--color-primary-dark-100-alpha-500: rgba(69, 158, 112, 0.50);
|
||||
--color-primary-dark-100-alpha-600: rgba(69, 158, 112, 0.40);
|
||||
--color-primary-dark-100-alpha-700: rgba(69, 158, 112, 0.30);
|
||||
--color-primary-dark-100-alpha-800: rgba(69, 158, 112, 0.20);
|
||||
--color-primary-dark-100-alpha-900: rgba(69, 158, 112, 0.10);
|
||||
--color-primary-dark-200: rgb(62,142,101);
|
||||
--color-primary-dark-200-alpha-100: rgba(62, 142, 101, 0.90);
|
||||
--color-primary-dark-200-alpha-200: rgba(62, 142, 101, 0.80);
|
||||
--color-primary-dark-200-alpha-300: rgba(62, 142, 101, 0.70);
|
||||
--color-primary-dark-200-alpha-400: rgba(62, 142, 101, 0.60);
|
||||
--color-primary-dark-200-alpha-500: rgba(62, 142, 101, 0.50);
|
||||
--color-primary-dark-200-alpha-600: rgba(62, 142, 101, 0.40);
|
||||
--color-primary-dark-200-alpha-700: rgba(62, 142, 101, 0.30);
|
||||
--color-primary-dark-200-alpha-800: rgba(62, 142, 101, 0.20);
|
||||
--color-primary-dark-200-alpha-900: rgba(62, 142, 101, 0.10);
|
||||
--color-primary-dark-300: rgb(56,128,91);
|
||||
--color-primary-dark-300-alpha-100: rgba(56, 128, 91, 0.90);
|
||||
--color-primary-dark-300-alpha-200: rgba(56, 128, 91, 0.80);
|
||||
--color-primary-dark-300-alpha-300: rgba(56, 128, 91, 0.70);
|
||||
--color-primary-dark-300-alpha-400: rgba(56, 128, 91, 0.60);
|
||||
--color-primary-dark-300-alpha-500: rgba(56, 128, 91, 0.50);
|
||||
--color-primary-dark-300-alpha-600: rgba(56, 128, 91, 0.40);
|
||||
--color-primary-dark-300-alpha-700: rgba(56, 128, 91, 0.30);
|
||||
--color-primary-dark-300-alpha-800: rgba(56, 128, 91, 0.20);
|
||||
--color-primary-dark-300-alpha-900: rgba(56, 128, 91, 0.10);
|
||||
--color-primary-dark-400: rgb(50,115,82);
|
||||
--color-primary-dark-400-alpha-100: rgba(50, 115, 82, 0.90);
|
||||
--color-primary-dark-400-alpha-200: rgba(50, 115, 82, 0.80);
|
||||
--color-primary-dark-400-alpha-300: rgba(50, 115, 82, 0.70);
|
||||
--color-primary-dark-400-alpha-400: rgba(50, 115, 82, 0.60);
|
||||
--color-primary-dark-400-alpha-500: rgba(50, 115, 82, 0.50);
|
||||
--color-primary-dark-400-alpha-600: rgba(50, 115, 82, 0.40);
|
||||
--color-primary-dark-400-alpha-700: rgba(50, 115, 82, 0.30);
|
||||
--color-primary-dark-400-alpha-800: rgba(50, 115, 82, 0.20);
|
||||
--color-primary-dark-400-alpha-900: rgba(50, 115, 82, 0.10);
|
||||
--color-primary-dark-500: rgb(45,104,74);
|
||||
--color-primary-dark-500-alpha-100: rgba(45, 104, 74, 0.90);
|
||||
--color-primary-dark-500-alpha-200: rgba(45, 104, 74, 0.80);
|
||||
--color-primary-dark-500-alpha-300: rgba(45, 104, 74, 0.70);
|
||||
--color-primary-dark-500-alpha-400: rgba(45, 104, 74, 0.60);
|
||||
--color-primary-dark-500-alpha-500: rgba(45, 104, 74, 0.50);
|
||||
--color-primary-dark-500-alpha-600: rgba(45, 104, 74, 0.40);
|
||||
--color-primary-dark-500-alpha-700: rgba(45, 104, 74, 0.30);
|
||||
--color-primary-dark-500-alpha-800: rgba(45, 104, 74, 0.20);
|
||||
--color-primary-dark-500-alpha-900: rgba(45, 104, 74, 0.10);
|
||||
--color-primary-dark-600: rgb(41,94,67);
|
||||
--color-primary-dark-600-alpha-100: rgba(41, 94, 67, 0.90);
|
||||
--color-primary-dark-600-alpha-200: rgba(41, 94, 67, 0.80);
|
||||
--color-primary-dark-600-alpha-300: rgba(41, 94, 67, 0.70);
|
||||
--color-primary-dark-600-alpha-400: rgba(41, 94, 67, 0.60);
|
||||
--color-primary-dark-600-alpha-500: rgba(41, 94, 67, 0.50);
|
||||
--color-primary-dark-600-alpha-600: rgba(41, 94, 67, 0.40);
|
||||
--color-primary-dark-600-alpha-700: rgba(41, 94, 67, 0.30);
|
||||
--color-primary-dark-600-alpha-800: rgba(41, 94, 67, 0.20);
|
||||
--color-primary-dark-600-alpha-900: rgba(41, 94, 67, 0.10);
|
||||
--color-primary-dark-700: rgb(37,85,60);
|
||||
--color-primary-dark-700-alpha-100: rgba(37, 85, 60, 0.90);
|
||||
--color-primary-dark-700-alpha-200: rgba(37, 85, 60, 0.80);
|
||||
--color-primary-dark-700-alpha-300: rgba(37, 85, 60, 0.70);
|
||||
--color-primary-dark-700-alpha-400: rgba(37, 85, 60, 0.60);
|
||||
--color-primary-dark-700-alpha-500: rgba(37, 85, 60, 0.50);
|
||||
--color-primary-dark-700-alpha-600: rgba(37, 85, 60, 0.40);
|
||||
--color-primary-dark-700-alpha-700: rgba(37, 85, 60, 0.30);
|
||||
--color-primary-dark-700-alpha-800: rgba(37, 85, 60, 0.20);
|
||||
--color-primary-dark-700-alpha-900: rgba(37, 85, 60, 0.10);
|
||||
--color-primary-dark-800: rgb(33,77,54);
|
||||
--color-primary-dark-800-alpha-100: rgba(33, 77, 54, 0.90);
|
||||
--color-primary-dark-800-alpha-200: rgba(33, 77, 54, 0.80);
|
||||
--color-primary-dark-800-alpha-300: rgba(33, 77, 54, 0.70);
|
||||
--color-primary-dark-800-alpha-400: rgba(33, 77, 54, 0.60);
|
||||
--color-primary-dark-800-alpha-500: rgba(33, 77, 54, 0.50);
|
||||
--color-primary-dark-800-alpha-600: rgba(33, 77, 54, 0.40);
|
||||
--color-primary-dark-800-alpha-700: rgba(33, 77, 54, 0.30);
|
||||
--color-primary-dark-800-alpha-800: rgba(33, 77, 54, 0.20);
|
||||
--color-primary-dark-800-alpha-900: rgba(33, 77, 54, 0.10);
|
||||
--color-primary-dark-900: rgb(30,69,49);
|
||||
--color-primary-dark-900-alpha-100: rgba(30, 69, 49, 0.90);
|
||||
--color-primary-dark-900-alpha-200: rgba(30, 69, 49, 0.80);
|
||||
--color-primary-dark-900-alpha-300: rgba(30, 69, 49, 0.70);
|
||||
--color-primary-dark-900-alpha-400: rgba(30, 69, 49, 0.60);
|
||||
--color-primary-dark-900-alpha-500: rgba(30, 69, 49, 0.50);
|
||||
--color-primary-dark-900-alpha-600: rgba(30, 69, 49, 0.40);
|
||||
--color-primary-dark-900-alpha-700: rgba(30, 69, 49, 0.30);
|
||||
--color-primary-dark-900-alpha-800: rgba(30, 69, 49, 0.20);
|
||||
--color-primary-dark-900-alpha-900: rgba(30, 69, 49, 0.10);
|
||||
--color-primary-dark-1000: rgb(27,62,44);
|
||||
--color-primary-dark-1000-alpha-100: rgba(27, 62, 44, 0.90);
|
||||
--color-primary-dark-1000-alpha-200: rgba(27, 62, 44, 0.80);
|
||||
--color-primary-dark-1000-alpha-300: rgba(27, 62, 44, 0.70);
|
||||
--color-primary-dark-1000-alpha-400: rgba(27, 62, 44, 0.60);
|
||||
--color-primary-dark-1000-alpha-500: rgba(27, 62, 44, 0.50);
|
||||
--color-primary-dark-1000-alpha-600: rgba(27, 62, 44, 0.40);
|
||||
--color-primary-dark-1000-alpha-700: rgba(27, 62, 44, 0.30);
|
||||
--color-primary-dark-1000-alpha-800: rgba(27, 62, 44, 0.20);
|
||||
--color-primary-dark-1000-alpha-900: rgba(27, 62, 44, 0.10);
|
||||
--color-primary-light-100: rgb(113,191,150);
|
||||
--color-primary-light-100-alpha-100: rgba(113, 191, 150, 0.90);
|
||||
--color-primary-light-100-alpha-200: rgba(113, 191, 150, 0.80);
|
||||
--color-primary-light-100-alpha-300: rgba(113, 191, 150, 0.70);
|
||||
--color-primary-light-100-alpha-400: rgba(113, 191, 150, 0.60);
|
||||
--color-primary-light-100-alpha-500: rgba(113, 191, 150, 0.50);
|
||||
--color-primary-light-100-alpha-600: rgba(113, 191, 150, 0.40);
|
||||
--color-primary-light-100-alpha-700: rgba(113, 191, 150, 0.30);
|
||||
--color-primary-light-100-alpha-800: rgba(113, 191, 150, 0.20);
|
||||
--color-primary-light-100-alpha-900: rgba(113, 191, 150, 0.10);
|
||||
--color-primary-light-200: rgb(141,204,171);
|
||||
--color-primary-light-200-alpha-100: rgba(141, 204, 171, 0.90);
|
||||
--color-primary-light-200-alpha-200: rgba(141, 204, 171, 0.80);
|
||||
--color-primary-light-200-alpha-300: rgba(141, 204, 171, 0.70);
|
||||
--color-primary-light-200-alpha-400: rgba(141, 204, 171, 0.60);
|
||||
--color-primary-light-200-alpha-500: rgba(141, 204, 171, 0.50);
|
||||
--color-primary-light-200-alpha-600: rgba(141, 204, 171, 0.40);
|
||||
--color-primary-light-200-alpha-700: rgba(141, 204, 171, 0.30);
|
||||
--color-primary-light-200-alpha-800: rgba(141, 204, 171, 0.20);
|
||||
--color-primary-light-200-alpha-900: rgba(141, 204, 171, 0.10);
|
||||
--color-primary-light-300: rgb(164,214,188);
|
||||
--color-primary-light-300-alpha-100: rgba(164, 214, 188, 0.90);
|
||||
--color-primary-light-300-alpha-200: rgba(164, 214, 188, 0.80);
|
||||
--color-primary-light-300-alpha-300: rgba(164, 214, 188, 0.70);
|
||||
--color-primary-light-300-alpha-400: rgba(164, 214, 188, 0.60);
|
||||
--color-primary-light-300-alpha-500: rgba(164, 214, 188, 0.50);
|
||||
--color-primary-light-300-alpha-600: rgba(164, 214, 188, 0.40);
|
||||
--color-primary-light-300-alpha-700: rgba(164, 214, 188, 0.30);
|
||||
--color-primary-light-300-alpha-800: rgba(164, 214, 188, 0.20);
|
||||
--color-primary-light-300-alpha-900: rgba(164, 214, 188, 0.10);
|
||||
--color-primary-light-400: rgb(182,222,201);
|
||||
--color-primary-light-400-alpha-100: rgba(182, 222, 201, 0.90);
|
||||
--color-primary-light-400-alpha-200: rgba(182, 222, 201, 0.80);
|
||||
--color-primary-light-400-alpha-300: rgba(182, 222, 201, 0.70);
|
||||
--color-primary-light-400-alpha-400: rgba(182, 222, 201, 0.60);
|
||||
--color-primary-light-400-alpha-500: rgba(182, 222, 201, 0.50);
|
||||
--color-primary-light-400-alpha-600: rgba(182, 222, 201, 0.40);
|
||||
--color-primary-light-400-alpha-700: rgba(182, 222, 201, 0.30);
|
||||
--color-primary-light-400-alpha-800: rgba(182, 222, 201, 0.20);
|
||||
--color-primary-light-400-alpha-900: rgba(182, 222, 201, 0.10);
|
||||
--color-primary-light-500: rgb(197,229,212);
|
||||
--color-primary-light-500-alpha-100: rgba(197, 229, 212, 0.90);
|
||||
--color-primary-light-500-alpha-200: rgba(197, 229, 212, 0.80);
|
||||
--color-primary-light-500-alpha-300: rgba(197, 229, 212, 0.70);
|
||||
--color-primary-light-500-alpha-400: rgba(197, 229, 212, 0.60);
|
||||
--color-primary-light-500-alpha-500: rgba(197, 229, 212, 0.50);
|
||||
--color-primary-light-500-alpha-600: rgba(197, 229, 212, 0.40);
|
||||
--color-primary-light-500-alpha-700: rgba(197, 229, 212, 0.30);
|
||||
--color-primary-light-500-alpha-800: rgba(197, 229, 212, 0.20);
|
||||
--color-primary-light-500-alpha-900: rgba(197, 229, 212, 0.10);
|
||||
--color-primary-light-600: rgb(209,234,221);
|
||||
--color-primary-light-600-alpha-100: rgba(209, 234, 221, 0.90);
|
||||
--color-primary-light-600-alpha-200: rgba(209, 234, 221, 0.80);
|
||||
--color-primary-light-600-alpha-300: rgba(209, 234, 221, 0.70);
|
||||
--color-primary-light-600-alpha-400: rgba(209, 234, 221, 0.60);
|
||||
--color-primary-light-600-alpha-500: rgba(209, 234, 221, 0.50);
|
||||
--color-primary-light-600-alpha-600: rgba(209, 234, 221, 0.40);
|
||||
--color-primary-light-600-alpha-700: rgba(209, 234, 221, 0.30);
|
||||
--color-primary-light-600-alpha-800: rgba(209, 234, 221, 0.20);
|
||||
--color-primary-light-600-alpha-900: rgba(209, 234, 221, 0.10);
|
||||
--color-primary-light-700: rgb(218,238,228);
|
||||
--color-primary-light-700-alpha-100: rgba(218, 238, 228, 0.90);
|
||||
--color-primary-light-700-alpha-200: rgba(218, 238, 228, 0.80);
|
||||
--color-primary-light-700-alpha-300: rgba(218, 238, 228, 0.70);
|
||||
--color-primary-light-700-alpha-400: rgba(218, 238, 228, 0.60);
|
||||
--color-primary-light-700-alpha-500: rgba(218, 238, 228, 0.50);
|
||||
--color-primary-light-700-alpha-600: rgba(218, 238, 228, 0.40);
|
||||
--color-primary-light-700-alpha-700: rgba(218, 238, 228, 0.30);
|
||||
--color-primary-light-700-alpha-800: rgba(218, 238, 228, 0.20);
|
||||
--color-primary-light-700-alpha-900: rgba(218, 238, 228, 0.10);
|
||||
--color-primary-light-800: rgb(225,241,233);
|
||||
--color-primary-light-800-alpha-100: rgba(225, 241, 233, 0.90);
|
||||
--color-primary-light-800-alpha-200: rgba(225, 241, 233, 0.80);
|
||||
--color-primary-light-800-alpha-300: rgba(225, 241, 233, 0.70);
|
||||
--color-primary-light-800-alpha-400: rgba(225, 241, 233, 0.60);
|
||||
--color-primary-light-800-alpha-500: rgba(225, 241, 233, 0.50);
|
||||
--color-primary-light-800-alpha-600: rgba(225, 241, 233, 0.40);
|
||||
--color-primary-light-800-alpha-700: rgba(225, 241, 233, 0.30);
|
||||
--color-primary-light-800-alpha-800: rgba(225, 241, 233, 0.20);
|
||||
--color-primary-light-800-alpha-900: rgba(225, 241, 233, 0.10);
|
||||
--color-primary-light-900: rgb(231,244,237);
|
||||
--color-primary-light-900-alpha-100: rgba(231, 244, 237, 0.90);
|
||||
--color-primary-light-900-alpha-200: rgba(231, 244, 237, 0.80);
|
||||
--color-primary-light-900-alpha-300: rgba(231, 244, 237, 0.70);
|
||||
--color-primary-light-900-alpha-400: rgba(231, 244, 237, 0.60);
|
||||
--color-primary-light-900-alpha-500: rgba(231, 244, 237, 0.50);
|
||||
--color-primary-light-900-alpha-600: rgba(231, 244, 237, 0.40);
|
||||
--color-primary-light-900-alpha-700: rgba(231, 244, 237, 0.30);
|
||||
--color-primary-light-900-alpha-800: rgba(231, 244, 237, 0.20);
|
||||
--color-primary-light-900-alpha-900: rgba(231, 244, 237, 0.10);
|
||||
--color-primary-light-1000: rgb(255,255,255);
|
||||
--color-primary-light-1000-alpha-100: rgba(255, 255, 255, 0.90);
|
||||
--color-primary-light-1000-alpha-200: rgba(255, 255, 255, 0.80);
|
||||
--color-primary-light-1000-alpha-300: rgba(255, 255, 255, 0.70);
|
||||
--color-primary-light-1000-alpha-400: rgba(255, 255, 255, 0.60);
|
||||
--color-primary-light-1000-alpha-500: rgba(255, 255, 255, 0.50);
|
||||
--color-primary-light-1000-alpha-600: rgba(255, 255, 255, 0.40);
|
||||
--color-primary-light-1000-alpha-700: rgba(255, 255, 255, 0.30);
|
||||
--color-primary-light-1000-alpha-800: rgba(255, 255, 255, 0.20);
|
||||
--color-primary-light-1000-alpha-900: rgba(255, 255, 255, 0.10);
|
||||
--color-theme: rgb(77, 175, 124);
|
||||
// --color-scrollbar-track:
|
||||
|
||||
// --color-900: #fff;
|
||||
// --color-800: #fafafa;
|
||||
// --color-700: #f5f5f5;
|
||||
// --color-600: #eeeeee;
|
||||
// --color-500: #e0e0e0;
|
||||
// --color-400: #bdbdbd;
|
||||
// --color-300: #9e9e9e;
|
||||
// --color-200: #757575;
|
||||
// --color-100: #616161;
|
||||
// --color-050: #424242;
|
||||
// --color-000: #212121;
|
||||
--color-000: #fff;
|
||||
--color-050: #fafafa;
|
||||
--color-100: #f5f5f5;
|
||||
--color-200: #eeeeee;
|
||||
--color-300: #e0e0e0;
|
||||
--color-400: #bdbdbd;
|
||||
--color-500: #9e9e9e;
|
||||
--color-600: #757575;
|
||||
--color-700: #616161;
|
||||
--color-800: #424242;
|
||||
--color-900: #212121;
|
||||
|
||||
--color-app-background: var(--color-primary-light-600-alpha-600);
|
||||
--color-main-background: rgba(255, 255, 255, 0.9);
|
||||
--color-nav-font: var(--color-primary);
|
||||
// --color-app-background: rgba(0, 0, 0, .5);
|
||||
// --color-main-background: rgba(0, 0, 0, 0.26);
|
||||
--color-btn-hide: #3bc2b2;
|
||||
--color-btn-min: #85c43b;
|
||||
// --color-btn-max: #e7aa36;
|
||||
--color-btn-close: #fab4a0;
|
||||
|
||||
--color-badge-primary: var(--color-primary);
|
||||
--color-badge-secondary: #4baed5;
|
||||
--color-badge-tertiary: #e7aa36;
|
||||
|
||||
--color-font: var(--color-700);
|
||||
--color-font-label: var(--color-200);
|
||||
--color-primary-font: var(--color-primary);
|
||||
--color-primary-font-hover: var(--color-primary-alpha-300);
|
||||
--color-primary-font-active: var(--color-primary-dark-100-alpha-200);
|
||||
--color-primary-background: var(--color-primary-light-400-alpha-700);
|
||||
--color-primary-background-hover: var(--color-primary-light-300-alpha-800);
|
||||
--color-primary-background-active: var(--color-primary-light-100-alpha-800);
|
||||
--color-button-font: var(--color-primary-alpha-100);
|
||||
--color-button-font-selected: var(--color-primary-dark-100-alpha-100);
|
||||
--color-button-background: var(--color-primary-light-400-alpha-700);
|
||||
--color-button-background-selected: var(--color-primary-alpha-600);
|
||||
--color-button-background-hover: var(--color-primary-light-300-alpha-600);
|
||||
--color-button-background-active: var(--color-primary-light-100-alpha-600);
|
||||
--color-list-header-border-bottom: 1px solid var(--color-primary-alpha-900);
|
||||
--color-content-background: var(--color-primary-light-1000);
|
||||
|
||||
|
||||
--background-image: none;
|
||||
--background-image-position: center;
|
||||
--background-image-size: cover;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
@@ -33,90 +307,6 @@ html {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.thead {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
overflow: hidden;
|
||||
color: @color-theme_2-font;
|
||||
th {
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
line-height: 38px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
tbody {
|
||||
tr {
|
||||
// border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||
transition: background-color 0.2s ease;
|
||||
+ tr {
|
||||
border-top: 1px solid @color-theme_2-line;
|
||||
}
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&.active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
&.selected {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
td {
|
||||
padding: 6px;
|
||||
position: relative;
|
||||
transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: @color-theme_2-font;
|
||||
.list-item {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
// border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||
transition: 0.2s ease;
|
||||
transition-property: background-color, color;
|
||||
border-bottom: 1px solid @color-theme_2-line;
|
||||
box-sizing: border-box;
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&.active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
&.selected {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
.list-item-cell {
|
||||
flex: none;
|
||||
padding: 0 6px;
|
||||
position: relative;
|
||||
// transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
vertical-align: middle;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
&.auto {
|
||||
flex: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.25em 0.4em;
|
||||
@@ -125,39 +315,41 @@ table {
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: 2px;
|
||||
// vertical-align: baseline;
|
||||
vertical-align: text-top;
|
||||
// border-radius: 2px;
|
||||
|
||||
&.badge-light {
|
||||
background-color: #f8f9fa;
|
||||
// &.badge-light {
|
||||
// background-color: #f8f9fa;
|
||||
// }
|
||||
// &.badge-secondary {
|
||||
// color: #fff;
|
||||
// background-color: #6c757d;
|
||||
// }
|
||||
// &.badge-info {
|
||||
// color: #fff;
|
||||
// background-color: #4baed5;
|
||||
// }
|
||||
// &.badge-warning {
|
||||
// color: #fff;
|
||||
// background-color: #ffa45a;
|
||||
// }
|
||||
// &.badge-danger {
|
||||
// color: #fff;
|
||||
// background-color: #ff705a;
|
||||
// }
|
||||
// &.badge-success {
|
||||
// color: #fff;
|
||||
// background-color: #32bc63;
|
||||
// }
|
||||
&.badge-theme-primary {
|
||||
color: var(--color-badge-primary);
|
||||
}
|
||||
&.badge-secondary {
|
||||
color: #fff;
|
||||
background-color: #6c757d;
|
||||
&.badge-theme-secondary {
|
||||
color: var(--color-badge-secondary);
|
||||
}
|
||||
&.badge-info {
|
||||
color: #fff;
|
||||
background-color: #4baed5;
|
||||
}
|
||||
&.badge-warning {
|
||||
color: #fff;
|
||||
background-color: #ffa45a;
|
||||
}
|
||||
&.badge-danger {
|
||||
color: #fff;
|
||||
background-color: #ff705a;
|
||||
}
|
||||
&.badge-success {
|
||||
color: #fff;
|
||||
background-color: #32bc63;
|
||||
}
|
||||
&.badge-theme-success {
|
||||
color: @color-badge-success-font;
|
||||
background-color: @color-badge-success;
|
||||
}
|
||||
&.badge-theme-info {
|
||||
color: @color-badge-info-font;
|
||||
background-color: @color-badge-info;
|
||||
&.badge-theme-tertiary {
|
||||
color: var(--color-badge-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +360,7 @@ small {
|
||||
font-size: .9em;
|
||||
}
|
||||
.tip {
|
||||
color: @color-theme_2-font-label;
|
||||
color: var(--color-label);
|
||||
}
|
||||
strong {
|
||||
font-weight: bold;
|
||||
@@ -179,32 +371,32 @@ strong {
|
||||
}
|
||||
|
||||
svg {
|
||||
transition: @transition-theme;
|
||||
transition: @transition-normal;
|
||||
transition-property: fill;
|
||||
}
|
||||
|
||||
button, input, textarea, a {
|
||||
color: @color-theme_2-font;
|
||||
color: var(--color-font);
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
&::placeholder {
|
||||
color: @color-theme_2-font-label;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
}
|
||||
::selection {
|
||||
color: @color-theme_2;
|
||||
background: @color-btn;
|
||||
color: var(--color-primary-dark-500-alpha-200);
|
||||
background: var(--color-primary-light-100-alpha-500);
|
||||
}
|
||||
|
||||
.hover, a {
|
||||
cursor: pointer;
|
||||
transition: color .2s ease;
|
||||
&:hover {
|
||||
color: @color-theme;
|
||||
color: var(--color-primary-font-hover);
|
||||
}
|
||||
&:active {
|
||||
color: @color-theme-active;
|
||||
color: var(--color-primary-font-active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,24 +408,150 @@ input, textarea {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: @color-scrollbar-track;
|
||||
background-color: var(--color-primary-light-100-alpha-800);
|
||||
border-radius: 3px;
|
||||
// background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 3px;
|
||||
background-color: @color-scrollbar-thumb;
|
||||
background-color: var(--color-primary-alpha-600);
|
||||
// background-color: rgba(0, 0, 0, 0.2);
|
||||
transition: background-color 0.4s ease;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
border-radius: 3px;
|
||||
background-color: @color-scrollbar-thumb-hover;
|
||||
background-color: var(--color-primary-alpha-400);
|
||||
// background-color: rgba(0, 0, 0, 0.4);
|
||||
transition: background-color 0.4s ease;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.thead {
|
||||
border-bottom: var(--color-list-header-border-bottom);
|
||||
padding-right: 6px;
|
||||
flex: none;
|
||||
|
||||
.num {
|
||||
.nobreak;
|
||||
.center;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
// box-shadow: 0 0 2px var(--color-primary-dark-500-alpha-800);
|
||||
// position: relative;
|
||||
// z-index: 2;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
overflow: hidden;
|
||||
color: var(--color-font);
|
||||
th {
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
line-height: 38px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.list {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: var(--color-font);
|
||||
flex: auto;
|
||||
|
||||
|
||||
.list-item {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
// border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||
transition: 0.2s ease;
|
||||
transition-property: background-color, color;
|
||||
// border-bottom: 1px solid @color-theme_2-line;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-background-hover);
|
||||
|
||||
// .list-item-cell-action {
|
||||
// display: block;
|
||||
// }
|
||||
}
|
||||
&.active {
|
||||
background-color: var(--color-primary-background-active);
|
||||
}
|
||||
&.selected {
|
||||
background-color: var(--color-primary-background-hover);
|
||||
}
|
||||
&.disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
.list-item-cell {
|
||||
flex: none;
|
||||
padding: 0 6px;
|
||||
position: relative;
|
||||
// transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
line-height: 16px;
|
||||
vertical-align: middle;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
&.auto {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
&.num {
|
||||
.nobreak;
|
||||
.center;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
font-size: 11px;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
|
||||
&.name {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
overflow: hidden;
|
||||
white-space: initial;
|
||||
text-overflow: initial;
|
||||
align-items: center;
|
||||
|
||||
>.name {
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
}
|
||||
.badge {
|
||||
margin-left: 3px;
|
||||
opacity: .85;
|
||||
}
|
||||
}
|
||||
|
||||
// .list-item-cell-action {
|
||||
// white-space: nowrap;
|
||||
// display: none;
|
||||
// flex: auto;
|
||||
// text-align: right;
|
||||
// // position: absolute;
|
||||
// // right: 5px;
|
||||
// // top: -2px;
|
||||
// // opacity: 0;
|
||||
// // transition: opacity .1s ease;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
.copying {
|
||||
.no-select {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.gap-left {
|
||||
+ .gap-left {
|
||||
margin-left: 20px;
|
||||
@@ -249,132 +567,6 @@ input, textarea {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#waiting-mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: #fff;
|
||||
z-index: 999999;
|
||||
transition: all .8s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
-webkit-app-region: drag;
|
||||
background-color: @color-theme;
|
||||
|
||||
&.hide {
|
||||
opacity: 0;
|
||||
}
|
||||
.color-picker {
|
||||
border-radius: @radius-border !important;
|
||||
}
|
||||
#logo-path-1 {fill: #5ed698; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
|
||||
#logo-path-2 {fill: #4daf7c; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
body.@{value} #waiting-mask {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
|
||||
#logo-path-1 {fill: ~'@{color-@{value}-btn}'; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
|
||||
#logo-path-2 {fill: ~'@{color-@{value}-theme}'; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
|
||||
}
|
||||
}) */
|
||||
// each(@themes, {
|
||||
// body.@{value} #waiting-mask {
|
||||
// background-color: ~'@{color-@{value}-theme}';
|
||||
|
||||
// #logo-path-1 {fill: ~'@{color-@{value}-theme_2-background_1}'; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
|
||||
// #logo-path-2 {fill: ~'@{color-@{value}-theme-font-label}'; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
|
||||
// }
|
||||
// })
|
||||
|
||||
each(@themes, {
|
||||
#root.@{value} {
|
||||
button, input, textarea, a {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.tip {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
|
||||
.hover, a {
|
||||
&:hover {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
&:active {
|
||||
color: ~'@{color-@{value}-theme-active}';
|
||||
}
|
||||
}
|
||||
.scroll {
|
||||
&::-webkit-scrollbar {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: ~'@{color-@{value}-scrollbar-track}';
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: ~'@{color-@{value}-scrollbar-thumb}';
|
||||
}
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: ~'@{color-@{value}-scrollbar-thumb-hover}';
|
||||
}
|
||||
}
|
||||
table {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
border-top-color: ~'@{color-@{value}-theme_2-line}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&.active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
&.selected {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
.list-item {
|
||||
border-bottom-color: ~'@{color-@{value}-theme_2-line}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&.active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
&.selected {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
&::placeholder {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
::selection {
|
||||
color: ~'@{color-@{value}-theme_2}';
|
||||
background: ~'@{color-@{value}-btn}';
|
||||
}
|
||||
|
||||
.badge {
|
||||
&.badge-theme-success {
|
||||
color: ~'@{color-@{value}-badge-success-font}';
|
||||
background-color: ~'@{color-@{value}-badge-success}';
|
||||
}
|
||||
&.badge-theme-info {
|
||||
color: ~'@{color-@{value}-badge-info-font}';
|
||||
background-color: ~'@{color-@{value}-badge-info}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.mixin-ellipsis() {
|
||||
.mixin-ellipsis(@n: 1) {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
white-space: normal !important;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-line-clamp: @n;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.mixin-ellipsis-2() {
|
||||
@@ -28,3 +28,8 @@
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.mixin-after() {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: '';
|
||||
}
|
||||
|
||||
@@ -16,16 +16,19 @@ time, mark, audio, video {
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
font-size: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
html {
|
||||
font-family: "Microsoft YaHei", "Microsoft Jhenghei", "PingFang SC", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Helvetica, Arial, "Hiragino Sans GB", "Source Han Sans", "Noto Sans CJK Sc", sans-serif;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
// html {
|
||||
// }
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@@ -1,985 +1,12 @@
|
||||
@import './colors.less';
|
||||
|
||||
@themes: green, yellow, blue, red, pink, purple, orange, grey, ming, blue2, black, mid_autumn, naruto, happy_new_year;
|
||||
|
||||
|
||||
// Colors
|
||||
// @color-theme: #03a678;
|
||||
@color-theme: #4daf7c;
|
||||
@color-theme-bgimg: none;
|
||||
@color-theme-bgposition: center;
|
||||
@color-theme-bgsize: cover;
|
||||
@color-theme-hover: fadeout(lighten(@color-theme, 10%), 30%);
|
||||
@color-theme-active: fadeout(darken(@color-theme, 20%), 60%);
|
||||
@color-theme-font: #fff;
|
||||
@color-theme-font-label: lighten(@color-theme, 35%);
|
||||
|
||||
@color-theme_2: rgba(255, 255, 255, .9);
|
||||
@color-theme_2-background_1: #fff;
|
||||
@color-theme_2-background_2: fadeout(@color-theme_2-background_1, 2%);
|
||||
@color-theme_2-hover: fadeout(lighten(@color-theme, 10%), 80%);
|
||||
@color-theme_2-active: fadeout(lighten(@color-theme, 10%), 70%);
|
||||
@color-theme_2-font:desaturate( darken(@color-theme_2, 70%), 90%);
|
||||
@color-theme_2-font-label: fadeout(@color-theme_2-font, 55%);
|
||||
// @color-theme_2-line: transparent;
|
||||
@color-theme_2-line: lighten(@color-theme, 35%);
|
||||
@color-reply-floor: fadeout(@color-theme, 95%);
|
||||
@color-theme-sidebar: @color-theme;
|
||||
@color-btn: lighten(@color-theme, 5%);
|
||||
@color-btn-background: fadeout(lighten(@color-theme, 35%), 70%);
|
||||
@color-btn-hover: fadeout(lighten(@color-theme, 5%), 70%);
|
||||
@color-btn-active: fadeout(darken(@color-theme, 5%), 70%);
|
||||
@color-btn-select: fadeout(lighten(@color-theme, 5%), 50%);
|
||||
|
||||
@color-pagination-background: fadeout(lighten(@color-theme, 45%), 30%);
|
||||
@color-pagination-hover: fadeout(lighten(@color-theme, 10%), 70%);
|
||||
@color-pagination-active: fadeout(darken(@color-theme, 10%), 70%);
|
||||
@color-pagination-select: fadeout(lighten(@color-theme, 10%), 50%);
|
||||
|
||||
@color-search-form-background: fadeout(lighten(@color-theme, 35%), 10%);
|
||||
// @color-search-list-background: fadeout(lighten(@color-theme, 35%), 10%);
|
||||
@color-search-list-hover: fadeout(darken(@color-theme, 10%), 70%);
|
||||
|
||||
@color-scrollbar-track: fadeout(lighten(@color-theme, 10%), 65%);
|
||||
@color-scrollbar-thumb: fadeout(lighten(@color-theme, 10%), 45%);
|
||||
@color-scrollbar-thumb-hover: fadeout(lighten(@color-theme, 10%), 25%);
|
||||
|
||||
|
||||
@color-player-pic-c1: fadeout(@color-theme_2, 50%);
|
||||
@color-player-pic-c2: darken(@color-theme_2, 30%);
|
||||
@color-player-progress: darken(@color-theme_2, 6%);
|
||||
@color-player-progress-bar1: darken(@color-theme_2, 12%);
|
||||
@color-player-progress-bar2: fadeout(lighten(@color-theme, 12%), 20%);
|
||||
@color-player-status-text: lighten(@color-theme_2-font, 32%);
|
||||
@color-player-detail-lyric: fadeout(@color-theme_2-font, 40%);
|
||||
@color-player-detail-lyric-active: darken(@color-theme, 2%);
|
||||
@color-player-detail-play-btn: lighten(@color-theme, 7%);
|
||||
|
||||
@color-tab-btn-background: fadeout(lighten(@color-theme, 10%), 80%);
|
||||
@color-tab-btn-background-hover: @color-theme_2-hover;
|
||||
@color-tab-border-top: fadeout(lighten(@color-theme, 5%), 50%);
|
||||
@color-tab-border-bottom: lighten(@color-theme, 5%);
|
||||
@color-hideBtn: #3bc2b2;
|
||||
@color-minBtn: #85c43b;
|
||||
@color-maxBtn: #e7aa36;
|
||||
@color-closeBtn: #ea6e4d;
|
||||
@color-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-minBtn-hover: fadeout(@color-minBtn, 10%);
|
||||
@color-maxBtn-hover: fadeout(@color-maxBtn, 10%);
|
||||
@color-closeBtn-hover: fadeout(@color-closeBtn, 10%);
|
||||
@color-green-badge-success: #32bc63;
|
||||
@color-green-badge-success-font: #fff;
|
||||
@color-green-badge-info: #4baed5;
|
||||
@color-green-badge-info-font: #fff;
|
||||
@color-badge-success: @color-theme;
|
||||
@color-badge-success-font: #fff;
|
||||
@color-badge-info: #4baed5;
|
||||
@color-badge-info-font: #fff;
|
||||
|
||||
|
||||
@color-green-theme: #4daf7c;
|
||||
@color-green-theme-bgimg: none;
|
||||
@color-green-theme-bgposition: center;
|
||||
@color-green-theme-bgsize: cover;
|
||||
@color-green-theme-hover: fadeout(lighten(@color-green-theme, 10%), 30%);
|
||||
@color-green-theme-active: fadeout(darken(@color-green-theme, 20%), 60%);
|
||||
@color-green-theme-font: #fff;
|
||||
@color-green-theme-font-label: lighten(@color-green-theme, 35%);
|
||||
@color-green-theme_2: #fff;
|
||||
@color-green-theme_2-background_1: #fff;
|
||||
@color-green-theme_2-background_2: fadeout(@color-green-theme_2-background_1, 2%);
|
||||
@color-green-theme_2-hover: fadeout(lighten(@color-green-theme, 10%), 80%);
|
||||
@color-green-theme_2-active: fadeout(lighten(@color-green-theme, 10%), 70%);
|
||||
@color-green-theme_2-font: darken(desaturate(@color-green-theme_2, 70%), 90%);
|
||||
@color-green-theme_2-font-label: fadeout(@color-green-theme_2-font, 55%);
|
||||
// @color-green-theme_2-line: transparent;
|
||||
@color-green-theme_2-line: lighten(@color-green-theme, 45%);
|
||||
@color-green-reply-floor: fadeout(@color-green-theme, 95%);
|
||||
@color-green-theme-sidebar: @color-green-theme;
|
||||
@color-green-btn: lighten(@color-green-theme, 5%);
|
||||
@color-green-btn-background: fadeout(lighten(@color-green-theme, 35%), 70%);
|
||||
@color-green-btn-hover: fadeout(lighten(@color-green-theme, 5%), 70%);
|
||||
@color-green-btn-active: fadeout(darken(@color-green-theme, 5%), 70%);
|
||||
@color-green-btn-select: fadeout(lighten(@color-green-theme, 5%), 50%);
|
||||
@color-green-pagination-background: fadeout(lighten(@color-green-theme, 45%), 30%);
|
||||
@color-green-pagination-hover: fadeout(lighten(@color-green-theme, 10%), 70%);
|
||||
@color-green-pagination-active: fadeout(darken(@color-green-theme, 10%), 70%);
|
||||
@color-green-pagination-select: fadeout(lighten(@color-green-theme, 10%), 50%);
|
||||
@color-green-search-form-background: fadeout(lighten(@color-green-theme, 35%), 10%);
|
||||
@color-green-search-list-hover: fadeout(darken(@color-green-theme, 10%), 70%);
|
||||
@color-green-scrollbar-track: fadeout(lighten(@color-green-theme, 10%), 75%);
|
||||
@color-green-scrollbar-thumb: fadeout(lighten(@color-green-theme, 10%), 50%);
|
||||
@color-green-scrollbar-thumb-hover: fadeout(lighten(@color-green-theme, 10%), 30%);
|
||||
@color-green-player-pic-c1: fadeout(@color-green-theme_2, 50%);
|
||||
@color-green-player-pic-c2: darken(@color-green-theme_2, 30%);
|
||||
@color-green-player-progress: darken(@color-green-theme_2, 6%);
|
||||
@color-green-player-progress-bar1: darken(@color-green-theme_2, 12%);
|
||||
@color-green-player-progress-bar2: fadeout(lighten(@color-green-theme, 12%), 20%);
|
||||
@color-green-player-status-text: lighten(@color-green-theme_2-font, 32%);
|
||||
@color-green-player-detail-lyric: fadeout(@color-green-theme_2-font, 40%);
|
||||
@color-green-player-detail-lyric-active: darken(@color-green-theme, 2%);
|
||||
@color-green-player-detail-play-btn: lighten(@color-green-theme, 7%);
|
||||
@color-green-tab-btn-background: fadeout(lighten(@color-green-theme, 10%), 80%);
|
||||
@color-green-tab-btn-background-hover: @color-green-theme_2-hover;
|
||||
@color-green-tab-border-top: fadeout(lighten(@color-green-theme, 5%), 50%);
|
||||
@color-green-tab-border-bottom: lighten(@color-green-theme, 5%);
|
||||
@color-green-hideBtn: #3bc2b2;
|
||||
@color-green-minBtn: #85c43b;
|
||||
@color-green-maxBtn: #e7aa36;
|
||||
@color-green-closeBtn: #ea6e4d;
|
||||
@color-green-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-green-minBtn-hover: fadeout(@color-green-minBtn, 10%);
|
||||
@color-green-maxBtn-hover: fadeout(@color-green-maxBtn, 10%);
|
||||
@color-green-closeBtn-hover: fadeout(@color-green-closeBtn, 10%);
|
||||
@color-green-badge-success: lighten(@color-green-theme, 7%);
|
||||
@color-green-badge-success-font: #fff;
|
||||
@color-green-badge-info: #66a2bd;
|
||||
@color-green-badge-info-font: #fff;
|
||||
|
||||
|
||||
@color-yellow-theme: #e9d460;
|
||||
@color-yellow-theme-bgimg: none;
|
||||
@color-yellow-theme-bgposition: center;
|
||||
@color-yellow-theme-bgsize: cover;
|
||||
@color-yellow-theme-hover: fadeout(lighten(@color-yellow-theme, 10%), 30%);
|
||||
@color-yellow-theme-active: fadeout(darken(@color-yellow-theme, 20%), 60%);
|
||||
@color-yellow-theme-font: #fff;
|
||||
@color-yellow-theme-font-label: lighten(@color-yellow-theme, 35%);
|
||||
@color-yellow-theme_2: #fff;
|
||||
@color-yellow-theme_2-background_1: #fff;
|
||||
@color-yellow-theme_2-background_2: fadeout(@color-yellow-theme_2-background_1, 2%);
|
||||
@color-yellow-theme_2-hover: fadeout(lighten(@color-yellow-theme, 10%), 70%);
|
||||
@color-yellow-theme_2-active: fadeout(lighten(@color-yellow-theme, 10%), 60%);
|
||||
@color-yellow-theme_2-font: darken(desaturate(@color-yellow-theme_2, 70%), 90%);
|
||||
@color-yellow-theme_2-font-label: fadeout(@color-yellow-theme_2-font, 55%);
|
||||
// @color-yellow-theme_2-line: transparent;
|
||||
@color-yellow-theme_2-line: lighten(@color-yellow-theme, 28%);
|
||||
@color-yellow-reply-floor: fadeout(@color-yellow-theme, 94%);
|
||||
@color-yellow-theme-sidebar: @color-yellow-theme;
|
||||
@color-yellow-btn: darken(@color-yellow-theme, 5%);
|
||||
@color-yellow-btn-background: fadeout(lighten(@color-yellow-theme, 25%), 60%);
|
||||
@color-yellow-btn-hover: fadeout(lighten(@color-yellow-theme, 5%), 70%);
|
||||
@color-yellow-btn-active: fadeout(darken(@color-yellow-theme, 5%), 70%);
|
||||
@color-yellow-btn-select: fadeout(lighten(@color-yellow-theme, 5%), 50%);
|
||||
@color-yellow-pagination-background: fadeout(lighten(@color-yellow-theme, 30%), 30%);
|
||||
@color-yellow-pagination-hover: fadeout(lighten(@color-yellow-theme, 5%), 70%);
|
||||
@color-yellow-pagination-active: fadeout(darken(@color-yellow-theme, 5%), 70%);
|
||||
@color-yellow-pagination-select: fadeout(lighten(@color-yellow-theme, 5%), 50%);
|
||||
@color-yellow-search-form-background: fadeout(lighten(@color-yellow-theme, 25%), 10%);
|
||||
@color-yellow-search-list-hover: fadeout(darken(@color-yellow-theme, 10%), 60%);
|
||||
@color-yellow-scrollbar-track: fadeout(lighten(@color-yellow-theme, 5%), 65%);
|
||||
@color-yellow-scrollbar-thumb: fadeout(lighten(@color-yellow-theme, 5%), 30%);
|
||||
@color-yellow-scrollbar-thumb-hover: fadeout(lighten(@color-yellow-theme, 5%), 15%);
|
||||
@color-yellow-player-pic-c1: fadeout(@color-yellow-theme_2, 50%);
|
||||
@color-yellow-player-pic-c2: darken(@color-yellow-theme_2, 30%);
|
||||
@color-yellow-player-progress: darken(@color-yellow-theme_2, 6%);
|
||||
@color-yellow-player-progress-bar1: darken(@color-yellow-theme_2, 12%);
|
||||
@color-yellow-player-progress-bar2: fadeout(lighten(@color-yellow-theme, 2%), 20%);
|
||||
@color-yellow-player-status-text: lighten(@color-yellow-theme_2-font, 32%);
|
||||
@color-yellow-player-detail-lyric: fadeout(@color-yellow-theme_2-font, 40%);
|
||||
@color-yellow-player-detail-lyric-active: darken(@color-yellow-theme, 7%);
|
||||
@color-yellow-player-detail-play-btn: lighten(@color-yellow-theme, 7%);
|
||||
@color-yellow-tab-btn-background: fadeout(lighten(@color-yellow-theme, 10%), 70%);
|
||||
@color-yellow-tab-btn-background-hover: @color-yellow-theme_2-hover;
|
||||
@color-yellow-tab-border-top: fadeout(lighten(@color-yellow-theme, 5%), 40%);
|
||||
@color-yellow-tab-border-bottom: @color-yellow-theme;
|
||||
@color-yellow-hideBtn: #3bc2b2;
|
||||
@color-yellow-minBtn: #85c43b;
|
||||
@color-yellow-maxBtn: #e7aa36;
|
||||
@color-yellow-closeBtn: #ea6e4d;
|
||||
@color-yellow-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-yellow-minBtn-hover: fadeout(@color-yellow-minBtn, 10%);
|
||||
@color-yellow-maxBtn-hover: fadeout(@color-yellow-maxBtn, 10%);
|
||||
@color-yellow-closeBtn-hover: fadeout(@color-yellow-closeBtn, 10%);
|
||||
@color-yellow-badge-success: lighten(@color-yellow-theme, 10%);
|
||||
@color-yellow-badge-success-font: #fff;
|
||||
@color-yellow-badge-info: #7cd295;
|
||||
@color-yellow-badge-info-font: #fff;
|
||||
|
||||
@color-orange-theme: #f5ab35;
|
||||
@color-orange-theme-bgimg: none;
|
||||
@color-orange-theme-bgposition: center;
|
||||
@color-orange-theme-bgsize: cover;
|
||||
@color-orange-theme-hover: fadeout(lighten(@color-orange-theme, 10%), 30%);
|
||||
@color-orange-theme-active: fadeout(darken(@color-orange-theme, 20%), 60%);
|
||||
@color-orange-theme-font: #fff;
|
||||
@color-orange-theme-font-label: lighten(@color-orange-theme, 35%);
|
||||
@color-orange-theme_2: #fff;
|
||||
@color-orange-theme_2-background_1: #fff;
|
||||
@color-orange-theme_2-background_2: fadeout(@color-orange-theme_2-background_1, 2%);
|
||||
@color-orange-theme_2-hover: fadeout(darken(@color-orange-theme, 5%), 80%);
|
||||
@color-orange-theme_2-active: fadeout(darken(@color-orange-theme, 5%), 70%);
|
||||
@color-orange-theme_2-font: darken(desaturate(@color-orange-theme_2, 70%), 90%);
|
||||
@color-orange-theme_2-font-label: fadeout(@color-orange-theme_2-font, 55%);
|
||||
// @color-orange-theme_2-line: transparent;
|
||||
@color-orange-theme_2-line: lighten(@color-orange-theme, 36%);
|
||||
@color-orange-reply-floor: fadeout(@color-orange-theme, 95%);
|
||||
@color-orange-theme-sidebar: @color-orange-theme;
|
||||
@color-orange-btn: lighten(@color-orange-theme, 5%);
|
||||
@color-orange-btn-background: fadeout(lighten(@color-orange-theme, 25%), 60%);
|
||||
@color-orange-btn-hover: fadeout(lighten(@color-orange-theme, 5%), 70%);
|
||||
@color-orange-btn-active: fadeout(darken(@color-orange-theme, 5%), 70%);
|
||||
@color-orange-btn-select: fadeout(lighten(@color-orange-theme, 5%), 50%);
|
||||
@color-orange-pagination-background: fadeout(lighten(@color-orange-theme, 25%), 40%);
|
||||
@color-orange-pagination-hover: fadeout(lighten(@color-orange-theme, 10%), 70%);
|
||||
@color-orange-pagination-active: fadeout(darken(@color-orange-theme, 10%), 70%);
|
||||
@color-orange-pagination-select: fadeout(lighten(@color-orange-theme, 10%), 50%);
|
||||
@color-orange-search-form-background: fadeout(lighten(@color-orange-theme, 30%), 10%);
|
||||
@color-orange-search-list-hover: fadeout(darken(@color-orange-theme, 10%), 70%);
|
||||
@color-orange-scrollbar-track: fadeout(lighten(@color-orange-theme, 10%), 75%);
|
||||
@color-orange-scrollbar-thumb: fadeout(lighten(@color-orange-theme, 10%), 45%);
|
||||
@color-orange-scrollbar-thumb-hover: fadeout(lighten(@color-orange-theme, 10%), 25%);
|
||||
@color-orange-player-pic-c1: fadeout(@color-orange-theme_2, 50%);
|
||||
@color-orange-player-pic-c2: darken(@color-orange-theme_2, 30%);
|
||||
@color-orange-player-progress: darken(@color-orange-theme_2, 6%);
|
||||
@color-orange-player-progress-bar1: darken(@color-orange-theme_2, 12%);
|
||||
@color-orange-player-progress-bar2: fadeout(lighten(@color-orange-theme, 12%), 20%);
|
||||
@color-orange-player-status-text: lighten(@color-orange-theme_2-font, 32%);
|
||||
@color-orange-player-detail-lyric: fadeout(@color-orange-theme_2-font, 40%);
|
||||
@color-orange-player-detail-lyric-active: darken(@color-orange-theme, 7%);
|
||||
@color-orange-player-detail-play-btn: lighten(@color-orange-theme, 7%);
|
||||
@color-orange-tab-btn-background: fadeout(lighten(@color-orange-theme, 10%), 80%);
|
||||
@color-orange-tab-btn-background-hover: @color-orange-theme_2-hover;
|
||||
@color-orange-tab-border-top: fadeout(lighten(@color-orange-theme, 5%), 50%);
|
||||
@color-orange-tab-border-bottom: lighten(@color-orange-theme, 5%);
|
||||
@color-orange-hideBtn: #3bc2b2;
|
||||
@color-orange-minBtn: #85c43b;
|
||||
@color-orange-maxBtn: #e7aa36;
|
||||
@color-orange-closeBtn: #ea6e4d;
|
||||
@color-orange-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-orange-minBtn-hover: fadeout(@color-orange-minBtn, 10%);
|
||||
@color-orange-maxBtn-hover: fadeout(@color-orange-maxBtn, 10%);
|
||||
@color-orange-closeBtn-hover: fadeout(@color-orange-closeBtn, 10%);
|
||||
@color-orange-badge-success: lighten(@color-orange-theme, 10%);
|
||||
@color-orange-badge-success-font: #fff;
|
||||
@color-orange-badge-info: #9ed458;
|
||||
@color-orange-badge-info-font: #fff;
|
||||
|
||||
@color-blue-theme: #3498db;
|
||||
@color-blue-theme-bgimg: none;
|
||||
@color-blue-theme-bgposition: center;
|
||||
@color-blue-theme-bgsize: cover;
|
||||
@color-blue-theme-hover: fadeout(lighten(@color-blue-theme, 10%), 30%);
|
||||
@color-blue-theme-active: fadeout(darken(@color-blue-theme, 20%), 60%);
|
||||
@color-blue-theme-font: #fff;
|
||||
@color-blue-theme-font-label: lighten(@color-blue-theme, 35%);
|
||||
@color-blue-theme_2: #fff;
|
||||
@color-blue-theme_2-background_1: #fff;
|
||||
@color-blue-theme_2-background_2: fadeout(@color-blue-theme_2-background_1, 2%);
|
||||
@color-blue-theme_2-hover: fadeout(lighten(@color-blue-theme, 10%), 80%);
|
||||
@color-blue-theme_2-active: fadeout(lighten(@color-blue-theme, 10%), 70%);
|
||||
@color-blue-theme_2-font: darken(desaturate(@color-blue-theme_2, 70%), 90%);
|
||||
@color-blue-theme_2-font-label: fadeout(@color-blue-theme_2-font, 55%);
|
||||
// @color-blue-theme_2-line: transparent;
|
||||
@color-blue-theme_2-line: lighten(@color-blue-theme, 42%);
|
||||
@color-blue-reply-floor: fadeout(@color-blue-theme, 95%);
|
||||
@color-blue-theme-sidebar: @color-blue-theme;
|
||||
@color-blue-btn: lighten(@color-blue-theme, 5%);
|
||||
@color-blue-btn-background: fadeout(lighten(@color-blue-theme, 35%), 50%);
|
||||
@color-blue-btn-hover: fadeout(lighten(@color-blue-theme, 5%), 70%);
|
||||
@color-blue-btn-active: fadeout(darken(@color-blue-theme, 5%), 70%);
|
||||
@color-blue-btn-select: fadeout(lighten(@color-blue-theme, 5%), 50%);
|
||||
@color-blue-pagination-background: fadeout(lighten(@color-blue-theme, 40%), 30%);
|
||||
@color-blue-pagination-hover: fadeout(lighten(@color-blue-theme, 15%), 70%);
|
||||
@color-blue-pagination-active: fadeout(darken(@color-blue-theme, 15%), 70%);
|
||||
@color-blue-pagination-select: fadeout(lighten(@color-blue-theme, 15%), 50%);
|
||||
@color-blue-search-form-background: fadeout(lighten(@color-blue-theme, 35%), 10%);
|
||||
@color-blue-search-list-hover: fadeout(darken(@color-blue-theme, 10%), 70%);
|
||||
@color-blue-scrollbar-track: fadeout(lighten(@color-blue-theme, 10%), 75%);
|
||||
@color-blue-scrollbar-thumb: fadeout(lighten(@color-blue-theme, 10%), 50%);
|
||||
@color-blue-scrollbar-thumb-hover: fadeout(lighten(@color-blue-theme, 10%), 35%);
|
||||
@color-blue-player-pic-c1: fadeout(@color-blue-theme_2, 50%);
|
||||
@color-blue-player-pic-c2: darken(@color-blue-theme_2, 30%);
|
||||
@color-blue-player-progress: darken(@color-blue-theme_2, 6%);
|
||||
@color-blue-player-progress-bar1: darken(@color-blue-theme_2, 12%);
|
||||
@color-blue-player-progress-bar2: fadeout(lighten(@color-blue-theme, 12%), 20%);
|
||||
@color-blue-player-status-text: lighten(@color-blue-theme_2-font, 32%);
|
||||
@color-blue-player-detail-lyric: fadeout(@color-blue-theme_2-font, 40%);
|
||||
@color-blue-player-detail-lyric-active: darken(@color-blue-theme, 2%);
|
||||
@color-blue-player-detail-play-btn: lighten(@color-blue-theme, 7%);
|
||||
@color-blue-tab-btn-background: fadeout(lighten(@color-blue-theme, 10%), 80%);
|
||||
@color-blue-tab-btn-background-hover: @color-blue-theme_2-hover;
|
||||
@color-blue-tab-border-top: fadeout(lighten(@color-blue-theme, 5%), 50%);
|
||||
@color-blue-tab-border-bottom: lighten(@color-blue-theme, 5%);
|
||||
@color-blue-hideBtn: #3bc2b2;
|
||||
@color-blue-minBtn: #85c43b;
|
||||
@color-blue-maxBtn: #e7aa36;
|
||||
@color-blue-closeBtn: #ea6e4d;
|
||||
@color-blue-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-blue-minBtn-hover: fadeout(@color-blue-minBtn, 10%);
|
||||
@color-blue-maxBtn-hover: fadeout(@color-blue-maxBtn, 10%);
|
||||
@color-blue-closeBtn-hover: fadeout(@color-blue-closeBtn, 10%);
|
||||
@color-blue-badge-success: #5cbf9b;
|
||||
@color-blue-badge-success-font: #fff;
|
||||
@color-blue-badge-info: lighten(@color-blue-theme, 10%);
|
||||
@color-blue-badge-info-font: #fff;
|
||||
|
||||
@color-red-theme: #d64541;
|
||||
@color-red-theme-bgimg: none;
|
||||
@color-red-theme-bgposition: center;
|
||||
@color-red-theme-bgsize: cover;
|
||||
@color-red-theme-hover: fadeout(lighten(@color-red-theme, 10%), 30%);
|
||||
@color-red-theme-active: fadeout(darken(@color-red-theme, 20%), 60%);
|
||||
@color-red-theme-font: #fff;
|
||||
@color-red-theme-font-label: lighten(@color-red-theme, 35%);
|
||||
@color-red-theme_2: #fff;
|
||||
@color-red-theme_2-hover: fadeout(lighten(@color-red-theme, 10%), 80%);
|
||||
@color-red-theme_2-active: fadeout(lighten(@color-red-theme, 10%), 70%);
|
||||
@color-red-theme_2-background_1: #fff;
|
||||
@color-red-theme_2-background_2: fadeout(@color-red-theme_2-background_1, 2%);
|
||||
@color-red-theme_2-font: darken(desaturate(@color-red-theme_2, 70%), 90%);
|
||||
@color-red-theme_2-font-label: fadeout(@color-red-theme_2-font, 55%);
|
||||
// @color-red-theme_2-line: transparent;
|
||||
@color-red-theme_2-line: lighten(@color-red-theme, 42%);
|
||||
@color-red-reply-floor: fadeout(@color-red-theme, 95%);
|
||||
@color-red-theme-sidebar: @color-red-theme;
|
||||
@color-red-btn: lighten(@color-red-theme, 5%);
|
||||
@color-red-btn-background: fadeout(lighten(@color-red-theme, 35%), 70%);
|
||||
@color-red-btn-hover: fadeout(lighten(@color-red-theme, 5%), 70%);
|
||||
@color-red-btn-active: fadeout(darken(@color-red-theme, 5%), 70%);
|
||||
@color-red-btn-select: fadeout(lighten(@color-red-theme, 5%), 50%);
|
||||
@color-red-pagination-background: fadeout(lighten(@color-red-theme, 40%), 30%);
|
||||
@color-red-pagination-hover: fadeout(lighten(@color-red-theme, 15%), 70%);
|
||||
@color-red-pagination-active: fadeout(darken(@color-red-theme, 15%), 70%);
|
||||
@color-red-pagination-select: fadeout(lighten(@color-red-theme, 15%), 50%);
|
||||
@color-red-search-form-background: fadeout(lighten(@color-red-theme, 35%), 10%);
|
||||
@color-red-search-list-hover: fadeout(darken(@color-red-theme, 10%), 70%);
|
||||
@color-red-scrollbar-track: fadeout(lighten(@color-red-theme, 10%), 75%);
|
||||
@color-red-scrollbar-thumb: fadeout(lighten(@color-red-theme, 10%), 45%);
|
||||
@color-red-scrollbar-thumb-hover: fadeout(lighten(@color-red-theme, 10%), 25%);
|
||||
@color-red-player-pic-c1: fadeout(@color-red-theme_2, 50%);
|
||||
@color-red-player-pic-c2: darken(@color-red-theme_2, 30%);
|
||||
@color-red-player-progress: darken(@color-red-theme_2, 6%);
|
||||
@color-red-player-progress-bar1: darken(@color-red-theme_2, 12%);
|
||||
@color-red-player-progress-bar2: fadeout(lighten(@color-red-theme, 12%), 20%);
|
||||
@color-red-player-status-text: lighten(@color-red-theme_2-font, 32%);
|
||||
@color-red-player-detail-lyric: fadeout(@color-red-theme_2-font, 40%);
|
||||
@color-red-player-detail-lyric-active: lighten(@color-red-theme, 2%);
|
||||
@color-red-player-detail-play-btn: lighten(@color-red-theme, 7%);
|
||||
@color-red-tab-border-top: fadeout(lighten(@color-red-theme, 25%), 70%);
|
||||
@color-red-tab-border-bottom: lighten(@color-red-theme, 35%);
|
||||
@color-red-tab-btn-background: fadeout(lighten(@color-red-theme, 10%), 80%);
|
||||
@color-red-tab-btn-background-hover: @color-red-theme_2-hover;
|
||||
@color-red-tab-border-top: fadeout(lighten(@color-red-theme, 5%), 50%);
|
||||
@color-red-tab-border-bottom: lighten(@color-red-theme, 5%);
|
||||
@color-red-hideBtn: #3bc2b2;
|
||||
@color-red-minBtn: #85c43b;
|
||||
@color-red-maxBtn: #e7aa36;
|
||||
@color-red-closeBtn: #ea6e4d;
|
||||
@color-red-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-red-minBtn-hover: fadeout(@color-red-minBtn, 10%);
|
||||
@color-red-maxBtn-hover: fadeout(@color-red-maxBtn, 10%);
|
||||
@color-red-closeBtn-hover: fadeout(@color-red-closeBtn, 10%);
|
||||
@color-red-badge-success: lighten(@color-red-theme, 10%);
|
||||
@color-red-badge-success-font: #fff;
|
||||
@color-red-badge-info: #dfbb6b;
|
||||
@color-red-badge-info-font: #fff;
|
||||
|
||||
@color-pink-theme: #f1828d;
|
||||
@color-pink-theme-bgimg: none;
|
||||
@color-pink-theme-bgposition: center;
|
||||
@color-pink-theme-bgsize: cover;
|
||||
@color-pink-theme-hover: fadeout(lighten(@color-pink-theme, 10%), 30%);
|
||||
@color-pink-theme-active: fadeout(darken(@color-pink-theme, 20%), 60%);
|
||||
@color-pink-theme-font: #fff;
|
||||
@color-pink-theme-font-label: lighten(@color-pink-theme, 35%);
|
||||
@color-pink-theme_2: #fff;
|
||||
@color-pink-theme_2-background_1: #fff;
|
||||
@color-pink-theme_2-background_2: fadeout(@color-pink-theme_2-background_1, 2%);
|
||||
@color-pink-theme_2-hover: fadeout(lighten(@color-pink-theme, 10%), 75%);
|
||||
@color-pink-theme_2-active: fadeout(lighten(@color-pink-theme, 10%), 60%);
|
||||
@color-pink-theme_2-font: darken(desaturate(@color-pink-theme_2, 70%), 90%);
|
||||
@color-pink-theme_2-font-label: fadeout(@color-pink-theme_2-font, 55%);
|
||||
// @color-pink-theme_2-line: transparent;
|
||||
@color-pink-theme_2-line: lighten(@color-pink-theme, 25%);
|
||||
@color-pink-reply-floor: fadeout(@color-pink-theme, 93%);
|
||||
@color-pink-theme-sidebar: @color-pink-theme;
|
||||
@color-pink-btn: darken(@color-pink-theme, 3%);
|
||||
@color-pink-btn-background: fadeout(lighten(@color-pink-theme, 20%), 50%);
|
||||
@color-pink-btn-hover: fadeout(lighten(@color-pink-theme, 5%), 70%);
|
||||
@color-pink-btn-active: fadeout(darken(@color-pink-theme, 5%), 70%);
|
||||
@color-pink-btn-select: fadeout(lighten(@color-pink-theme, 5%), 50%);
|
||||
@color-pink-pagination-background: fadeout(lighten(@color-pink-theme, 23%), 25%);
|
||||
@color-pink-pagination-hover: fadeout(lighten(@color-pink-theme, 5%), 70%);
|
||||
@color-pink-pagination-active: fadeout(darken(@color-pink-theme, 5%), 70%);
|
||||
@color-pink-pagination-select: fadeout(lighten(@color-pink-theme, 5%), 50%);
|
||||
@color-pink-search-form-background: fadeout(lighten(@color-pink-theme, 20%), 10%);
|
||||
@color-pink-search-list-hover: fadeout(darken(@color-pink-theme, 10%), 60%);
|
||||
@color-pink-scrollbar-track: fadeout(lighten(@color-pink-theme, 6%), 75%);
|
||||
@color-pink-scrollbar-thumb: fadeout(lighten(@color-pink-theme, 6%), 35%);
|
||||
@color-pink-scrollbar-thumb-hover: fadeout(lighten(@color-pink-theme, 6%), 20%);
|
||||
@color-pink-player-pic-c1: fadeout(@color-pink-theme_2, 50%);
|
||||
@color-pink-player-pic-c2: darken(@color-pink-theme_2, 30%);
|
||||
@color-pink-player-progress: darken(@color-pink-theme_2, 6%);
|
||||
@color-pink-player-progress-bar1: darken(@color-pink-theme_2, 12%);
|
||||
@color-pink-player-progress-bar2: fadeout(lighten(@color-pink-theme, 2%), 20%);
|
||||
@color-pink-player-status-text: lighten(@color-pink-theme_2-font, 32%);
|
||||
@color-pink-player-detail-lyric: fadeout(@color-pink-theme_2-font, 40%);
|
||||
@color-pink-player-detail-lyric-active: darken(@color-pink-theme, 2%);
|
||||
@color-pink-player-detail-play-btn: lighten(@color-pink-theme, 7%);
|
||||
@color-pink-tab-btn-background: fadeout(lighten(@color-pink-theme, 10%), 70%);
|
||||
@color-pink-tab-btn-background-hover: @color-pink-theme_2-hover;
|
||||
@color-pink-tab-border-top: fadeout(lighten(@color-pink-theme, 5%), 40%);
|
||||
@color-pink-tab-border-bottom: @color-pink-theme;
|
||||
@color-pink-hideBtn: #3bc2b2;
|
||||
@color-pink-minBtn: #85c43b;
|
||||
@color-pink-maxBtn: #e7aa36;
|
||||
@color-pink-closeBtn: #ea6e4d;
|
||||
@color-pink-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-pink-minBtn-hover: fadeout(@color-pink-minBtn, 10%);
|
||||
@color-pink-maxBtn-hover: fadeout(@color-pink-maxBtn, 10%);
|
||||
@color-pink-closeBtn-hover: fadeout(@color-pink-closeBtn, 10%);
|
||||
@color-pink-badge-success: lighten(@color-pink-theme, 10%);
|
||||
@color-pink-badge-success-font: #fff;
|
||||
@color-pink-badge-info: #f5b684;
|
||||
@color-pink-badge-info-font: #fff;
|
||||
|
||||
@color-purple-theme: #9b59b6;
|
||||
@color-purple-theme-bgimg: none;
|
||||
@color-purple-theme-bgposition: center;
|
||||
@color-purple-theme-bgsize: cover;
|
||||
@color-purple-theme-hover: fadeout(lighten(@color-purple-theme, 10%), 30%);
|
||||
@color-purple-theme-active: fadeout(darken(@color-purple-theme, 20%), 60%);
|
||||
@color-purple-theme-font: #fff;
|
||||
@color-purple-theme-font-label: lighten(@color-purple-theme, 35%);
|
||||
@color-purple-theme_2: #fff;
|
||||
@color-purple-theme_2-background_1: #fff;
|
||||
@color-purple-theme_2-background_2: fadeout(@color-purple-theme_2-background_1, 2%);
|
||||
@color-purple-theme_2-hover: fadeout(lighten(@color-purple-theme, 10%), 80%);
|
||||
@color-purple-theme_2-active: fadeout(lighten(@color-purple-theme, 10%), 70%);
|
||||
@color-purple-theme_2-font: darken(desaturate(@color-purple-theme_2, 70%), 90%);
|
||||
@color-purple-theme_2-font-label: fadeout(@color-purple-theme_2-font, 55%);
|
||||
// @color-purple-theme_2-line: transparent;
|
||||
@color-purple-theme_2-line: lighten(@color-purple-theme, 43%);
|
||||
@color-purple-reply-floor: fadeout(@color-purple-theme, 95%);
|
||||
@color-purple-theme-sidebar: @color-purple-theme;
|
||||
@color-purple-btn: lighten(@color-purple-theme, 5%);
|
||||
@color-purple-btn-background: fadeout(lighten(@color-purple-theme, 35%), 70%);
|
||||
@color-purple-btn-hover: fadeout(lighten(@color-purple-theme, 5%), 70%);
|
||||
@color-purple-btn-active: fadeout(darken(@color-purple-theme, 5%), 70%);
|
||||
@color-purple-btn-select: fadeout(lighten(@color-purple-theme, 5%), 50%);
|
||||
@color-purple-pagination-background: fadeout(lighten(@color-purple-theme, 40%), 30%);
|
||||
@color-purple-pagination-hover: fadeout(lighten(@color-purple-theme, 15%), 70%);
|
||||
@color-purple-pagination-active: fadeout(darken(@color-purple-theme, 15%), 70%);
|
||||
@color-purple-pagination-select: fadeout(lighten(@color-purple-theme, 15%), 50%);
|
||||
@color-purple-search-form-background: fadeout(lighten(@color-purple-theme, 35%), 10%);
|
||||
@color-purple-search-list-hover: fadeout(darken(@color-purple-theme, 10%), 70%);
|
||||
@color-purple-scrollbar-track: fadeout(lighten(@color-purple-theme, 10%), 77%);
|
||||
@color-purple-scrollbar-thumb: fadeout(lighten(@color-purple-theme, 10%), 50%);
|
||||
@color-purple-scrollbar-thumb-hover: fadeout(lighten(@color-purple-theme, 10%), 35%);
|
||||
@color-purple-player-pic-c1: fadeout(@color-purple-theme_2, 50%);
|
||||
@color-purple-player-pic-c2: darken(@color-purple-theme_2, 30%);
|
||||
@color-purple-player-progress: darken(@color-purple-theme_2, 6%);
|
||||
@color-purple-player-progress-bar1: darken(@color-purple-theme_2, 12%);
|
||||
@color-purple-player-progress-bar2: fadeout(lighten(@color-purple-theme, 12%), 20%);
|
||||
@color-purple-player-status-text: lighten(@color-purple-theme_2-font, 32%);
|
||||
@color-purple-player-detail-lyric: fadeout(@color-purple-theme_2-font, 40%);
|
||||
@color-purple-player-detail-lyric-active: darken(@color-purple-theme, 2%);
|
||||
@color-purple-player-detail-play-btn: lighten(@color-purple-theme, 7%);
|
||||
@color-purple-tab-btn-background: fadeout(lighten(@color-purple-theme, 10%), 80%);
|
||||
@color-purple-tab-btn-background-hover: @color-purple-theme_2-hover;
|
||||
@color-purple-tab-border-top: fadeout(lighten(@color-purple-theme, 5%), 50%);
|
||||
@color-purple-tab-border-bottom: lighten(@color-purple-theme, 5%);
|
||||
@color-purple-hideBtn: #3bc2b2;
|
||||
@color-purple-minBtn: #85c43b;
|
||||
@color-purple-maxBtn: #e7aa36;
|
||||
@color-purple-closeBtn: #ea6e4d;
|
||||
@color-purple-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-purple-minBtn-hover: fadeout(@color-purple-minBtn, 10%);
|
||||
@color-purple-maxBtn-hover: fadeout(@color-purple-maxBtn, 10%);
|
||||
@color-purple-closeBtn-hover: fadeout(@color-purple-closeBtn, 10%);
|
||||
@color-purple-badge-success: lighten(@color-purple-theme, 15%);
|
||||
@color-purple-badge-success-font: #fff;
|
||||
@color-purple-badge-info: #e5a39f;
|
||||
@color-purple-badge-info-font: #fff;
|
||||
|
||||
@color-grey-theme: #6c7a89;
|
||||
@color-grey-theme-bgimg: none;
|
||||
@color-grey-theme-bgposition: center;
|
||||
@color-grey-theme-bgsize: cover;
|
||||
@color-grey-theme-hover: fadeout(lighten(@color-grey-theme, 10%), 30%);
|
||||
@color-grey-theme-active: fadeout(darken(@color-grey-theme, 20%), 60%);
|
||||
@color-grey-theme-font: #fff;
|
||||
@color-grey-theme-font-label: lighten(@color-grey-theme, 35%);
|
||||
@color-grey-theme_2: #fff;
|
||||
@color-grey-theme_2-background_1: #fff;
|
||||
@color-grey-theme_2-background_2: fadeout(@color-grey-theme_2-background_1, 2%);
|
||||
@color-grey-theme_2-hover: fadeout(lighten(@color-grey-theme, 10%), 80%);
|
||||
@color-grey-theme_2-active: fadeout(lighten(@color-grey-theme, 10%), 70%);
|
||||
@color-grey-theme_2-font: darken(desaturate(@color-grey-theme_2, 70%), 90%);
|
||||
@color-grey-theme_2-font-label: fadeout(@color-grey-theme_2-font, 55%);
|
||||
// @color-grey-theme_2-line: transparent;
|
||||
@color-grey-theme_2-line: lighten(@color-grey-theme, 47%);
|
||||
@color-grey-reply-floor: fadeout(@color-grey-theme, 95%);
|
||||
@color-grey-theme-sidebar: @color-grey-theme;
|
||||
@color-grey-btn: lighten(@color-grey-theme, 5%);
|
||||
@color-grey-btn-background: fadeout(lighten(@color-grey-theme, 35%), 70%);
|
||||
@color-grey-btn-hover: fadeout(lighten(@color-grey-theme, 5%), 70%);
|
||||
@color-grey-btn-active: fadeout(darken(@color-grey-theme, 5%), 70%);
|
||||
@color-grey-btn-select: fadeout(lighten(@color-grey-theme, 5%), 50%);
|
||||
@color-grey-pagination-background: fadeout(lighten(@color-grey-theme, 45%), 30%);
|
||||
@color-grey-pagination-hover: fadeout(lighten(@color-grey-theme, 10%), 70%);
|
||||
@color-grey-pagination-active: fadeout(darken(@color-grey-theme, 10%), 70%);
|
||||
@color-grey-pagination-select: fadeout(lighten(@color-grey-theme, 10%), 50%);
|
||||
@color-grey-search-form-background: fadeout(lighten(@color-grey-theme, 35%), 10%);
|
||||
@color-grey-search-list-hover: fadeout(darken(@color-grey-theme, 10%), 70%);
|
||||
@color-grey-scrollbar-track: fadeout(lighten(@color-grey-theme, 10%), 75%);
|
||||
@color-grey-scrollbar-thumb: fadeout(lighten(@color-grey-theme, 10%), 50%);
|
||||
@color-grey-scrollbar-thumb-hover: fadeout(lighten(@color-grey-theme, 10%), 35%);
|
||||
@color-grey-player-pic-c1: fadeout(@color-grey-theme_2, 50%);
|
||||
@color-grey-player-pic-c2: darken(@color-grey-theme_2, 30%);
|
||||
@color-grey-player-progress: darken(@color-grey-theme_2, 6%);
|
||||
@color-grey-player-progress-bar1: darken(@color-grey-theme_2, 12%);
|
||||
@color-grey-player-progress-bar2: fadeout(lighten(@color-grey-theme, 12%), 20%);
|
||||
@color-grey-player-status-text: lighten(@color-grey-theme_2-font, 32%);
|
||||
@color-grey-player-detail-lyric: fadeout(@color-grey-theme_2-font, 55%);
|
||||
@color-grey-player-detail-lyric-active: darken(@color-grey-theme, 3%);
|
||||
@color-grey-player-detail-play-btn: lighten(@color-grey-theme, 7%);
|
||||
@color-grey-tab-btn-background: fadeout(lighten(@color-grey-theme, 10%), 80%);
|
||||
@color-grey-tab-btn-background-hover: @color-grey-theme_2-hover;
|
||||
@color-grey-tab-border-top: fadeout(lighten(@color-grey-theme, 5%), 50%);
|
||||
@color-grey-tab-border-bottom: lighten(@color-grey-theme, 5%);
|
||||
@color-grey-hideBtn: #3bc2b2;
|
||||
@color-grey-minBtn: #85c43b;
|
||||
@color-grey-maxBtn: #e7aa36;
|
||||
@color-grey-closeBtn: #ea6e4d;
|
||||
@color-grey-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-grey-minBtn-hover: fadeout(@color-grey-minBtn, 10%);
|
||||
@color-grey-maxBtn-hover: fadeout(@color-grey-maxBtn, 10%);
|
||||
@color-grey-closeBtn-hover: fadeout(@color-grey-closeBtn, 10%);
|
||||
@color-grey-badge-success: lighten(@color-grey-theme, 15%);
|
||||
@color-grey-badge-success-font: #fff;
|
||||
@color-grey-badge-info: #b19b9f;
|
||||
@color-grey-badge-info-font: #fff;
|
||||
|
||||
|
||||
@color-ming-theme: #336e7b;
|
||||
@color-ming-theme-bgimg: none;
|
||||
@color-ming-theme-bgposition: center;
|
||||
@color-ming-theme-bgsize: cover;
|
||||
@color-ming-theme-hover: fadeout(lighten(@color-ming-theme, 7%), 30%);
|
||||
@color-ming-theme-active: fadeout(darken(@color-ming-theme, 20%), 60%);
|
||||
@color-ming-theme-font: #fff;
|
||||
@color-ming-theme-font-label: lighten(@color-ming-theme, 35%);
|
||||
@color-ming-theme_2: #fff;
|
||||
@color-ming-theme_2-hover: fadeout(lighten(@color-ming-theme, 10%), 85%);
|
||||
@color-ming-theme_2-active: fadeout(lighten(@color-ming-theme, 10%), 75%);
|
||||
@color-ming-theme_2-background_1: #fff;
|
||||
@color-ming-theme_2-background_2: fadeout(@color-ming-theme_2-background_1, 2%);
|
||||
@color-ming-theme_2-font: darken(desaturate(@color-ming-theme_2, 70%), 90%);
|
||||
@color-ming-theme_2-font-label: fadeout(@color-ming-theme_2-font, 55%);
|
||||
// @color-ming-theme_2-line: transparent;
|
||||
@color-ming-theme_2-line: lighten(@color-ming-theme, 60%);
|
||||
@color-ming-reply-floor: fadeout(@color-ming-theme, 95%);
|
||||
@color-ming-theme-sidebar: @color-ming-theme;
|
||||
@color-ming-btn: lighten(@color-ming-theme, 5%);
|
||||
@color-ming-btn-background: fadeout(lighten(@color-ming-theme, 35%), 75%);
|
||||
@color-ming-btn-hover: fadeout(lighten(@color-ming-theme, 5%), 70%);
|
||||
@color-ming-btn-active: fadeout(darken(@color-ming-theme, 5%), 70%);
|
||||
@color-ming-btn-select: fadeout(lighten(@color-ming-theme, 5%), 50%);
|
||||
@color-ming-pagination-background: fadeout(lighten(@color-ming-theme, 50%), 35%);
|
||||
@color-ming-pagination-hover: fadeout(lighten(@color-ming-theme, 15%), 70%);
|
||||
@color-ming-pagination-active: fadeout(darken(@color-ming-theme, 15%), 70%);
|
||||
@color-ming-pagination-select: fadeout(lighten(@color-ming-theme, 15%), 50%);
|
||||
@color-ming-search-form-background: fadeout(lighten(@color-ming-theme, 45%), 15%);
|
||||
@color-ming-search-list-hover: fadeout(darken(@color-ming-theme, 10%), 70%);
|
||||
@color-ming-scrollbar-track: fadeout(lighten(@color-ming-theme, 10%), 75%);
|
||||
@color-ming-scrollbar-thumb: fadeout(lighten(@color-ming-theme, 10%), 50%);
|
||||
@color-ming-scrollbar-thumb-hover: fadeout(lighten(@color-ming-theme, 10%), 35%);
|
||||
@color-ming-player-pic-c1: fadeout(@color-ming-theme_2, 50%);
|
||||
@color-ming-player-pic-c2: darken(@color-ming-theme_2, 30%);
|
||||
@color-ming-player-progress: darken(@color-ming-theme_2, 6%);
|
||||
@color-ming-player-progress-bar1: darken(@color-ming-theme_2, 12%);
|
||||
@color-ming-player-progress-bar2: fadeout(lighten(@color-ming-theme, 12%), 20%);
|
||||
@color-ming-player-status-text: lighten(@color-ming-theme_2-font, 32%);
|
||||
@color-ming-player-detail-lyric: fadeout(@color-ming-theme_2-font, 50%);
|
||||
@color-ming-player-detail-lyric-active: lighten(@color-ming-theme, 2%);
|
||||
@color-ming-player-detail-play-btn: lighten(@color-ming-theme, 10%);
|
||||
@color-ming-tab-border-top: fadeout(lighten(@color-ming-theme, 25%), 70%);
|
||||
@color-ming-tab-border-bottom: lighten(@color-ming-theme, 35%);
|
||||
@color-ming-tab-btn-background: fadeout(lighten(@color-ming-theme, 10%), 80%);
|
||||
@color-ming-tab-btn-background-hover: @color-ming-theme_2-hover;
|
||||
@color-ming-tab-border-top: fadeout(lighten(@color-ming-theme, 5%), 50%);
|
||||
@color-ming-tab-border-bottom: lighten(@color-ming-theme, 5%);
|
||||
@color-ming-hideBtn: #3bc2b2;
|
||||
@color-ming-minBtn: #85c43b;
|
||||
@color-ming-maxBtn: #e7aa36;
|
||||
@color-ming-closeBtn: #ea6e4d;
|
||||
@color-ming-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-ming-minBtn-hover: fadeout(@color-ming-minBtn, 10%);
|
||||
@color-ming-maxBtn-hover: fadeout(@color-ming-maxBtn, 10%);
|
||||
@color-ming-closeBtn-hover: fadeout(@color-ming-closeBtn, 10%);
|
||||
@color-ming-badge-success: lighten(@color-ming-theme, 10%);
|
||||
@color-ming-badge-success-font: #fff;
|
||||
@color-ming-badge-info: #6376a2;
|
||||
@color-ming-badge-info-font: #fff;
|
||||
|
||||
|
||||
@color-blue2-theme: #4f62d0;
|
||||
@color-blue2-theme-bgimg: none;
|
||||
@color-blue2-theme-bgposition: center;
|
||||
@color-blue2-theme-bgsize: cover;
|
||||
@color-blue2-theme-hover: fadeout(lighten(@color-blue2-theme, 10%), 30%);
|
||||
@color-blue2-theme-active: fadeout(darken(@color-blue2-theme, 20%), 60%);
|
||||
@color-blue2-theme-font: #fff;
|
||||
@color-blue2-theme-font-label: lighten(@color-blue2-theme, 35%);
|
||||
@color-blue2-theme_2: #fff;
|
||||
@color-blue2-theme_2-background_1: #fff;
|
||||
@color-blue2-theme_2-background_2: fadeout(@color-blue2-theme_2-background_1, 2%);
|
||||
@color-blue2-theme_2-hover: fadeout(lighten(@color-blue2-theme, 10%), 80%);
|
||||
@color-blue2-theme_2-active: fadeout(lighten(@color-blue2-theme, 10%), 70%);
|
||||
@color-blue2-theme_2-font: darken(desaturate(@color-blue2-theme_2, 70%), 90%);
|
||||
@color-blue2-theme_2-font-label: fadeout(@color-blue2-theme_2-font, 55%);
|
||||
// @color-blue2-theme_2-line: transparent;
|
||||
@color-blue2-theme_2-line: lighten(@color-blue2-theme, 41%);
|
||||
@color-blue2-reply-floor: fadeout(@color-blue2-theme, 95%);
|
||||
@color-blue2-theme-sidebar: @color-blue2-theme;
|
||||
@color-blue2-btn: lighten(@color-blue2-theme, 5%);
|
||||
@color-blue2-btn-background: fadeout(lighten(@color-blue2-theme, 30%), 70%);
|
||||
@color-blue2-btn-hover: fadeout(lighten(@color-blue2-theme, 5%), 70%);
|
||||
@color-blue2-btn-active: fadeout(darken(@color-blue2-theme, 5%), 70%);
|
||||
@color-blue2-btn-select: fadeout(lighten(@color-blue2-theme, 5%), 50%);
|
||||
@color-blue2-pagination-background: fadeout(lighten(@color-blue2-theme, 35%), 30%);
|
||||
@color-blue2-pagination-hover: fadeout(lighten(@color-blue2-theme, 10%), 70%);
|
||||
@color-blue2-pagination-active: fadeout(darken(@color-blue2-theme, 10%), 70%);
|
||||
@color-blue2-pagination-select: fadeout(lighten(@color-blue2-theme, 10%), 50%);
|
||||
@color-blue2-search-form-background: fadeout(lighten(@color-blue2-theme, 35%), 10%);
|
||||
@color-blue2-search-list-hover: fadeout(darken(@color-blue2-theme, 10%), 70%);
|
||||
@color-blue2-scrollbar-track: fadeout(lighten(@color-blue2-theme, 10%), 75%);
|
||||
@color-blue2-scrollbar-thumb: fadeout(lighten(@color-blue2-theme, 10%), 50%);
|
||||
@color-blue2-scrollbar-thumb-hover: fadeout(lighten(@color-blue2-theme, 10%), 35%);
|
||||
@color-blue2-player-pic-c1: fadeout(@color-blue2-theme_2, 50%);
|
||||
@color-blue2-player-pic-c2: darken(@color-blue2-theme_2, 30%);
|
||||
@color-blue2-player-progress: darken(@color-blue2-theme_2, 6%);
|
||||
@color-blue2-player-progress-bar1: darken(@color-blue2-theme_2, 12%);
|
||||
@color-blue2-player-progress-bar2: fadeout(lighten(@color-blue2-theme, 12%), 20%);
|
||||
@color-blue2-player-status-text: lighten(@color-blue2-theme_2-font, 32%);
|
||||
@color-blue2-player-detail-lyric: fadeout(@color-blue2-theme_2-font, 50%);
|
||||
@color-blue2-player-detail-lyric-active: darken(@color-blue2-theme, 2%);
|
||||
@color-blue2-player-detail-play-btn: lighten(@color-blue2-theme, 7%);
|
||||
@color-blue2-tab-btn-background: fadeout(lighten(@color-blue2-theme, 10%), 80%);
|
||||
@color-blue2-tab-btn-background-hover: @color-blue2-theme_2-hover;
|
||||
@color-blue2-tab-border-top: fadeout(lighten(@color-blue2-theme, 5%), 50%);
|
||||
@color-blue2-tab-border-bottom: lighten(@color-blue2-theme, 5%);
|
||||
@color-blue2-hideBtn: #3bc2b2;
|
||||
@color-blue2-minBtn: #85c43b;
|
||||
@color-blue2-maxBtn: #e7aa36;
|
||||
@color-blue2-closeBtn: #ea6e4d;
|
||||
@color-blue2-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-blue2-minBtn-hover: fadeout(@color-blue2-minBtn, 10%);
|
||||
@color-blue2-maxBtn-hover: fadeout(@color-blue2-maxBtn, 10%);
|
||||
@color-blue2-closeBtn-hover: fadeout(@color-blue2-closeBtn, 10%);
|
||||
@color-blue2-badge-success: lighten(@color-blue2-theme, 15%);
|
||||
@color-blue2-badge-success-font: #fff;
|
||||
@color-blue2-badge-info: #b080db;
|
||||
@color-blue2-badge-info-font: #fff;
|
||||
|
||||
|
||||
@color-black-theme: #272727;
|
||||
@color-black-theme-bgimg: url(../images/landingMoon.png);
|
||||
@color-black-theme-bgposition: center;
|
||||
@color-black-theme-bgsize: cover;
|
||||
@color-black-theme-hover: fadeout(lighten(@color-black-theme, 13%), 30%);
|
||||
@color-black-theme-active: fadeout(lighten(@color-black-theme, 8%), 30%);
|
||||
@color-black-theme-font: lighten(@color-black-theme, 55%);
|
||||
@color-black-theme-font-label: lighten(@color-black-theme, 35%);
|
||||
@color-black-theme_2: rgba(19, 19, 19, 0.93);
|
||||
@color-black-theme_2-background_1: #080808;
|
||||
@color-black-theme_2-background_2: #1f1f1f;
|
||||
@color-black-theme_2-hover: fadeout(lighten(@color-black-theme, 10%), 80%);
|
||||
@color-black-theme_2-active: fadeout(lighten(@color-black-theme, 10%), 70%);
|
||||
@color-black-theme_2-font: lighten(@color-black-theme_2, 73%);
|
||||
@color-black-theme_2-font-label: darken(@color-black-theme_2-font, 30%);
|
||||
// @color-black-theme_2-line: transparent;
|
||||
@color-black-theme_2-line: lighten(@color-black-theme_2, 3.2%);
|
||||
@color-black-reply-floor: fadeout(lighten(@color-black-theme, 60%), 95%);
|
||||
@color-black-theme-sidebar: @color-black-theme;
|
||||
@color-black-btn: lighten(@color-black-theme, 40%);
|
||||
@color-black-btn-background: fadeout(lighten(@color-black-theme, 30%), 70%);
|
||||
@color-black-btn-hover: fadeout(lighten(@color-black-theme, 40%), 70%);
|
||||
@color-black-btn-active: fadeout(lighten(@color-black-theme, 20%), 70%);
|
||||
@color-black-btn-select: fadeout(lighten(@color-black-theme, 25%), 70%);
|
||||
@color-black-pagination-background: fadeout(lighten(@color-black-theme, 25%), 20%);
|
||||
@color-black-pagination-hover: fadeout(lighten(@color-black-theme, 1%), 70%);
|
||||
@color-black-pagination-active: fadeout(darken(@color-black-theme, 5%), 60%);
|
||||
@color-black-pagination-select: fadeout(darken(@color-black-theme, 3%), 60%);
|
||||
@color-black-search-form-background: fadeout(lighten(@color-black-theme, 8%), 6%);
|
||||
@color-black-search-list-hover: fadeout(darken(@color-black-theme, 10%), 70%);
|
||||
@color-black-scrollbar-track: fadeout(lighten(@color-black-theme, 10%), 75%);
|
||||
@color-black-scrollbar-thumb: fadeout(lighten(@color-black-theme, 10%), 50%);
|
||||
@color-black-scrollbar-thumb-hover: fadeout(lighten(@color-black-theme, 10%), 35%);
|
||||
@color-black-player-pic-c1: fadeout(@color-black-theme_2, 50%);
|
||||
@color-black-player-pic-c2: lighten(@color-black-theme_2, 30%);
|
||||
@color-black-player-progress: fadeout(lighten(@color-black-theme, 20%), 80%);
|
||||
@color-black-player-progress-bar1: lighten(@color-black-theme_2, 12%);
|
||||
@color-black-player-progress-bar2: fadeout(lighten(@color-black-theme, 12%), 20%);
|
||||
@color-black-player-status-text: darken(@color-black-theme_2-font, 20%);
|
||||
@color-black-player-detail-lyric: fadeout(darken(@color-black-theme_2-font, 30%), 10%);
|
||||
@color-black-player-detail-lyric-active: lighten(@color-black-theme, 50%);
|
||||
@color-black-player-detail-play-btn: lighten(@color-black-theme, 30%);
|
||||
@color-black-tab-btn-background: fadeout(lighten(@color-black-theme, 10%), 80%);
|
||||
@color-black-tab-btn-background-hover: @color-black-theme_2-hover;
|
||||
@color-black-tab-border-top: fadeout(lighten(@color-black-theme, 5%), 50%);
|
||||
@color-black-tab-border-bottom: lighten(@color-black-theme, 5%);
|
||||
@color-black-hideBtn: #4e4e4e;
|
||||
@color-black-minBtn: #5e5e5e;
|
||||
@color-black-maxBtn: #535353;
|
||||
@color-black-closeBtn: #6e6e6e;
|
||||
@color-black-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-black-minBtn-hover: fadeout(@color-black-minBtn, 10%);
|
||||
@color-black-maxBtn-hover: fadeout(@color-black-maxBtn, 10%);
|
||||
@color-black-closeBtn-hover: fadeout(@color-black-closeBtn, 10%);
|
||||
@color-black-badge-success: lighten(@color-black-theme, 10%);
|
||||
@color-black-badge-success-font: #fff;
|
||||
@color-black-badge-info: lighten(@color-black-theme, 25%);
|
||||
@color-black-badge-info-font: #fff;
|
||||
|
||||
|
||||
@color-mid_autumn-theme: rgba(74, 55, 82, 1);
|
||||
@color-mid_autumn-theme-bgimg: url(../images/jqbg.jpg);
|
||||
@color-mid_autumn-theme-bgposition: center;
|
||||
@color-mid_autumn-theme-bgsize: cover;
|
||||
@color-mid_autumn-theme-hover: fadeout(lighten(@color-mid_autumn-theme, 10%), 40%);
|
||||
@color-mid_autumn-theme-active: fadeout(lighten(@color-mid_autumn-theme, 15%), 60%);
|
||||
@color-mid_autumn-theme-font: rgba(246, 233, 255, 0.9);
|
||||
@color-mid_autumn-theme-font-label: fadeout(lighten(@color-mid_autumn-theme, 20%), 20%);
|
||||
@color-mid_autumn-theme_2: rgba(255, 255, 255, .9);
|
||||
@color-mid_autumn-theme_2-background_1: #ececec;
|
||||
@color-mid_autumn-theme_2-background_2: fadeout(@color-mid_autumn-theme_2-background_1, 2%);
|
||||
@color-mid_autumn-theme_2-hover: fadeout(lighten(@color-mid_autumn-theme, 15%), 80%);
|
||||
@color-mid_autumn-theme_2-active: fadeout(lighten(@color-mid_autumn-theme, 15%), 70%);
|
||||
@color-mid_autumn-theme_2-font: darken(desaturate(@color-mid_autumn-theme_2, 70%), 90%);
|
||||
@color-mid_autumn-theme_2-font-label: desaturate(lighten(@color-mid_autumn-theme, 30%), 45%);
|
||||
// @color-mid_autumn-theme_2-line: transparent;
|
||||
@color-mid_autumn-theme_2-line: lighten(@color-mid_autumn-theme, 61%);
|
||||
@color-mid_autumn-reply-floor: fadeout(@color-mid_autumn-theme, 95%);
|
||||
@color-mid_autumn-theme-sidebar: rgba(255, 255, 255, 0);
|
||||
@color-mid_autumn-btn: lighten(@color-mid_autumn-theme, 10%);
|
||||
@color-mid_autumn-btn-background: fadeout(lighten(@color-mid_autumn-theme, 35%), 70%);
|
||||
@color-mid_autumn-btn-hover: fadeout(lighten(@color-mid_autumn-theme, 5%), 70%);
|
||||
@color-mid_autumn-btn-active: fadeout(darken(@color-mid_autumn-theme, 5%), 70%);
|
||||
@color-mid_autumn-btn-select: fadeout(lighten(@color-mid_autumn-theme, 5%), 50%);
|
||||
@color-mid_autumn-pagination-background: fadeout(lighten(@color-mid_autumn-theme, 45%), 50%);
|
||||
@color-mid_autumn-pagination-hover: fadeout(lighten(@color-mid_autumn-theme, 10%), 70%);
|
||||
@color-mid_autumn-pagination-active: fadeout(darken(@color-mid_autumn-theme, 10%), 70%);
|
||||
@color-mid_autumn-pagination-select: fadeout(lighten(@color-mid_autumn-theme, 10%), 55%);
|
||||
@color-mid_autumn-search-form-background: fadeout(lighten(@color-mid_autumn-theme, 50%), 10%);
|
||||
@color-mid_autumn-search-list-hover: fadeout(darken(@color-mid_autumn-theme, 10%), 70%);
|
||||
@color-mid_autumn-scrollbar-track: fadeout(lighten(@color-mid_autumn-theme, 10%), 80%);
|
||||
@color-mid_autumn-scrollbar-thumb: fadeout(lighten(@color-mid_autumn-theme, 10%), 60%);
|
||||
@color-mid_autumn-scrollbar-thumb-hover: fadeout(lighten(@color-mid_autumn-theme, 10%), 40%);
|
||||
@color-mid_autumn-player-pic-c1: fadeout(@color-mid_autumn-theme_2, 50%);
|
||||
@color-mid_autumn-player-pic-c2: darken(@color-mid_autumn-theme_2, 30%);
|
||||
@color-mid_autumn-player-progress: fadeout(lighten(@color-mid_autumn-theme, 20%), 82%);
|
||||
@color-mid_autumn-player-progress-bar1: darken(@color-mid_autumn-theme_2, 12%);
|
||||
@color-mid_autumn-player-progress-bar2: fadeout(lighten(@color-mid_autumn-theme, 12%), 20%);
|
||||
@color-mid_autumn-player-status-text: lighten(@color-mid_autumn-theme_2-font, 32%);
|
||||
@color-mid_autumn-player-detail-lyric: fadeout(@color-mid_autumn-theme_2-font, 40%);
|
||||
@color-mid_autumn-player-detail-lyric-active: lighten(@color-mid_autumn-theme, 7%);
|
||||
@color-mid_autumn-player-detail-play-btn: lighten(@color-mid_autumn-theme, 7%);
|
||||
@color-mid_autumn-tab-btn-background: fadeout(lighten(@color-mid_autumn-theme, 10%), 80%);
|
||||
@color-mid_autumn-tab-btn-background-hover: @color-mid_autumn-theme_2-hover;
|
||||
@color-mid_autumn-tab-border-top: fadeout(lighten(@color-mid_autumn-theme, 5%), 50%);
|
||||
@color-mid_autumn-tab-border-bottom: lighten(@color-mid_autumn-theme, 5%);
|
||||
@color-mid_autumn-hideBtn: #3bc2b2;
|
||||
@color-mid_autumn-minBtn: #85c43b;
|
||||
@color-mid_autumn-maxBtn: #e7aa36;
|
||||
@color-mid_autumn-closeBtn: #ea6e4d;
|
||||
@color-mid_autumn-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-mid_autumn-minBtn-hover: fadeout(@color-mid_autumn-minBtn, 10%);
|
||||
@color-mid_autumn-maxBtn-hover: fadeout(@color-mid_autumn-maxBtn, 10%);
|
||||
@color-mid_autumn-closeBtn-hover: fadeout(@color-mid_autumn-closeBtn, 10%);
|
||||
@color-mid_autumn-badge-success: lighten(@color-mid_autumn-theme, 25%);
|
||||
@color-mid_autumn-badge-success-font: #fff;
|
||||
@color-mid_autumn-badge-info: #af9479;
|
||||
@color-mid_autumn-badge-info-font: #fff;
|
||||
|
||||
/*
|
||||
@color-mid_autumn-theme: #312438;
|
||||
@color-mid_autumn-theme-bgimg: url(../images/jqbg.jpg);
|
||||
@color-mid_autumn-theme-bgposition: center;
|
||||
@color-mid_autumn-theme-bgsize: cover;
|
||||
@color-mid_autumn-theme-hover: fadeout(lighten(@color-mid_autumn-theme, 5%), 60%);
|
||||
@color-mid_autumn-theme-active: fadeout(lighten(@color-mid_autumn-theme, 10%), 70%);
|
||||
@color-mid_autumn-theme-font: rgba(246, 233, 255, 0.9);
|
||||
@color-mid_autumn-theme-font-label: fadeout(lighten(@color-mid_autumn-theme, 20%), 20%);
|
||||
@color-mid_autumn-theme_2: rgba(0, 0, 0, .8);
|
||||
@color-mid_autumn-theme_2-background_1: #030107;
|
||||
@color-mid_autumn-theme_2-background_2: fadeout(darken(desaturate(@color-mid_autumn-theme, 10%), 7%), 2%);
|
||||
@color-mid_autumn-theme_2-hover: fadeout(lighten(@color-mid_autumn-theme, 15%), 80%);
|
||||
@color-mid_autumn-theme_2-active: fadeout(lighten(@color-mid_autumn-theme, 15%), 70%);
|
||||
@color-mid_autumn-theme_2-font: lighten(@color-mid_autumn-theme_2, 90%);
|
||||
@color-mid_autumn-theme_2-font-label: desaturate(lighten(@color-mid_autumn-theme, 30%), 45%);
|
||||
// @color-mid_autumn-theme_2-line: transparent;
|
||||
@color-mid_autumn-theme_2-line: darken(@color-mid_autumn-theme, 18%);
|
||||
@color-mid_autumn-reply-floor: fadeout(@color-mid_autumn-theme, 95%);
|
||||
@color-mid_autumn-theme-sidebar: rgba(255, 255, 255, 0);
|
||||
@color-mid_autumn-btn: darken(@color-mid_autumn-theme, 10%);
|
||||
@color-mid_autumn-btn-background: fadeout(lighten(@color-mid_autumn-theme, 35%), 70%);
|
||||
@color-mid_autumn-btn-hover: fadeout(lighten(@color-mid_autumn-theme, 5%), 70%);
|
||||
@color-mid_autumn-btn-active: fadeout(darken(@color-mid_autumn-theme, 5%), 70%);
|
||||
@color-mid_autumn-btn-select: fadeout(lighten(@color-mid_autumn-theme, 5%), 50%);
|
||||
@color-mid_autumn-pagination-background: fadeout(lighten(@color-mid_autumn-theme, 45%), 50%);
|
||||
@color-mid_autumn-pagination-hover: fadeout(lighten(@color-mid_autumn-theme, 10%), 70%);
|
||||
@color-mid_autumn-pagination-active: fadeout(darken(@color-mid_autumn-theme, 10%), 70%);
|
||||
@color-mid_autumn-pagination-select: fadeout(lighten(@color-mid_autumn-theme, 10%), 55%);
|
||||
@color-mid_autumn-search-form-background: fadeout(lighten(@color-mid_autumn-theme, 50%), 10%);
|
||||
@color-mid_autumn-search-list-hover: fadeout(darken(@color-mid_autumn-theme, 10%), 70%);
|
||||
@color-mid_autumn-scrollbar-track: fadeout(lighten(@color-mid_autumn-theme, 10%), 80%);
|
||||
@color-mid_autumn-scrollbar-thumb: fadeout(lighten(@color-mid_autumn-theme, 10%), 60%);
|
||||
@color-mid_autumn-scrollbar-thumb-hover: fadeout(lighten(@color-mid_autumn-theme, 10%), 40%);
|
||||
@color-mid_autumn-player-pic-c1: fadeout(@color-mid_autumn-theme_2, 50%);
|
||||
@color-mid_autumn-player-pic-c2: darken(@color-mid_autumn-theme_2, 30%);
|
||||
@color-mid_autumn-player-progress: darken(@color-mid_autumn-theme_2, 10%);
|
||||
@color-mid_autumn-player-progress-bar1: darken(@color-mid_autumn-theme_2, 12%);
|
||||
@color-mid_autumn-player-progress-bar2: fadeout(lighten(@color-mid_autumn-theme, 12%), 20%);
|
||||
@color-mid_autumn-player-status-text: lighten(@color-mid_autumn-theme_2-font, 32%);
|
||||
@color-mid_autumn-player-detail-lyric: lighten(@color-mid_autumn-theme, 7%);
|
||||
@color-mid_autumn-player-detail-lyric-active: lighten(@color-mid_autumn-theme, 7%);
|
||||
@color-mid_autumn-player-detail-play-btn: lighten(@color-mid_autumn-theme, 7%);
|
||||
@color-mid_autumn-tab-btn-background: fadeout(lighten(@color-mid_autumn-theme, 10%), 80%);
|
||||
@color-mid_autumn-tab-btn-background-hover: @color-mid_autumn-theme_2-hover;
|
||||
@color-mid_autumn-tab-border-top: fadeout(lighten(@color-mid_autumn-theme, 5%), 50%);
|
||||
@color-mid_autumn-tab-border-bottom: lighten(@color-mid_autumn-theme, 5%);
|
||||
@color-mid_autumn-hideBtn: #3bc2b2;
|
||||
@color-mid_autumn-minBtn: #85c43b;
|
||||
@color-mid_autumn-maxBtn: #e7aa36;
|
||||
@color-mid_autumn-closeBtn: #ea6e4d;
|
||||
@color-mid_autumn-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-mid_autumn-minBtn-hover: fadeout(@color-mid_autumn-minBtn, 10%);
|
||||
@color-mid_autumn-maxBtn-hover: fadeout(@color-mid_autumn-maxBtn, 10%);
|
||||
@color-mid_autumn-closeBtn-hover: fadeout(@color-mid_autumn-closeBtn, 10%);
|
||||
@color-mid_autumn-badge-success: lighten(@color-mid_autumn-theme, 25%);
|
||||
@color-mid_autumn-badge-success-font: #fff;
|
||||
@color-mid_autumn-badge-info: #af9479;
|
||||
@color-mid_autumn-badge-info-font: #fff; */
|
||||
|
||||
@color-naruto-theme: rgb(87, 144, 167);
|
||||
@color-naruto-theme-bgimg: url(../images/myzcbg.jpg);
|
||||
@color-naruto-theme-bgposition: center;
|
||||
@color-naruto-theme-bgsize: cover;
|
||||
@color-naruto-theme-hover: fadeout(lighten(@color-naruto-theme, 10%), 45%);
|
||||
@color-naruto-theme-active: fadeout(lighten(@color-naruto-theme, 15%), 60%);
|
||||
@color-naruto-theme-font: rgba(67, 118, 148, 0.9);
|
||||
@color-naruto-theme-font-label: fadeout(darken(@color-naruto-theme, 20%), 20%);
|
||||
@color-naruto-theme_2: rgba(255, 255, 255, 0.8);
|
||||
@color-naruto-theme_2-background_1: #eafaff;
|
||||
@color-naruto-theme_2-background_2: fadeout(@color-naruto-theme_2-background_1, 2%);
|
||||
@color-naruto-theme_2-hover: fadeout(lighten(@color-naruto-theme, 10%), 75%);
|
||||
@color-naruto-theme_2-active: fadeout(lighten(@color-naruto-theme, 10%), 65%);
|
||||
@color-naruto-theme_2-font: darken(desaturate(@color-naruto-theme_2, 70%), 90%);
|
||||
@color-naruto-theme_2-font-label: desaturate(lighten(@color-naruto-theme, 10%), 45%);
|
||||
// @color-naruto-theme_2-line: transparent;
|
||||
@color-naruto-theme_2-line: fadeout(lighten(@color-naruto-theme, 36%), 70%);
|
||||
@color-naruto-reply-floor: fadeout(lighten(@color-naruto-theme, 12%), 90%);
|
||||
@color-naruto-theme-sidebar: rgba(255, 255, 255, .3);
|
||||
@color-naruto-btn: lighten(@color-naruto-theme, 2%);
|
||||
@color-naruto-btn-background: fadeout(lighten(@color-naruto-theme, 35%), 70%);
|
||||
@color-naruto-btn-hover: fadeout(lighten(@color-naruto-theme, 5%), 70%);
|
||||
@color-naruto-btn-active: fadeout(darken(@color-naruto-theme, 5%), 70%);
|
||||
@color-naruto-btn-select: fadeout(lighten(@color-naruto-theme, 5%), 50%);
|
||||
@color-naruto-pagination-background: fadeout(lighten(@color-naruto-theme, 20%), 70%);
|
||||
@color-naruto-pagination-hover: fadeout(lighten(@color-naruto-theme, 10%), 60%);
|
||||
@color-naruto-pagination-active: fadeout(darken(@color-naruto-theme, 10%), 70%);
|
||||
@color-naruto-pagination-select: fadeout(lighten(@color-naruto-theme, 10%), 50%);
|
||||
@color-naruto-search-form-background: fadeout(lighten(@color-naruto-theme, 30%), 20%);
|
||||
@color-naruto-search-list-hover: fadeout(darken(@color-naruto-theme, 10%), 65%);
|
||||
@color-naruto-scrollbar-track: fadeout(lighten(@color-naruto-theme, 10%), 75%);
|
||||
@color-naruto-scrollbar-thumb: fadeout(lighten(@color-naruto-theme, 10%), 50%);
|
||||
@color-naruto-scrollbar-thumb-hover: fadeout(lighten(@color-naruto-theme, 10%), 35%);
|
||||
@color-naruto-player-pic-c1: fadeout(@color-naruto-theme_2, 50%);
|
||||
@color-naruto-player-pic-c2: darken(@color-naruto-theme_2, 30%);
|
||||
@color-naruto-player-progress: fadeout(lighten(@color-naruto-theme, 20%), 70%);
|
||||
@color-naruto-player-progress-bar1: darken(@color-naruto-theme_2, 12%);
|
||||
@color-naruto-player-progress-bar2: fadeout(lighten(@color-naruto-theme, 12%), 20%);
|
||||
@color-naruto-player-status-text: lighten(@color-naruto-theme_2-font, 32%);
|
||||
@color-naruto-player-detail-lyric: fadeout(@color-naruto-theme_2-font, 36%);
|
||||
@color-naruto-player-detail-lyric-active: darken(@color-naruto-theme, 7%);
|
||||
@color-naruto-player-detail-play-btn: lighten(@color-naruto-theme, 7%);
|
||||
@color-naruto-tab-btn-background: fadeout(lighten(@color-naruto-theme, 10%), 80%);
|
||||
@color-naruto-tab-btn-background-hover: @color-naruto-theme_2-hover;
|
||||
@color-naruto-tab-border-top: fadeout(lighten(@color-naruto-theme, 5%), 50%);
|
||||
@color-naruto-tab-border-bottom: lighten(@color-naruto-theme, 5%);
|
||||
@color-naruto-hideBtn: #3bc2b2;
|
||||
@color-naruto-minBtn: #85c43b;
|
||||
@color-naruto-maxBtn: #e7aa36;
|
||||
@color-naruto-closeBtn: #ea6e4d;
|
||||
@color-naruto-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-naruto-minBtn-hover: fadeout(@color-naruto-minBtn, 10%);
|
||||
@color-naruto-maxBtn-hover: fadeout(@color-naruto-maxBtn, 10%);
|
||||
@color-naruto-closeBtn-hover: fadeout(@color-naruto-closeBtn, 10%);
|
||||
@color-naruto-badge-success: #7fb575;
|
||||
@color-naruto-badge-success-font: #fff;
|
||||
@color-naruto-badge-info: lighten(@color-naruto-theme, 10%);
|
||||
@color-naruto-badge-info-font: #fff;
|
||||
|
||||
@color-happy_new_year-theme: rgba(192, 57, 43, 1);
|
||||
@color-happy_new_year-theme-bgimg: url(../images/xnkl.png);
|
||||
@color-happy_new_year-theme-bgposition: center;
|
||||
@color-happy_new_year-theme-bgsize: cover;
|
||||
@color-happy_new_year-theme-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 45%);
|
||||
@color-happy_new_year-theme-active: fadeout(lighten(@color-happy_new_year-theme, 15%), 60%);
|
||||
@color-happy_new_year-theme-font: rgba(148, 67, 67, 0.9);
|
||||
@color-happy_new_year-theme-font-label: fadeout(darken(@color-happy_new_year-theme, 20%), 20%);
|
||||
@color-happy_new_year-theme_2: rgba(255, 255, 255, 0.8);
|
||||
@color-happy_new_year-theme_2-background_1: #fef9f2;
|
||||
@color-happy_new_year-theme_2-background_2: fadeout(@color-happy_new_year-theme_2-background_1, 2%);
|
||||
@color-happy_new_year-theme_2-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 75%);
|
||||
@color-happy_new_year-theme_2-active: fadeout(lighten(@color-happy_new_year-theme, 10%), 65%);
|
||||
@color-happy_new_year-theme_2-font: darken(desaturate(@color-happy_new_year-theme_2, 70%), 90%);
|
||||
@color-happy_new_year-theme_2-font-label: desaturate(lighten(@color-happy_new_year-theme, 10%), 45%);
|
||||
// @color-happy_new_year-theme_2-line: transparent;
|
||||
@color-happy_new_year-theme_2-line: fadeout(lighten(@color-happy_new_year-theme, 36%), 80%);
|
||||
@color-happy_new_year-reply-floor: fadeout(lighten(@color-happy_new_year-theme, 12%), 90%);
|
||||
@color-happy_new_year-theme-sidebar: rgba(255, 255, 255, .3);
|
||||
@color-happy_new_year-btn: lighten(@color-happy_new_year-theme, 2%);
|
||||
@color-happy_new_year-btn-background: fadeout(lighten(@color-happy_new_year-theme, 35%), 70%);
|
||||
@color-happy_new_year-btn-hover: fadeout(lighten(@color-happy_new_year-theme, 5%), 70%);
|
||||
@color-happy_new_year-btn-active: fadeout(darken(@color-happy_new_year-theme, 5%), 70%);
|
||||
@color-happy_new_year-btn-select: fadeout(lighten(@color-happy_new_year-theme, 5%), 50%);
|
||||
@color-happy_new_year-pagination-background: fadeout(lighten(@color-happy_new_year-theme, 20%), 70%);
|
||||
@color-happy_new_year-pagination-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 60%);
|
||||
@color-happy_new_year-pagination-active: fadeout(darken(@color-happy_new_year-theme, 10%), 70%);
|
||||
@color-happy_new_year-pagination-select: fadeout(lighten(@color-happy_new_year-theme, 10%), 50%);
|
||||
@color-happy_new_year-search-form-background: fadeout(lighten(@color-happy_new_year-theme, 32%), 20%);
|
||||
@color-happy_new_year-search-list-hover: fadeout(darken(@color-happy_new_year-theme, 1%), 70%);
|
||||
@color-happy_new_year-scrollbar-track: fadeout(lighten(@color-happy_new_year-theme, 10%), 70%);
|
||||
@color-happy_new_year-scrollbar-thumb: fadeout(lighten(@color-happy_new_year-theme, 10%), 50%);
|
||||
@color-happy_new_year-scrollbar-thumb-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 35%);
|
||||
@color-happy_new_year-player-pic-c1: fadeout(@color-happy_new_year-theme_2, 50%);
|
||||
@color-happy_new_year-player-pic-c2: darken(@color-happy_new_year-theme_2, 30%);
|
||||
@color-happy_new_year-player-progress: fadeout(lighten(@color-happy_new_year-theme, 20%), 82%);
|
||||
@color-happy_new_year-player-progress-bar1: darken(@color-happy_new_year-theme_2, 5%);
|
||||
@color-happy_new_year-player-progress-bar2: fadeout(lighten(@color-happy_new_year-theme, 5%), 20%);
|
||||
@color-happy_new_year-player-status-text: lighten(@color-happy_new_year-theme_2-font, 32%);
|
||||
@color-happy_new_year-player-detail-lyric: fadeout(@color-happy_new_year-theme_2-font, 36%);
|
||||
@color-happy_new_year-player-detail-lyric-active: darken(@color-happy_new_year-theme, 7%);
|
||||
@color-happy_new_year-player-detail-play-btn: lighten(@color-happy_new_year-theme, 7%);
|
||||
@color-happy_new_year-tab-btn-background: fadeout(lighten(@color-happy_new_year-theme, 10%), 80%);
|
||||
@color-happy_new_year-tab-btn-background-hover: @color-happy_new_year-theme_2-hover;
|
||||
@color-happy_new_year-tab-border-top: fadeout(lighten(@color-happy_new_year-theme, 5%), 50%);
|
||||
@color-happy_new_year-tab-border-bottom: lighten(@color-happy_new_year-theme, 5%);
|
||||
@color-happy_new_year-hideBtn: #3bc2b2;
|
||||
@color-happy_new_year-minBtn: #85c43b;
|
||||
@color-happy_new_year-maxBtn: #e7aa36;
|
||||
@color-happy_new_year-closeBtn: #ea6e4d;
|
||||
@color-happy_new_year-hideBtn-hover: fadeout(@color-hideBtn, 10%);
|
||||
@color-happy_new_year-minBtn-hover: fadeout(@color-happy_new_year-minBtn, 10%);
|
||||
@color-happy_new_year-maxBtn-hover: fadeout(@color-happy_new_year-maxBtn, 10%);
|
||||
@color-happy_new_year-closeBtn-hover: fadeout(@color-happy_new_year-closeBtn, 10%);
|
||||
@color-happy_new_year-badge-success: lighten(@color-happy_new_year-theme, 10%);
|
||||
@color-happy_new_year-badge-success-font: #fff;
|
||||
@color-happy_new_year-badge-info: #dfbb6b;
|
||||
@color-happy_new_year-badge-info-font: #fff;
|
||||
|
||||
|
||||
|
||||
// Width
|
||||
@width-app-left: 6.6%;
|
||||
|
||||
// Height
|
||||
@height-toolbar: 54px;
|
||||
@height-player: 60px;
|
||||
@height-player: 70px;
|
||||
|
||||
|
||||
// Shadow
|
||||
@@ -990,6 +17,8 @@
|
||||
@radius-progress-border: 5px;
|
||||
@radius-border: 4px;
|
||||
|
||||
@transition-theme: .4s ease;
|
||||
@transition-slow: .6s ease;
|
||||
@transition-normal: .4s ease;
|
||||
@transition-fast: .3s ease;
|
||||
|
||||
@form-radius: 3px;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" ?><svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M3 5C3 3.89543 3.89543 3 5 3H15C16.1046 3 17 3.89543 17 5V15C17 16.1046 16.1046 17 15 17H5C3.89543 17 3 16.1046 3 15V5ZM5 4C4.44772 4 4 4.44772 4 5V15C4 15.5523 4.44772 16 5 16H15C15.5523 16 16 15.5523 16 15V5C16 4.44772 15.5523 4 15 4H5Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 407 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" /></svg>
|
||||
|
After Width: | Height: | Size: 120 B |
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<button
|
||||
:class="[$style.btn, {[$style.min]: min}, {[$style.outline]: outline}]"
|
||||
:disabled="disabled">
|
||||
<slot />
|
||||
</button>
|
||||
<button
|
||||
:class="[$style.btn, {[$style.min]: min}, {[$style.outline]: outline}]"
|
||||
tabindex="0"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -34,13 +36,14 @@ export default {
|
||||
border-radius: @form-radius;
|
||||
cursor: pointer;
|
||||
padding: 8px 15px;
|
||||
color: @color-btn;
|
||||
color: var(--color-button-font);
|
||||
outline: none;
|
||||
transition: background-color 0.2s ease;
|
||||
background-color: @color-btn-background;
|
||||
background-color: var(--color-button-background);
|
||||
font-size: 14px;
|
||||
&[disabled] {
|
||||
opacity: .4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.outline {
|
||||
@@ -48,10 +51,10 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-btn-active;
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,22 +63,4 @@ export default {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.btn {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&.outline {
|
||||
background-color: transparent;
|
||||
}
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-btn-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,50 +1,60 @@
|
||||
<template lang="pug">
|
||||
div(:class="$style.checkbox")
|
||||
input(:type="need ? 'radio' : 'checkbox'" :class="$style.input" :id="id" :disabled="disabled" :value="value" :name="name" @change="change" v-model="bool")
|
||||
label(:for="id" :class="$style.content")
|
||||
div(v-if="indeterminate" :class="$style.container")
|
||||
svg(v-show="indeterminate" :class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
|
||||
use(xlink:href='#icon-check-indeterminate')
|
||||
svg(v-show="!indeterminate" :class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 0 448 512' space='preserve')
|
||||
use(xlink:href='#icon-check-true')
|
||||
div(v-else :class="$style.container")
|
||||
svg(version='1.1' :class="$style.icon" xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
|
||||
use(xlink:href='#icon-check-true')
|
||||
span(v-if="label != null" v-html="label" :class="$style.label")
|
||||
<template>
|
||||
<div :class="$style.checkbox">
|
||||
<input
|
||||
:id="id" :type="need ? 'radio' : 'checkbox'" :aria-hidden="true" :checked="checked"
|
||||
:class="$style.input" :disabled="disabled" :value="value" :name="name" @input="handleInput"
|
||||
>
|
||||
<label :for="id" :class="$style.content">
|
||||
<div :class="$style.container" role="checkbox" tabindex="0" :aria-label="ariaLabel || label" :aria-checked="checked" :aria-disabled="disabled">
|
||||
<svg version="1.1" :class="$style.icon" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" width="100%" viewBox="0 32 448 448" space="preserve">
|
||||
<use xlink:href="#icon-check-true" />
|
||||
</svg>
|
||||
</div>
|
||||
<span v-if="label != null" :class="$style.label" v-text="label" />
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {},
|
||||
value: {},
|
||||
modelValue: {
|
||||
type: [String, Boolean, Number, Array],
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: [String, Boolean, Number, Array],
|
||||
default: undefined,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
need: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ariaLabel: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
indeterminate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
data() {
|
||||
return {
|
||||
bool: false,
|
||||
checked: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -56,57 +66,38 @@ export default {
|
||||
this.setValue(this.modelValue)
|
||||
},
|
||||
methods: {
|
||||
change() {
|
||||
let checked
|
||||
handleInput(event) {
|
||||
const checked = event.target.checked
|
||||
let modelValue
|
||||
if (Array.isArray(this.modelValue)) {
|
||||
checked = [...this.modelValue]
|
||||
const index = checked.indexOf(this.value)
|
||||
if (index < 0) {
|
||||
checked.push(this.value)
|
||||
} else {
|
||||
checked.splice(index, 1)
|
||||
modelValue = [...this.modelValue]
|
||||
if (checked) modelValue.push(this.value)
|
||||
else {
|
||||
const index = modelValue.indexOf(this.value)
|
||||
if (index > -1) modelValue.splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
let bool = this.bool
|
||||
switch (typeof this.modelValue) {
|
||||
case 'boolean':
|
||||
if (this.indeterminate) {
|
||||
bool = true
|
||||
this.$nextTick(() => {
|
||||
this.bool = bool
|
||||
})
|
||||
}
|
||||
checked = bool
|
||||
break
|
||||
case 'number':
|
||||
checked = this.value
|
||||
break
|
||||
default:
|
||||
checked = bool ? this.value : ''
|
||||
break
|
||||
}
|
||||
if (typeof this.modelValue == 'boolean') {
|
||||
modelValue = checked
|
||||
} else modelValue = checked ? this.value : ''
|
||||
}
|
||||
this.$emit('update:modelValue', checked)
|
||||
this.$emit('change', checked)
|
||||
this.$emit('update:modelValue', modelValue)
|
||||
this.$emit('change', modelValue)
|
||||
},
|
||||
setValue(value) {
|
||||
let bool
|
||||
let checked
|
||||
if (Array.isArray(value)) {
|
||||
bool = value.includes(this.value)
|
||||
checked = value.includes(this.value)
|
||||
} else {
|
||||
switch (typeof value) {
|
||||
case 'boolean':
|
||||
bool = value
|
||||
break
|
||||
// case 'string':
|
||||
// case 'number':
|
||||
default:
|
||||
bool = value === this.value
|
||||
break
|
||||
}
|
||||
if (typeof this.modelValue == 'boolean') {
|
||||
checked = this.modelValue
|
||||
} else if (value == null) checked = this.modelValue != ''
|
||||
else checked = this.modelValue == this.value
|
||||
}
|
||||
|
||||
this.bool = this.need ? bool && this.value : bool
|
||||
// console.log(this.need, this.value, checked)
|
||||
// this.checked = this.need ? checked && this.value : checked
|
||||
if (this.checked == checked) return
|
||||
this.checked = checked
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -125,13 +116,16 @@ export default {
|
||||
&[disabled] {
|
||||
+ .content {
|
||||
opacity: .5;
|
||||
.container, .label {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:checked {
|
||||
+ .content {
|
||||
.container {
|
||||
&:after {
|
||||
border-color: @color-theme;
|
||||
border-color: var(--color-primary-font);
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
@@ -143,19 +137,16 @@ export default {
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
align-items: center;
|
||||
}
|
||||
.container {
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
color: @color-theme;
|
||||
margin-top: .25em;
|
||||
color: var(--color-primary);
|
||||
// border: 1px solid #ccc;
|
||||
&:after {
|
||||
position: absolute;
|
||||
@@ -164,7 +155,7 @@ export default {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 1px solid @color-theme_2-font-label;
|
||||
border: 1px solid var(--color-font-label);
|
||||
transition: border-color 0.2s ease;
|
||||
border-radius: 2px;
|
||||
}
|
||||
@@ -179,36 +170,9 @@ export default {
|
||||
|
||||
.label {
|
||||
flex: auto;
|
||||
line-height: 1;
|
||||
margin-left: 5px;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.checkbox {
|
||||
> input {
|
||||
&:checked {
|
||||
+ .content {
|
||||
.container {
|
||||
&:after {
|
||||
border-color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
.container {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
// border: 1px solid #ccc;
|
||||
&:after {
|
||||
border-color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<input
|
||||
:class="$style.input"
|
||||
ref="dom_input"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:value="modelValue"
|
||||
:disabled="disabled"
|
||||
@input="handleInput"
|
||||
@change="$emit('change', $event.target.value.trim())"
|
||||
@keyup.enter="$emit('submit', $event.target.value.trim())"
|
||||
/>
|
||||
<input
|
||||
ref="dom_input"
|
||||
:class="$style.input"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:value="modelValue"
|
||||
:disabled="disabled"
|
||||
tabindex="0"
|
||||
@input="handleInput"
|
||||
@change="$emit('change', $event.target.value.trim())"
|
||||
@keyup.enter="$emit('submit', $event.target.value.trim())"
|
||||
>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -55,10 +56,10 @@ export default {
|
||||
border: none;
|
||||
border-radius: @form-radius;
|
||||
padding: 7px 8px;
|
||||
color: @color-btn;
|
||||
color: var(--color-button-font);
|
||||
outline: none;
|
||||
transition: background-color 0.2s ease;
|
||||
background-color: @color-btn-background;
|
||||
background-color: var(--color-primary-background);
|
||||
font-size: 13.3px;
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
@@ -72,10 +73,10 @@ export default {
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: @color-theme_2-hover;
|
||||
background-color: var(--color-primary-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
background-color: var(--color-primary-background-active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,19 +85,4 @@ export default {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.input {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&:hover, &:focus {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,42 @@
|
||||
<template>
|
||||
<ul :class="$style.list" :style="listStyles" ref="dom_list" role="toolbar" :aria-hidden="!show">
|
||||
<li v-for="item in menus"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
:aria-label="item[itemName]"
|
||||
ignore-tip
|
||||
:key="item.action"
|
||||
@click="handleClick(item)"
|
||||
v-show="!item.hide && (item.action == 'download' ? setting.download.enable : true)"
|
||||
:disabled="item.disabled ? true : null">{{item[itemName]}}</li>
|
||||
</ul>
|
||||
<teleport to="#root">
|
||||
<ul ref="dom_menu" :class="$style.list" :style="menuStyles" role="toolbar" :aria-hidden="!modelValue">
|
||||
<li
|
||||
v-for="item in menus"
|
||||
v-show="!item.hide && (item.action == 'download' ? appSetting['download.enable'] : true)"
|
||||
:key="item.action"
|
||||
:class="$style.listItem"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
:aria-label="item[itemName]"
|
||||
ignore-tip
|
||||
:disabled="item.disabled ? true : null"
|
||||
@click="menuClick(item)"
|
||||
>
|
||||
{{ item[itemName] }}
|
||||
</li>
|
||||
</ul>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { computed } from '@common/utils/vueTools'
|
||||
import useMenuLocation from '@renderer/utils/compositions/useMenuLocation'
|
||||
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'MenuToolBar',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
xy: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
menus: {
|
||||
type: Array,
|
||||
default() {
|
||||
@@ -27,103 +47,34 @@ export default {
|
||||
type: String,
|
||||
default: 'name',
|
||||
},
|
||||
isShow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
location: {
|
||||
type: Object,
|
||||
default() {
|
||||
return { x: 0, y: 0 }
|
||||
},
|
||||
},
|
||||
},
|
||||
emits: ['menu-click'],
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
},
|
||||
watch: {
|
||||
isShow: {
|
||||
handler(n) {
|
||||
n ? this.handleShow() : this.handleHide()
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
location: {
|
||||
handler(n) {
|
||||
this.listStyles.left = n.x + 2 + 'px'
|
||||
this.listStyles.top = n.y + 'px'
|
||||
if (this.show) {
|
||||
if (this.listStyles.transitionProperty != this.transition2) this.listStyles.transitionProperty = this.transition2
|
||||
this.listStyles.transform = `scaleY(1) translate(${this.handleGetOffsetXY(n.x, n.y)})`
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
listStyles: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
opacity: 0,
|
||||
transitionProperty: 'transform, opacity',
|
||||
transform: 'scale(0) translate(0,0)',
|
||||
},
|
||||
transition1: 'transform, opacity',
|
||||
transition2: 'transform, opacity, top, left',
|
||||
transitionProperty: 'transform, opacity',
|
||||
emits: ['update:modelValue', 'menu-click'],
|
||||
setup(props, { emit }) {
|
||||
const visible = computed(() => props.modelValue)
|
||||
const location = computed(() => props.xy)
|
||||
|
||||
const onHide = () => {
|
||||
emit('update:modelValue', false)
|
||||
menuClick(null)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleDocumentClick)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.handleDocumentClick)
|
||||
},
|
||||
methods: {
|
||||
handleShow() {
|
||||
this.show = true
|
||||
this.listStyles.opacity = 1
|
||||
this.listStyles.transform = `scaleY(1) translate(${this.handleGetOffsetXY(this.location.x, this.location.y)})`
|
||||
},
|
||||
handleHide(e) {
|
||||
this.listStyles.opacity = 0
|
||||
this.listStyles.transform = 'scale(0) translate(0, 0)'
|
||||
this.show = false
|
||||
},
|
||||
handleGetOffsetXY(left, top) {
|
||||
const listWidth = this.$refs.dom_list.clientWidth
|
||||
const listHeight = this.$refs.dom_list.clientHeight
|
||||
const dom_container = this.$refs.dom_list.offsetParent
|
||||
const containerWidth = dom_container.clientWidth
|
||||
const containerHeight = dom_container.clientHeight
|
||||
const offsetWidth = containerWidth - left - listWidth
|
||||
const offsetHeight = containerHeight - top - listHeight
|
||||
let x = 0
|
||||
let y = 0
|
||||
if (containerWidth > listWidth && offsetWidth < 17) {
|
||||
x = offsetWidth - 17
|
||||
}
|
||||
if (containerHeight > listHeight && offsetHeight < 5) {
|
||||
y = offsetHeight - 5
|
||||
}
|
||||
return `${x}px, ${y}px`
|
||||
},
|
||||
handleDocumentClick(event) {
|
||||
if (!this.show) return
|
||||
|
||||
if (event.target == this.$refs.dom_list || this.$refs.dom_list.contains(event.target)) return
|
||||
const { dom_menu, menuStyles } = useMenuLocation({
|
||||
visible,
|
||||
location,
|
||||
onHide,
|
||||
})
|
||||
|
||||
if (this.show && this.listStyles.transitionProperty != this.transition1) this.listStyles.transitionProperty = this.transition1
|
||||
this.handleClick(null)
|
||||
},
|
||||
handleClick(item) {
|
||||
const menuClick = (item) => {
|
||||
if (item && item.disabled) return
|
||||
emit('menu-click', item)
|
||||
}
|
||||
|
||||
this.$emit('menu-click', item)
|
||||
},
|
||||
return {
|
||||
dom_menu,
|
||||
menuStyles,
|
||||
menuClick,
|
||||
appSetting,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -138,60 +89,43 @@ export default {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transform-origin: 0 0 0;
|
||||
transition: .25s ease;
|
||||
transition: .14s ease;
|
||||
transition-property: transform, opacity;
|
||||
border-radius: @radius-border;
|
||||
background-color: @color-theme_2-background_2;
|
||||
background-color: var(--color-content-background);
|
||||
box-shadow: 0 1px 8px 0 rgba(0,0,0,.2);
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
// will-change: transform;
|
||||
li {
|
||||
cursor: pointer;
|
||||
min-width: 96px;
|
||||
line-height: 34px;
|
||||
// color: @color-btn;
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
transition: @transition-theme;
|
||||
transition-property: background-color, opacity;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
.listItem {
|
||||
cursor: pointer;
|
||||
min-width: 96px;
|
||||
line-height: 34px;
|
||||
// color: var(--color-button-font);
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
transition: @transition-normal;
|
||||
transition-property: background-color, opacity;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
// background-color: var(--color-primary-light-600-alpha-800);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-primary-background-active);
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: default;
|
||||
opacity: .4;
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: default;
|
||||
opacity: .4;
|
||||
&:hover {
|
||||
background: none !important;
|
||||
}
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.list {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
li {
|
||||
// color: ~'@{color-@{value}-btn}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<component :is="containerEl" ref="dom_scrollContainer" :class="containerClass" tabindex="0" style="outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict;">
|
||||
<component :is="contentEl" ref="dom_list" :class="contentClass" :style="contentStyle">
|
||||
<!-- <div v-for="item in views" :key="item.key" :style="item.style">
|
||||
<slot name="default" v-bind="{ item: item.item, index: item.index }" />
|
||||
</div> -->
|
||||
</component>
|
||||
<slot name="footer" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
computed,
|
||||
ref,
|
||||
nextTick,
|
||||
watch,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
render,
|
||||
// h,
|
||||
} from 'vue'
|
||||
|
||||
const easeInOutQuad = (t, b, c, d) => {
|
||||
t /= d / 2
|
||||
if (t < 1) return (c / 2) * t * t + b
|
||||
t--
|
||||
return (-c / 2) * (t * (t - 2) - 1) + b
|
||||
}
|
||||
const handleScroll = (element, to, duration = 300, callback = () => {}, onCancel = () => {}) => {
|
||||
if (!element) return callback()
|
||||
const start = element.scrollTop || element.scrollY || 0
|
||||
let cancel = false
|
||||
if (to > start) {
|
||||
let maxScrollTop = element.scrollHeight - element.clientHeight
|
||||
if (to > maxScrollTop) to = maxScrollTop
|
||||
} else if (to < start) {
|
||||
if (to < 0) to = 0
|
||||
} else return callback()
|
||||
const change = to - start
|
||||
const increment = 10
|
||||
if (!change) return callback()
|
||||
|
||||
let currentTime = 0
|
||||
let val
|
||||
let cancelCallback
|
||||
|
||||
const animateScroll = () => {
|
||||
currentTime += increment
|
||||
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
|
||||
if (element.scrollTo) {
|
||||
element.scrollTo(0, val)
|
||||
} else {
|
||||
element.scrollTop = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
if (cancel) {
|
||||
cancelCallback()
|
||||
onCancel()
|
||||
return
|
||||
}
|
||||
setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
animateScroll()
|
||||
return (callback) => {
|
||||
cancelCallback = callback
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'VirtualizedList',
|
||||
props: {
|
||||
containerEl: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
default: 'virtualized-list',
|
||||
},
|
||||
contentEl: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
contentClass: {
|
||||
type: String,
|
||||
default: 'virtualized-list-content',
|
||||
},
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
keyName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['scroll'],
|
||||
setup(props, { emit, slots }) {
|
||||
const views = ref([])
|
||||
const dom_scrollContainer = ref(null)
|
||||
const dom_list = ref(null)
|
||||
let startIndex = -1
|
||||
let endIndex = -1
|
||||
let scrollTop = -1
|
||||
let cachedList = []
|
||||
let cancelScroll = null
|
||||
let isScrolling = false
|
||||
let scrollToValue = 0
|
||||
|
||||
const createList = (startIndex, endIndex) => {
|
||||
if (startIndex == endIndex) return []
|
||||
console.log(startIndex, endIndex)
|
||||
const cache = cachedList.slice(startIndex, endIndex)
|
||||
const list = props.list.slice(startIndex, endIndex).map((item, i) => {
|
||||
if (cache[i]) return cache[i]
|
||||
const top = (startIndex + i) * props.itemHeight
|
||||
const index = startIndex + i
|
||||
return cachedList[index] = {
|
||||
item,
|
||||
top,
|
||||
style: { position: 'absolute', left: 0, right: 0, top: top + 'px', height: props.itemHeight + 'px' },
|
||||
index,
|
||||
key: item[props.keyName],
|
||||
}
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
const renderListItem = (list, type) => {
|
||||
if (!list.length) return
|
||||
console.log(list)
|
||||
// const dom = document.createDocumentFragment()
|
||||
switch (type) {
|
||||
case 'up':
|
||||
break
|
||||
case 'down':
|
||||
break
|
||||
default:
|
||||
console.log(render(null, slots.default(list[0])[0]))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const updateView = (force = false, currentScrollTop = dom_scrollContainer.value.scrollTop) => {
|
||||
// const currentScrollTop = this.$refs.dom_scrollContainer.scrollTop
|
||||
const itemHeight = props.itemHeight
|
||||
const currentStartIndex = Math.floor(currentScrollTop / itemHeight)
|
||||
const scrollContainerHeight = dom_scrollContainer.value.clientHeight
|
||||
const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
|
||||
const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
|
||||
const currentStartRenderIndex = Math.max(currentStartIndex, 0)
|
||||
const currentEndRenderIndex = currentEndIndex + 1
|
||||
// console.log(continuous, currentStartIndex, endIndex, currentEndIndex, startIndex)
|
||||
// debugger
|
||||
if (!force && continuous) {
|
||||
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * 0.6) return
|
||||
// console.log('update')
|
||||
if (currentScrollTop > scrollTop) { // scroll down
|
||||
console.log('scroll down')
|
||||
renderListItem(createList(endIndex + 1, currentEndRenderIndex))
|
||||
// // views.value.push(...list.slice(list.indexOf(views.value[views.value.length - 1]) + 1))
|
||||
// // // if (this.views.length > 100) {
|
||||
// // nextTick(() => {
|
||||
// // views.value.splice(0, views.value.indexOf(list[0]))
|
||||
// // })
|
||||
// // }
|
||||
} else if (currentScrollTop < scrollTop) { // scroll up
|
||||
console.log('scroll up')
|
||||
renderListItem(createList(currentStartRenderIndex, startIndex))
|
||||
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
} else return
|
||||
// if (currentScrollTop == scrollTop && endIndex >= currentEndIndex) return
|
||||
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
} else {
|
||||
renderListItem(createList(currentStartRenderIndex, currentEndRenderIndex))
|
||||
}
|
||||
startIndex = currentStartIndex
|
||||
endIndex = currentEndIndex
|
||||
scrollTop = currentScrollTop
|
||||
}
|
||||
|
||||
const onScroll = event => {
|
||||
const currentScrollTop = dom_scrollContainer.value.scrollTop
|
||||
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * 0.6) {
|
||||
updateView(false, currentScrollTop)
|
||||
}
|
||||
emit('scroll', event)
|
||||
}
|
||||
|
||||
const scrollTo = (scrollTop, animate = false) => {
|
||||
return new Promise(resolve => {
|
||||
if (cancelScroll) {
|
||||
cancelScroll(resolve)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
}).then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (animate) {
|
||||
isScrolling = true
|
||||
scrollToValue = scrollTop
|
||||
cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
|
||||
cancelScroll = null
|
||||
isScrolling = false
|
||||
resolve()
|
||||
}, () => {
|
||||
cancelScroll = null
|
||||
isScrolling = false
|
||||
reject('canceled')
|
||||
})
|
||||
} else {
|
||||
dom_scrollContainer.value.scrollTop = scrollTop
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const scrollToIndex = (index, offset = 0, animate = false) => {
|
||||
return scrollTo(Math.max(index * props.itemHeight + offset, 0), animate)
|
||||
}
|
||||
|
||||
const getScrollTop = () => {
|
||||
return isScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
setTimeout(updateView)
|
||||
}
|
||||
|
||||
const contentStyle = computed(() => ({
|
||||
display: 'block',
|
||||
height: props.list.length * props.itemHeight + 'px',
|
||||
}))
|
||||
|
||||
const handleReset = list => {
|
||||
cachedList = Array(list.length)
|
||||
startIndex = -1
|
||||
endIndex = -1
|
||||
nextTick(() => {
|
||||
updateView(true)
|
||||
})
|
||||
}
|
||||
watch(() => props.itemHeight, () => {
|
||||
handleReset(props.list)
|
||||
})
|
||||
watch(() => props.list, (list) => {
|
||||
handleReset(list)
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
dom_scrollContainer.value.addEventListener('scroll', onScroll, false)
|
||||
cachedList = Array(props.list.length)
|
||||
startIndex = -1
|
||||
endIndex = -1
|
||||
updateView(true)
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
dom_scrollContainer.value.removeEventListener('scroll', onScroll)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
if (cancelScroll) cancelScroll()
|
||||
})
|
||||
|
||||
return {
|
||||
views,
|
||||
dom_scrollContainer,
|
||||
dom_list,
|
||||
contentStyle,
|
||||
scrollTo,
|
||||
scrollToIndex,
|
||||
getScrollTop,
|
||||
}
|
||||
},
|
||||
// render() {
|
||||
// console.log('render')
|
||||
// // const list = this.views.map((item) => {
|
||||
// // return h('div', { style: item.style, key: item.key }, this.$slots.default(item))
|
||||
// // })
|
||||
// return h(this.containerEl, {
|
||||
// ref: 'dom_scrollContainer',
|
||||
// class: this.containerClass,
|
||||
// style: 'outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict;',
|
||||
// }, [
|
||||
// h(this.contentEl, { style: this.contentStyle, class: this.contentClass }, this.views.map((item) => {
|
||||
// return h('div', { style: item.style, key: item.key }, 'this.$slots.default(item)')
|
||||
// })),
|
||||
// ])
|
||||
// },
|
||||
}
|
||||
</script>
|
||||
@@ -1,167 +0,0 @@
|
||||
<template>
|
||||
<div :class="[$style.select, {[$style.active]: show}]">
|
||||
<div :class="$style.label" ref="dom_btn" @click="handleShow">{{modelValue ? itemName ? list.find(l => l.id == modelValue).name : modelValue : ''}}</div>
|
||||
<ul :class="$style.list">
|
||||
<li v-for="(item, index) in list" :key="index" @click="handleClick(itemKey ? item[itemKey] : item)">{{itemName ? item[itemName] : item}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
},
|
||||
itemName: {
|
||||
type: String,
|
||||
},
|
||||
itemKey: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleHide)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.handleHide)
|
||||
},
|
||||
methods: {
|
||||
handleHide(e) {
|
||||
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
|
||||
if (e && e.target == this.$refs.dom_btn) return
|
||||
setTimeout(() => {
|
||||
this.show = false
|
||||
}, 50)
|
||||
},
|
||||
handleClick(item) {
|
||||
if (item === this.modelValue) return
|
||||
this.$emit('update:modelValue', item)
|
||||
this.$emit('change', item)
|
||||
},
|
||||
handleShow() {
|
||||
this.show = !this.show
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.select {
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
.list {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 8px 15px;
|
||||
// background-color: @color-btn-background;
|
||||
transition: background-color @transition-theme;
|
||||
border-top: 2px solid @color-tab-border-bottom;
|
||||
border-left: 2px solid @color-tab-border-bottom;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
border-top-left-radius: @radius-border;
|
||||
color: @color-btn;
|
||||
cursor: pointer;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-btn-active;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
border-bottom: 2px solid @color-tab-border-bottom;
|
||||
border-left: 2px solid @color-tab-border-bottom;
|
||||
border-bottom-left-radius: @radius-border;
|
||||
background-color: @color-theme_2-background_2;
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
transform-origin: 0 0 0;
|
||||
transition: .25s ease;
|
||||
transition-property: transform, opacity;
|
||||
z-index: 10;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: 8px 15px;
|
||||
// color: @color-btn;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
transition: background-color @transition-theme;
|
||||
background-color: @color-btn-background;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-btn-active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.label {
|
||||
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-btn-active}';
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
li {
|
||||
// color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-btn-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -1,18 +1,22 @@
|
||||
<template>
|
||||
<div class="content" :class="[$style.select, show ? $style.active : '']">
|
||||
<div class="label-content" :class="$style.label" ref="dom_btn" @click="handleShow">
|
||||
<span class="label">{{label}}</span>
|
||||
<div class="icon" :class="$style.icon">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.847 451.847" space="preserve">
|
||||
<use xlink:href="#icon-down"></use>
|
||||
</svg>
|
||||
<div class="content" :class="[$style.select, show ? $style.active : '']">
|
||||
<div ref="dom_btn" class="label-content" :class="$style.label" @click="handleShow">
|
||||
<span class="label">{{ label }}</span>
|
||||
<div class="icon" :class="$style.icon">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.847 451.847" space="preserve">
|
||||
<use xlink:href="#icon-down" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<ul v-if="show" ref="dom_list" class="selection-list scroll" :class="$style.list" :style="listStyles">
|
||||
<li
|
||||
v-for="(item, index) in list" :key="index" :class="[$style.listItem, (itemKey ? item[itemKey] : item) == modelValue ? $style.active : null]"
|
||||
:aria-label="itemName ? item[itemName] : item" @click="handleClick(item)"
|
||||
>
|
||||
{{ itemName ? item[itemName] : item }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="selection-list scroll" :class="$style.list" :style="listStyles" ref="dom_list">
|
||||
<li v-for="(item, index) in list" :key="index" :class="(itemKey ? item[itemKey] : item) == modelValue ? $style.active : null"
|
||||
@click="handleClick(item)" :aria-label="itemName ? item[itemName] : item">{{itemName ? item[itemName] : item}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -27,14 +31,18 @@ export default {
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
itemName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
itemKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
@@ -43,12 +51,6 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleHide)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.handleHide)
|
||||
},
|
||||
computed: {
|
||||
label() {
|
||||
if (this.modelValue == null) return ''
|
||||
@@ -58,8 +60,15 @@ export default {
|
||||
return item[this.itemName]
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleHide, true)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.handleHide, true)
|
||||
},
|
||||
methods: {
|
||||
handleHide(e) {
|
||||
if (!this.show) return
|
||||
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
|
||||
if (e && (e.target == this.$refs.dom_btn || this.$refs.dom_btn.contains(e.target))) return
|
||||
this.listStyles.transform = 'scaleY(0) translateY(0)'
|
||||
@@ -75,7 +84,9 @@ export default {
|
||||
},
|
||||
handleShow() {
|
||||
this.show = true
|
||||
this.listStyles.transform = `scaleY(1) translateY(${this.handleGetOffset()}px)`
|
||||
this.$nextTick(() => {
|
||||
this.listStyles.transform = `scaleY(1) translateY(${this.handleGetOffset()}px)`
|
||||
})
|
||||
},
|
||||
handleGetOffset() {
|
||||
const listHeight = this.$refs.dom_list.clientHeight
|
||||
@@ -105,7 +116,7 @@ export default {
|
||||
|
||||
&.active {
|
||||
.label {
|
||||
background-color: @color-btn-background;
|
||||
background-color: var(--color-button-background);
|
||||
}
|
||||
.list {
|
||||
opacity: 1;
|
||||
@@ -119,16 +130,17 @@ export default {
|
||||
}
|
||||
|
||||
.label {
|
||||
background-color: @color-btn-background;
|
||||
background-color: var(--color-button-background);
|
||||
padding: 0 10px;
|
||||
transition: background-color @transition-theme;
|
||||
transition: background-color @transition-normal;
|
||||
height: @selection-height;
|
||||
line-height: 27px;
|
||||
// line-height: 27px;
|
||||
box-sizing: border-box;
|
||||
color: @color-btn;
|
||||
color: var(--color-button-font);
|
||||
border-radius: @form-radius;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
flex: auto;
|
||||
@@ -146,10 +158,10 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-btn-active;
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +170,7 @@ export default {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: @color-theme_2-background_2;
|
||||
background-color: var(--color-content-background);
|
||||
opacity: 0;
|
||||
transform: scaleY(0) translateY(0);
|
||||
transform-origin: 0 (@selection-height / 2) 0;
|
||||
@@ -169,61 +181,28 @@ export default {
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, .15);
|
||||
overflow: auto;
|
||||
max-height: 200px;
|
||||
}
|
||||
.listItem {
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
line-height: @selection-height;
|
||||
// color: var(--color-button-font);
|
||||
outline: none;
|
||||
transition: background-color @transition-normal;
|
||||
background-color: var(--color-button-font)-background;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
line-height: @selection-height;
|
||||
// color: @color-btn;
|
||||
outline: none;
|
||||
transition: background-color @transition-theme;
|
||||
background-color: @color-btn-background;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-btn-active;
|
||||
}
|
||||
&.active {
|
||||
color: @color-btn;
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
&.active {
|
||||
color: var(--color-button-font);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.label {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-btn-active}';
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
li {
|
||||
// color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-btn-active}';
|
||||
}
|
||||
&.active {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
<template lang="pug">
|
||||
div.scroll(:class="$style.tab")
|
||||
//- div(:class="$style.content")
|
||||
ul
|
||||
li.ignore-to-rem(v-for="item in list" :key="itemKey ? item[itemKey] : item" :class="modelValue === (itemKey ? item[itemKey] : item) ? $style.active : ''")
|
||||
button(type="button"
|
||||
@click="handleClick(itemKey ? item[itemKey] : item)") {{ itemName ? item[itemName] : item }}
|
||||
//- div(:class="$style.control")
|
||||
div(:class="$style.left")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
|
||||
use(xlink:href='#icon-left')
|
||||
div(:class="$style.right")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
|
||||
use(xlink:href='#icon-right')
|
||||
<template>
|
||||
<ul :class="[$style.list, $style[align]]" role="tablist">
|
||||
<li
|
||||
v-for="item in list" :key="item[itemKey]" :class="[$style.listItem, {[$style.active]: modelValue == item[itemKey]}]" tabindex="-1"
|
||||
role="tab" :aria-label="item[itemLabel]" ignore-tip :aria-selected="modelValue == item[itemKey]"
|
||||
@click="handleToggle(item[itemKey])"
|
||||
>
|
||||
<span :class="$style.label">{{ item[itemLabel] }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
list: {
|
||||
@@ -23,203 +20,101 @@ export default {
|
||||
return []
|
||||
},
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
},
|
||||
itemName: {
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left',
|
||||
},
|
||||
itemKey: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
itemLabel: {
|
||||
type: String,
|
||||
default: 'label',
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick(item) {
|
||||
if (item === this.modelValue) return
|
||||
this.$emit('update:modelValue', item)
|
||||
this.$emit('change', item)
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { emit }) {
|
||||
const handleToggle = id => {
|
||||
if (id == props.modelValue) return
|
||||
emit('update:modelValue', id)
|
||||
emit('change', id)
|
||||
}
|
||||
|
||||
return {
|
||||
handleToggle,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.tab {
|
||||
// overflow: hidden;
|
||||
// display: flex;
|
||||
// flex-flow: row nowrap;
|
||||
overflow-x: auto;
|
||||
ul {
|
||||
display: flex;
|
||||
flex: none;
|
||||
border-bottom: 2px solid @color-tab-border-bottom;
|
||||
transition: border-bottom-color @transition-theme;
|
||||
li {
|
||||
position: relative;
|
||||
flex: none;
|
||||
border-top: 2px solid @color-tab-border-top;
|
||||
border-left: 2px solid @color-tab-btn-background;
|
||||
border-right: 2px solid @color-tab-btn-background;
|
||||
// box-sizing: border-box;
|
||||
transition: border-color @transition-theme;
|
||||
.list {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
font-size: 12px;
|
||||
gap: 25px;
|
||||
padding: 0 15px;
|
||||
|
||||
&:global(.ignore-to-rem) {
|
||||
margin-left: -2px;
|
||||
margin-bottom: -2px;
|
||||
&:after, &:before {
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
&.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
.listItem {
|
||||
display: block;
|
||||
// padding: 5px 15px;
|
||||
cursor: pointer;
|
||||
transition: color @transition-normal;
|
||||
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
|
||||
&.active {
|
||||
color: var(--color-primary);
|
||||
cursor: default;
|
||||
|
||||
>.label {
|
||||
&:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
width: 50%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: @color-tab-border-bottom;
|
||||
transition: width @transition-theme;
|
||||
}
|
||||
&:before {
|
||||
content: ' ';
|
||||
display: block;
|
||||
width: 50%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: @color-tab-border-bottom;
|
||||
transition: width @transition-theme;
|
||||
}
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
margin-left: 0;
|
||||
button {
|
||||
border-top-left-radius: 3px;
|
||||
// border-bottom-left-radius: @radius-border;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
border-right: 2px solid @color-tab-border-top;
|
||||
border-top-right-radius: @radius-border;
|
||||
button {
|
||||
border-top-right-radius: 3px;
|
||||
// border-bottom-right-radius: @radius-border;
|
||||
}
|
||||
}
|
||||
button {
|
||||
display: inline-block;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
min-width: 56px;
|
||||
// color: @color-btn;
|
||||
outline: none;
|
||||
transition: background-color @transition-theme;
|
||||
background-color: @color-tab-btn-background;
|
||||
line-height: 28px;
|
||||
}
|
||||
&:hover {
|
||||
// border-left-color: @color-theme_2-hover;
|
||||
// border-right-color: @color-theme_2-hover;
|
||||
button {
|
||||
background-color: @color-tab-btn-background-hover;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
// border-left-color: @color-theme_2-active;
|
||||
// border-right-color: @color-theme_2-active;
|
||||
button {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
border-bottom-color: @color-theme_2-background_1;
|
||||
border-top-color: @color-tab-border-bottom;
|
||||
border-left-color: @color-tab-border-bottom;
|
||||
border-right-color: @color-tab-border-bottom;
|
||||
&::after {
|
||||
width: 0;
|
||||
}
|
||||
&::before {
|
||||
width: 0;
|
||||
}
|
||||
button {
|
||||
background-color: @color-theme_2-background_1;
|
||||
}
|
||||
// background-color: var(--color-primary);
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// .control {
|
||||
// flex: none;
|
||||
// border-bottom: 2px solid @color-tab-border-bottom;
|
||||
// width: 1em;
|
||||
// svg {
|
||||
// width: .85em;
|
||||
// }
|
||||
// }
|
||||
// .left, .right {
|
||||
// line-height: 0;
|
||||
// }
|
||||
// .content {
|
||||
// flex: auto;
|
||||
// overflow: hidden;
|
||||
// }
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.tab {
|
||||
ul {
|
||||
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
li {
|
||||
border-top-color: ~'@{color-@{value}-tab-border-top}';
|
||||
border-left-color: ~'@{color-@{value}-tab-btn-background}';
|
||||
border-right-color: ~'@{color-@{value}-tab-btn-background}';
|
||||
|
||||
&::after {
|
||||
background-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
}
|
||||
&::before {
|
||||
background-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
}
|
||||
&:last-child {
|
||||
border-right-color: ~'@{color-@{value}-tab-border-top}';
|
||||
}
|
||||
button {
|
||||
// color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-tab-btn-background}';
|
||||
}
|
||||
&:hover {
|
||||
// border-left-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
// border-right-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
button {
|
||||
background-color: ~'@{color-@{value}-tab-btn-background-hover}';
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
// border-left-color: ~'@{color-@{value}-theme_2-active}';
|
||||
// border-right-color: ~'@{color-@{value}-theme_2-active}';
|
||||
button {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
border-bottom-color: ~'@{color-@{value}-theme_2}';
|
||||
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
border-right-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
button {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.label {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 8px 0;
|
||||
&:after {
|
||||
.mixin-after;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
border-radius: 20px;
|
||||
background-color: transparent;
|
||||
transform: translateY(-4px);
|
||||
opacity: 0;
|
||||
background-color: var(--color-primary-alpha-300);
|
||||
transition: @transition-fast;
|
||||
transition-property: transform, opacity;
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<component :is="containerEl" :class="containerClass" tabindex="0" ref="dom_scrollContainer" style="outline: none; height: 100%; overflow: auto; position: relative; display: block; contain: strict;">
|
||||
<component :is="containerEl" ref="dom_scrollContainer" :class="containerClass" tabindex="0" style="outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict;">
|
||||
<component :is="contentEl" :class="contentClass" :style="contentStyle">
|
||||
<div v-for="item in views" :key="item.key" :style="item.style">
|
||||
<slot name="default" v-bind="{ item: item.item, index: item.index }" />
|
||||
@@ -57,7 +57,7 @@ const handleScroll = (element, to, duration = 300, callback = () => {}, onCancel
|
||||
onCancel()
|
||||
return
|
||||
}
|
||||
setTimeout(animateScroll, increment)
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
@@ -88,21 +88,17 @@ export default {
|
||||
type: String,
|
||||
default: 'virtualized-list-content',
|
||||
},
|
||||
outsideNum: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
keyName: {
|
||||
type: String,
|
||||
require: true,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
require: true,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['scroll'],
|
||||
@@ -141,12 +137,12 @@ export default {
|
||||
const scrollContainerHeight = dom_scrollContainer.value.clientHeight
|
||||
const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
|
||||
const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
|
||||
const currentStartRenderIndex = Math.max(currentStartIndex - props.outsideNum, 0)
|
||||
const currentEndRenderIndex = currentEndIndex + props.outsideNum + 1
|
||||
const currentStartRenderIndex = Math.max(currentStartIndex, 0)
|
||||
const currentEndRenderIndex = currentEndIndex + 1
|
||||
// console.log(continuous)
|
||||
// debugger
|
||||
if (continuous) {
|
||||
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * this.outsideNum * 0.6) return
|
||||
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * 0.6) return
|
||||
// console.log('update')
|
||||
// if (currentScrollTop > scrollTop) { // scroll down
|
||||
// // console.log('scroll down')
|
||||
@@ -162,9 +158,13 @@ export default {
|
||||
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
// } else return
|
||||
if (currentScrollTop == scrollTop && endIndex >= currentEndIndex) return
|
||||
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
requestAnimationFrame(() => {
|
||||
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
})
|
||||
} else {
|
||||
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
requestAnimationFrame(() => {
|
||||
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
})
|
||||
}
|
||||
startIndex = currentStartIndex
|
||||
endIndex = currentEndIndex
|
||||
@@ -173,42 +173,47 @@ export default {
|
||||
|
||||
const onScroll = event => {
|
||||
const currentScrollTop = dom_scrollContainer.value.scrollTop
|
||||
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * props.outsideNum * 0.6) {
|
||||
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * 0.6) {
|
||||
updateView(currentScrollTop)
|
||||
}
|
||||
emit('scroll', event)
|
||||
}
|
||||
|
||||
const scrollTo = (scrollTop, animate = false) => {
|
||||
return new Promise(resolve => {
|
||||
if (cancelScroll) {
|
||||
cancelScroll(resolve)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
}).then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const scrollTo = (scrollTop, animate = false, onScrollEnd) => {
|
||||
if (onScrollEnd) {
|
||||
new Promise(resolve => {
|
||||
if (cancelScroll) {
|
||||
cancelScroll(resolve)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
}).then(() => {
|
||||
if (animate) {
|
||||
isScrolling = true
|
||||
scrollToValue = scrollTop
|
||||
cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
|
||||
cancelScroll = null
|
||||
isScrolling = false
|
||||
resolve()
|
||||
onScrollEnd(true)
|
||||
}, () => {
|
||||
cancelScroll = null
|
||||
isScrolling = false
|
||||
reject('canceled')
|
||||
onScrollEnd('canceled')
|
||||
})
|
||||
} else {
|
||||
dom_scrollContainer.value.scrollTop = scrollTop
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
dom_scrollContainer.value.scrollTo({
|
||||
top: scrollTop,
|
||||
behavior: animate ? 'smooth' : 'instant',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const scrollToIndex = (index, offset = 0, animate = false) => {
|
||||
return scrollTo(Math.max(index * props.itemHeight + offset, 0), animate)
|
||||
const scrollToIndex = (index, offset = 0, animate = false, onScrollEnd) => {
|
||||
scrollTo(Math.max(index * props.itemHeight + offset, 0), animate, onScrollEnd)
|
||||
}
|
||||
|
||||
const getScrollTop = () => {
|
||||
@@ -216,7 +221,7 @@ export default {
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
setTimeout(updateView)
|
||||
window.setTimeout(updateView)
|
||||
}
|
||||
|
||||
const contentStyle = computed(() => ({
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// import {
|
||||
// computed,
|
||||
// ref,
|
||||
// nextTick,
|
||||
// watch,
|
||||
// onMounted,
|
||||
// onBeforeUnmount,
|
||||
// } from 'vue'
|
||||
// import { scrollTo } from '@common/utils/renderer'
|
||||
|
||||
// interface ListItem {
|
||||
// item: LX.Music.MusicInfo
|
||||
// top: number
|
||||
// style: {
|
||||
// position: string
|
||||
// left: number
|
||||
// right: number
|
||||
// top: string
|
||||
// height: string
|
||||
// }
|
||||
// index: number
|
||||
// key: string
|
||||
// }
|
||||
|
||||
// export default (props: { list: LX.Music.MusicInfo[], itemHeight: number }) => {
|
||||
// const dom_scrollContainer = ref<HTMLElement | null>(null)
|
||||
// const dom_list = ref<HTMLElement | null>(null)
|
||||
// let startIndex = -1
|
||||
// let endIndex = -1
|
||||
// let scrollTop = -1
|
||||
// let cachedList: ListItem[] = []
|
||||
// let cancelScroll: null | (() => void) = null
|
||||
// let isScrolling = false
|
||||
// let scrollToValue = 0
|
||||
|
||||
// const createList = (startIndex: number, endIndex: number) => {
|
||||
// if (startIndex == endIndex) return []
|
||||
// console.log(startIndex, endIndex)
|
||||
// const cache = cachedList.slice(startIndex, endIndex)
|
||||
// const list = props.list.slice(startIndex, endIndex).map((item, i) => {
|
||||
// if (cache[i]) return cache[i]
|
||||
// const top = (startIndex + i) * props.itemHeight
|
||||
// const index = startIndex + i
|
||||
// return cachedList[index] = {
|
||||
// item,
|
||||
// top,
|
||||
// style: { position: 'absolute', left: 0, right: 0, top: `${top}px`, height: `${props.itemHeight}px` },
|
||||
// index,
|
||||
// key: item.id,
|
||||
// }
|
||||
// })
|
||||
// return list
|
||||
// }
|
||||
|
||||
// // div.list-item(@click="handleListItemClick($event, index)" @contextmenu="handleListItemRightClick($event, index)"
|
||||
// // :class="[{ selected: rightClickSelectedIndex == index }, { active: selectedList.includes(item) }]")
|
||||
// // div.list-item-cell.nobreak.center(:style="{ width: rowWidth.r1 }" style="padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
|
||||
// // div.list-item-cell.auto(:style="{ width: rowWidth.r2 }" :aria-label="item.name + (item.meta._qualitys.flac32bit ? ` - ${$t('tag__lossless_24bit')}` : (item.meta._qualitys.ape || item.meta._qualitys.flac || item.meta._qualitys.wav) ? ` - ${$t('tag__lossless')}` : item.meta._qualitys['320k'] ? ` - ${$t('tag__high_quality')}` : '') + (sourceTag ? ` - ${item.source}` : '')")
|
||||
// // span.select {{item.name}}
|
||||
// // span.badge.badge-theme-primary(:class="[$style.labelQuality, $style.noSelect]" v-if="item.meta._qualitys.flac32bit") {{$t('tag__lossless_24bit')}}
|
||||
// // span.badge.badge-theme-primary(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item.meta._qualitys.ape || item.meta._qualitys.flac || item.meta._qualitys.wav") {{$t('tag__lossless')}}
|
||||
// // span.badge.badge-theme-secondary(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item.meta._qualitys['320k']") {{$t('tag__high_quality')}}
|
||||
// // span.badge.badge-theme-tertiary(:class="[$style.labelQuality, $style.noSelect]" v-if="sourceTag") {{item.source}}
|
||||
// // div.list-item-cell(:style="{ width: rowWidth.r3 }" :aria-label="item.singer")
|
||||
// // span.select {{item.singer}}
|
||||
// // div.list-item-cell(:style="{ width: rowWidth.r4 }" :aria-label="item.albumName")
|
||||
// // span.select {{item.meta.albumName}}
|
||||
// // div.list-item-cell(:style="{ width: rowWidth.r5 }")
|
||||
// // span(:class="[$style.time, $style.noSelect]") {{item.meta.interval || '--/--'}}
|
||||
// // div.list-item-cell(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
|
||||
// // material-list-buttons(:index="index" :class="$style.btns"
|
||||
// // :remove-btn="false" @btn-click="handleListBtnClick"
|
||||
// // :download-btn="assertApiSupport(item.source)")
|
||||
// const renderListItem = (list: ListItem) => {
|
||||
// const dom_listItem = document.createElement('div')
|
||||
// dom_listItem.className = 'list-item'
|
||||
// }
|
||||
|
||||
// const renderList = (list: ListItem[], type?: 'up' | 'down') => {
|
||||
// if (!list.length) return
|
||||
// console.log(list)
|
||||
// const dom = document.createDocumentFragment()
|
||||
// for (const item of list) {
|
||||
// dom.appendChild(renderListItem(item))
|
||||
// }
|
||||
// switch (type) {
|
||||
// case 'up':
|
||||
// break
|
||||
// case 'down':
|
||||
// break
|
||||
// default:
|
||||
// // console.log()
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// const updateView = (force = false, currentScrollTop = dom_scrollContainer.value.scrollTop) => {
|
||||
// // const currentScrollTop = this.$refs.dom_scrollContainer.scrollTop
|
||||
// const itemHeight = props.itemHeight
|
||||
// const currentStartIndex = Math.floor(currentScrollTop / itemHeight)
|
||||
// const scrollContainerHeight = dom_scrollContainer.value.clientHeight
|
||||
// const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
|
||||
// const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
|
||||
// const currentStartRenderIndex = Math.max(currentStartIndex, 0)
|
||||
// const currentEndRenderIndex = currentEndIndex + 1
|
||||
// // console.log(continuous, currentStartIndex, endIndex, currentEndIndex, startIndex)
|
||||
// // debugger
|
||||
// if (!force && continuous) {
|
||||
// // if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * 0.6) return
|
||||
// // console.log('update')
|
||||
// if (currentScrollTop > scrollTop) { // scroll down
|
||||
// console.log('scroll down')
|
||||
// renderList(createList(endIndex + 1, currentEndRenderIndex))
|
||||
// // // views.value.push(...list.slice(list.indexOf(views.value[views.value.length - 1]) + 1))
|
||||
// // // // if (this.views.length > 100) {
|
||||
// // // nextTick(() => {
|
||||
// // // views.value.splice(0, views.value.indexOf(list[0]))
|
||||
// // // })
|
||||
// // // }
|
||||
// } else if (currentScrollTop < scrollTop) { // scroll up
|
||||
// console.log('scroll up')
|
||||
// renderList(createList(currentStartRenderIndex, startIndex))
|
||||
// // views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
// } else return
|
||||
// // if (currentScrollTop == scrollTop && endIndex >= currentEndIndex) return
|
||||
// // views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
// } else {
|
||||
// renderList(createList(currentStartRenderIndex, currentEndRenderIndex))
|
||||
// }
|
||||
// startIndex = currentStartIndex
|
||||
// endIndex = currentEndIndex
|
||||
// scrollTop = currentScrollTop
|
||||
// }
|
||||
|
||||
// const onScroll = event => {
|
||||
// const currentScrollTop = dom_scrollContainer.value.scrollTop
|
||||
// if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * 0.6) {
|
||||
// updateView(false, currentScrollTop)
|
||||
// }
|
||||
// emit('scroll', event)
|
||||
// }
|
||||
|
||||
// const scrollTo = async(scrollTop, animate = false) => {
|
||||
// return await new Promise(resolve => {
|
||||
// if (cancelScroll) {
|
||||
// cancelScroll(resolve)
|
||||
// } else {
|
||||
// resolve()
|
||||
// }
|
||||
// }).then(async() => {
|
||||
// return await new Promise((resolve, reject) => {
|
||||
// if (animate) {
|
||||
// isScrolling = true
|
||||
// scrollToValue = scrollTop
|
||||
// cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
|
||||
// cancelScroll = null
|
||||
// isScrolling = false
|
||||
// resolve()
|
||||
// }, () => {
|
||||
// cancelScroll = null
|
||||
// isScrolling = false
|
||||
// reject('canceled')
|
||||
// })
|
||||
// } else {
|
||||
// dom_scrollContainer.value.scrollTop = scrollTop
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
|
||||
// const scrollToIndex = async(index, offset = 0, animate = false) => {
|
||||
// return await scrollTo(Math.max(index * props.itemHeight + offset, 0), animate)
|
||||
// }
|
||||
|
||||
// const getScrollTop = () => {
|
||||
// return isScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop
|
||||
// }
|
||||
|
||||
// const handleResize = () => {
|
||||
// setTimeout(updateView)
|
||||
// }
|
||||
|
||||
// const contentStyle = computed(() => ({
|
||||
// display: 'block',
|
||||
// height: props.list.length * props.itemHeight + 'px',
|
||||
// }))
|
||||
|
||||
// const handleReset = list => {
|
||||
// cachedList = Array(list.length)
|
||||
// startIndex = -1
|
||||
// endIndex = -1
|
||||
// void nextTick(() => {
|
||||
// updateView(true)
|
||||
// })
|
||||
// }
|
||||
// watch(() => props.itemHeight, () => {
|
||||
// handleReset(props.list)
|
||||
// })
|
||||
// watch(() => props.list, (list) => {
|
||||
// handleReset(list)
|
||||
// }, {
|
||||
// deep: true,
|
||||
// })
|
||||
|
||||
// onMounted(() => {
|
||||
// dom_scrollContainer.value!.addEventListener('scroll', onScroll, false)
|
||||
// cachedList = Array(props.list.length)
|
||||
// startIndex = -1
|
||||
// endIndex = -1
|
||||
// updateView(true)
|
||||
// window.addEventListener('resize', handleResize)
|
||||
// })
|
||||
// onBeforeUnmount(() => {
|
||||
// dom_scrollContainer.value!.removeEventListener('scroll', onScroll)
|
||||
// window.removeEventListener('resize', handleResize)
|
||||
// if (cancelScroll) cancelScroll()
|
||||
// })
|
||||
|
||||
// return {
|
||||
// dom_scrollContainer,
|
||||
// dom_list,
|
||||
// contentStyle,
|
||||
// scrollTo,
|
||||
// scrollToIndex,
|
||||
// getScrollTop,
|
||||
// }
|
||||
// }
|
||||
@@ -1,31 +1,31 @@
|
||||
<template>
|
||||
<div :class="$style.content">
|
||||
<canvas :class="$style.canvas" ref="dom_canvas"></canvas>
|
||||
<canvas ref="dom_canvas" :class="$style.canvas" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onBeforeUnmount, onMounted, useRefGetter, watch } from '@renderer/utils/vueTools'
|
||||
import { ref, onBeforeUnmount, onMounted } from '@common/utils/vueTools'
|
||||
import { getAnalyser } from '@renderer/plugins/player'
|
||||
import { isPlay } from '@renderer/core/share/player'
|
||||
import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
import { isPlay } from '@renderer/store/player/state'
|
||||
// import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
const themes = {
|
||||
green: 'rgba(77,175,124,.16)',
|
||||
blue: 'rgba(52,152,219,.16)',
|
||||
yellow: 'rgba(233,212,96,.22)',
|
||||
orange: 'rgba(245,171,53,.16)',
|
||||
red: 'rgba(214,69,65,.12)',
|
||||
pink: 'rgba(241,130,141,.16)',
|
||||
purple: 'rgba(155,89,182,.14)',
|
||||
grey: 'rgba(108,122,137,.16)',
|
||||
ming: 'rgba(51,110,123,.14)',
|
||||
blue2: 'rgba(79,98,208,.14)',
|
||||
black: 'rgba(39,39,39,.4)',
|
||||
mid_autumn: 'rgba(74,55,82,.1)',
|
||||
naruto: 'rgba(87,144,167,.15)',
|
||||
happy_new_year: 'rgba(192,57,43,.1)',
|
||||
}
|
||||
// const themes = {
|
||||
// green: 'rgba(77,175,124,.16)',
|
||||
// blue: 'rgba(52,152,219,.16)',
|
||||
// yellow: 'rgba(233,212,96,.22)',
|
||||
// orange: 'rgba(245,171,53,.16)',
|
||||
// red: 'rgba(214,69,65,.12)',
|
||||
// pink: 'rgba(241,130,141,.16)',
|
||||
// purple: 'rgba(155,89,182,.14)',
|
||||
// grey: 'rgba(108,122,137,.16)',
|
||||
// ming: 'rgba(51,110,123,.14)',
|
||||
// blue2: 'rgba(79,98,208,.14)',
|
||||
// black: 'rgba(39,39,39,.4)',
|
||||
// mid_autumn: 'rgba(74,55,82,.1)',
|
||||
// naruto: 'rgba(87,144,167,.15)',
|
||||
// happy_new_year: 'rgba(192,57,43,.1)',
|
||||
// }
|
||||
|
||||
const getBarWidth = canvasWidth => {
|
||||
let barWidth = (canvasWidth / 128) * 2.5
|
||||
@@ -60,18 +60,15 @@ export default {
|
||||
const maxNum = 255
|
||||
let frequencyAvg = 0
|
||||
|
||||
const theme = useRefGetter('theme')
|
||||
// const theme = useRefGetter('theme')
|
||||
// const setting = useRefGetter('setting')
|
||||
let themeColor = themes[theme.value || 'green']
|
||||
watch(theme, theme => {
|
||||
themeColor = themes[theme || 'green']
|
||||
})
|
||||
let themeColor = getComputedStyle(document.documentElement).getPropertyValue('--color-primary-light-200-alpha-800')
|
||||
// watch(theme, theme => {
|
||||
// themeColor = themes[theme || 'green']
|
||||
// })
|
||||
|
||||
// https://developer.mozilla.org/zh-CN/docs/Web/API/AnalyserNode/smoothingTimeConstant
|
||||
const renderFrame = () => {
|
||||
animationFrameId = null
|
||||
if (isPlaying) animationFrameId = window.requestAnimationFrame(renderFrame)
|
||||
|
||||
x = 0
|
||||
|
||||
analyser.getByteFrequencyData(dataArray)
|
||||
@@ -107,11 +104,14 @@ export default {
|
||||
|
||||
x += barWidth
|
||||
}
|
||||
|
||||
animationFrameId = null
|
||||
if (isPlaying) animationFrameId = window.requestAnimationFrame(renderFrame)
|
||||
}
|
||||
|
||||
const handlePlay = () => {
|
||||
isPlaying = true
|
||||
analyser.fftSize = 256
|
||||
// analyser.fftSize = 256
|
||||
bufferLength = analyser.frequencyBinCount
|
||||
// console.log(bufferLength)
|
||||
barWidth = getBarWidth(WIDTH)
|
||||
@@ -134,15 +134,15 @@ export default {
|
||||
barWidth = getBarWidth(WIDTH)
|
||||
}
|
||||
|
||||
window.eventHub.on(eventPlayerNames.play, handlePlay)
|
||||
window.eventHub.on(eventPlayerNames.pause, handlePause)
|
||||
window.eventHub.on(eventPlayerNames.error, handlePause)
|
||||
window.app_event.on('play', handlePlay)
|
||||
window.app_event.on('pause', handlePause)
|
||||
window.app_event.on('error', handlePause)
|
||||
window.addEventListener('resize', handleResize)
|
||||
onBeforeUnmount(() => {
|
||||
handlePause()
|
||||
window.eventHub.off(eventPlayerNames.play, handlePlay)
|
||||
window.eventHub.off(eventPlayerNames.pause, handlePause)
|
||||
window.eventHub.off(eventPlayerNames.error, handlePause)
|
||||
window.app_event.off('play', handlePlay)
|
||||
window.app_event.off('pause', handlePause)
|
||||
window.app_event.off('error', handlePause)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<template>
|
||||
<material-modal :show="show" :bg-close="bgClose" @close="handleClose" :teleport="teleport">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ info.name }}<br/>{{ info.singer }}</h2>
|
||||
<base-btn :class="$style.btn" :key="type.type" @click="handleClick(type.type)" v-for="type in types"
|
||||
>{{getTypeName(type.type)}}{{ type.size && ` - ${type.size.toUpperCase()}` }}</base-btn>
|
||||
</main>
|
||||
</material-modal>
|
||||
<material-modal :show="show" :bg-close="bgClose" :teleport="teleport" @close="handleClose">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ info.name }}<br>{{ info.singer }}</h2>
|
||||
<base-btn v-for="quality in qualitys" :key="quality.type" :class="$style.btn" @click="handleClick(quality.type)">
|
||||
{{ getTypeName(quality.type) }}{{ quality.size && ` - ${quality.size.toUpperCase()}` }}
|
||||
</base-btn>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
import { qualityList } from '@renderer/core/share'
|
||||
import { qualityList } from '@renderer/store'
|
||||
import { createDownloadTasks } from '@renderer/store/download/action'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -19,13 +20,17 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
musicInfo: {
|
||||
type: Object,
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
bgClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
teleport: String,
|
||||
teleport: {
|
||||
type: String,
|
||||
default: '#root',
|
||||
},
|
||||
},
|
||||
emits: ['update:show'],
|
||||
setup() {
|
||||
@@ -40,36 +45,35 @@ export default {
|
||||
sourceQualityList() {
|
||||
return this.qualityList[this.musicInfo.source] || []
|
||||
},
|
||||
types() {
|
||||
return this.info?.types?.filter(type => this.checkSource(type.type)) || []
|
||||
qualitys() {
|
||||
return this.info.meta?.qualitys?.filter(quality => this.checkSource(quality.type)) || []
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('download', ['createDownload']),
|
||||
handleClick(type) {
|
||||
this.createDownload({ musicInfo: this.musicInfo, type })
|
||||
handleClick(quality) {
|
||||
createDownloadTasks([this.musicInfo], quality)
|
||||
this.handleClose()
|
||||
},
|
||||
handleClose() {
|
||||
this.$emit('update:show', false)
|
||||
},
|
||||
getTypeName(type) {
|
||||
switch (type) {
|
||||
case 'flac32bit':
|
||||
getTypeName(quality) {
|
||||
switch (quality) {
|
||||
case 'flac24bit':
|
||||
return this.$t('download__lossless') + ' FLAC Hires'
|
||||
case 'flac':
|
||||
case 'ape':
|
||||
case 'wav':
|
||||
return this.$t('download__lossless') + ' ' + type.toUpperCase()
|
||||
return this.$t('download__lossless') + ' ' + quality.toUpperCase()
|
||||
case '320k':
|
||||
return this.$t('download__high_quality') + ' ' + type.toUpperCase()
|
||||
return this.$t('download__high_quality') + ' ' + quality.toUpperCase()
|
||||
case '192k':
|
||||
case '128k':
|
||||
return this.$t('download__normal') + ' ' + type.toUpperCase()
|
||||
return this.$t('download__normal') + ' ' + quality.toUpperCase()
|
||||
}
|
||||
},
|
||||
checkSource(type) {
|
||||
return this.sourceQualityList.includes(type)
|
||||
checkSource(quality) {
|
||||
return this.sourceQualityList.includes(quality)
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -88,7 +92,7 @@ export default {
|
||||
justify-content: center;
|
||||
h2 {
|
||||
font-size: 13px;
|
||||
color: @color-theme_2-font;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
@@ -103,14 +107,4 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<material-modal :show="show" :bg-close="bgClose" @close="handleClose" :teleport="teleport">
|
||||
<main :class="$style.main">
|
||||
<h2>{{$t('download__multiple_tip', { len: list.length })}}<br/>{{$t('download__multiple_tip2')}}</h2>
|
||||
<base-btn :class="$style.btn" @click="handleClick('128k')">{{$t('download__normal')}} - 128K</base-btn>
|
||||
<base-btn :class="$style.btn" @click="handleClick('320k')">{{$t('download__high_quality')}} - 320K</base-btn>
|
||||
<base-btn :class="$style.btn" @click="handleClick('flac')">{{$t('download__lossless')}} - FLAC</base-btn>
|
||||
<base-btn :class="$style.btn" @click="handleClick('flac32bit')">{{$t('download__lossless')}} - FLAC Hires</base-btn>
|
||||
</main>
|
||||
</material-modal>
|
||||
<material-modal :show="show" :bg-close="bgClose" :teleport="teleport" @close="handleClose">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ $t('download__multiple_tip', { len: list.length }) }}<br>{{ $t('download__multiple_tip2') }}</h2>
|
||||
<base-btn :class="$style.btn" @click="handleClick('128k')">{{ $t('download__normal') }} - 128K</base-btn>
|
||||
<base-btn :class="$style.btn" @click="handleClick('320k')">{{ $t('download__high_quality') }} - 320K</base-btn>
|
||||
<base-btn :class="$style.btn" @click="handleClick('flac')">{{ $t('download__lossless') }} - FLAC</base-btn>
|
||||
<base-btn :class="$style.btn" @click="handleClick('flac24bit')">{{ $t('download__lossless') }} - FLAC Hires</base-btn>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
import { createDownloadTasks } from '@renderer/store/download/action'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -29,13 +29,15 @@ export default {
|
||||
return []
|
||||
},
|
||||
},
|
||||
teleport: String,
|
||||
teleport: {
|
||||
type: String,
|
||||
default: '#root',
|
||||
},
|
||||
},
|
||||
emits: ['update:show', 'confirm'],
|
||||
methods: {
|
||||
...mapActions('download', ['createDownloadMultiple']),
|
||||
handleClick(type) {
|
||||
this.createDownloadMultiple({ list: [...this.list], type })
|
||||
handleClick(quality) {
|
||||
createDownloadTasks(this.list.filter(item => item.source != 'local'), quality)
|
||||
this.handleClose()
|
||||
this.$emit('confirm')
|
||||
},
|
||||
@@ -59,7 +61,7 @@ export default {
|
||||
justify-content: center;
|
||||
h2 {
|
||||
font-size: 13px;
|
||||
color: @color-theme_2-font;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
@@ -74,14 +76,4 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<template>
|
||||
<material-modal :show="show" :bg-close="bgClose" @close="handleClose" :teleport="teleport" max-width="70%">
|
||||
<main :class="$style.main">
|
||||
<h2>{{$t('list_add__' + (isMove ? 'title_first_move' : 'title_first_add'))}} <span :class="$style.name">{{this.musicInfo && `${musicInfo.name}`}}</span> {{$t('list_add__title_last')}}</h2>
|
||||
<div class="scroll" :class="$style.btnContent">
|
||||
<base-btn :class="$style.btn" :aria-label="$t('list_add__btn_title', { name: item.name })" :key="item.id" :disabled="item.isExist" @click="handleClick(index)" v-for="(item, index) in lists">{{item.name}}</base-btn>
|
||||
<base-btn :class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :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" viewBox="0 0 42 42" space="preserve">
|
||||
<use xlink:href="#icon-addTo"></use>
|
||||
</svg>
|
||||
<input :class="$style.newListInput" :value="newListName" type="text" :placeholder="$t('lists__new_list_input')" @keyup.enter="handleSaveList($event)" @blur="handleSaveList($event)"/>
|
||||
</base-btn>
|
||||
<span :class="$style.btn" :key="i" v-for="i in spaceNum"></span>
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
<material-modal :show="show" :bg-close="bgClose" :teleport="teleport" max-width="70%" min-width="200px" @close="handleClose">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ $t('list_add__' + (isMove ? 'title_first_move' : 'title_first_add')) }} <span :class="$style.name">{{ currentMusicInfo.name }}</span> {{ $t('list_add__title_last') }}</h2>
|
||||
<div class="scroll" :class="$style.btnContent">
|
||||
<base-btn v-for="(item, index) in lists" :key="item.id" :class="$style.btn" :aria-label="$t('list_add__btn_title', { name: item.name })" :disabled="item.isExist" @click="handleClick(index)">{{ item.name }}</base-btn>
|
||||
<base-btn :class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" :aria-label="$t('lists__new_list_btn')" @click="handleEditing($event)">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 0 42 42" space="preserve">
|
||||
<use xlink:href="#icon-addTo" />
|
||||
</svg>
|
||||
<input :class="$style.newListInput" :value="newListName" type="text" :placeholder="$t('lists__new_list_input')" @keyup.enter="handleSaveList($event)" @blur="handleSaveList($event)">
|
||||
</base-btn>
|
||||
<span v-for="i in spaceNum" :key="i" :class="$style.btn" />
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from 'vuex'
|
||||
import { computed } from '@renderer/utils/vueTools'
|
||||
import { defaultList, loveList, userLists } from '@renderer/core/share/list'
|
||||
import { getList } from '@renderer/core/share/utils'
|
||||
// import { mapMutations } from 'vuex'
|
||||
import { watch, ref } from '@common/utils/vueTools'
|
||||
import { defaultList, loveList, userLists } from '@renderer/store/list/state'
|
||||
import { addListMusics, moveListMusics, createUserList, getMusicExistListIds } from '@renderer/store/list/action'
|
||||
import useKeyDown from '@renderer/utils/compositions/useKeyDown'
|
||||
|
||||
export default {
|
||||
@@ -30,7 +30,8 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
musicInfo: {
|
||||
type: Object,
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
bgClose: {
|
||||
type: Boolean,
|
||||
@@ -54,24 +55,48 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
teleport: String,
|
||||
teleport: {
|
||||
type: String,
|
||||
default: '#root',
|
||||
},
|
||||
},
|
||||
emits: ['update:show'],
|
||||
setup(props) {
|
||||
const keyModDown = useKeyDown('mod')
|
||||
const lists = ref([])
|
||||
|
||||
const lists = computed(() => {
|
||||
if (!props.musicInfo) return []
|
||||
const targetMid = props.musicInfo.songmid
|
||||
return [
|
||||
const currentMusicInfo = ref({})
|
||||
|
||||
const checkMusicExist = (musicInfo) => {
|
||||
const mid = musicInfo.id
|
||||
getMusicExistListIds(mid).then(ids => {
|
||||
if (mid != musicInfo.id) return
|
||||
for (const list of lists.value) {
|
||||
if (ids.includes(list.id)) list.isExist = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => props.show, show => {
|
||||
if (!show) return
|
||||
if (!props.musicInfo) return lists.value = []
|
||||
|
||||
currentMusicInfo.value = 'progress' in props.musicInfo ? props.musicInfo.metadata.musicInfo : props.musicInfo
|
||||
|
||||
lists.value = [
|
||||
defaultList,
|
||||
loveList,
|
||||
...userLists,
|
||||
].filter(l => !props.excludeListId.includes(l.id)).map(l => ({ ...l, isExist: getList(l.id).some(s => s.songmid == targetMid) }))
|
||||
].filter(l => !props.excludeListId.includes(l.id)).map(l => ({ ...l, isExist: false }))
|
||||
|
||||
checkMusicExist(currentMusicInfo.value)
|
||||
})
|
||||
|
||||
return {
|
||||
keyModDown,
|
||||
lists,
|
||||
checkMusicExist,
|
||||
currentMusicInfo,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -94,7 +119,6 @@ export default {
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('list', ['listAdd', 'listMove', 'createUserList']),
|
||||
handleResize() {
|
||||
const width = window.innerWidth
|
||||
this.rowNum = width < 1920
|
||||
@@ -105,9 +129,10 @@ export default {
|
||||
},
|
||||
handleClick(index) {
|
||||
this.isMove
|
||||
? this.listMove({ fromId: this.fromListId, toId: this.lists[index].id, musicInfo: this.musicInfo })
|
||||
: this.listAdd({ id: this.lists[index].id, musicInfo: this.musicInfo })
|
||||
? moveListMusics(this.fromListId, this.lists[index].id, [this.currentMusicInfo])
|
||||
: addListMusics(this.lists[index].id, [this.currentMusicInfo])
|
||||
|
||||
this.lists[index].isExist = true
|
||||
if (this.keyModDown && !this.isMove) return
|
||||
this.$nextTick(() => {
|
||||
this.handleClose()
|
||||
@@ -127,7 +152,7 @@ export default {
|
||||
this.newListName = event.target.value = ''
|
||||
this.isEditing = false
|
||||
if (!name) return
|
||||
this.createUserList({ name })
|
||||
createUserList({ name })
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -140,7 +165,7 @@ export default {
|
||||
.main {
|
||||
// padding: 15px 0;
|
||||
// max-width: 70%;
|
||||
min-width: 200px;
|
||||
// min-width: 200px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
@@ -149,7 +174,7 @@ export default {
|
||||
// overflow: hidden;
|
||||
h2 {
|
||||
font-size: 13px;
|
||||
color: @color-theme_2-font;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
@@ -157,7 +182,7 @@ export default {
|
||||
}
|
||||
|
||||
.name {
|
||||
color: @color-theme;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-content {
|
||||
@@ -184,9 +209,9 @@ export default {
|
||||
}
|
||||
|
||||
.newList {
|
||||
border: 1px dashed @color-theme-hover;
|
||||
background-color: @color-theme_2-background_2;
|
||||
color: @color-theme-hover;
|
||||
border: 1px dashed var(--color-primary-font-hover);
|
||||
// background-color: var(--color-main-background);
|
||||
color: var(--color-primary-font-hover);
|
||||
opacity: .7;
|
||||
|
||||
svg {
|
||||
@@ -243,22 +268,4 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
.name {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
.newList {
|
||||
border-color: ~'@{color-@{value}-theme-hover}';
|
||||
color: ~'@{color-@{value}-theme-hover}';
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<material-modal :show="show" :bg-close="bgClose" @close="handleClose" max-width="70%">
|
||||
<main :class="$style.main">
|
||||
<h2>{{$t('list_add__multiple_' + (isMove ? 'title_move' : 'title_add'), { num: musicList.length })}}</h2>
|
||||
<div class="scroll" :class="$style.btnContent">
|
||||
<base-btn :class="$style.btn" :aria-label="$t('list_add__multiple_btn_title', { name: item.name })" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists">{{item.name}}</base-btn>
|
||||
<base-btn :class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :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" viewBox="0 0 42 42" space="preserve">
|
||||
<use xlink:href="#icon-addTo"></use>
|
||||
</svg>
|
||||
<input :class="$style.newListInput" :value="newListName" type="text" :placeholder="$t('lists__new_list_input')" @keyup.enter="handleSaveList($event)" @blur="handleSaveList($event)"/>
|
||||
</base-btn>
|
||||
<span :class="$style.btn" :key="i" v-for="i in spaceNum"></span>
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
<material-modal :show="show" :bg-close="bgClose" max-width="70%" :teleport="teleport" @close="handleClose">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ $t('list_add__multiple_' + (isMove ? 'title_move' : 'title_add'), { num: musicList.length }) }}</h2>
|
||||
<div class="scroll" :class="$style.btnContent">
|
||||
<base-btn v-for="(item, index) in lists" :key="item.id" :class="$style.btn" :aria-label="$t('list_add__multiple_btn_title', { name: item.name })" @click="handleClick(index)">{{ item.name }}</base-btn>
|
||||
<base-btn :class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" :aria-label="$t('lists__new_list_btn')" @click="handleEditing($event)">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 0 42 42" space="preserve">
|
||||
<use xlink:href="#icon-addTo" />
|
||||
</svg>
|
||||
<input :class="$style.newListInput" :value="newListName" type="text" :placeholder="$t('lists__new_list_input')" @keyup.enter="handleSaveList($event)" @blur="handleSaveList($event)">
|
||||
</base-btn>
|
||||
<span v-for="i in spaceNum" :key="i" :class="$style.btn" />
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapMutations } from 'vuex'
|
||||
import { computed } from '@renderer/utils/vueTools'
|
||||
import { defaultList, loveList, userLists } from '@renderer/core/share/list'
|
||||
import { computed } from '@common/utils/vueTools'
|
||||
import { defaultList, loveList, userLists } from '@renderer/store/list/state'
|
||||
import { addListMusics, moveListMusics, createUserList } from '@renderer/store/list/action'
|
||||
import useKeyDown from '@renderer/utils/compositions/useKeyDown'
|
||||
|
||||
export default {
|
||||
@@ -56,7 +56,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
teleport: String,
|
||||
teleport: {
|
||||
type: String,
|
||||
default: '#root',
|
||||
},
|
||||
},
|
||||
emits: ['update:show', 'confirm'],
|
||||
setup(props) {
|
||||
@@ -95,7 +98,6 @@ export default {
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('list', ['listAddMultiple', 'listMoveMultiple', 'createUserList']),
|
||||
handleResize() {
|
||||
const width = window.innerWidth
|
||||
this.rowNum = width < 1920
|
||||
@@ -105,9 +107,11 @@ export default {
|
||||
: width < 3840 ? 5 : 6
|
||||
},
|
||||
handleClick(index) {
|
||||
const list = 'progress' in this.musicList[0] ? this.musicList.map(t => t.metadata.musicInfo) : this.musicList
|
||||
|
||||
this.isMove
|
||||
? this.listMoveMultiple({ fromId: this.fromListId, toId: this.lists[index].id, list: this.musicList })
|
||||
: this.listAddMultiple({ id: this.lists[index].id, list: this.musicList })
|
||||
? moveListMusics(this.fromListId, this.lists[index].id, list)
|
||||
: addListMusics(this.lists[index].id, list)
|
||||
|
||||
if (this.keyModDown && !this.isMove) return
|
||||
this.$nextTick(() => {
|
||||
@@ -129,7 +133,7 @@ export default {
|
||||
this.newListName = event.target.value = ''
|
||||
this.isEditing = false
|
||||
if (!name) return
|
||||
this.createUserList({ name })
|
||||
createUserList({ name })
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -151,7 +155,7 @@ export default {
|
||||
// overflow: hidden;
|
||||
h2 {
|
||||
font-size: 13px;
|
||||
color: @color-theme_2-font;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
@@ -182,9 +186,9 @@ export default {
|
||||
}
|
||||
|
||||
.newList {
|
||||
border: 1px dashed @color-theme-hover;
|
||||
background-color: @color-theme_2-background_2;
|
||||
color: @color-theme-hover;
|
||||
border: 1px dashed var(--color-primary-font-hover);
|
||||
// background-color: var(--color-main-background);
|
||||
color: var(--color-primary-font-hover);
|
||||
opacity: .7;
|
||||
|
||||
svg {
|
||||
@@ -241,20 +245,4 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
.newList {
|
||||
border-color: ~'@{color-@{value}-theme-hover}';
|
||||
color: ~'@{color-@{value}-theme-hover}';
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<template>
|
||||
<div :class="[$style.progress, className]">
|
||||
<div :class="[$style.progressBar, $style.progressBar2, {[$style.barTransition]: isActiveTransition}]" @transitionend="handleTransitionEnd" :style="{ transform: `scaleX(${progress || 0})` }"></div>
|
||||
<div v-show="dragging" :class="[$style.progressBar, $style.progressBar3]" :style="{ transform: `scaleX(${dragProgress || 0})` }"></div>
|
||||
</div>
|
||||
<div :class="$style.progressMask" @mousedown="handleMsDown" ref="dom_progress"></div>
|
||||
<div :class="[$style.progress, className]">
|
||||
<div :class="[$style.progressBar, $style.progressBar2, {[$style.barTransition]: isActiveTransition}]" :style="{ transform: `scaleX(${progress || 0})` }" @transitionend="handleTransitionEnd" />
|
||||
<div v-show="dragging" :class="[$style.progressBar, $style.progressBar3]" :style="{ transform: `scaleX(${dragProgress || 0})` }" />
|
||||
</div>
|
||||
<div ref="dom_progress" :class="$style.progressMask" @mousedown="handleMsDown" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onBeforeUnmount } from '@renderer/utils/vueTools'
|
||||
import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
import { playProgress } from '@renderer/core/share/playProgress'
|
||||
import { ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { playProgress } from '@renderer/store/player/playProgress'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
className: String,
|
||||
className: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
progress: {
|
||||
type: Number,
|
||||
required: true,
|
||||
@@ -70,7 +72,7 @@ export default {
|
||||
})
|
||||
|
||||
const setProgress = num => {
|
||||
window.eventHub.emit(eventPlayerNames.setProgress, num)
|
||||
window.app_event.setProgress(num)
|
||||
}
|
||||
|
||||
// const handleSetProgress = event => {
|
||||
@@ -93,11 +95,11 @@ export default {
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
height: 5px;
|
||||
// overflow: hidden;
|
||||
transition: @transition-theme;
|
||||
transition: @transition-normal;
|
||||
transition-property: background-color;
|
||||
background-color: @color-player-progress;
|
||||
background-color: var(--color-primary-light-100-alpha-800);
|
||||
// background-color: #f5f5f5;
|
||||
position: relative;
|
||||
border-radius: 20px;
|
||||
@@ -120,16 +122,16 @@ export default {
|
||||
border-radius: 20px;
|
||||
}
|
||||
.progress-bar1 {
|
||||
background-color: @color-player-progress-bar1;
|
||||
background-color: var(--color-primary-light-100-alpha-600);
|
||||
}
|
||||
|
||||
.progress-bar2 {
|
||||
background-color: @color-player-progress-bar2;
|
||||
background-color: var(--color-primary-light-100-alpha-400);
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.progress-bar3 {
|
||||
background-color: @color-player-progress-bar2;
|
||||
background-color: var(--color-primary-light-100-alpha-200);
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -140,20 +142,4 @@ export default {
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.progress {
|
||||
background-color: ~'@{color-@{value}-player-progress}';
|
||||
}
|
||||
.progress-bar1 {
|
||||
background-color: ~'@{color-@{value}-player-progress-bar1}';
|
||||
}
|
||||
.progress-bar2 {
|
||||
background-color: ~'@{color-@{value}-player-progress-bar2}';
|
||||
}
|
||||
.progress-bar3 {
|
||||
background-color: ~'@{color-@{value}-player-progress-bar2}';
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
<template>
|
||||
<div :class="[$style.volumeContent, className]">
|
||||
<div :class="[$style.volume, {[$style.muted]: setting.player.isMute} ]">
|
||||
<div :class="$style.volumeBar" ref="dom_volumeBar" :style="{ transform: `scaleX(${volume || 0})` }"></div>
|
||||
<div :class="[$style.volumeContent, className]">
|
||||
<div :class="[$style.volume, {[$style.muted]: appSetting['player.isMute']} ]">
|
||||
<div ref="dom_volumeBar" :class="$style.volumeBar" :style="{ transform: `scaleX(${volume || 0})` }" />
|
||||
</div>
|
||||
<div :class="$style.volumeMask" :aria-label="`${$t('player__volume')}${parseInt(volume * 100)}%`" @mousedown="handleVolumeMsDown" />
|
||||
</div>
|
||||
<div :class="$style.volumeMask" @mousedown="handleVolumeMsDown" :aria-label="`${$t('player__volume')}${parseInt(volume * 100)}%`"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onBeforeUnmount } from '@renderer/utils/vueTools'
|
||||
import { ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
// import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
import { volume, isMute, setVolume, setMute } from '@renderer/core/share/volume'
|
||||
|
||||
import { volume, isMute } from '@renderer/store/player/volume'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
},
|
||||
setting: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
@@ -38,9 +37,9 @@ export default {
|
||||
if (val < 0) val = 0
|
||||
if (val > 1) val = 1
|
||||
|
||||
setVolume(volumeEvent.msDownVolume = val)
|
||||
window.app_event.setVolume(volumeEvent.msDownVolume = val)
|
||||
|
||||
if (isMute.value) setMute(false)
|
||||
if (isMute.value) window.app_event.setVolumeIsMute(false)
|
||||
}
|
||||
const handleVolumeMsUp = () => {
|
||||
volumeEvent.isMsDown = false
|
||||
@@ -50,7 +49,7 @@ export default {
|
||||
let volume = volumeEvent.msDownVolume + (event.clientX - volumeEvent.msDownX) / dom_volumeBar.value.clientWidth
|
||||
if (volume > 1) volume = 1
|
||||
else if (volume < 0) volume = 0
|
||||
setVolume(volume)
|
||||
window.app_event.setVolume(volume)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', handleVolumeMsMove)
|
||||
@@ -65,6 +64,7 @@ export default {
|
||||
dom_volumeBar,
|
||||
volume,
|
||||
isMute,
|
||||
appSetting,
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: .5;
|
||||
transition: opacity @transition-theme;
|
||||
transition: opacity @transition-normal;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -93,9 +93,9 @@ export default {
|
||||
height: 0.25em;
|
||||
border-radius: 10px;
|
||||
// overflow: hidden;
|
||||
transition: @transition-theme;
|
||||
transition: @transition-normal;
|
||||
transition-property: background-color, opacity;
|
||||
background-color: @color-player-progress-bar1;
|
||||
background-color: var(--color-button-background);
|
||||
// background-color: #f5f5f5;
|
||||
position: relative;
|
||||
border-radius: @radius-progress-border;
|
||||
@@ -117,7 +117,7 @@ export default {
|
||||
height: 100%;
|
||||
border-radius: @radius-progress-border;
|
||||
transition-duration: 0.2s;
|
||||
background-color: @color-btn;
|
||||
background-color: var(--color-button-background);
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@@ -129,16 +129,4 @@ export default {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.volume {
|
||||
background-color: ~'@{color-@{value}-player-progress-bar1}';
|
||||
}
|
||||
|
||||
.volume-bar {
|
||||
background-color: ~'@{color-@{value}-btn}';
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
<template>
|
||||
<div :class="$style.controlBtn" v-show="!isFullscreen">
|
||||
<button type="button" :class="[$style.btn, $style.close]" :aria-label="$t('close')" @click="close">
|
||||
<svg :class="$style.controlBtniIcon" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-window-close"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" :class="[$style.btn, $style.min]" :aria-label="$t('min')" @click="min">
|
||||
<svg :class="$style.controlBtniIcon" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-window-minimize"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { base as eventBaseName } from '@renderer/event/names'
|
||||
// import { getRandom } from '../../utils'
|
||||
import { isFullscreen } from '@renderer/core/share'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
return {
|
||||
isFullscreen,
|
||||
min() {
|
||||
window.eventHub.emit(eventBaseName.min)
|
||||
},
|
||||
max() {
|
||||
window.eventHub.emit(eventBaseName.max)
|
||||
},
|
||||
close() {
|
||||
window.eventHub.emit(eventBaseName.close)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
@control-btn-width: @height-toolbar * .26;
|
||||
@control-btn-height: 6%;
|
||||
.controlBtn {
|
||||
box-sizing: border-box;
|
||||
padding: 0 7px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
width: 100%;
|
||||
height: @control-btn-height;
|
||||
-webkit-app-region: no-drag;
|
||||
opacity: .5;
|
||||
transition: opacity @transition-theme;
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
.controlBtniIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.btn {
|
||||
position: relative;
|
||||
width: @control-btn-width;
|
||||
height: @control-btn-width;
|
||||
background: none;
|
||||
border: none;
|
||||
display: flex;
|
||||
// justify-content: center;
|
||||
// align-items: center;
|
||||
outline: none;
|
||||
padding: 1px;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
color: @color-theme_2;
|
||||
|
||||
&.min {
|
||||
background-color: @color-minBtn;
|
||||
}
|
||||
&.max {
|
||||
background-color: @color-maxBtn;
|
||||
}
|
||||
&.close {
|
||||
background-color: @color-closeBtn;
|
||||
}
|
||||
}
|
||||
|
||||
.controlBtniIcon {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.btn {
|
||||
color: ~'@{color-@{value}-theme_2}';
|
||||
|
||||
&.min {
|
||||
background-color: ~'@{color-@{value}-minBtn}';
|
||||
}
|
||||
&.max {
|
||||
background-color: ~'@{color-@{value}-maxBtn}';
|
||||
}
|
||||
&.close {
|
||||
background-color: ~'@{color-@{value}-closeBtn}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<div :class="[$style.aside, { [$style.fullscreen]: isFullscreen }]">
|
||||
<ControlBtns v-if="setting.controlBtnPosition == 'left'" />
|
||||
<div :class="$style.logo" v-else>L X</div>
|
||||
<NavBar />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { isFullscreen } from '@renderer/core/share'
|
||||
|
||||
import ControlBtns from './ControlBtns'
|
||||
import NavBar from './NavBar'
|
||||
|
||||
export default {
|
||||
name: 'CoreAside',
|
||||
components: { ControlBtns, NavBar },
|
||||
setup() {
|
||||
return {
|
||||
isFullscreen,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.aside {
|
||||
// box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
transition: @transition-theme;
|
||||
transition-property: background-color;
|
||||
background-color: @color-theme-sidebar;
|
||||
// background-color: @color-aside-background;
|
||||
// border-right: 2px solid @color-theme;
|
||||
-webkit-app-region: drag;
|
||||
-webkit-user-select: none;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
&.fullscreen {
|
||||
-webkit-app-region: no-drag;
|
||||
.logo {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
box-sizing: border-box;
|
||||
padding: 0 13%;
|
||||
height: 50px;
|
||||
color: @color-theme-font;
|
||||
flex: none;
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
font-weight: bold;
|
||||
// -webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.aside {
|
||||
background-color: ~'@{color-@{value}-theme-sidebar}';
|
||||
}
|
||||
.logo {
|
||||
color: ~'@{color-@{value}-theme-font}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,186 +0,0 @@
|
||||
<template lang="pug">
|
||||
material-modal(:show="!setting.isAgreePact || isShowPact" @close="handleClose(false)" :bgClose="setting.isAgreePact" :close-btn="setting.isAgreePact")
|
||||
main(:class="$style.main")
|
||||
h2 许可协议
|
||||
div.select.scroll(:class="$style.content")
|
||||
p
|
||||
| 本项目(软件)基于
|
||||
strong.hover.underline(@click="openUrl('http://www.apache.org/licenses/LICENSE-2.0')") Apache License 2.0
|
||||
| 许可证发行,在使用本软件前,你(使用者)需签署本协议才可继续使用,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
|
||||
br
|
||||
p 词语约定:本协议中的“本软件”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本软件内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
|
||||
br
|
||||
p
|
||||
strong 1、
|
||||
| 本软件的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本软件不对数据的准确性负责。
|
||||
p
|
||||
strong 2、
|
||||
| 使用本软件的过程中可能会产生版权数据,对于这些版权数据,本软件不拥有它们的所有权,为了避免造成侵权,使用者务必在
|
||||
strong 24小时内
|
||||
| 清除使用本软件的过程中所产生的版权数据。
|
||||
p
|
||||
strong 3、
|
||||
| 本软件内的官方音乐平台别名为本软件内对官方音乐平台的一个称呼,不包含恶意,如果官方音乐平台觉得不妥,可联系本软件更改或移除。
|
||||
p
|
||||
strong 4、
|
||||
| 本软件内使用的部分包括但不限于字体、图片等资源来源于互联网,如果出现侵权可联系本软件移除。
|
||||
p
|
||||
strong 5、
|
||||
| 由于使用本软件产生的包括由于本协议或由于使用或无法使用本软件而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
|
||||
p
|
||||
strong 6、
|
||||
| 本项目完全免费,且开源发布于
|
||||
span.hover.underline(@click="openUrl('https://github.com/lyswhut/lx-music-desktop#readme')") GitHub
|
||||
| 面向全世界人用作对技术的学习交流,本软件不对项目内的技术可能存在违反当地法律法规的行为作保证,
|
||||
strong 禁止在违反当地法律法规的情况下使用本软件
|
||||
|,对于使用者在明知或不知当地法律法规不允许的情况下使用本软件所造成的任何违法违规行为由使用者承担,本软件不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
|
||||
br
|
||||
p
|
||||
strong *
|
||||
| 若协议更新,恕不另行通知,可到开源地址查看。
|
||||
p
|
||||
strong *
|
||||
| 本软件的初衷是帮助官方音乐平台简化数据后代为展示,帮助使用者根据歌曲名、艺术家等关键字快速地定位所需内容所在的音乐平台。
|
||||
p
|
||||
strong *
|
||||
| 音乐平台不易,建议到对应音乐平台支持正版资源。
|
||||
|
||||
br
|
||||
p(v-if="!setting.isAgreePact")
|
||||
strong 若你(使用者)接受以上协议,请点击下面的“接受”按钮签署本协议,若不接受,请点击“不接受”后退出软件并清除本软件的所有数据。
|
||||
|
||||
|
||||
div(:class="$style.btns" v-if="!setting.isAgreePact")
|
||||
base-btn(:class="$style.btn" @click="handleClose(true)") {{$t('not_agree')}}
|
||||
base-btn(:class="$style.btn" :disabled="!btnEnable" @click="handleClick()") {{$t('agree')}} {{timeStr}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
import { rendererSend, NAMES } from '@common/ipc'
|
||||
import { openUrl } from '@renderer/utils'
|
||||
import { isShowPact } from '@renderer/core/share'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
return {
|
||||
isShowPact,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
time: 20,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
btnEnable() {
|
||||
return this.time == 0
|
||||
},
|
||||
timeStr() {
|
||||
return this.btnEnable ? '' : `(${this.time})`
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'setting.isAgreePact'(n) {
|
||||
if (n) return
|
||||
this.time = 5
|
||||
this.startTimeout()
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (!this.setting.isAgreePact) {
|
||||
this.startTimeout()
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setAgreePact']),
|
||||
handleClick() {
|
||||
this.setAgreePact()
|
||||
setTimeout(() => {
|
||||
this.$dialog({
|
||||
message: Buffer.from('e69cace8bdafe4bbb6e5ae8ce585a8e5858de8b4b9e4b894e5bc80e6ba90efbc8ce5a682e69e9ce4bda0e698afe88ab1e992b1e8b4ade4b9b0e79a84efbc8ce8afb7e79bb4e68ea5e7bb99e5b7aee8af84efbc810a0a5468697320736f667477617265206973206672656520616e64206f70656e20736f757263652e', 'hex').toString(),
|
||||
confirmButtonText: Buffer.from('e5a5bde79a8420284f4b29', 'hex').toString(),
|
||||
})
|
||||
}, 2e3)
|
||||
},
|
||||
handleClose(isExit) {
|
||||
if (isExit) return rendererSend(NAMES.mainWindow.close, true)
|
||||
isShowPact.value = false
|
||||
},
|
||||
openUrl(url) {
|
||||
openUrl(url)
|
||||
},
|
||||
startTimeout() {
|
||||
setTimeout(() => {
|
||||
if (--this.time > 0) this.startTimeout()
|
||||
}, 1e3)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 15px;
|
||||
max-width: 550px;
|
||||
min-width: 200px;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: @color-theme_2-font;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
margin: 15px 0;
|
||||
padding-right: 5px;
|
||||
h3 {
|
||||
font-weight: bold;
|
||||
line-height: 2;
|
||||
}
|
||||
p {
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.btn {
|
||||
display: block;
|
||||
width: 48%;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
.name {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -1,129 +0,0 @@
|
||||
<template>
|
||||
<div :class="$style.controlBtn">
|
||||
<common-volume-bar :setting="setting" />
|
||||
<div :class="$style.titleBtn" @click="toggleDesktopLyric" @contextmenu="toggleLockDesktopLyric" :aria-label="toggleDesktopLyricBtnTitle">
|
||||
<svg v-show="setting.desktopLyric.enable" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 512 512" space="preserve">
|
||||
<use xlink:href="#icon-desktop-lyric-on"></use>
|
||||
</svg>
|
||||
<svg v-show="!setting.desktopLyric.enable" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 512 512" space="preserve">
|
||||
<use xlink:href="#icon-desktop-lyric-off"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div :class="$style.titleBtn" @click="toggleNextPlayMode" :aria-label="nextTogglePlayName">
|
||||
<svg v-show="setting.player.togglePlayMethod == 'listLoop'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="80%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-list-loop"></use>
|
||||
</svg>
|
||||
<svg v-show="setting.player.togglePlayMethod == 'random'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-list-random"></use>
|
||||
</svg>
|
||||
<svg v-show="setting.player.togglePlayMethod == 'list'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="120%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-list-order"></use>
|
||||
</svg>
|
||||
<svg v-show="setting.player.togglePlayMethod == 'singleLoop'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-single-loop"></use>
|
||||
</svg>
|
||||
<svg v-show="!setting.player.togglePlayMethod" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="120%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-single"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div :class="$style.titleBtn" @click="addMusicTo" :aria-label="$t('player__add_music_to')">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="80%" viewBox="0 0 512 512" space="preserve">
|
||||
<use xlink:href="#icon-add-2"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<common-list-add-modal v-model:show="isShowAddMusicTo" :musicInfo="musicInfoItem" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useI18n, ref, useRefGetter } from '@renderer/utils/vueTools'
|
||||
import useNextTogglePlay from '@renderer/utils/compositions/useNextTogglePlay'
|
||||
import useToggleDesktopLyric from '@renderer/utils/compositions/useToggleDesktopLyric'
|
||||
import { musicInfo, musicInfoItem } from '@renderer/core/share/player'
|
||||
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
const isShowAddMusicTo = ref(false)
|
||||
|
||||
const setting = useRefGetter('setting')
|
||||
|
||||
|
||||
const {
|
||||
nextTogglePlayName,
|
||||
toggleNextPlayMode,
|
||||
} = useNextTogglePlay({ setting, t })
|
||||
|
||||
const {
|
||||
toggleDesktopLyricBtnTitle,
|
||||
toggleDesktopLyric,
|
||||
toggleLockDesktopLyric,
|
||||
} = useToggleDesktopLyric({ setting, t })
|
||||
|
||||
|
||||
const addMusicTo = () => {
|
||||
if (!musicInfo.songmid) return
|
||||
isShowAddMusicTo.value = true
|
||||
}
|
||||
|
||||
return {
|
||||
setting,
|
||||
isShowAddMusicTo,
|
||||
nextTogglePlayName,
|
||||
toggleNextPlayMode,
|
||||
toggleDesktopLyricBtnTitle,
|
||||
toggleDesktopLyric,
|
||||
toggleLockDesktopLyric,
|
||||
addMusicTo,
|
||||
musicInfoItem,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.controlBtn {
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
|
||||
.titleBtn {
|
||||
flex: none;
|
||||
margin-left: 5px;
|
||||
height: 100%;
|
||||
width: 20px;
|
||||
color: @color-btn;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
transition: opacity 0.2s ease;
|
||||
opacity: .6;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
&:active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.titleBtn {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -1,376 +0,0 @@
|
||||
<template lang="pug">
|
||||
div(:class="$style.player")
|
||||
div(:class="$style.left" @contextmenu="handleToMusicLocation" @click="showPlayerDetail" :aria-label="$t('player__pic_tip')")
|
||||
img(v-if="musicInfo.img" :src="musicInfo.img" @error="imgError")
|
||||
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='102%' width='100%' viewBox='0 0 60 60' space='preserve')
|
||||
use(:xlink:href='`#${$style.iconPic}`')
|
||||
div(:class="$style.middle")
|
||||
div(:class="$style.column1")
|
||||
div(:class="$style.container")
|
||||
div(:class="$style.title" @click="handleCopy(title)" :aria-label="title + $t('copy_tip')") {{title}}
|
||||
control-btns
|
||||
div(:class="$style.column2")
|
||||
common-progress-bar(:progress="progress" :handleTransitionEnd="handleTransitionEnd" :isActiveTransition="isActiveTransition" v-if="!isShowPlayerDetail")
|
||||
div(:class="$style.column3")
|
||||
template(v-if="!isShowPlayerDetail")
|
||||
span(:class="$style.statusText") {{statusText}}
|
||||
span {{nowPlayTimeStr}}
|
||||
span(style="margin: 0 5px;") /
|
||||
span {{maxPlayTimeStr}}
|
||||
div(:class="$style.right")
|
||||
div(:class="$style.playBtn" @click='playPrev' :aria-label="$t('player__prev')" style="transform: rotate(180deg);")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
|
||||
use(xlink:href='#icon-nextMusic')
|
||||
div(:class="$style.playBtn" :aria-label="isPlay ? $t('player__pause') : $t('player__play')" @click='togglePlay')
|
||||
svg(v-if="isPlay" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
|
||||
use(xlink:href='#icon-pause')
|
||||
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
|
||||
use(xlink:href='#icon-play')
|
||||
div(:class="$style.playBtn" @click='playNext' :aria-label="$t('player__next')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
|
||||
use(xlink:href='#icon-nextMusic')
|
||||
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style="display: none;")
|
||||
g(:id="$style.iconPic")
|
||||
path(d='M29,0C12.984,0,0,12.984,0,29c0,16.016,12.984,29,29,29s29-12.984,29-29C58,12.984,45.016,0,29,0zM29,36.08c-3.91,0-7.08-3.17-7.08-7.08c0-3.91,3.17-7.08,7.08-7.08s7.08,3.17,7.08,7.08C36.08,32.91,32.91,36.08,29,36.08z')
|
||||
path(:class="$style.c1" d='M6.487,22.932c-0.077,0-0.156-0.009-0.234-0.027c-0.537-0.13-0.868-0.67-0.739-1.206c0.946-3.935,2.955-7.522,5.809-10.376s6.441-4.862,10.376-5.809c0.536-0.127,1.077,0.202,1.206,0.739c0.129,0.536-0.202,1.076-0.739,1.206c-3.575,0.859-6.836,2.685-9.429,5.277s-4.418,5.854-5.277,9.429C7.349,22.624,6.938,22.932,6.487,22.932z')
|
||||
path(:class="$style.c1" d='M36.066,52.514c-0.451,0-0.861-0.308-0.972-0.767c-0.129-0.536,0.202-1.076,0.739-1.206c3.576-0.859,6.837-2.685,9.43-5.277s4.418-5.854,5.277-9.429c0.129-0.538,0.668-0.868,1.206-0.739c0.537,0.13,0.868,0.67,0.739,1.206c-0.946,3.935-2.955,7.522-5.809,10.376s-6.441,4.862-10.377,5.809C36.223,52.505,36.144,52.514,36.066,52.514z')
|
||||
path(:class="$style.c1" d='M11.313,24.226c-0.075,0-0.151-0.008-0.228-0.026c-0.538-0.125-0.873-0.663-0.747-1.2c0.72-3.09,2.282-5.904,4.52-8.141c2.236-2.237,5.051-3.8,8.141-4.52c0.535-0.131,1.075,0.209,1.2,0.747c0.126,0.537-0.209,1.075-0.747,1.2c-2.725,0.635-5.207,2.014-7.18,3.986s-3.352,4.455-3.986,7.18C12.179,23.914,11.768,24.226,11.313,24.226z')
|
||||
path(:class="$style.c1" d='M34.773,47.688c-0.454,0-0.865-0.312-0.973-0.773c-0.126-0.537,0.209-1.075,0.747-1.2c2.725-0.635,5.207-2.014,7.18-3.986s3.352-4.455,3.986-7.18c0.125-0.538,0.662-0.88,1.2-0.747c0.538,0.125,0.873,0.663,0.747,1.2c-0.72,3.09-2.282,5.904-4.52,8.141c-2.236,2.237-5.051,3.8-8.141,4.52C34.925,47.68,34.849,47.688,34.773,47.688z')
|
||||
path(:class="$style.c1" d='M16.14,25.519c-0.071,0-0.143-0.008-0.215-0.023c-0.539-0.118-0.881-0.651-0.763-1.19c0.997-4.557,4.586-8.146,9.143-9.143c0.537-0.116,1.071,0.222,1.19,0.763c0.118,0.539-0.224,1.072-0.763,1.19c-3.796,0.831-6.786,3.821-7.617,7.617C17.013,25.2,16.6,25.519,16.14,25.519z')
|
||||
path(:class="$style.c1" d='M33.48,42.861c-0.46,0-0.873-0.318-0.976-0.786c-0.118-0.539,0.224-1.072,0.763-1.19c3.796-0.831,6.786-3.821,7.617-7.617c0.118-0.541,0.65-0.881,1.19-0.763c0.539,0.118,0.881,0.651,0.763,1.19c-0.997,4.557-4.586,8.146-9.143,9.143C33.623,42.854,33.552,42.861,33.48,42.861z')
|
||||
path(:class="$style.c2" d='M29,38.08c-5.007,0-9.08-4.073-9.08-9.08s4.073-9.08,9.08-9.08s9.08,4.073,9.08,9.08S34.007,38.08,29,38.08z M29,23.92c-2.801,0-5.08,2.279-5.08,5.08s2.279,5.08,5.08,5.08s5.08-2.279,5.08-5.08S31.801,23.92,29,23.92z')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useRefGetter, computed, useRouter } from '@renderer/utils/vueTools'
|
||||
import { clipboardWriteText } from '@renderer/utils'
|
||||
import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
import ControlBtns from './ControlBtns'
|
||||
import usePlayProgress from '@renderer/utils/compositions/usePlayProgress'
|
||||
// import { lyric } from '@renderer/core/share/lyric'
|
||||
import {
|
||||
statusText,
|
||||
musicInfo,
|
||||
setMusicInfo,
|
||||
isShowPlayerDetail,
|
||||
setShowPlayerDetail,
|
||||
isPlay,
|
||||
musicInfoItem,
|
||||
playInfo,
|
||||
playMusicInfo,
|
||||
} from '@renderer/core/share/player'
|
||||
|
||||
export default {
|
||||
name: 'CorePlayBar',
|
||||
components: {
|
||||
ControlBtns,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShowAddMusicTo: false,
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const setting = useRefGetter('setting')
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const {
|
||||
nowPlayTimeStr,
|
||||
maxPlayTimeStr,
|
||||
progress,
|
||||
isActiveTransition,
|
||||
handleTransitionEnd,
|
||||
} = usePlayProgress()
|
||||
|
||||
const showPlayerDetail = () => {
|
||||
if (!musicInfoItem.value.songmid) return
|
||||
setShowPlayerDetail(true)
|
||||
}
|
||||
const handleCopy = (text) => {
|
||||
clipboardWriteText(text)
|
||||
}
|
||||
|
||||
const imgError = () => {
|
||||
// console.log(e)
|
||||
setMusicInfo({ img: null })
|
||||
}
|
||||
|
||||
const handleToMusicLocation = () => {
|
||||
const listId = playMusicInfo.listId
|
||||
if (!listId || listId == '__temp__' || listId == 'download' || !musicInfoItem.value.songmid) return
|
||||
if (playInfo.playIndex == -1) return
|
||||
router.push({
|
||||
path: 'list',
|
||||
query: {
|
||||
id: listId,
|
||||
scrollIndex: playInfo.playIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const title = computed(() => {
|
||||
return musicInfo.name
|
||||
? setting.value.download.fileName.replace('歌名', musicInfo.name).replace('歌手', musicInfo.singer)
|
||||
: '^-^'
|
||||
})
|
||||
|
||||
|
||||
const togglePlay = () => {
|
||||
window.eventHub.emit(eventPlayerNames.setTogglePlay)
|
||||
}
|
||||
const playNext = () => {
|
||||
window.eventHub.emit(eventPlayerNames.setPlayNext)
|
||||
}
|
||||
const playPrev = () => {
|
||||
window.eventHub.emit(eventPlayerNames.setPlayPrev)
|
||||
}
|
||||
|
||||
// onBeforeUnmount(() => {
|
||||
// window.eventHub.emit(eventPlayerNames.setTogglePlay)
|
||||
// })
|
||||
|
||||
return {
|
||||
musicInfo,
|
||||
nowPlayTimeStr,
|
||||
maxPlayTimeStr,
|
||||
progress,
|
||||
isActiveTransition,
|
||||
handleTransitionEnd,
|
||||
handleCopy,
|
||||
imgError,
|
||||
statusText,
|
||||
title,
|
||||
showPlayerDetail,
|
||||
isPlay,
|
||||
togglePlay,
|
||||
setting,
|
||||
playNext,
|
||||
playPrev,
|
||||
handleToMusicLocation,
|
||||
isShowPlayerDetail,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.player {
|
||||
height: @height-player;
|
||||
background-color: @color-theme_2;
|
||||
transition: @transition-theme;
|
||||
transition-property: border-color;
|
||||
border-top: 2px solid @color-theme;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
contain: strict;
|
||||
z-index: 2;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
.left {
|
||||
width: @height-player - 2;
|
||||
height: 100%;
|
||||
color: @color-theme;
|
||||
transition: @transition-theme;
|
||||
transition-property: color;
|
||||
flex: none;
|
||||
padding: 2PX;
|
||||
opacity: 1;
|
||||
transition: @transition-theme;
|
||||
transition-property: opacity;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
// align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
img {
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
transition: @transition-theme;
|
||||
transition-property: border-color;
|
||||
// border-radius: 50%;
|
||||
border-radius: @radius-border;
|
||||
// border: 2px solid @color-theme_2-background_1;
|
||||
}
|
||||
}
|
||||
.middle {
|
||||
flex: auto;
|
||||
height: 100%;
|
||||
padding: 5px 10px 5px 8px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
.right {
|
||||
height: 100%;
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding-left: 15px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.column1 {
|
||||
flex: auto;
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
.container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
padding-right: 5px;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
|
||||
.play-btn {
|
||||
+ .play-btn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
flex: none;
|
||||
height: 46%;
|
||||
// margin-top: -2px;
|
||||
// transition: @transition-theme;
|
||||
// transition-property: color;
|
||||
// color: @color-theme;
|
||||
transition: opacity 0.2s ease;
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.column2 {
|
||||
flex: none;
|
||||
padding: 3px 0;
|
||||
height: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.column3 {
|
||||
transition: @transition-theme;
|
||||
transition-property: color;
|
||||
color: @color-theme_2-font;
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
padding-top: 2px;
|
||||
// justify-content: space-between;
|
||||
height: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 0.98em;
|
||||
transition: @transition-theme;
|
||||
transition-property: color;
|
||||
color: @color-player-status-text;
|
||||
.mixin-ellipsis-1;
|
||||
// padding: 0 5px;
|
||||
padding-right: 5px;
|
||||
flex: 1 1 0;
|
||||
// text-align: center;
|
||||
line-height: 1.2;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#icon-pic {
|
||||
color: @color-theme;
|
||||
.c1 {
|
||||
transition: @transition-theme;
|
||||
transition-property: fill;
|
||||
fill: @color-player-pic-c1;
|
||||
}
|
||||
.c2 {
|
||||
transition: @transition-theme;
|
||||
transition-property: fill;
|
||||
fill: @color-player-pic-c2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.player {
|
||||
background-color: ~'@{color-@{value}-theme_2}';
|
||||
border-top-color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
.left {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
// img {
|
||||
// border-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
// }
|
||||
}
|
||||
.titleBtn {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
}
|
||||
.play-btn {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
svg {
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3));
|
||||
}
|
||||
}
|
||||
.volume {
|
||||
background-color: ~'@{color-@{value}-player-progress-bar1}';
|
||||
}
|
||||
|
||||
.volume-bar {
|
||||
background-color: ~'@{color-@{value}-btn}';
|
||||
}
|
||||
|
||||
|
||||
.column3 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
|
||||
.status-text {
|
||||
color: ~'@{color-@{value}-player-status-text}';
|
||||
}
|
||||
|
||||
#icon-pic {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
.c1 {
|
||||
fill: ~'@{color-@{value}-player-pic-c1}';
|
||||
}
|
||||
.c2 {
|
||||
fill: ~'@{color-@{value}-player-pic-c2}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -1,378 +0,0 @@
|
||||
<template>
|
||||
<div :class="['right', $style.right]" :style="lrcFontSize">
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div v-show="!isShowLrcSelectContent"
|
||||
:class="['lyric', $style.lyric, { [$style.draging]: isMsDown }, { [$style.lrcActiveZoom]: isZoomActiveLrc }]"
|
||||
:style="lrcStyles" @wheel="handleWheel"
|
||||
@mousedown="handleLyricMouseDown" ref="dom_lyric"
|
||||
@contextmenu.stop="handleShowLyricMenu"
|
||||
>
|
||||
<div :class="['pre', $style.lyricSpace]"></div>
|
||||
<div ref="dom_lyric_text"></div>
|
||||
<div :class="$style.lyricSpace"></div>
|
||||
</div>
|
||||
</transition>
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div :class="$style.skip" v-if="isShowLyricProgressSetting" v-show="isStopScroll && !isShowLrcSelectContent">
|
||||
<div :class="$style.line" ref="dom_skip_line"></div>
|
||||
<span :class="$style.label">{{timeStr}}</span>
|
||||
<base-btn :class="$style.skipBtn" @mouseenter="handleSkipMouseEnter" @mouseleave="handleSkipMouseLeave" @click="handleSkipPlay">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="50%" viewBox="0 0 170 170" space="preserve">
|
||||
<use xlink:href="#icon-play"></use>
|
||||
</svg>
|
||||
</base-btn>
|
||||
</div>
|
||||
</transition>
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div :class="[$style.lyricSelectContent, 'select', 'scroll', 'lyricSelectContent']" v-if="isShowLrcSelectContent" @contextmenu="handleCopySelectText">
|
||||
<div v-for="(info, index) in lyric.lines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]">
|
||||
<span>{{info.text}}</span>
|
||||
<template v-for="(lrc, index) in info.extendedLyrics" :key="index">
|
||||
<br />
|
||||
<span :class="$style.lyricSelectlineExtended">{{lrc}}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<LyricMenu v-model="lyricMenuVisible" :xy="lyricMenuXY" :lyricInfo="lyricInfo" @updateLyric="handleUpdateLyric" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { clipboardWriteText } from '@renderer/utils'
|
||||
import { lyric } from '@renderer/core/share/lyric'
|
||||
import { isFullscreen } from '@renderer/core/share'
|
||||
import { isPlay, isShowLrcSelectContent, isShowPlayComment, musicInfo as playerMusicInfo, musicInfoItem, setMusicInfo } from '@renderer/core/share/player'
|
||||
import { onMounted, onBeforeUnmount, useRefGetter, computed, reactive, ref } from '@renderer/utils/vueTools'
|
||||
import useLyric from '@renderer/utils/compositions/useLyric'
|
||||
import LyricMenu from './components/LyricMenu'
|
||||
import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LyricMenu,
|
||||
},
|
||||
setup() {
|
||||
const setting = useRefGetter('setting')
|
||||
const isZoomActiveLrc = computed(() => setting.value.playDetail.isZoomActiveLrc)
|
||||
const isShowLyricProgressSetting = computed(() => setting.value.playDetail.isShowLyricProgressSetting)
|
||||
|
||||
const {
|
||||
dom_lyric,
|
||||
dom_lyric_text,
|
||||
dom_skip_line,
|
||||
isMsDown,
|
||||
isStopScroll,
|
||||
timeStr,
|
||||
handleLyricMouseDown,
|
||||
handleWheel,
|
||||
handleSkipPlay,
|
||||
handleSkipMouseEnter,
|
||||
handleSkipMouseLeave,
|
||||
} = useLyric({ isPlay, lyric, isShowLyricProgressSetting })
|
||||
|
||||
const lyricMenuVisible = ref(false)
|
||||
const lyricMenuXY = reactive({
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
const lyricInfo = reactive({
|
||||
lyric: '',
|
||||
tlyric: '',
|
||||
rlyric: '',
|
||||
lxlyric: '',
|
||||
rawlyric: '',
|
||||
musicInfo: null,
|
||||
})
|
||||
const updateMusicInfo = () => {
|
||||
lyricInfo.lyric = playerMusicInfo.lrc
|
||||
lyricInfo.tlyric = playerMusicInfo.tlrc
|
||||
lyricInfo.rlyric = playerMusicInfo.rlrc
|
||||
lyricInfo.lxlyric = playerMusicInfo.lxlrc
|
||||
lyricInfo.rawlyric = playerMusicInfo.rawlrc
|
||||
lyricInfo.musicInfo = musicInfoItem.value
|
||||
}
|
||||
const handleShowLyricMenu = event => {
|
||||
updateMusicInfo()
|
||||
lyricMenuXY.x = event.pageX
|
||||
lyricMenuXY.y = event.pageY
|
||||
lyricMenuVisible.value = true
|
||||
}
|
||||
const handleUpdateLyric = ({ lyric, tlyric, rlyric, lxlyric, offset }) => {
|
||||
setMusicInfo({
|
||||
lrc: lyric,
|
||||
tlrc: tlyric,
|
||||
rlrc: rlyric,
|
||||
lxlrc: lxlyric,
|
||||
})
|
||||
// console.log(offset)
|
||||
window.eventHub.emit(eventPlayerNames.updateLyricOffset, offset)
|
||||
}
|
||||
|
||||
const lrcStyles = computed(() => {
|
||||
return {
|
||||
textAlign: setting.value.playDetail.style.align,
|
||||
}
|
||||
})
|
||||
const lrcFontSize = computed(() => {
|
||||
let size = setting.value.playDetail.style.fontSize / 100
|
||||
if (isFullscreen.value) size = size *= 1.4
|
||||
return {
|
||||
'--playDetail-lrc-font-size': (isShowPlayComment.value ? size * 0.82 : size) + 'rem',
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.eventHub.on(eventPlayerNames.updateLyric, updateMusicInfo)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.eventHub.off(eventPlayerNames.updateLyric, updateMusicInfo)
|
||||
})
|
||||
|
||||
return {
|
||||
dom_lyric,
|
||||
dom_lyric_text,
|
||||
dom_skip_line,
|
||||
isMsDown,
|
||||
timeStr,
|
||||
handleLyricMouseDown,
|
||||
handleWheel,
|
||||
handleSkipPlay,
|
||||
handleSkipMouseEnter,
|
||||
handleSkipMouseLeave,
|
||||
lyric,
|
||||
lrcStyles,
|
||||
lrcFontSize,
|
||||
isShowLrcSelectContent,
|
||||
isShowLyricProgressSetting,
|
||||
isZoomActiveLrc,
|
||||
isStopScroll,
|
||||
lyricMenuVisible,
|
||||
lyricMenuXY,
|
||||
handleShowLyricMenu,
|
||||
handleUpdateLyric,
|
||||
lyricInfo,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCopySelectText() {
|
||||
let str = window.getSelection().toString()
|
||||
str = str.trim()
|
||||
if (!str.length) return
|
||||
clipboardWriteText(str)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.right {
|
||||
flex: 0 0 60%;
|
||||
// padding: 0 30px;
|
||||
position: relative;
|
||||
transition: flex-basis @transition-theme;
|
||||
}
|
||||
.lyric {
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: var(--playDetail-lrc-font-size, 16px);
|
||||
-webkit-mask-image: linear-gradient(transparent 0%, #fff 20%, #fff 80%, transparent 100%);
|
||||
cursor: grab;
|
||||
&.draging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
:global {
|
||||
.lrc-content {
|
||||
line-height: 1.2;
|
||||
padding: calc(var(--playDetail-lrc-font-size, 16px) / 2) 1px;
|
||||
overflow-wrap: break-word;
|
||||
color: @color-player-detail-lyric;
|
||||
transition: @transition-theme;
|
||||
transition-property: padding;
|
||||
|
||||
.extended {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size, color;
|
||||
font-size: .9em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.line {
|
||||
transition-property: font-size, color !important;
|
||||
background: none !important;
|
||||
-webkit-text-fill-color: unset;
|
||||
// -webkit-text-fill-color: none !important;
|
||||
}
|
||||
&.active {
|
||||
.line {
|
||||
color: @color-theme;
|
||||
}
|
||||
.extended {
|
||||
color: @color-theme;
|
||||
}
|
||||
// span {
|
||||
// // color: @color-theme;
|
||||
// font-size: 1.1em;
|
||||
// }
|
||||
}
|
||||
|
||||
span {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size !important;
|
||||
font-size: 1em;
|
||||
background-repeat: no-repeat;
|
||||
background-color: rgba(77, 77, 77, 0.9);
|
||||
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-size: 0 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
// p {
|
||||
// padding: 8px 0;
|
||||
// line-height: 1.2;
|
||||
// overflow-wrap: break-word;
|
||||
// transition: @transition-theme !important;
|
||||
// transition-property: color, font-size;
|
||||
// }
|
||||
// .lrc-active {
|
||||
// color: @color-theme;
|
||||
// font-size: 1.2em;
|
||||
// }
|
||||
}
|
||||
.lrcActiveZoom {
|
||||
:global {
|
||||
.lrc-content {
|
||||
&.active {
|
||||
.extended {
|
||||
font-size: .94em;
|
||||
}
|
||||
span {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skip {
|
||||
position: absolute;
|
||||
top: calc(38% + var(--playDetail-lrc-font-size, 16px) + 4px);
|
||||
left: 0;
|
||||
// height: 6px;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
// opacity: .5;
|
||||
.line {
|
||||
border-top: 2px dotted @color-player-detail-lyric-active;
|
||||
opacity: .15;
|
||||
margin-right: 30px;
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 15%, #fff 100%);
|
||||
}
|
||||
.label {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: -14px;
|
||||
line-height: 1;
|
||||
font-size: 12px;
|
||||
color: @color-player-detail-lyric-active;
|
||||
opacity: .7;
|
||||
}
|
||||
.skipBtn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transform: translateY(-50%);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none !important;
|
||||
pointer-events: initial;
|
||||
transition: @transition-theme;
|
||||
transition-property: opacity;
|
||||
opacity: .8;
|
||||
&:hover {
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
}
|
||||
.lyricSelectContent {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
// text-align: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
z-index: 10;
|
||||
color: @color-player-detail-lyric;
|
||||
|
||||
.lyricSelectline {
|
||||
padding: 8px 0;
|
||||
overflow-wrap: break-word;
|
||||
transition: @transition-theme !important;
|
||||
transition-property: color, font-size;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.lyricSelectlineExtended {
|
||||
font-size: 14px;
|
||||
}
|
||||
.lrc-active {
|
||||
color: @color-theme;
|
||||
}
|
||||
}
|
||||
|
||||
.lyric-space {
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.lyric {
|
||||
:global {
|
||||
.lrc-content {
|
||||
color: ~'@{color-@{value}-player-detail-lyric}';
|
||||
|
||||
&.active {
|
||||
.extended {
|
||||
color: ~'@{color-@{value}-player-detail-lyric-active}';
|
||||
}
|
||||
.line {
|
||||
color: ~'@{color-@{value}-player-detail-lyric-active}';
|
||||
}
|
||||
}
|
||||
span {
|
||||
background-color: ~'@{color-@{value}-player-detail-lyric}';
|
||||
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-player-detail-lyric-active}', ~'@{color-@{value}-player-detail-lyric-active}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// .lrc-active {
|
||||
// color: ~'@{color-@{value}-theme}';
|
||||
// }
|
||||
.skip {
|
||||
.line {
|
||||
border-top-color: ~'@{color-@{value}-player-detail-lyric-active}';
|
||||
}
|
||||
.label {
|
||||
color:~'@{color-@{value}-player-detail-lyric-active}';
|
||||
}
|
||||
}
|
||||
.lyricSelectContent {
|
||||
color: ~'@{color-@{value}-player-detail-lyric}';
|
||||
.lrc-active {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -1,161 +0,0 @@
|
||||
<template lang="pug">
|
||||
div(:class="$style.footer")
|
||||
div(:class="$style.footerLeft")
|
||||
control-btns
|
||||
div(:class="$style.progressContainer")
|
||||
div(:class="$style.progressContent")
|
||||
common-progress-bar(:class-name="$style.progress" :progress="progress" :handleTransitionEnd="handleTransitionEnd" :isActiveTransition="isActiveTransition")
|
||||
div(:class="$style.timeLabel")
|
||||
span(:class="$style.status" style="margin-left: 15px") {{status}}
|
||||
span {{nowPlayTimeStr}}
|
||||
span(style="margin: 0 5px;") /
|
||||
span {{maxPlayTimeStr}}
|
||||
div(:class="$style.playControl")
|
||||
div(:class="$style.playBtn" @click="playPrev" style="transform: rotate(180deg);" :aria-label="$t('player__prev')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
|
||||
use(xlink:href='#icon-nextMusic')
|
||||
div(:class="$style.playBtn" @click="togglePlay" :aria-label="isPlay ? $t('player__pause') : $t('player__play')")
|
||||
svg(v-if="isPlay" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
|
||||
use(xlink:href='#icon-pause')
|
||||
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
|
||||
use(xlink:href='#icon-play')
|
||||
div(:class="$style.playBtn" @click="playNext" :aria-label="$t('player__next')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
|
||||
use(xlink:href='#icon-nextMusic')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { status, isPlay } from '@renderer/core/share/player'
|
||||
import usePlayProgress from '@renderer/utils/compositions/usePlayProgress'
|
||||
import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
|
||||
import ControlBtns from './components/ControlBtns'
|
||||
export default {
|
||||
components: {
|
||||
ControlBtns,
|
||||
},
|
||||
setup() {
|
||||
const {
|
||||
nowPlayTimeStr,
|
||||
maxPlayTimeStr,
|
||||
progress,
|
||||
isActiveTransition,
|
||||
handleTransitionEnd,
|
||||
} = usePlayProgress()
|
||||
|
||||
return {
|
||||
nowPlayTimeStr,
|
||||
maxPlayTimeStr,
|
||||
status,
|
||||
progress,
|
||||
isActiveTransition,
|
||||
handleTransitionEnd,
|
||||
isPlay,
|
||||
togglePlay() {
|
||||
window.eventHub.emit(eventPlayerNames.setTogglePlay)
|
||||
},
|
||||
playNext() {
|
||||
window.eventHub.emit(eventPlayerNames.setPlayNext)
|
||||
},
|
||||
playPrev() {
|
||||
window.eventHub.emit(eventPlayerNames.setPlayPrev)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.footer {
|
||||
flex: 0 0 100px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.footerLeft {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
padding: 13px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
.progress-content {
|
||||
position: relative;
|
||||
height: 16px;
|
||||
padding: 5px 0;
|
||||
width: 100%;
|
||||
}
|
||||
.progress {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bar-transition {
|
||||
transition-property: transform;
|
||||
transition-timing-function: ease-out;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
.time-label {
|
||||
width: 100%;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
span {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.status {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.play-control {
|
||||
flex: none;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 0 25px;
|
||||
color: @color-btn;
|
||||
}
|
||||
.play-btn {
|
||||
height: 40%;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
// transition: @transition-theme;
|
||||
// transition-property: color;
|
||||
color: @color-player-detail-play-btn;
|
||||
transition: opacity 0.2s ease;
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
|
||||
+.play-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
svg {
|
||||
fill: currentColor;
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.play-btn {
|
||||
color: ~'@{color-@{value}-player-detail-play-btn}';
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
@@ -1,159 +0,0 @@
|
||||
<template lang="pug">
|
||||
material-modal(:show="sync.isShowSyncMode" @close="handleClose(false)" :bgClose="false" :close-btn="false")
|
||||
main(:class="$style.main")
|
||||
h2 {{$t('sync__title', { name: sync.deviceName })}}
|
||||
div.scroll(:class="$style.content")
|
||||
dl(:class="$style.btnGroup")
|
||||
dt(:class="$style.label") {{$t('sync__merge_label')}}
|
||||
dd(:class="$style.btns")
|
||||
base-btn(:class="$style.btn" @click="handleSelectMode('merge_local_remote')") {{$t('sync__merge_btn_local_remote')}}
|
||||
base-btn(:class="$style.btn" @click="handleSelectMode('merge_remote_local')") {{$t('sync__merge_btn_remote_local')}}
|
||||
dl(:class="$style.btnGroup")
|
||||
dt(:class="$style.label") {{$t('sync__overwrite_label')}}
|
||||
dd(:class="$style.btns")
|
||||
base-btn(:class="$style.btn" @click="handleSelectMode('overwrite_local_remote')") {{$t('sync__overwrite_btn_local_remote')}}
|
||||
base-btn(:class="$style.btn" @click="handleSelectMode('overwrite_remote_local')") {{$t('sync__overwrite_btn_remote_local')}}
|
||||
dd(style="font-size: 14px; margin-top: 5px;")
|
||||
base-checkbox(id="sync_mode_modal_isOverwrite" v-model="isOverwrite" :label="$t('sync__overwrite')")
|
||||
|
||||
dl(:class="$style.btnGroup")
|
||||
dt(:class="$style.label") {{$t('sync__other_label')}}
|
||||
dd(:class="$style.btns")
|
||||
base-btn(:class="$style.btn" @click="handleSelectMode('none')") {{$t('sync__overwrite_btn_none')}}
|
||||
base-btn(:class="$style.btn" @click="handleSelectMode('cancel')") {{$t('sync__overwrite_btn_cancel')}}
|
||||
dl(:class="$style.btnGroup")
|
||||
dd
|
||||
section(:class="$style.tipGroup")
|
||||
h3(:class="$style.title") {{$t('sync__merge_tip')}}
|
||||
p(:class="$style.tip") {{$t('sync__merge_tip_desc')}}
|
||||
section(:class="$style.tipGroup")
|
||||
h3(:class="$style.title") {{$t('sync__overwrite_tip')}}
|
||||
p(:class="$style.tip") {{$t('sync__overwrite_tip_desc')}}
|
||||
section(:class="$style.tipGroup")
|
||||
h3(:class="$style.title") {{$t('sync__other_tip')}}
|
||||
p(:class="$style.tip") {{$t('sync__other_tip_desc')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { sync as eventSyncName } from '@renderer/event/names'
|
||||
import { sync } from '@renderer/core/share'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
return {
|
||||
sync,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOverwrite: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
handleSelectMode(mode) {
|
||||
if (mode.startsWith('overwrite') && this.isOverwrite) mode += '_full'
|
||||
window.eventHub.emit(eventSyncName.send_sync_list, {
|
||||
action: 'selectMode',
|
||||
data: mode,
|
||||
})
|
||||
this.handleClose()
|
||||
},
|
||||
handleClose() {
|
||||
sync.isShowSyncMode = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 15px;
|
||||
max-width: 700px;
|
||||
min-width: 200px;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: @color-theme_2-font;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
padding: 15px 0 5px;
|
||||
padding-right: 5px;
|
||||
.btnGroup + .btnGroup {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.label {
|
||||
color: @color-theme_2-font-label;
|
||||
font-size: 14px;
|
||||
line-height: 2;
|
||||
}
|
||||
.desc {
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.tipGroup {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 12px;
|
||||
|
||||
+ .tipGroup {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.title {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
+.btn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
.name {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<div :class="$style.control" v-show="!isFullscreen">
|
||||
<button type="button" :class="[$style.btn, $style.min]" :aria-label="$t('min')" @click="min">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="60%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-window-minimize-2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" :class="[$style.btn, $style.close]" :aria-label="$t('close')" @click="close">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="60%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-window-close-2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { base as eventBaseName } from '@renderer/event/names'
|
||||
import { isFullscreen } from '@renderer/core/share'
|
||||
// import { getRandom } from '../../utils'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
return {
|
||||
min() {
|
||||
window.eventHub.emit(eventBaseName.min)
|
||||
},
|
||||
max() {
|
||||
window.eventHub.emit(eventBaseName.max)
|
||||
},
|
||||
close() {
|
||||
window.eventHub.emit(eventBaseName.close)
|
||||
},
|
||||
isFullscreen,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.control {
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
-webkit-app-region: no-drag;
|
||||
height: 30px;
|
||||
|
||||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 46px;
|
||||
height: 30px;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 1px;
|
||||
cursor: pointer;
|
||||
color: @color-theme;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
&:hover {
|
||||
&.min, &.max {
|
||||
background-color: @color-btn-hover;
|
||||
}
|
||||
&.close {
|
||||
background-color: @color-closeBtn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.btn {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
&:hover {
|
||||
&.min, &.max {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&.close {
|
||||
background-color: ~'@{color-@{value}-closeBtn}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
@@ -1,28 +0,0 @@
|
||||
<template>
|
||||
<div :class="$style.view">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.view {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
> * {
|
||||
position: absolute !important;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
// background: #fff;
|
||||
// overflow: hidden;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div v-show="!isFullscreen" :class="$style.controlBtn">
|
||||
<button type="button" :class="[$style.btn, $style.close]" :aria-label="$t('close')" @click="closeWindow">
|
||||
<svg :class="$style.controlBtniIcon" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-window-close" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" :class="[$style.btn, $style.min]" :aria-label="$t('min')" @click="minWindow">
|
||||
<svg :class="$style.controlBtniIcon" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-window-minimize" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { minWindow, closeWindow } from '@renderer/utils/ipc'
|
||||
// import { getRandom } from '../../utils'
|
||||
import { isFullscreen } from '@renderer/store'
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
@control-btn-width: @height-toolbar * .26;
|
||||
@control-btn-height: 6%;
|
||||
.controlBtn {
|
||||
box-sizing: border-box;
|
||||
padding: 0 7px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
width: 100%;
|
||||
height: @control-btn-height;
|
||||
-webkit-app-region: no-drag;
|
||||
opacity: .5;
|
||||
transition: opacity @transition-normal;
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
.controlBtniIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.btn {
|
||||
position: relative;
|
||||
width: @control-btn-width;
|
||||
height: @control-btn-width;
|
||||
background: none;
|
||||
border: none;
|
||||
display: flex;
|
||||
// justify-content: center;
|
||||
// align-items: center;
|
||||
outline: none;
|
||||
padding: 1px;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
color: var(--color-font);
|
||||
|
||||
&.min {
|
||||
background-color: var(--color-btn-min);
|
||||
}
|
||||
// &.max {
|
||||
// background-color: var(--color-btn-max);
|
||||
// }
|
||||
&.close {
|
||||
background-color: var(--color-btn-close);
|
||||
}
|
||||
}
|
||||
|
||||
.controlBtniIcon {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
+64
-52
@@ -1,68 +1,79 @@
|
||||
<template>
|
||||
<div :class="$style.menu">
|
||||
<ul :class="$style.list" role="toolbar">
|
||||
<li v-for="item in menus" :key="item.to" :class="$style.navItem" role="presentation">
|
||||
<router-link :class="$style.link" :active-class="$style.active" role="tab" :aria-selected="$route.name == item.to" :to="item.to" :aria-label="item.tips">
|
||||
<div :class="$style.icon">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" :viewBox="item.iconSize" space="preserve">
|
||||
<use :xlink:href="item.icon"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div :class="$style.menu">
|
||||
<ul :class="$style.list" role="toolbar">
|
||||
<li v-for="item in menus" :key="item.to" :class="$style.navItem" role="presentation">
|
||||
<router-link :class="[$style.link, {[$style.active]: $route.meta.name == item.name}]" role="tab" :aria-selected="$route.meta.name == item.name" :to="item.to" :aria-label="item.tips">
|
||||
<div :class="$style.icon">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" :viewBox="item.iconSize" space="preserve">
|
||||
<use :xlink:href="item.icon" />
|
||||
</svg>
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
name: 'Nav',
|
||||
name: 'NavBar',
|
||||
setup() {
|
||||
return {
|
||||
appSetting,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
menus() {
|
||||
return [
|
||||
{
|
||||
to: 'search',
|
||||
to: '/search',
|
||||
tips: this.$t('search'),
|
||||
icon: '#icon-search-2',
|
||||
iconSize: '0 0 425.2 425.2',
|
||||
name: 'Search',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
to: 'songList',
|
||||
to: '/songList/list',
|
||||
tips: this.$t('song_list'),
|
||||
icon: '#icon-album',
|
||||
iconSize: '0 0 425.2 425.2',
|
||||
name: 'SongList',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
to: 'leaderboard',
|
||||
to: '/leaderboard',
|
||||
tips: this.$t('leaderboard'),
|
||||
icon: '#icon-leaderboard',
|
||||
iconSize: '0 0 425.22 425.2',
|
||||
name: 'Leaderboard',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
to: 'list',
|
||||
to: '/list',
|
||||
tips: this.$t('my_list'),
|
||||
icon: '#icon-love',
|
||||
iconSize: '0 0 444.87 391.18',
|
||||
name: 'List',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
to: 'download',
|
||||
to: '/download',
|
||||
tips: this.$t('download'),
|
||||
icon: '#icon-download-2',
|
||||
iconSize: '0 0 425.2 425.2',
|
||||
enable: this.setting.download.enable,
|
||||
enable: this.appSetting['download.enable'],
|
||||
name: 'Download',
|
||||
},
|
||||
{
|
||||
to: 'setting',
|
||||
to: '/setting',
|
||||
tips: this.$t('setting'),
|
||||
icon: '#icon-setting',
|
||||
iconSize: '0 0 493.23 436.47',
|
||||
enable: true,
|
||||
name: 'Setting',
|
||||
},
|
||||
].filter(m => m.enable)
|
||||
},
|
||||
@@ -89,10 +100,11 @@ export default {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
// background-color: pink;
|
||||
// dt {
|
||||
// padding-left: 5px;
|
||||
// font-size: 11px;
|
||||
// transition: @transition-theme;
|
||||
// transition: @transition-normal;
|
||||
// transition-property: color;
|
||||
// color: @color-theme-font-label;
|
||||
// .mixin-ellipsis-1;
|
||||
@@ -109,20 +121,25 @@ export default {
|
||||
}
|
||||
.link {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
left: 0%;
|
||||
top: 0%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// left: 15%;
|
||||
// top: 15%;
|
||||
// width: 70%;
|
||||
// height: 70%;
|
||||
// display: block;
|
||||
box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
// text-decoration: none;
|
||||
// border-radius: 20%;
|
||||
|
||||
// padding: 18px 3px;
|
||||
// margin: 5px 0;
|
||||
// border-left: 5px solid transparent;
|
||||
transition: @transition-theme;
|
||||
transition: @transition-normal;
|
||||
transition-property: color;
|
||||
color: @color-theme-font !important;
|
||||
color: var(--color-nav-font);
|
||||
cursor: pointer;
|
||||
font-size: 11.5px;
|
||||
text-align: center;
|
||||
@@ -131,20 +148,32 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
transition: background-color 0.3s ease;
|
||||
transition: 0.3s ease;
|
||||
transition-property: background-color, opacity;
|
||||
// border-radius: @radius-border;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
&.active {
|
||||
// border-left-color: @color-theme-active;
|
||||
background-color: @color-theme-active;
|
||||
background-color: var(--color-primary-light-400-alpha-600);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-light-300-alpha-600);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(.active) {
|
||||
background-color: @color-theme-hover;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-nav-font);
|
||||
|
||||
&:not(.active) {
|
||||
opacity: .8;
|
||||
background-color: var(--color-primary-light-500-alpha-600);
|
||||
}
|
||||
}
|
||||
&:hover:not(.active) {
|
||||
background-color: @color-theme-active;
|
||||
&:active:not(.active) {
|
||||
opacity: .6;
|
||||
background-color: var(--color-primary-light-200-alpha-600);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,21 +184,4 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.link {
|
||||
color: ~'@{color-@{value}-theme-font}' !important;
|
||||
&.active {
|
||||
background-color: ~'@{color-@{value}-theme-active}';
|
||||
}
|
||||
&:hover:not(.active) {
|
||||
background-color: ~'@{color-@{value}-theme-hover}';
|
||||
}
|
||||
&:active:not(.active) {
|
||||
background-color: ~'@{color-@{value}-theme-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div :class="[$style.aside, { [$style.fullscreen]: isFullscreen }]">
|
||||
<ControlBtns v-if="appSetting['common.controlBtnPosition'] == 'left'" />
|
||||
<div v-else :class="$style.logo">L X</div>
|
||||
<NavBar />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { isFullscreen } from '@renderer/store'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
import ControlBtns from './ControlBtns'
|
||||
import NavBar from './NavBar'
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.aside {
|
||||
// box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
transition: @transition-normal;
|
||||
transition-property: background-color;
|
||||
// background-color: @color-theme-sidebar;
|
||||
// background-color: @color-aside-background;
|
||||
// border-right: 2px solid var(--color-primary);
|
||||
-webkit-app-region: drag;
|
||||
-webkit-user-select: none;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
&.fullscreen {
|
||||
-webkit-app-region: no-drag;
|
||||
.logo {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
box-sizing: border-box;
|
||||
padding: 0 13%;
|
||||
height: 50px;
|
||||
color: var(--color-nav-font);
|
||||
opacity: .8;
|
||||
flex: none;
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
font-weight: bold;
|
||||
// -webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<material-modal :show="!isAgreePact || isShowPact" max-width="70%" :bg-close="isAgreePact" :close-btn="isAgreePact" @close="handleClose(false)">
|
||||
<main :class="$style.main">
|
||||
<h2>许可协议</h2>
|
||||
<div class="select scroll" :class="$style.content">
|
||||
<p>本项目(软件)基于<strong class="hover underline" @click="openUrl('http://www.apache.org/licenses/LICENSE-2.0')">Apache License 2.0</strong> 许可证发行,在使用本软件前,你(使用者)需签署本协议才可继续使用,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。</p><br>
|
||||
<p>词语约定:本协议中的“本软件”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本软件内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。</p><br>
|
||||
<p><strong>1、</strong>本软件的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本软件不对数据的准确性负责。</p>
|
||||
<p><strong>2、</strong>使用本软件的过程中可能会产生版权数据,对于这些版权数据,本软件不拥有它们的所有权,为了避免造成侵权,使用者务必在<strong>24小时内</strong>清除使用本软件的过程中所产生的版权数据。</p>
|
||||
<p><strong>3、</strong>本软件内的官方音乐平台别名为本软件内对官方音乐平台的一个称呼,不包含恶意,如果官方音乐平台觉得不妥,可联系本软件更改或移除。</p>
|
||||
<p><strong>4、</strong>本软件内使用的部分包括但不限于字体、图片等资源来源于互联网,如果出现侵权可联系本软件移除。</p>
|
||||
<p><strong>5、</strong>由于使用本软件产生的包括由于本协议或由于使用或无法使用本软件而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。</p>
|
||||
<p><strong>6、</strong>本项目完全免费,且开源发布于 <span class="hover underline" @click="openUrl('https://github.com/lyswhut/lx-music-desktop#readme')">GitHub</span> 面向全世界人用作对技术的学习交流,本软件不对项目内的技术可能存在违反当地法律法规的行为作保证,<strong>禁止在违反当地法律法规的情况下使用本软件</strong>,对于使用者在明知或不知当地法律法规不允许的情况下使用本软件所造成的任何违法违规行为由使用者承担,本软件不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。</p><br>
|
||||
<p><strong>*</strong> 若协议更新,恕不另行通知,可到开源地址查看。</p>
|
||||
<p><strong>*</strong> 本软件的初衷是帮助官方音乐平台简化数据后代为展示,帮助使用者根据歌曲名、艺术家等关键字快速地定位所需内容所在的音乐平台。</p>
|
||||
<p><strong>*</strong> 音乐平台不易,建议到对应音乐平台支持正版资源。</p><br>
|
||||
<p v-if="!isAgreePact"><strong>若你(使用者)接受以上协议,请点击下面的“接受”按钮签署本协议,若不接受,请点击“不接受”后退出软件并清除本软件的所有数据。</strong></p>
|
||||
</div>
|
||||
<div v-if="!isAgreePact" :class="$style.btns">
|
||||
<base-btn :class="$style.btn" @click="handleClose(true)">{{ $t('not_agree') }}</base-btn>
|
||||
<base-btn :class="$style.btn" :disabled="!btnEnable" @click="handleClick()">{{ $t('agree') }} {{ timeStr }}</base-btn>
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { quitApp } from '@renderer/utils/ipc'
|
||||
import { openUrl } from '@common/utils/electron'
|
||||
import { isShowPact } from '@renderer/store'
|
||||
import { appSetting, saveAgreePact } from '@renderer/store/setting'
|
||||
import { computed } from '@common/utils/vueTools'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const isAgreePact = computed(() => appSetting['common.isAgreePact'])
|
||||
|
||||
return {
|
||||
isShowPact,
|
||||
isAgreePact,
|
||||
appSetting,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
time: 20,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
btnEnable() {
|
||||
return this.time == 0
|
||||
},
|
||||
timeStr() {
|
||||
return this.btnEnable ? '' : `(${this.time})`
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isAgreePact(n) {
|
||||
if (n) return
|
||||
this.time = 5
|
||||
this.startTimeout()
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (!this.isAgreePact) {
|
||||
this.startTimeout()
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
saveAgreePact(true)
|
||||
window.setTimeout(() => {
|
||||
this.$dialog({
|
||||
message: Buffer.from('e69cace8bdafe4bbb6e5ae8ce585a8e5858de8b4b9e4b894e5bc80e6ba90efbc8ce5a682e69e9ce4bda0e698afe88ab1e992b1e8b4ade4b9b0e79a84efbc8ce8afb7e79bb4e68ea5e7bb99e5b7aee8af84efbc810a0a5468697320736f667477617265206973206672656520616e64206f70656e20736f757263652e', 'hex').toString(),
|
||||
confirmButtonText: Buffer.from('e5a5bde79a8420284f4b29', 'hex').toString(),
|
||||
})
|
||||
}, 2e3)
|
||||
},
|
||||
handleClose(isExit) {
|
||||
if (isExit) return quitApp(true)
|
||||
isShowPact.value = false
|
||||
},
|
||||
openUrl(url) {
|
||||
openUrl(url)
|
||||
},
|
||||
startTimeout() {
|
||||
window.setTimeout(() => {
|
||||
if (--this.time > 0) this.startTimeout()
|
||||
}, 1e3)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 15px 8px 12px;
|
||||
min-width: 200px;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
margin: 15px 0;
|
||||
padding: 0 7px;
|
||||
h3 {
|
||||
font-weight: bold;
|
||||
line-height: 2;
|
||||
}
|
||||
p {
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.btn {
|
||||
display: block;
|
||||
width: 48%;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div :class="$style.controlBtn">
|
||||
<common-volume-bar />
|
||||
<div :class="$style.titleBtn" :aria-label="toggleDesktopLyricBtnTitle" @click="toggleDesktopLyric" @contextmenu="toggleLockDesktopLyric">
|
||||
<svg v-show="appSetting['desktopLyric.enable']" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 512 512" space="preserve">
|
||||
<use xlink:href="#icon-desktop-lyric-on" />
|
||||
</svg>
|
||||
<svg v-show="!appSetting['desktopLyric.enable']" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 512 512" space="preserve">
|
||||
<use xlink:href="#icon-desktop-lyric-off" />
|
||||
</svg>
|
||||
</div>
|
||||
<div :class="$style.titleBtn" :aria-label="nextTogglePlayName" @click="toggleNextPlayMode">
|
||||
<svg v-if="appSetting['player.togglePlayMethod'] == 'listLoop'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="80%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-list-loop" />
|
||||
</svg>
|
||||
<svg v-else-if="appSetting['player.togglePlayMethod'] == 'random'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-list-random" />
|
||||
</svg>
|
||||
<svg v-else-if="appSetting['player.togglePlayMethod'] == 'list'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="120%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-list-order" />
|
||||
</svg>
|
||||
<svg v-else-if="appSetting['player.togglePlayMethod'] == 'singleLoop'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-single-loop" />
|
||||
</svg>
|
||||
<svg v-else version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="120%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-single" />
|
||||
</svg>
|
||||
</div>
|
||||
<div :class="$style.titleBtn" :aria-label="$t('player__add_music_to')" @click="addMusicTo">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="80%" viewBox="0 0 512 512" space="preserve">
|
||||
<use xlink:href="#icon-add-2" />
|
||||
</svg>
|
||||
</div>
|
||||
<common-list-add-modal v-model:show="isShowAddMusicTo" :music-info="playMusicInfo.musicInfo" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import useNextTogglePlay from '@renderer/utils/compositions/useNextTogglePlay'
|
||||
import useToggleDesktopLyric from '@renderer/utils/compositions/useToggleDesktopLyric'
|
||||
import { musicInfo, playMusicInfo } from '@renderer/store/player/state'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const isShowAddMusicTo = ref(false)
|
||||
|
||||
|
||||
const {
|
||||
nextTogglePlayName,
|
||||
toggleNextPlayMode,
|
||||
} = useNextTogglePlay()
|
||||
|
||||
const {
|
||||
toggleDesktopLyricBtnTitle,
|
||||
toggleDesktopLyric,
|
||||
toggleLockDesktopLyric,
|
||||
} = useToggleDesktopLyric()
|
||||
|
||||
|
||||
const addMusicTo = () => {
|
||||
if (!musicInfo.id) return
|
||||
isShowAddMusicTo.value = true
|
||||
}
|
||||
|
||||
return {
|
||||
appSetting,
|
||||
isShowAddMusicTo,
|
||||
nextTogglePlayName,
|
||||
toggleNextPlayMode,
|
||||
toggleDesktopLyricBtnTitle,
|
||||
toggleDesktopLyric,
|
||||
toggleLockDesktopLyric,
|
||||
addMusicTo,
|
||||
playMusicInfo,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.controlBtn {
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
|
||||
.titleBtn {
|
||||
flex: none;
|
||||
margin-left: 5px;
|
||||
height: 100%;
|
||||
width: 20px;
|
||||
color: var(--color-button-font);
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
transition: opacity 0.2s ease;
|
||||
opacity: .6;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
&:active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<div :class="$style.player">
|
||||
<div :class="$style.left" :aria-label="$t('player__pic_tip')" @contextmenu="handleToMusicLocation" @click="showPlayerDetail">
|
||||
<img v-if="musicInfo.pic" :src="musicInfo.pic" loading="lazy" decoding="async" @error="imgError">
|
||||
<svg v-else version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="102%" width="100%" viewBox="0 0 60 60" space="preserve">
|
||||
<use :xlink:href="`#${$style.iconPic}`" />
|
||||
</svg>
|
||||
</div>
|
||||
<div :class="$style.middle">
|
||||
<div :class="$style.column1">
|
||||
<div :class="$style.container">
|
||||
<div :class="$style.title" :aria-label="title + $t('copy_tip')" @click="handleCopy(title)">{{ title }}</div>
|
||||
<control-btns />
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.column2">
|
||||
<common-progress-bar v-if="!isShowPlayerDetail" :progress="progress" :handle-transition-end="handleTransitionEnd" :is-active-transition="isActiveTransition" />
|
||||
</div>
|
||||
<div :class="$style.column3">
|
||||
<template v-if="!isShowPlayerDetail"><span :class="$style.statusText">{{ statusText }}</span><span>{{ nowPlayTimeStr }}</span><span style="margin: 0 5px;">/</span><span>{{ maxPlayTimeStr }}</span></template>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.right">
|
||||
<div :class="$style.playBtn" :aria-label="$t('player__prev')" style="transform: rotate(180deg);" @click="playPrev()">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 220.847 220.847" space="preserve">
|
||||
<use xlink:href="#icon-nextMusic" />
|
||||
</svg>
|
||||
</div>
|
||||
<div :class="$style.playBtn" :aria-label="isPlay ? $t('player__pause') : $t('player__play')" @click="togglePlay">
|
||||
<svg v-if="isPlay" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 277.338 277.338" space="preserve">
|
||||
<use xlink:href="#icon-pause" />
|
||||
</svg>
|
||||
<svg v-else version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 170 170" space="preserve">
|
||||
<use xlink:href="#icon-play" />
|
||||
</svg>
|
||||
</div>
|
||||
<div :class="$style.playBtn" :aria-label="$t('player__next')" @click="playNext()">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 220.847 220.847" space="preserve">
|
||||
<use xlink:href="#icon-nextMusic" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" style="display: none;">
|
||||
<g :id="$style.iconPic">
|
||||
<path d="M29,0C12.984,0,0,12.984,0,29c0,16.016,12.984,29,29,29s29-12.984,29-29C58,12.984,45.016,0,29,0zM29,36.08c-3.91,0-7.08-3.17-7.08-7.08c0-3.91,3.17-7.08,7.08-7.08s7.08,3.17,7.08,7.08C36.08,32.91,32.91,36.08,29,36.08z" />
|
||||
<path :class="$style.c1" d="M6.487,22.932c-0.077,0-0.156-0.009-0.234-0.027c-0.537-0.13-0.868-0.67-0.739-1.206c0.946-3.935,2.955-7.522,5.809-10.376s6.441-4.862,10.376-5.809c0.536-0.127,1.077,0.202,1.206,0.739c0.129,0.536-0.202,1.076-0.739,1.206c-3.575,0.859-6.836,2.685-9.429,5.277s-4.418,5.854-5.277,9.429C7.349,22.624,6.938,22.932,6.487,22.932z" />
|
||||
<path :class="$style.c1" d="M36.066,52.514c-0.451,0-0.861-0.308-0.972-0.767c-0.129-0.536,0.202-1.076,0.739-1.206c3.576-0.859,6.837-2.685,9.43-5.277s4.418-5.854,5.277-9.429c0.129-0.538,0.668-0.868,1.206-0.739c0.537,0.13,0.868,0.67,0.739,1.206c-0.946,3.935-2.955,7.522-5.809,10.376s-6.441,4.862-10.377,5.809C36.223,52.505,36.144,52.514,36.066,52.514z" />
|
||||
<path :class="$style.c1" d="M11.313,24.226c-0.075,0-0.151-0.008-0.228-0.026c-0.538-0.125-0.873-0.663-0.747-1.2c0.72-3.09,2.282-5.904,4.52-8.141c2.236-2.237,5.051-3.8,8.141-4.52c0.535-0.131,1.075,0.209,1.2,0.747c0.126,0.537-0.209,1.075-0.747,1.2c-2.725,0.635-5.207,2.014-7.18,3.986s-3.352,4.455-3.986,7.18C12.179,23.914,11.768,24.226,11.313,24.226z" />
|
||||
<path :class="$style.c1" d="M34.773,47.688c-0.454,0-0.865-0.312-0.973-0.773c-0.126-0.537,0.209-1.075,0.747-1.2c2.725-0.635,5.207-2.014,7.18-3.986s3.352-4.455,3.986-7.18c0.125-0.538,0.662-0.88,1.2-0.747c0.538,0.125,0.873,0.663,0.747,1.2c-0.72,3.09-2.282,5.904-4.52,8.141c-2.236,2.237-5.051,3.8-8.141,4.52C34.925,47.68,34.849,47.688,34.773,47.688z" />
|
||||
<path :class="$style.c1" d="M16.14,25.519c-0.071,0-0.143-0.008-0.215-0.023c-0.539-0.118-0.881-0.651-0.763-1.19c0.997-4.557,4.586-8.146,9.143-9.143c0.537-0.116,1.071,0.222,1.19,0.763c0.118,0.539-0.224,1.072-0.763,1.19c-3.796,0.831-6.786,3.821-7.617,7.617C17.013,25.2,16.6,25.519,16.14,25.519z" />
|
||||
<path :class="$style.c1" d="M33.48,42.861c-0.46,0-0.873-0.318-0.976-0.786c-0.118-0.539,0.224-1.072,0.763-1.19c3.796-0.831,6.786-3.821,7.617-7.617c0.118-0.541,0.65-0.881,1.19-0.763c0.539,0.118,0.881,0.651,0.763,1.19c-0.997,4.557-4.586,8.146-9.143,9.143C33.623,42.854,33.552,42.861,33.48,42.861z" />
|
||||
<path :class="$style.c2" d="M29,38.08c-5.007,0-9.08-4.073-9.08-9.08s4.073-9.08,9.08-9.08s9.08,4.073,9.08,9.08S34.007,38.08,29,38.08z M29,23.92c-2.801,0-5.08,2.279-5.08,5.08s2.279,5.08,5.08,5.08s5.08-2.279,5.08-5.08S31.801,23.92,29,23.92z" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from '@common/utils/vueTools'
|
||||
import { useRouter } from '@common/utils/vueRouter'
|
||||
import { clipboardWriteText } from '@common/utils/electron'
|
||||
import ControlBtns from './ControlBtns'
|
||||
import usePlayProgress from '@renderer/utils/compositions/usePlayProgress'
|
||||
// import { lyric } from '@renderer/core/share/lyric'
|
||||
import {
|
||||
statusText,
|
||||
musicInfo,
|
||||
isShowPlayerDetail,
|
||||
isPlay,
|
||||
playInfo,
|
||||
playMusicInfo,
|
||||
} from '@renderer/store/player/state'
|
||||
import {
|
||||
setMusicInfo,
|
||||
setShowPlayerDetail,
|
||||
} from '@renderer/store/player/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { togglePlay, playNext, playPrev } from '@renderer/core/player'
|
||||
|
||||
export default {
|
||||
name: 'CorePlayBar',
|
||||
components: {
|
||||
ControlBtns,
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
|
||||
const {
|
||||
nowPlayTimeStr,
|
||||
maxPlayTimeStr,
|
||||
progress,
|
||||
isActiveTransition,
|
||||
handleTransitionEnd,
|
||||
} = usePlayProgress()
|
||||
|
||||
const showPlayerDetail = () => {
|
||||
if (!playMusicInfo.musicInfo) return
|
||||
setShowPlayerDetail(true)
|
||||
}
|
||||
const handleCopy = (text) => {
|
||||
clipboardWriteText(text)
|
||||
}
|
||||
|
||||
const imgError = () => {
|
||||
// console.log(e)
|
||||
setMusicInfo({ pic: null })
|
||||
}
|
||||
|
||||
const handleToMusicLocation = () => {
|
||||
const listId = playMusicInfo.listId
|
||||
if (!listId || listId == '__temp__' || listId == 'download' || !playMusicInfo.musicInfo) return
|
||||
if (playInfo.playIndex == -1) return
|
||||
router.push({
|
||||
path: '/list',
|
||||
query: {
|
||||
id: listId,
|
||||
scrollIndex: playInfo.playIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const title = computed(() => {
|
||||
return musicInfo.name
|
||||
? appSetting['download.fileName'].replace('歌名', musicInfo.name).replace('歌手', musicInfo.singer)
|
||||
: '^-^'
|
||||
})
|
||||
|
||||
// onBeforeUnmount(() => {
|
||||
// window.eventHub.emit(eventPlayerNames.setTogglePlay)
|
||||
// })
|
||||
|
||||
return {
|
||||
musicInfo,
|
||||
nowPlayTimeStr,
|
||||
maxPlayTimeStr,
|
||||
progress,
|
||||
isActiveTransition,
|
||||
handleTransitionEnd,
|
||||
handleCopy,
|
||||
imgError,
|
||||
statusText,
|
||||
title,
|
||||
showPlayerDetail,
|
||||
isPlay,
|
||||
togglePlay,
|
||||
playNext,
|
||||
playPrev,
|
||||
handleToMusicLocation,
|
||||
isShowPlayerDetail,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.player {
|
||||
height: @height-player;
|
||||
background-color: var(--color-main-background);
|
||||
border-top: 1px solid var(--color-primary-alpha-900);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
contain: strict;
|
||||
padding: 5px 0;
|
||||
z-index: 2;
|
||||
// box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.1);
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
.left {
|
||||
width: @height-player - 2;
|
||||
height: 100%;
|
||||
// color: var(--color-primary);
|
||||
// transition: @transition-normal;
|
||||
// transition-property: color;
|
||||
flex: none;
|
||||
padding: 2PX;
|
||||
opacity: 1;
|
||||
// transition: @transition-normal;
|
||||
// transition-property: opacity;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
// align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
img {
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
transition: @transition-normal;
|
||||
transition-property: border-color;
|
||||
// border-radius: 50%;
|
||||
border-radius: @radius-border;
|
||||
// border: 2px solid @color-theme_2-background_1;
|
||||
}
|
||||
}
|
||||
.middle {
|
||||
flex: auto;
|
||||
height: 100%;
|
||||
padding: 2px 10px 2px 8px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
.right {
|
||||
height: 100%;
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding-left: 15px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.column1 {
|
||||
flex: auto;
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
.container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
padding-right: 5px;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
|
||||
.play-btn {
|
||||
+ .play-btn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
flex: none;
|
||||
height: 46%;
|
||||
// margin-top: -2px;
|
||||
// transition: @transition-normal;
|
||||
// transition-property: color;
|
||||
color: var(--color-button-font);
|
||||
transition: opacity 0.2s ease;
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.column2 {
|
||||
flex: none;
|
||||
padding: 3px 0;
|
||||
height: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.column3 {
|
||||
transition: @transition-normal;
|
||||
transition-property: color;
|
||||
color: var(--color-font);
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
padding-top: 2px;
|
||||
// justify-content: space-between;
|
||||
height: 22px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 0.98em;
|
||||
transition: @transition-normal;
|
||||
transition-property: color;
|
||||
color: var(--color-font-label);
|
||||
.mixin-ellipsis-1;
|
||||
// padding: 0 5px;
|
||||
padding-right: 5px;
|
||||
flex: 1 1 0;
|
||||
// text-align: center;
|
||||
line-height: 1.2;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#icon-pic {
|
||||
// color: var(--color-primary);
|
||||
.c1 {
|
||||
transition: @transition-normal;
|
||||
transition-property: fill;
|
||||
// fill: @color-player-pic-c1;
|
||||
}
|
||||
.c2 {
|
||||
transition: @transition-normal;
|
||||
transition-property: fill;
|
||||
// fill: @color-player-pic-c2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<div :class="['right', $style.right]" :style="lrcFontSize">
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div
|
||||
v-show="!isShowLrcSelectContent"
|
||||
ref="dom_lyric"
|
||||
:class="['lyric', $style.lyric, { [$style.draging]: isMsDown }, { [$style.lrcActiveZoom]: isZoomActiveLrc }]" :style="lrcStyles"
|
||||
@wheel="handleWheel" @mousedown="handleLyricMouseDown"
|
||||
@contextmenu.stop="handleShowLyricMenu"
|
||||
>
|
||||
<div :class="['pre', $style.lyricSpace]" />
|
||||
<div ref="dom_lyric_text" />
|
||||
<div :class="$style.lyricSpace" />
|
||||
</div>
|
||||
</transition>
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div v-if="isShowLyricProgressSetting" v-show="isStopScroll && !isShowLrcSelectContent" :class="$style.skip">
|
||||
<div ref="dom_skip_line" :class="$style.line" />
|
||||
<span :class="$style.label">{{ timeStr }}</span>
|
||||
<base-btn :class="$style.skipBtn" @mouseenter="handleSkipMouseEnter" @mouseleave="handleSkipMouseLeave" @click="handleSkipPlay">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="50%" viewBox="0 0 170 170" space="preserve">
|
||||
<use xlink:href="#icon-play" />
|
||||
</svg>
|
||||
</base-btn>
|
||||
</div>
|
||||
</transition>
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div v-if="isShowLrcSelectContent" :class="[$style.lyricSelectContent, 'select', 'scroll', 'lyricSelectContent']" @contextmenu="handleCopySelectText">
|
||||
<div v-for="(info, index) in lyric.lines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]">
|
||||
<span>{{ info.text }}</span>
|
||||
<template v-for="(lrc, i) in info.extendedLyrics" :key="i">
|
||||
<br>
|
||||
<span :class="$style.lyricSelectlineExtended">{{ lrc }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<LyricMenu v-model="lyricMenuVisible" :xy="lyricMenuXY" :lyric-info="lyricInfo" @update-lyric="handleUpdateLyric" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { clipboardWriteText } from '@common/utils/electron'
|
||||
import { lyric } from '@renderer/store/player/lyric'
|
||||
import { playProgress } from '@renderer/store/player/playProgress'
|
||||
import { isFullscreen } from '@renderer/store'
|
||||
import {
|
||||
isPlay,
|
||||
isShowLrcSelectContent,
|
||||
isShowPlayComment,
|
||||
musicInfo as playerMusicInfo,
|
||||
playMusicInfo,
|
||||
} from '@renderer/store/player/state'
|
||||
import {
|
||||
setMusicInfo,
|
||||
} from '@renderer/store/player/action'
|
||||
import { onMounted, onBeforeUnmount, computed, reactive, ref, nextTick, watch } from '@common/utils/vueTools'
|
||||
import useLyric from '@renderer/utils/compositions/useLyric'
|
||||
import LyricMenu from './components/LyricMenu'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { setLyricOffset } from '@renderer/core/lyric'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LyricMenu,
|
||||
},
|
||||
setup() {
|
||||
const isZoomActiveLrc = computed(() => appSetting['playDetail.isZoomActiveLrc'])
|
||||
const isShowLyricProgressSetting = computed(() => appSetting['playDetail.isShowLyricProgressSetting'])
|
||||
|
||||
const {
|
||||
dom_lyric,
|
||||
dom_lyric_text,
|
||||
dom_skip_line,
|
||||
isMsDown,
|
||||
isStopScroll,
|
||||
timeStr,
|
||||
handleLyricMouseDown,
|
||||
handleWheel,
|
||||
handleSkipPlay,
|
||||
handleSkipMouseEnter,
|
||||
handleSkipMouseLeave,
|
||||
handleScrollLrc,
|
||||
} = useLyric({ isPlay, lyric, playProgress, isShowLyricProgressSetting })
|
||||
|
||||
watch([isFullscreen, isShowPlayComment], () => {
|
||||
setTimeout(handleScrollLrc, 400)
|
||||
})
|
||||
|
||||
const lyricMenuVisible = ref(false)
|
||||
const lyricMenuXY = reactive({
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
const lyricInfo = reactive({
|
||||
lyric: '',
|
||||
tlyric: '',
|
||||
rlyric: '',
|
||||
lxlyric: '',
|
||||
rawlyric: '',
|
||||
musicInfo: null,
|
||||
})
|
||||
const updateMusicInfo = () => {
|
||||
lyricInfo.lyric = playerMusicInfo.lrc
|
||||
lyricInfo.tlyric = playerMusicInfo.tlrc
|
||||
lyricInfo.rlyric = playerMusicInfo.rlrc
|
||||
lyricInfo.lxlyric = playerMusicInfo.lxlrc
|
||||
lyricInfo.rawlyric = playerMusicInfo.rawlrc
|
||||
lyricInfo.musicInfo = playMusicInfo.musicInfo
|
||||
}
|
||||
const handleShowLyricMenu = event => {
|
||||
updateMusicInfo()
|
||||
lyricMenuXY.x = event.pageX
|
||||
lyricMenuXY.y = event.pageY
|
||||
if (lyricMenuVisible.value) return
|
||||
nextTick(() => {
|
||||
lyricMenuVisible.value = true
|
||||
})
|
||||
}
|
||||
const handleUpdateLyric = ({ lyric, tlyric, rlyric, lxlyric, offset }) => {
|
||||
setMusicInfo({
|
||||
lrc: lyric,
|
||||
tlrc: tlyric,
|
||||
rlrc: rlyric,
|
||||
lxlrc: lxlyric,
|
||||
})
|
||||
console.log(offset)
|
||||
setLyricOffset(offset)
|
||||
}
|
||||
|
||||
const lrcStyles = computed(() => {
|
||||
return {
|
||||
textAlign: appSetting['playDetail.style.align'],
|
||||
}
|
||||
})
|
||||
const lrcFontSize = computed(() => {
|
||||
let size = appSetting['playDetail.style.fontSize'] / 100
|
||||
if (isFullscreen.value) size = size *= 1.4
|
||||
return {
|
||||
'--playDetail-lrc-font-size': (isShowPlayComment.value ? size * 0.82 : size) + 'rem',
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.app_event.on('lyricUpdated', updateMusicInfo)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.app_event.off('lyricUpdated', updateMusicInfo)
|
||||
})
|
||||
|
||||
return {
|
||||
dom_lyric,
|
||||
dom_lyric_text,
|
||||
dom_skip_line,
|
||||
isMsDown,
|
||||
timeStr,
|
||||
handleLyricMouseDown,
|
||||
handleWheel,
|
||||
handleSkipPlay,
|
||||
handleSkipMouseEnter,
|
||||
handleSkipMouseLeave,
|
||||
lyric,
|
||||
lrcStyles,
|
||||
lrcFontSize,
|
||||
isShowLrcSelectContent,
|
||||
isShowLyricProgressSetting,
|
||||
isZoomActiveLrc,
|
||||
isStopScroll,
|
||||
lyricMenuVisible,
|
||||
lyricMenuXY,
|
||||
handleShowLyricMenu,
|
||||
handleUpdateLyric,
|
||||
lyricInfo,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCopySelectText() {
|
||||
let str = window.getSelection().toString()
|
||||
str = str.trim()
|
||||
if (!str.length) return
|
||||
clipboardWriteText(str)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.right {
|
||||
flex: 0 0 60%;
|
||||
// padding: 0 30px;
|
||||
position: relative;
|
||||
transition: flex-basis @transition-normal;
|
||||
}
|
||||
.lyric {
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: var(--playDetail-lrc-font-size, 16px);
|
||||
-webkit-mask-image: linear-gradient(transparent 0%, #fff 20%, #fff 80%, transparent 100%);
|
||||
cursor: grab;
|
||||
&.draging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
:global {
|
||||
.font-lrc {
|
||||
color: var(--color-200);
|
||||
}
|
||||
.line-content {
|
||||
line-height: 1.2;
|
||||
padding: calc(var(--playDetail-lrc-font-size, 16px) / 2) 1px;
|
||||
overflow-wrap: break-word;
|
||||
color: var(--color-200);
|
||||
transition: @transition-normal;
|
||||
transition-property: padding;
|
||||
|
||||
.extended {
|
||||
font-size: 0.8em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
&.line-mode {
|
||||
.font-lrc {
|
||||
transition: @transition-fast;
|
||||
transition-property: font-size, color;
|
||||
}
|
||||
}
|
||||
&.line-mode.active .font-lrc, &.font-mode.played .font-lrc {
|
||||
color: var(--color-primary-dark-200);
|
||||
}
|
||||
&.font-mode .extended .font-lrc {
|
||||
transition: @transition-slow;
|
||||
transition-property: font-size, color;
|
||||
}
|
||||
|
||||
&.font-mode > .line > .font-lrc {
|
||||
> span {
|
||||
transition: @transition-normal;
|
||||
transition-property: font-size;
|
||||
font-size: 1em;
|
||||
background-repeat: no-repeat;
|
||||
background-color: var(--color-200);
|
||||
background-image: -webkit-linear-gradient(top, var(--color-primary-dark-200), var(--color-primary-dark-200));
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-size: 0 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// p {
|
||||
// padding: 8px 0;
|
||||
// line-height: 1.2;
|
||||
// overflow-wrap: break-word;
|
||||
// transition: @transition-normal !important;
|
||||
// transition-property: color, font-size;
|
||||
// }
|
||||
// .lrc-active {
|
||||
// color: var(--color-primary);
|
||||
// font-size: 1.2em;
|
||||
// }
|
||||
}
|
||||
.lrc-active-zoom {
|
||||
:global {
|
||||
.line-content {
|
||||
&.active {
|
||||
.extended {
|
||||
font-size: .94em;
|
||||
}
|
||||
.line {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skip {
|
||||
position: absolute;
|
||||
top: calc(38% + var(--playDetail-lrc-font-size, 16px) + 4px);
|
||||
left: 0;
|
||||
// height: 6px;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
// opacity: .5;
|
||||
.line {
|
||||
border-top: 2px dotted var(--color-primary-dark-100);
|
||||
opacity: .15;
|
||||
margin-right: 30px;
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 15%, #fff 100%);
|
||||
}
|
||||
.label {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: -14px;
|
||||
line-height: 1;
|
||||
font-size: 12px;
|
||||
color: var(--color-primary-dark-100);
|
||||
opacity: .7;
|
||||
}
|
||||
.skipBtn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transform: translateY(-50%);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none !important;
|
||||
pointer-events: initial;
|
||||
transition: @transition-normal;
|
||||
transition-property: opacity;
|
||||
opacity: .8;
|
||||
&:hover {
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
}
|
||||
.lyricSelectContent {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
// text-align: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
z-index: 10;
|
||||
color: var(--color-400);
|
||||
|
||||
.lyricSelectline {
|
||||
padding: 8px 0;
|
||||
overflow-wrap: break-word;
|
||||
transition: @transition-normal !important;
|
||||
transition-property: color, font-size;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.lyricSelectlineExtended {
|
||||
font-size: 14px;
|
||||
}
|
||||
.lrc-active {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.lyric-space {
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div :class="$style.footer">
|
||||
<div :class="$style.footerLeft">
|
||||
<control-btns />
|
||||
<div :class="$style.progressContainer">
|
||||
<div :class="$style.progressContent">
|
||||
<common-progress-bar
|
||||
:class-name="$style.progress"
|
||||
:progress="progress"
|
||||
:handle-transition-end="handleTransitionEnd"
|
||||
:is-active-transition="isActiveTransition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.timeLabel"><span :class="$style.status" style="margin-left: 15px">{{ status }}</span><span>{{ nowPlayTimeStr }}</span><span style="margin: 0 5px;">/</span><span>{{ maxPlayTimeStr }}</span></div>
|
||||
</div>
|
||||
<div :class="$style.playControl">
|
||||
<div :class="$style.playBtn" style="transform: rotate(180deg);" :aria-label="$t('player__prev')" @click="playPrev()">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 220.847 220.847" space="preserve">
|
||||
<use xlink:href="#icon-nextMusic" />
|
||||
</svg>
|
||||
</div>
|
||||
<div :class="$style.playBtn" :aria-label="isPlay ? $t('player__pause') : $t('player__play')" @click="togglePlay">
|
||||
<svg v-if="isPlay" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 277.338 277.338" space="preserve">
|
||||
<use xlink:href="#icon-pause" />
|
||||
</svg>
|
||||
<svg v-else version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 170 170" space="preserve">
|
||||
<use xlink:href="#icon-play" />
|
||||
</svg>
|
||||
</div>
|
||||
<div :class="$style.playBtn" :aria-label="$t('player__next')" @click="playNext()">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 220.847 220.847" space="preserve">
|
||||
<use xlink:href="#icon-nextMusic" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { playNext, playPrev, togglePlay } from '@renderer/core/player'
|
||||
import { status, isPlay } from '@renderer/store/player/state'
|
||||
import usePlayProgress from '@renderer/utils/compositions/usePlayProgress'
|
||||
|
||||
import ControlBtns from './components/ControlBtns'
|
||||
|
||||
const {
|
||||
nowPlayTimeStr,
|
||||
maxPlayTimeStr,
|
||||
progress,
|
||||
isActiveTransition,
|
||||
handleTransitionEnd,
|
||||
} = usePlayProgress()
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.footer {
|
||||
flex: 0 0 100px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.footerLeft {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
padding: 13px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
.progress-content {
|
||||
position: relative;
|
||||
height: 16px;
|
||||
padding: 5px 0;
|
||||
width: 100%;
|
||||
}
|
||||
.progress {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bar-transition {
|
||||
transition-property: transform;
|
||||
transition-timing-function: ease-out;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
.time-label {
|
||||
width: 100%;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
span {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.status {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.play-control {
|
||||
flex: none;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 0 25px;
|
||||
color: var(--color-button-font);
|
||||
}
|
||||
.play-btn {
|
||||
height: 40%;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
// transition: @transition-normal;
|
||||
// transition-property: color;
|
||||
color: var(--color-button-font);
|
||||
transition: opacity 0.2s ease;
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
|
||||
+.play-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
svg {
|
||||
fill: currentColor;
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { debounce } from '@renderer/utils/index'
|
||||
import { debounce } from '@common/utils/common'
|
||||
let isAutoHide = false
|
||||
let isLockedPointer = false
|
||||
let dom = null
|
||||
+32
-42
@@ -1,12 +1,12 @@
|
||||
<template lang="pug">
|
||||
div(:class="$style.footerLeftControlBtns")
|
||||
common-volume-bar(:setting="setting")
|
||||
common-volume-bar
|
||||
div(:class="[$style.footerLeftControlBtn, $style.lrcBtn]" @click="toggleDesktopLyric" @contextmenu="toggleLockDesktopLyric" :aria-label="toggleDesktopLyricBtnTitle")
|
||||
svg(v-show="setting.desktopLyric.enable" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve')
|
||||
svg(v-show="appSetting['desktopLyric.enable']" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve')
|
||||
use(xlink:href='#icon-desktop-lyric-on')
|
||||
svg(v-show="!setting.desktopLyric.enable" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve')
|
||||
svg(v-show="!appSetting['desktopLyric.enable']" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve')
|
||||
use(xlink:href='#icon-desktop-lyric-off')
|
||||
div(:class="[$style.footerLeftControlBtn, { [$style.active]: setting.player.audioVisualization }]" @click="toggleAudioVisualization" :aria-label="$t('audio_visualization')")
|
||||
div(:class="[$style.footerLeftControlBtn, { [$style.active]: appSetting['player.audioVisualization'] }]" @click="toggleAudioVisualization" :aria-label="$t('audio_visualization')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-audio-wave')
|
||||
div(:class="[$style.footerLeftControlBtn, { [$style.active]: isShowLrcSelectContent }]" @click="toggleVisibleLrc" :aria-label="$t('lyric__select')")
|
||||
@@ -16,45 +16,49 @@ div(:class="$style.footerLeftControlBtns")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-comment')
|
||||
div(:class="$style.footerLeftControlBtn" @click="toggleNextPlayMode" :aria-label="nextTogglePlayName")
|
||||
svg(v-show="setting.player.togglePlayMethod == 'listLoop'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
|
||||
svg(v-show="appSetting['player.togglePlayMethod'] == 'listLoop'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-list-loop')
|
||||
svg(v-show="setting.player.togglePlayMethod == 'random'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
|
||||
svg(v-show="appSetting['player.togglePlayMethod'] == 'random'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-list-random')
|
||||
svg(v-show="setting.player.togglePlayMethod == 'list'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='120%' viewBox='0 0 24 24' space='preserve')
|
||||
svg(v-show="appSetting['player.togglePlayMethod'] == 'list'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='120%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-list-order')
|
||||
svg(v-show="setting.player.togglePlayMethod == 'singleLoop'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='110%' viewBox='0 0 24 24' space='preserve')
|
||||
svg(v-show="appSetting['player.togglePlayMethod'] == 'singleLoop'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='110%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-single-loop')
|
||||
svg(v-show="!setting.player.togglePlayMethod" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='120%' viewBox='0 0 24 24' space='preserve')
|
||||
svg(v-show="!appSetting['player.togglePlayMethod']" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='120%' viewBox='0 0 24 24' space='preserve')
|
||||
use(xlink:href='#icon-single')
|
||||
div(:class="$style.footerLeftControlBtn" @click="isShowAddMusicTo = true" :aria-label="$t('player__add_music_to')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' space='preserve')
|
||||
use(xlink:href='#icon-add-2')
|
||||
common-list-add-modal(v-model:show="isShowAddMusicTo" :musicInfo="musicInfoItem")
|
||||
common-list-add-modal(v-model:show="isShowAddMusicTo" :musicInfo="playMusicInfo.musicInfo")
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useI18n, useRefGetter, ref, useCommit } from '@renderer/utils/vueTools'
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
|
||||
import {
|
||||
isShowLrcSelectContent,
|
||||
setShowPlayLrcSelectContentLrc,
|
||||
isShowPlayComment,
|
||||
playMusicInfo,
|
||||
} from '@renderer/store/player/state'
|
||||
import {
|
||||
setShowPlayLrcSelectContentLrc,
|
||||
setShowPlayComment,
|
||||
musicInfoItem,
|
||||
} from '@renderer/core/share/player'
|
||||
} from '@renderer/store/player/action'
|
||||
|
||||
import useNextTogglePlay from '@renderer/utils/compositions/useNextTogglePlay'
|
||||
import useToggleDesktopLyric from '@renderer/utils/compositions/useToggleDesktopLyric'
|
||||
import { dialog } from '@renderer/plugins/Dialog'
|
||||
import { setMediaDeviceId } from '@renderer/plugins/player'
|
||||
import { appSetting, saveMediaDeviceId, setEnableAudioVisualization } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
const setting = useRefGetter('setting')
|
||||
const setAudioVisualization = useCommit('setAudioVisualization')
|
||||
const saveMediaDeviceId = useCommit('setMediaDeviceId')
|
||||
const t = useI18n()
|
||||
// const setting = useRefGetter('setting')
|
||||
// const setAudioVisualization = useCommit('setAudioVisualization')
|
||||
// const saveMediaDeviceId = useCommit('setMediaDeviceId')
|
||||
|
||||
const toggleVisibleLrc = () => {
|
||||
setShowPlayLrcSelectContentLrc(!isShowLrcSelectContent.value)
|
||||
@@ -65,19 +69,19 @@ export default {
|
||||
const {
|
||||
nextTogglePlayName,
|
||||
toggleNextPlayMode,
|
||||
} = useNextTogglePlay({ setting, t })
|
||||
} = useNextTogglePlay()
|
||||
|
||||
const {
|
||||
toggleDesktopLyricBtnTitle,
|
||||
toggleDesktopLyric,
|
||||
toggleLockDesktopLyric,
|
||||
} = useToggleDesktopLyric({ setting, t })
|
||||
} = useToggleDesktopLyric()
|
||||
|
||||
const isShowAddMusicTo = ref(false)
|
||||
|
||||
const toggleAudioVisualization = async() => {
|
||||
const newSetting = !setting.value.player.audioVisualization
|
||||
if (newSetting && setting.value.player.mediaDeviceId != 'default') {
|
||||
const newSetting = !appSetting['player.audioVisualization']
|
||||
if (newSetting && appSetting['player.mediaDeviceId'] != 'default') {
|
||||
const confirm = await dialog.confirm({
|
||||
message: t('setting__player_audio_visualization_tip'),
|
||||
cancelButtonText: t('cancel_button_text'),
|
||||
@@ -87,11 +91,11 @@ export default {
|
||||
saveMediaDeviceId('default')
|
||||
await setMediaDeviceId('default').catch(_ => _)
|
||||
}
|
||||
setAudioVisualization(newSetting)
|
||||
setEnableAudioVisualization(newSetting)
|
||||
}
|
||||
|
||||
return {
|
||||
setting,
|
||||
appSetting,
|
||||
isShowLrcSelectContent,
|
||||
toggleVisibleLrc,
|
||||
isShowPlayComment,
|
||||
@@ -103,7 +107,7 @@ export default {
|
||||
toggleLockDesktopLyric,
|
||||
toggleAudioVisualization,
|
||||
isShowAddMusicTo,
|
||||
musicInfoItem,
|
||||
playMusicInfo,
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -113,7 +117,7 @@ export default {
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.footerLeftControlBtns {
|
||||
color: @color-theme_2-font;
|
||||
color: var(--color-font);
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-end;
|
||||
@@ -123,7 +127,7 @@ export default {
|
||||
height: 18px;
|
||||
opacity: .5;
|
||||
cursor: pointer;
|
||||
transition: opacity @transition-theme;
|
||||
transition: opacity @transition-normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -137,7 +141,7 @@ export default {
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: @color-theme;
|
||||
color: var(--color-primary);
|
||||
opacity: .8;
|
||||
}
|
||||
}
|
||||
@@ -147,18 +151,4 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.footerLeftControlBtns {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.footerLeftControlBtn {
|
||||
&.active {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<teleport to="#root">
|
||||
<div ref="dom_menu" :class="$style.container" :style="menuStyles" :aria-hidden="!modelValue">
|
||||
<!-- <div :class="$style.group">
|
||||
<div :class="$style.title">{{$t('lyric_menu__align')}}</div>
|
||||
<div :class="$style.subGroup">
|
||||
<div :class="[$style.btn, { [$style.active]: appSetting['playDetail.style.align'] == 'left' }]" role="button" @click="setFontAlign('left')" ignore-tip :aria-label="$t('lyric_menu__align_left')">{{$t('lyric_menu__align_left')}}</div>
|
||||
<div :class="[$style.btn, { [$style.active]: appSetting['playDetail.style.align'] == 'center' }]" role="button" @click="setFontAlign('center')" ignore-tip :aria-label="$t('lyric_menu__align_center')">{{$t('lyric_menu__align_center')}}</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div :class="$style.group">
|
||||
<div :class="$style.subGroup">
|
||||
<div :class="$style.title">{{ $t('lyric_menu__lrc_size', { size: appSetting['playDetail.style.fontSize'] }) }}</div>
|
||||
<button :class="[$style.btn, $style.titleBtn]" :disabled="appSetting['playDetail.style.fontSize'] == 100" ignore-tip :aria-label="$t('lyric_menu__size_reset')" @click="fontSizeReset">{{ $t('lyric_menu__size_reset') }}</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" :aria-label="$t('lyric_menu__size_add')" @click="fontSizeUp(5)" @contextmenu="fontSizeUp(1)">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="18px" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-font-increase" />
|
||||
</svg>
|
||||
</button>
|
||||
<button :class="$style.btn" :aria-label="$t('lyric_menu__size_dec')" @click="fontSizeDown(5)" @contextmenu="fontSizeDown(1)">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="18px" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-font-decrease" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.group">
|
||||
<div :class="$style.subGroup">
|
||||
<div :class="$style.title">{{ $t('lyric_menu__offset', { offset }) }}</div>
|
||||
<button :class="[$style.btn, $style.titleBtn]" :disabled="offsetDisabled || offset == originOffset" @click="offsetReset">{{ $t('lyric_menu__offset_reset') }}</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" ignore-tip :aria-label="$t('lyric_menu__offset_add_10')" @click="setOffset(10)">+ 10ms</button>
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" ignore-tip :aria-label="$t('lyric_menu__offset_dec_10')" @click="setOffset(-10)">- 10ms</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" ignore-tip :aria-label="$t('lyric_menu__offset_add_100')" @click="setOffset(100)">+ 100ms</button>
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" ignore-tip :aria-label="$t('lyric_menu__offset_dec_100')" @click="setOffset(-100)">- 100ms</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, ref, watch } from '@common/utils/vueTools'
|
||||
import useMenuLocation from '@renderer/utils/compositions/useMenuLocation'
|
||||
import { debounce } from '@common/utils/common'
|
||||
import { saveLyricEdited, removeLyricEdited } from '@renderer/utils/ipc'
|
||||
import { appSetting, setPlayDetailLyricFont, setPlayDetailLyricAlign } from '@renderer/store/setting'
|
||||
|
||||
const offsetTagRxp = /(?:^|\n)\s*\[offset:\s*(\S+(?:\d+)*)\s*\]/
|
||||
const offsetTagAllRxp = /(^|\n)\s*\[offset:\s*(\S+(?:\d+)*)\s*\]/g
|
||||
|
||||
const saveLyric = debounce((musicInfo, lyricInfo) => {
|
||||
saveLyricEdited(musicInfo, lyricInfo)
|
||||
})
|
||||
const removeLyric = debounce(musicInfo => {
|
||||
removeLyricEdited(musicInfo)
|
||||
})
|
||||
|
||||
const getOffset = lrc => {
|
||||
let offset = offsetTagRxp.exec(lrc)
|
||||
if (offset) {
|
||||
offset = parseInt(offset[1])
|
||||
if (Number.isNaN(offset)) offset = 0
|
||||
} else offset = 0
|
||||
return offset
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'LyricMenu',
|
||||
props: {
|
||||
modelValue: Boolean,
|
||||
xy: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
lyricInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['updateLyric', 'update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
// const appSetting = useRefGetter('appSetting')
|
||||
// const playDetailSetting = useRefGetter('playDetailSetting')
|
||||
// const setPlayDetailLyricAlign = useCommit('setPlayDetailLyricAlign')
|
||||
// const setPlayDetailLyricFont = useCommit('setPlayDetailLyricFont')
|
||||
|
||||
const offset = ref(0)
|
||||
const offsetDisabled = ref(true)
|
||||
const originOffset = ref(0)
|
||||
|
||||
const visible = computed(() => props.modelValue)
|
||||
const location = computed(() => props.xy)
|
||||
|
||||
const onHide = () => {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
const setFontAlign = val => {
|
||||
if (appSetting['playDetail.style.align'] == val) return
|
||||
setPlayDetailLyricAlign(val)
|
||||
}
|
||||
|
||||
const fontSizeUp = step => {
|
||||
if (appSetting['playDetail.style.fontSize'] >= 200) return
|
||||
setPlayDetailLyricFont(Math.min(appSetting['playDetail.style.fontSize'] + step, 200))
|
||||
}
|
||||
const fontSizeDown = step => {
|
||||
if (appSetting['playDetail.style.fontSize'] <= 70) return
|
||||
setPlayDetailLyricFont(Math.max(appSetting['playDetail.style.fontSize'] - step, 70))
|
||||
}
|
||||
const fontSizeReset = () => {
|
||||
setPlayDetailLyricFont(100)
|
||||
}
|
||||
|
||||
const updateLyric = offset => {
|
||||
let lyric = props.lyricInfo.lyric
|
||||
let tlyric = props.lyricInfo.tlyric
|
||||
let rlyric = props.lyricInfo.rlyric
|
||||
let lxlyric = props.lyricInfo.lxlyric
|
||||
if (offsetTagRxp.test(lyric)) {
|
||||
lyric = lyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
if (tlyric) tlyric = tlyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
if (lxlyric) lxlyric = lxlyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
if (rlyric) rlyric = rlyric.replace(offsetTagAllRxp, `$1[offset:${offset}]`)
|
||||
} else {
|
||||
lyric = `[offset:${offset}]\n` + lyric
|
||||
if (tlyric) tlyric = `[offset:${offset}]\n` + tlyric
|
||||
if (lxlyric) lxlyric = `[offset:${offset}]\n` + lxlyric
|
||||
if (rlyric) rlyric = `[offset:${offset}]\n` + rlyric
|
||||
}
|
||||
|
||||
const musicInfo = 'progress' in props.lyricInfo.musicInfo ? props.lyricInfo.musicInfo.meta.musicInfo : props.lyricInfo.musicInfo
|
||||
|
||||
if (offset == originOffset.value) {
|
||||
removeLyric(musicInfo)
|
||||
} else {
|
||||
saveLyric(musicInfo, {
|
||||
lyric,
|
||||
tlyric,
|
||||
rlyric,
|
||||
lxlyric,
|
||||
})
|
||||
}
|
||||
|
||||
emit('updateLyric', {
|
||||
lyric,
|
||||
tlyric,
|
||||
rlyric,
|
||||
lxlyric,
|
||||
offset,
|
||||
})
|
||||
}
|
||||
const setOffset = step => {
|
||||
offset.value += step
|
||||
updateLyric(offset.value)
|
||||
}
|
||||
const offsetReset = () => {
|
||||
if (offset.value == originOffset.value) return
|
||||
offset.value = originOffset.value
|
||||
updateLyric(originOffset.value)
|
||||
}
|
||||
|
||||
const parseLrcOffset = () => {
|
||||
offset.value = getOffset(props.lyricInfo.lyric)
|
||||
originOffset.value = getOffset(props.lyricInfo.rawlyric)
|
||||
offsetDisabled.value = !props.lyricInfo.lyric
|
||||
}
|
||||
|
||||
|
||||
const { dom_menu, menuStyles } = useMenuLocation({
|
||||
visible,
|
||||
location,
|
||||
onHide,
|
||||
})
|
||||
|
||||
watch(() => props.lyricInfo.musicInfo, () => {
|
||||
if (!props.modelValue) return
|
||||
parseLrcOffset()
|
||||
})
|
||||
watch(visible, val => {
|
||||
if (!val) return
|
||||
parseLrcOffset()
|
||||
})
|
||||
|
||||
return {
|
||||
appSetting,
|
||||
dom_menu,
|
||||
menuStyles,
|
||||
offset,
|
||||
originOffset,
|
||||
fontSizeUp,
|
||||
fontSizeDown,
|
||||
fontSizeReset,
|
||||
setOffset,
|
||||
offsetReset,
|
||||
setFontAlign,
|
||||
offsetDisabled,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.container {
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transform-origin: 0 0 0;
|
||||
transition: .14s ease;
|
||||
transition-property: transform, opacity;
|
||||
border-radius: @radius-border;
|
||||
background-color: var(--color-content-background);
|
||||
box-shadow: 0 1px 8px 0 rgba(0,0,0,.2);
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.title {
|
||||
flex: auto;
|
||||
padding: 10px 0 10px 10px;
|
||||
color: var(--color-font-label);
|
||||
white-space: nowrap;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.subGroup {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: auto;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
min-width: 60px;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// color: @color-btn;
|
||||
padding: 0 10px;
|
||||
outline: none;
|
||||
transition: @transition-normal;
|
||||
transition-property: background-color, opacity;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
background-color: var(--color-content-background);
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-primary-background-active);
|
||||
}
|
||||
&.active {
|
||||
background-color: var(--color-content-background);
|
||||
color: var(--color-button-font-selected);
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: default;
|
||||
opacity: .4;
|
||||
&:hover {
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.titleBtn {
|
||||
flex: none;
|
||||
padding: 0 10;
|
||||
min-width: 40px;
|
||||
opacity: .7;
|
||||
|
||||
&[disabled] {
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
+8
-28
@@ -22,7 +22,7 @@ div(:class="$style.container")
|
||||
import commentDefImg from '@renderer/assets/images/defaultUser.jpg'
|
||||
|
||||
export default {
|
||||
name: 'commentFloor',
|
||||
name: 'CommentFloor',
|
||||
props: {
|
||||
comments: {
|
||||
type: Array,
|
||||
@@ -53,18 +53,18 @@ export default {
|
||||
|
||||
@padding: 15px;
|
||||
|
||||
.container {
|
||||
// .container {
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
.listItem {
|
||||
border-bottom: 1px dashed @color-theme_2-line;
|
||||
border-bottom: 1px dashed var(--color-primary-alpha-700);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 12px 0;
|
||||
font-size: 13px;
|
||||
color: @color-theme_2-font;
|
||||
color: var(--color-font);
|
||||
display: flex;
|
||||
}
|
||||
.left {
|
||||
@@ -87,7 +87,7 @@ export default {
|
||||
align-items: flex-end;
|
||||
min-width: 0;
|
||||
line-height: 1.3;
|
||||
color: @color-theme_2-font-label;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
.name {
|
||||
flex: 0 1 auto;
|
||||
@@ -110,7 +110,7 @@ export default {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 3px;
|
||||
color: @color-theme-active;
|
||||
color: var(--color-primary-font);
|
||||
}
|
||||
.comment_text {
|
||||
text-align: justify;
|
||||
@@ -137,28 +137,8 @@ export default {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
background-color: @color-reply-floor;
|
||||
background-color: var(--color-primary-light-500-alpha-700);
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.listItem {
|
||||
border-bottom-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
.content {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.info {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
.likesIcon {
|
||||
color: ~'@{color-@{value}-theme-active}';
|
||||
}
|
||||
.reply_floor {
|
||||
background-color: ~'@{color-@{value}-reply-floor}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
+67
-63
@@ -11,35 +11,42 @@ div.comment(:class="$style.comment" ref="dom_container")
|
||||
use(xlink:href='#icon-close')
|
||||
|
||||
div(:class="$style.commentMain")
|
||||
header(:class="$style.tab_header")
|
||||
button(type="button" @click="handleToggleTab('hot')" :class="[$style.commentType, { [$style.active]: tabActiveId == 'hot' }]") {{$t('comment__hot_title')}} ({{hotComment.total}})
|
||||
button(type="button" @click="handleToggleTab('new')" :class="[$style.commentType, { [$style.active]: tabActiveId == 'new' }]") {{$t('comment__new_title')}} ({{newComment.total}})
|
||||
main(:class="$style.tab_main" ref="dom_tabMain")
|
||||
div(:class="$style.tab_content")
|
||||
div.scroll(:class="$style.tab_content_scroll" ref="dom_commentHot")
|
||||
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="hotComment.isLoadError" @click="handleGetHotComment(currentMusicInfo, hotComment.nextPage, hotComment.limit)") {{$t('comment__hot_load_error')}}
|
||||
p(:class="$style.commentLabel" v-else-if="hotComment.isLoading && !hotComment.list.length") {{$t('comment__hot_loading')}}
|
||||
comment-floor(v-if="!hotComment.isLoadError && hotComment.list.length" :class="[$style.commentFloor, hotComment.isLoading ? $style.loading : null]" :comments="hotComment.list")
|
||||
p(:class="$style.commentLabel" v-else-if="!hotComment.isLoadError && !hotComment.isLoading") {{$t('comment__no_content')}}
|
||||
div(:class="$style.pagination")
|
||||
material-pagination(:count="hotComment.total" :btnLength="5" :limit="hotComment.limit" :page="hotComment.page" @btn-click="handleToggleHotCommentPage")
|
||||
div(:class="$style.tab_content")
|
||||
div.scroll(:class="$style.tab_content_scroll" ref="dom_commentNew")
|
||||
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="newComment.isLoadError" @click="handleGetNewComment(currentMusicInfo, newComment.nextPage, newComment.limit)") {{$t('comment__new_load_error')}}
|
||||
p(:class="$style.commentLabel" v-else-if="newComment.isLoading && !newComment.list.length") {{$t('comment__new_loading')}}
|
||||
comment-floor(v-if="!newComment.isLoadError && newComment.list.length" :class="[$style.commentFloor, newComment.isLoading ? $style.loading : null]" :comments="newComment.list")
|
||||
p(:class="$style.commentLabel" v-else-if="!newComment.isLoadError && !newComment.isLoading") {{$t('comment__no_content')}}
|
||||
div(:class="$style.pagination")
|
||||
material-pagination(:count="newComment.total" :btnLength="5" :limit="newComment.limit" :page="newComment.page" @btn-click="handleToggleCommentPage")
|
||||
template(v-if="available")
|
||||
header(:class="$style.tab_header")
|
||||
button(type="button" @click="handleToggleTab('hot')" :class="[$style.commentType, { [$style.active]: tabActiveId == 'hot' }]") {{$t('comment__hot_title')}} ({{hotComment.total}})
|
||||
button(type="button" @click="handleToggleTab('new')" :class="[$style.commentType, { [$style.active]: tabActiveId == 'new' }]") {{$t('comment__new_title')}} ({{newComment.total}})
|
||||
main(:class="$style.tab_main" ref="dom_tabMain")
|
||||
div(:class="$style.tab_content")
|
||||
div.scroll(:class="$style.tab_content_scroll" ref="dom_commentHot")
|
||||
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="hotComment.isLoadError" @click="handleGetHotComment(currentMusicInfo, hotComment.nextPage, hotComment.limit)") {{$t('comment__hot_load_error')}}
|
||||
p(:class="$style.commentLabel" v-else-if="hotComment.isLoading && !hotComment.list.length") {{$t('comment__hot_loading')}}
|
||||
comment-floor(v-if="!hotComment.isLoadError && hotComment.list.length" :class="[$style.commentFloor, hotComment.isLoading ? $style.loading : null]" :comments="hotComment.list")
|
||||
p(:class="$style.commentLabel" v-else-if="!hotComment.isLoadError && !hotComment.isLoading") {{$t('comment__no_content')}}
|
||||
div(:class="$style.pagination")
|
||||
material-pagination(:count="hotComment.total" :btnLength="5" :limit="hotComment.limit" :page="hotComment.page" @btn-click="handleToggleHotCommentPage")
|
||||
div(:class="$style.tab_content")
|
||||
div.scroll(:class="$style.tab_content_scroll" ref="dom_commentNew")
|
||||
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="newComment.isLoadError" @click="handleGetNewComment(currentMusicInfo, newComment.nextPage, newComment.limit)") {{$t('comment__new_load_error')}}
|
||||
p(:class="$style.commentLabel" v-else-if="newComment.isLoading && !newComment.list.length") {{$t('comment__new_loading')}}
|
||||
comment-floor(v-if="!newComment.isLoadError && newComment.list.length" :class="[$style.commentFloor, newComment.isLoading ? $style.loading : null]" :comments="newComment.list")
|
||||
p(:class="$style.commentLabel" v-else-if="!newComment.isLoadError && !newComment.isLoading") {{$t('comment__no_content')}}
|
||||
div(:class="$style.pagination")
|
||||
material-pagination(:count="newComment.total" :btnLength="5" :limit="newComment.limit" :page="newComment.page" @btn-click="handleToggleCommentPage")
|
||||
div(v-else :class="$style.unavailable")
|
||||
p {{$t('comment__unavailable')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import music from '@renderer/utils/music'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { toOldMusicInfo } from '@renderer/utils'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
import CommentFloor from './CommentFloor'
|
||||
|
||||
export default {
|
||||
name: 'MusicComment',
|
||||
components: {
|
||||
CommentFloor,
|
||||
},
|
||||
props: {
|
||||
show: Boolean,
|
||||
musicInfo: {
|
||||
@@ -47,12 +54,15 @@ export default {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
CommentFloor,
|
||||
},
|
||||
emits: ['close'],
|
||||
setup() {
|
||||
return {
|
||||
appSetting,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
available: false,
|
||||
currentMusicInfo: {
|
||||
name: '',
|
||||
singer: '',
|
||||
@@ -110,13 +120,17 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
title() {
|
||||
return this.currentMusicInfo.name
|
||||
? this.setting.download.fileName.replace('歌名', this.currentMusicInfo.name).replace('歌手', this.currentMusicInfo.singer)
|
||||
? this.appSetting['download.fileName'].replace('歌名', this.currentMusicInfo.name).replace('歌手', this.currentMusicInfo.singer)
|
||||
: '^-^'
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
show(n) {
|
||||
if (n) this.handleShowComment()
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setWidth()
|
||||
window.addEventListener('resize', this.setWidth)
|
||||
@@ -124,11 +138,6 @@ export default {
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('resize', this.setWidth)
|
||||
},
|
||||
watch: {
|
||||
show(n) {
|
||||
if (n) this.handleShowComment()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setWidth() {
|
||||
setTimeout(() => {
|
||||
@@ -162,7 +171,7 @@ export default {
|
||||
handleGetNewComment(musicInfo, page, limit) {
|
||||
this.newComment.isLoadError = false
|
||||
this.newComment.isLoading = true
|
||||
this.getComment(musicInfo, page, limit).then(comment => {
|
||||
this.getComment(toOldMusicInfo(musicInfo), page, limit).then(comment => {
|
||||
this.newComment.isLoading = false
|
||||
this.newComment.total = comment.total
|
||||
this.newComment.maxPage = comment.maxPage
|
||||
@@ -181,7 +190,7 @@ export default {
|
||||
handleGetHotComment(musicInfo, page, limit) {
|
||||
this.hotComment.isLoadError = false
|
||||
this.hotComment.isLoading = true
|
||||
this.getHotComment(musicInfo, page, limit).then(hotComment => {
|
||||
this.getHotComment(toOldMusicInfo(musicInfo), page, limit).then(hotComment => {
|
||||
this.hotComment.isLoading = false
|
||||
this.hotComment.total = hotComment.total
|
||||
this.hotComment.maxPage = hotComment.maxPage
|
||||
@@ -198,7 +207,13 @@ export default {
|
||||
})
|
||||
},
|
||||
handleShowComment() {
|
||||
if (!this.musicInfo.songmid || !music[this.musicInfo.source].comment) return
|
||||
this.currentMusicInfo = 'progress' in this.musicInfo ? this.musicInfo.metadata.musicInfo : this.musicInfo
|
||||
|
||||
if (this.currentMusicInfo.source == 'local' || !music[this.currentMusicInfo.source].comment) {
|
||||
this.available = false
|
||||
return
|
||||
}
|
||||
this.available = true
|
||||
// if (this.musicInfo.songmid != this.currentMusicInfo.songmid) {
|
||||
this.hotComment.page = 1
|
||||
this.hotComment.total = 0
|
||||
@@ -211,7 +226,6 @@ export default {
|
||||
this.newComment.nextPage = 1
|
||||
// }
|
||||
this.isShowComment = true
|
||||
this.currentMusicInfo = this.musicInfo
|
||||
|
||||
this.handleGetHotComment(this.currentMusicInfo, this.hotComment.page, this.hotComment.limit)
|
||||
this.handleGetNewComment(this.currentMusicInfo, this.newComment.page, this.newComment.limit)
|
||||
@@ -225,7 +239,7 @@ export default {
|
||||
this.handleGetNewComment(this.currentMusicInfo, page, this.newComment.limit)
|
||||
},
|
||||
handleToggleTab(id, force) {
|
||||
if (!force && this.tabActiveId == id) return
|
||||
if (!this.available || (!force && this.tabActiveId == id)) return
|
||||
switch (id) {
|
||||
case 'hot':
|
||||
this.$refs.dom_tabMain.scrollLeft = 0
|
||||
@@ -246,7 +260,7 @@ export default {
|
||||
.comment {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
transition: @transition-theme;
|
||||
transition: @transition-normal;
|
||||
transition-property: transform,opacity;
|
||||
transform-origin: 100%;
|
||||
overflow: hidden;
|
||||
@@ -269,13 +283,13 @@ export default {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-end;
|
||||
color: @color-theme;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
.commentHeaderBtn {
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
cursor: pointer;
|
||||
transition: opacity @transition-theme;
|
||||
transition: opacity @transition-normal;
|
||||
|
||||
+.commentHeaderBtn {
|
||||
margin-left: 5px;
|
||||
@@ -287,7 +301,7 @@ export default {
|
||||
}
|
||||
.commentMain {
|
||||
flex: auto;
|
||||
background-color: @color-reply-floor;
|
||||
background-color: var(--color-primary-light-400-alpha-700);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -324,7 +338,7 @@ export default {
|
||||
}
|
||||
.commentLabel {
|
||||
padding: 15px;
|
||||
color: @color-theme_2-font-label;
|
||||
color: var(--color-font-label);
|
||||
font-size: 14px;
|
||||
}
|
||||
.commentType {
|
||||
@@ -334,18 +348,18 @@ export default {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: @transition-theme;
|
||||
transition: @transition-normal;
|
||||
transition-property: opacity, color;
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
&.active {
|
||||
color: @color-theme;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
.commentFloor {
|
||||
opacity: 1;
|
||||
transition: opacity @transition-theme;
|
||||
transition: opacity @transition-normal;
|
||||
|
||||
&.loading {
|
||||
opacity: .4;
|
||||
@@ -355,22 +369,12 @@ export default {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.commentHeaderBtns {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
.commentMain {
|
||||
background-color: ~'@{color-@{value}-reply-floor}';
|
||||
}
|
||||
.commentLabel {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
.commentType {
|
||||
&.active {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unavailable {
|
||||
flex: auto;
|
||||
padding-top: 10%;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
|
||||
</style>
|
||||
+55
-100
@@ -4,7 +4,7 @@ transition(enter-active-class="animated lightSpeedIn" leave-active-class="animat
|
||||
div(:class="$style.bg")
|
||||
//- div(:class="$style.bg" :style="bgStyle")
|
||||
//- div(:class="$style.bg2")
|
||||
div(:class="[$style.header, $style.controlBtnLeft]" v-if="setting.controlBtnPosition == 'left'")
|
||||
div(:class="[$style.header, $style.controlBtnLeft]" v-if="appSetting['common.controlBtnPosition'] == 'left'")
|
||||
div(:class="$style.controBtn")
|
||||
button(type="button" :class="$style.hide" :aria-label="$t('player__hide_detail_tip')" @click="hide")
|
||||
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='80%' viewBox='0 0 30.727 30.727' space='preserve')
|
||||
@@ -40,7 +40,7 @@ transition(enter-active-class="animated lightSpeedIn" leave-active-class="animat
|
||||
div.left(:class="$style.left")
|
||||
//- div(:class="$style.info")
|
||||
div(:class="$style.info")
|
||||
img(:class="$style.img" :src="musicInfo.img" v-if="musicInfo.img")
|
||||
img(:class="$style.img" :src="musicInfo.pic" v-if="musicInfo.pic")
|
||||
div.description(:class="$style.description")
|
||||
p {{$t('player__music_name')}}{{musicInfo.name}}
|
||||
p {{$t('player__music_singer')}}{{musicInfo.singer}}
|
||||
@@ -48,31 +48,34 @@ transition(enter-active-class="animated lightSpeedIn" leave-active-class="animat
|
||||
|
||||
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
|
||||
LyricPlayer(v-if="visibled")
|
||||
music-comment(:class="$style.comment" :musicInfo="musicInfoItem" :show="isShowPlayComment" @close="hideComment" v-if="visibled")
|
||||
music-comment(:class="$style.comment" :show="isShowPlayComment" :musicInfo="playMusicInfo.musicInfo" @close="hideComment" v-if="visibled")
|
||||
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
|
||||
play-bar(v-if="visibled")
|
||||
transition(enter-active-class="animated-slow fadeIn" leave-active-class="animated-slow fadeOut")
|
||||
common-audio-visualizer(v-if="setting.player.audioVisualization && visibled")
|
||||
common-audio-visualizer(v-if="appSetting['player.audioVisualization'] && visibled")
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { useRefGetter, ref, watch } from '@renderer/utils/vueTools'
|
||||
import { isFullscreen } from '@renderer/core/share'
|
||||
import { base as eventBaseName } from '@renderer/event/names'
|
||||
import { ref, watch } from '@common/utils/vueTools'
|
||||
import { isFullscreen } from '@renderer/store'
|
||||
import {
|
||||
isShowPlayerDetail,
|
||||
setShowPlayerDetail,
|
||||
isShowPlayComment,
|
||||
musicInfo,
|
||||
playMusicInfo,
|
||||
} from '@renderer/store/player/state'
|
||||
import {
|
||||
setShowPlayerDetail,
|
||||
setShowPlayComment,
|
||||
setShowPlayLrcSelectContentLrc,
|
||||
musicInfoItem,
|
||||
musicInfo,
|
||||
} from '@renderer/core/share/player'
|
||||
} from '@renderer/store/player/action'
|
||||
import LyricPlayer from './LyricPlayer'
|
||||
import PlayBar from './PlayBar'
|
||||
import MusicComment from './components/MusicComment'
|
||||
import { registerAutoHideMounse, unregisterAutoHideMounse } from './autoHideMounse'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { closeWindow, maxWindow, minWindow, setFullScreen } from '@renderer/utils/ipc'
|
||||
|
||||
export default {
|
||||
name: 'CorePlayDetail',
|
||||
@@ -82,7 +85,6 @@ export default {
|
||||
MusicComment,
|
||||
},
|
||||
setup() {
|
||||
const setting = useRefGetter('setting')
|
||||
const visibled = ref(false)
|
||||
|
||||
let clickTime = 0
|
||||
@@ -121,11 +123,12 @@ export default {
|
||||
(isFullscreen ? registerAutoHideMounse : unregisterAutoHideMounse)()
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
setting,
|
||||
appSetting,
|
||||
playMusicInfo,
|
||||
isShowPlayerDetail,
|
||||
isShowPlayComment,
|
||||
musicInfoItem,
|
||||
musicInfo,
|
||||
hide,
|
||||
handleContextMenu,
|
||||
@@ -135,16 +138,18 @@ export default {
|
||||
visibled,
|
||||
isFullscreen,
|
||||
fullscreenExit() {
|
||||
window.eventHub.emit(eventBaseName.fullscreenToggle, false)
|
||||
setFullScreen(false).then((fullscreen) => {
|
||||
isFullscreen.value = fullscreen
|
||||
})
|
||||
},
|
||||
min() {
|
||||
window.eventHub.emit(eventBaseName.min)
|
||||
minWindow()
|
||||
},
|
||||
max() {
|
||||
window.eventHub.emit(eventBaseName.max)
|
||||
maxWindow()
|
||||
},
|
||||
close() {
|
||||
window.eventHub.emit(eventBaseName.close)
|
||||
closeWindow()
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -165,13 +170,13 @@ export default {
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: @color-theme_2-background_1;
|
||||
background-color: var(--color-content-background);
|
||||
z-index: 10;
|
||||
// -webkit-app-region: drag;
|
||||
overflow: hidden;
|
||||
border-radius: @radius-border;
|
||||
color: @color-theme_2-font;
|
||||
border-left: 12px solid @color-theme;
|
||||
color: var(--color-font);
|
||||
border-left: 12px solid var(--color-primary-light-200-alpha-500);
|
||||
-webkit-app-region: no-drag;
|
||||
contain: strict;
|
||||
|
||||
@@ -203,17 +208,28 @@ export default {
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-image: @color-theme-bgimg;
|
||||
background: var(--background-image) var(--background-image-position) no-repeat;
|
||||
background-size: var(--background-image-size);
|
||||
// background-size: 110% 110%;
|
||||
// filter: blur(60px);
|
||||
opacity: .7;
|
||||
z-index: -1;
|
||||
&:after {
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: @color-theme_2;
|
||||
background-color: var(--color-app-background);
|
||||
}
|
||||
&:after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--color-main-background);
|
||||
}
|
||||
}
|
||||
// .bg2 {
|
||||
@@ -263,7 +279,10 @@ export default {
|
||||
left: 0;
|
||||
flex-direction: row-reverse;
|
||||
height: @height-toolbar * .7;
|
||||
transition: opacity @transition-normal;
|
||||
opacity: .5;
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
.controBtnIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -273,22 +292,22 @@ export default {
|
||||
width: @control-btn-width;
|
||||
height: @control-btn-width;
|
||||
border-radius: 50%;
|
||||
color: @color-theme_2;
|
||||
color: var(--color-font);
|
||||
+ button {
|
||||
margin-right: (@control-btn-width / 2);
|
||||
}
|
||||
|
||||
&.hide {
|
||||
background-color: @color-hideBtn;
|
||||
background-color: var(--color-btn-hide);
|
||||
}
|
||||
&.min, &.fullscreenExit {
|
||||
background-color: @color-minBtn;
|
||||
}
|
||||
&.max {
|
||||
background-color: @color-maxBtn;
|
||||
background-color: var(--color-btn-min);
|
||||
}
|
||||
// &.max {
|
||||
// background-color: var(--color-btn-max);
|
||||
// }
|
||||
&.close {
|
||||
background-color: @color-closeBtn;
|
||||
background-color: var(--color-btn-close);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,14 +324,14 @@ export default {
|
||||
button {
|
||||
width: 46px;
|
||||
height: 30px;
|
||||
color: @color-theme;
|
||||
color: var(--color-font-label);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
background-color: var(--color-button-background-hover);
|
||||
|
||||
&.close {
|
||||
background-color: @color-closeBtn;
|
||||
background-color: var(--color-btn-close);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -356,7 +375,7 @@ export default {
|
||||
align-items: center;
|
||||
padding: 13px;
|
||||
overflow: hidden;
|
||||
transition: flex-basis @transition-theme;
|
||||
transition: flex-basis @transition-normal;
|
||||
}
|
||||
|
||||
.info {
|
||||
@@ -370,12 +389,9 @@ export default {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
min-width: 100%;
|
||||
box-shadow: 0 0 4px @color-theme-hover;
|
||||
box-shadow: 0 0 6px var(--color-primary-alpha-500);
|
||||
border-radius: 6px;
|
||||
opacity: .8;
|
||||
// border: 5px solid @color-theme-hover;
|
||||
// border-radius: @radius-border;
|
||||
// border: 5px solid #fff;
|
||||
}
|
||||
.description {
|
||||
max-width: 300px;
|
||||
@@ -401,65 +417,4 @@ export default {
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.container {
|
||||
border-left-color: ~'@{color-@{value}-theme}';
|
||||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
// color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.bg {
|
||||
// background-color: ~'@{color-@{value}-theme}';
|
||||
background-image: ~'@{color-@{value}-theme-bgimg}';
|
||||
background-size: ~'@{color-@{value}-theme-bgsize}';
|
||||
background-position: ~'@{color-@{value}-theme-bgposition}';
|
||||
&:after {
|
||||
background-color: ~'@{color-@{value}-theme_2}';
|
||||
}
|
||||
}
|
||||
.header {
|
||||
&.controlBtnLeft {
|
||||
.controBtn {
|
||||
button {
|
||||
color: ~'@{color-@{value}-theme_2}';
|
||||
// &.hide:after {
|
||||
// background-color: ~'@{color-@{value}-hideBtn}';
|
||||
// }
|
||||
&.hide {
|
||||
background-color: ~'@{color-@{value}-hideBtn}';
|
||||
}
|
||||
&.min, &.fullscreenExit {
|
||||
background-color: ~'@{color-@{value}-minBtn}';
|
||||
}
|
||||
&.max {
|
||||
background-color: ~'@{color-@{value}-maxBtn}';
|
||||
}
|
||||
&.close {
|
||||
background-color: ~'@{color-@{value}-closeBtn}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.controlBtnRight {
|
||||
.controBtn {
|
||||
button {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
|
||||
&.close {
|
||||
background-color: ~'@{color-@{value}-closeBtn}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.img {
|
||||
box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}';
|
||||
// border-color: ~'@{color-@{value}-theme-hover}';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<material-modal :show="sync.isShowSyncMode" :bg-close="false" :close-btn="false" @close="handleClose(false)">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ $t('sync__title', { name: sync.deviceName }) }}</h2>
|
||||
<div class="scroll" :class="$style.content">
|
||||
<dl :class="$style.btnGroup">
|
||||
<dt :class="$style.label">{{ $t('sync__merge_label') }}</dt>
|
||||
<dd :class="$style.btns">
|
||||
<base-btn :class="$style.btn" @click="handleSelectMode('merge_local_remote')">{{ $t('sync__merge_btn_local_remote') }}</base-btn>
|
||||
<base-btn :class="$style.btn" @click="handleSelectMode('merge_remote_local')">{{ $t('sync__merge_btn_remote_local') }}</base-btn>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl :class="$style.btnGroup">
|
||||
<dt :class="$style.label">{{ $t('sync__overwrite_label') }}</dt>
|
||||
<dd :class="$style.btns">
|
||||
<base-btn :class="$style.btn" @click="handleSelectMode('overwrite_local_remote')">{{ $t('sync__overwrite_btn_local_remote') }}</base-btn>
|
||||
<base-btn :class="$style.btn" @click="handleSelectMode('overwrite_remote_local')">{{ $t('sync__overwrite_btn_remote_local') }}</base-btn>
|
||||
</dd>
|
||||
<dd style="font-size: 14px; margin-top: 5px;">
|
||||
<base-checkbox id="sync_mode_modal_isOverwrite" v-model="isOverwrite" :label="$t('sync__overwrite')" />
|
||||
</dd>
|
||||
</dl>
|
||||
<dl :class="$style.btnGroup">
|
||||
<dt :class="$style.label">{{ $t('sync__other_label') }}</dt>
|
||||
<dd :class="$style.btns">
|
||||
<base-btn :class="$style.btn" @click="handleSelectMode('none')">{{ $t('sync__overwrite_btn_none') }}</base-btn>
|
||||
<base-btn :class="$style.btn" @click="handleSelectMode('cancel')">{{ $t('sync__overwrite_btn_cancel') }}</base-btn>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl :class="$style.btnGroup">
|
||||
<dd>
|
||||
<section :class="$style.tipGroup">
|
||||
<h3 :class="$style.title">{{ $t('sync__merge_tip') }}</h3>
|
||||
<p :class="$style.tip">{{ $t('sync__merge_tip_desc') }}</p>
|
||||
</section>
|
||||
<section :class="$style.tipGroup">
|
||||
<h3 :class="$style.title">{{ $t('sync__overwrite_tip') }}</h3>
|
||||
<p :class="$style.tip">{{ $t('sync__overwrite_tip_desc') }}</p>
|
||||
</section>
|
||||
<section :class="$style.tipGroup">
|
||||
<h3 :class="$style.title">{{ $t('sync__other_tip') }}</h3>
|
||||
<p :class="$style.tip">{{ $t('sync__other_tip_desc') }}</p>
|
||||
</section>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { sync } from '@renderer/store'
|
||||
import { sendSyncAction } from '@renderer/utils/ipc'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const isOverwrite = ref(false)
|
||||
const handleClose = () => {
|
||||
sync.isShowSyncMode = false
|
||||
}
|
||||
const handleSelectMode = (mode) => {
|
||||
if (mode.startsWith('overwrite') && this.isOverwrite) mode += '_full'
|
||||
sendSyncAction({ action: 'select_mode', data: mode })
|
||||
handleClose()
|
||||
}
|
||||
return {
|
||||
sync,
|
||||
isOverwrite,
|
||||
handleClose,
|
||||
handleSelectMode,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 15px;
|
||||
max-width: 700px;
|
||||
min-width: 200px;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
padding: 15px 0 5px;
|
||||
padding-right: 5px;
|
||||
.btnGroup + .btnGroup {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.label {
|
||||
color: var(--color-font-label);
|
||||
font-size: 14px;
|
||||
line-height: 2;
|
||||
}
|
||||
.desc {
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.tipGroup {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 12px;
|
||||
|
||||
+ .tipGroup {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.title {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
+.btn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div v-show="!isFullscreen" :class="$style.control">
|
||||
<button type="button" :class="[$style.btn, $style.min]" :aria-label="$t('min')" @click="minWindow">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="60%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-window-minimize-2" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" :class="[$style.btn, $style.close]" :aria-label="$t('close')" @click="closeWindow">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="60%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-window-close-2" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { minWindow, closeWindow } from '@renderer/utils/ipc'
|
||||
// import { getRandom } from '../../utils'
|
||||
import { isFullscreen } from '@renderer/store'
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.control {
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
-webkit-app-region: no-drag;
|
||||
height: 30px;
|
||||
|
||||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 46px;
|
||||
height: 30px;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 1px;
|
||||
cursor: pointer;
|
||||
color: var(--color-font-label);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
&:hover {
|
||||
&.min, &.max {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&.close {
|
||||
background-color: var(--color-btn-close);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
+27
-22
@@ -1,19 +1,20 @@
|
||||
<template>
|
||||
<material-search-input @event="handleEvent" :list="tipList" :visibleList="visibleList" v-model="searchText" />
|
||||
<material-search-input v-model="searchText" :list="tipList" :visible-list="visibleList" @event="handleEvent" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import music from '@renderer/utils/music'
|
||||
import { debounce } from '@renderer/utils'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
import { debounce } from '@common/utils'
|
||||
import {
|
||||
ref,
|
||||
useRoute,
|
||||
watch,
|
||||
useRefGetter,
|
||||
useRouter,
|
||||
nextTick,
|
||||
useCommit,
|
||||
} from '@renderer/utils/vueTools'
|
||||
} from '@common/utils/vueTools'
|
||||
import { useRouter, useRoute } from '@common/utils/vueRouter'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { searchText as _searchText } from '@renderer/store/search/state'
|
||||
import { setSearchText } from '@renderer/store/search/action'
|
||||
import { getSearchSetting } from '@renderer/utils/data'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
@@ -21,23 +22,21 @@ export default {
|
||||
const visibleList = ref(false)
|
||||
const tipList = ref([])
|
||||
let isFocused = false
|
||||
let prevTempSearchSource = ''
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const setting = useRefGetter('setting')
|
||||
|
||||
const clearSearchList = useCommit('search', 'clearList')
|
||||
|
||||
watch(() => route.name, (newValue, oldValue) => {
|
||||
if (newValue.name != 'search') {
|
||||
if (setting.value.odc.isAutoClearSearchInput && searchText.value) searchText.value = ''
|
||||
if (setting.value.odc.isAutoClearSearchList) clearSearchList()
|
||||
setTimeout(() => {
|
||||
if (appSetting['odc.isAutoClearSearchInput'] && searchText.value) searchText.value = ''
|
||||
if (appSetting['odc.isAutoClearSearchList']) setSearchText('')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const storeSearchText = useRefGetter('search', 'searchText')
|
||||
watch(storeSearchText, (newValue, oldValue) => {
|
||||
watch(_searchText, (newValue, oldValue) => {
|
||||
searchText.value = newValue
|
||||
if (newValue !== searchText.value) searchText.value = newValue
|
||||
})
|
||||
@@ -46,13 +45,15 @@ export default {
|
||||
})
|
||||
|
||||
|
||||
const tipSearch = debounce(() => {
|
||||
if (searchText.value === '') {
|
||||
const tipSearch = debounce(async() => {
|
||||
if (searchText.value === '' && prevTempSearchSource) {
|
||||
tipList.value = []
|
||||
music[setting.value.search.tempSearchSource].tempSearch.cancelTempSearch()
|
||||
music[prevTempSearchSource].tempSearch.cancelTempSearch()
|
||||
return
|
||||
}
|
||||
music[setting.value.search.tempSearchSource].tempSearch.search(searchText.value).then(list => {
|
||||
const { temp_source } = await getSearchSetting()
|
||||
prevTempSearchSource = temp_source
|
||||
music[prevTempSearchSource].tempSearch.search(searchText.value).then(list => {
|
||||
tipList.value = list
|
||||
}).catch(() => {})
|
||||
}, 50)
|
||||
@@ -64,14 +65,18 @@ export default {
|
||||
|
||||
const handleSearch = () => {
|
||||
if (visibleList.value) visibleList.value = false
|
||||
if (!searchText.value && route.path != '/search') {
|
||||
setSearchText('')
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
router.push({
|
||||
path: 'search',
|
||||
path: '/search',
|
||||
query: {
|
||||
text: searchText.value,
|
||||
},
|
||||
}).catch(_ => _)
|
||||
}, 200)
|
||||
}, searchText.value ? 200 : 0)
|
||||
}
|
||||
|
||||
const handleEvent = ({ action, data }) => {
|
||||
+9
-29
@@ -1,30 +1,17 @@
|
||||
<template>
|
||||
<div :class="[$style.toolbar, { [$style.fullscreen]: isFullscreen }, setting.controlBtnPosition == 'left' ? $style.controlBtnLeft : $style.controlBtnRight]">
|
||||
<SearchInput />
|
||||
<div :class="$style.logo" v-if="setting.controlBtnPosition == 'left'">L X</div>
|
||||
<ControlBtns v-else />
|
||||
</div>
|
||||
<div :class="[$style.toolbar, { [$style.fullscreen]: isFullscreen }, appSetting['common.controlBtnPosition'] == 'left' ? $style.controlBtnLeft : $style.controlBtnRight]">
|
||||
<SearchInput />
|
||||
<div v-if="appSetting['common.controlBtnPosition'] == 'left'" :class="$style.logo">L X</div>
|
||||
<ControlBtns v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
<script setup>
|
||||
import { isFullscreen } from '@renderer/store'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import ControlBtns from './ControlBtns'
|
||||
import SearchInput from './SearchInput'
|
||||
import { isFullscreen } from '@renderer/core/share'
|
||||
|
||||
export default {
|
||||
name: 'CoreToolBar',
|
||||
components: { SearchInput, ControlBtns },
|
||||
setup() {
|
||||
return {
|
||||
isFullscreen,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -61,7 +48,7 @@ export default {
|
||||
box-sizing: border-box;
|
||||
padding: 0 @height-toolbar * .4;
|
||||
height: @height-toolbar;
|
||||
color: @color-theme;
|
||||
color: var(--color-primary);
|
||||
flex: none;
|
||||
text-align: center;
|
||||
line-height: @height-toolbar;
|
||||
@@ -69,11 +56,4 @@ export default {
|
||||
// -webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.logo {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
+18
-30
@@ -126,10 +126,10 @@ material-modal(:show="versionInfo.showModal" @close="handleClose")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
import { rendererSend, NAMES } from '@common/ipc'
|
||||
import { compareVer, openUrl, clipboardWriteText, sizeFormate } from '@renderer/utils'
|
||||
import { versionInfo } from '@renderer/core/share'
|
||||
import { compareVer, sizeFormate } from '@common/utils'
|
||||
import { openUrl, clipboardWriteText } from '@common/utils/electron'
|
||||
import { versionInfo } from '@renderer/store'
|
||||
import { getIgnoreVersion, saveIgnoreVersion, quitUpdate } from '@renderer/utils/ipc'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
@@ -137,8 +137,12 @@ export default {
|
||||
versionInfo,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ignoreVersion: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
history() {
|
||||
if (!this.versionInfo.newVersion || !this.versionInfo.newVersion?.history) return []
|
||||
let arr = []
|
||||
@@ -155,11 +159,13 @@ export default {
|
||||
: '处理更新中...'
|
||||
},
|
||||
isIgnored() {
|
||||
return this.setting.ignoreVersion == this.versionInfo.newVersion?.version
|
||||
return this.ignoreVersion == this.versionInfo.newVersion?.version
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.ignoreVersion = getIgnoreVersion()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setIgnoreVersion']),
|
||||
handleClose() {
|
||||
versionInfo.showModal = false
|
||||
},
|
||||
@@ -169,13 +175,13 @@ export default {
|
||||
handleRestartClick(event) {
|
||||
this.handleClose()
|
||||
event.target.disabled = true
|
||||
rendererSend(NAMES.mainWindow.quit_update)
|
||||
quitUpdate()
|
||||
},
|
||||
handleCopy(text) {
|
||||
clipboardWriteText(text)
|
||||
},
|
||||
handleIgnoreClick() {
|
||||
this.setIgnoreVersion(this.isIgnored ? null : this.versionInfo.newVersion?.version)
|
||||
saveIgnoreVersion(this.isIgnored ? null : this.versionInfo.newVersion?.version)
|
||||
this.handleClose()
|
||||
},
|
||||
},
|
||||
@@ -202,7 +208,7 @@ export default {
|
||||
h2 {
|
||||
flex: 0 0 none;
|
||||
font-size: 16px;
|
||||
color: @color-theme_2-font;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
@@ -276,12 +282,12 @@ export default {
|
||||
.desc {
|
||||
padding-top: 10px;
|
||||
font-size: 12px;
|
||||
color: @color-theme;
|
||||
color: var(--color-primary-font);
|
||||
line-height: 1.25;
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
color: @color-theme;
|
||||
color: var(--color-primary-font);
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
@@ -292,22 +298,4 @@ export default {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.main {
|
||||
h2 {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
.desc {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
p {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div :class="$style.view">
|
||||
<router-view v-slot="{ Component }">
|
||||
<!-- <transition enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut"> -->
|
||||
<component :is="Component" class="view-container" />
|
||||
<!-- </transition> -->
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.view {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
> :global(.view-container) {
|
||||
position: absolute !important;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
// background: #fff;
|
||||
// overflow: hidden;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,37 +1,50 @@
|
||||
<template lang="pug">
|
||||
div(:class="$style.btns")
|
||||
button(type="button" v-if="playBtn" @contextmenu.capture.stop :aria-label="$t('list__play')" @click.stop="handleClick('play')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 287.386 287.386' space='preserve' v-once)
|
||||
use(xlink:href='#icon-testPlay')
|
||||
button(type="button" v-if="listAddBtn" @contextmenu.capture.stop :aria-label="$t('list__add_to')" @click.stop="handleClick('listAdd')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve' v-once)
|
||||
use(xlink:href='#icon-addTo')
|
||||
button(type="button" v-if="downloadBtn && setting.download.enable" @contextmenu.capture.stop :aria-label="$t('list__download')" @click.stop="handleClick('download')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 475.078 475.077' space='preserve' v-once)
|
||||
use(xlink:href='#icon-download')
|
||||
//- button(type="button" :aria-label="$t('list__add')" v-if="userInfo" @click.stop="handleClick('add')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve')
|
||||
use(xlink:href='#icon-addTo')
|
||||
button(type="button" v-if="startBtn" @contextmenu.capture.stop :aria-label="$t('list__start')" @click.stop="handleClick('start')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve' v-once)
|
||||
use(xlink:href='#icon-play')
|
||||
button(type="button" v-if="pauseBtn" @contextmenu.capture.stop :aria-label="$t('list__pause')" @click.stop="handleClick('pause')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve' v-once)
|
||||
use(xlink:href='#icon-pause')
|
||||
button(type="button" v-if="fileBtn" @contextmenu.capture.stop :aria-label="$t('list__file')" @click.stop="handleClick('file')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='-61 0 512 512' space='preserve' v-once)
|
||||
use(xlink:href='#icon-musicFile')
|
||||
button(type="button" v-if="searchBtn" @contextmenu.capture.stop :aria-label="$t('list__search')" @click.stop="handleClick('search')")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 30.239 30.239' space='preserve' v-once)
|
||||
use(xlink:href='#icon-search')
|
||||
button(type="button" v-if="removeBtn" :aria-label="$t('list__remove')" @click.stop="handleClick('remove')")
|
||||
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' v-once)
|
||||
use(xlink:href='#icon-delete')
|
||||
|
||||
<template>
|
||||
<div :class="$style.btns">
|
||||
<button v-if="playBtn" type="button" :aria-label="$t('list__play')" @contextmenu.capture.stop @click.stop="handleClick('play')">
|
||||
<svg v-once version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 287.386 287.386" space="preserve">
|
||||
<use xlink:href="#icon-testPlay" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="listAddBtn" type="button" :aria-label="$t('list__add_to')" @contextmenu.capture.stop @click.stop="handleClick('listAdd')">
|
||||
<svg v-once version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 42 42" space="preserve">
|
||||
<use xlink:href="#icon-addTo" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="downloadBtn && appSetting['download.enable']" type="button" :aria-label="$t('list__download')" @contextmenu.capture.stop @click.stop="handleClick('download')">
|
||||
<svg v-once version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 475.078 475.077" space="preserve">
|
||||
<use xlink:href="#icon-download" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="startBtn" type="button" :aria-label="$t('list__start')" @contextmenu.capture.stop @click.stop="handleClick('start')">
|
||||
<svg v-once version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 170 170" space="preserve">
|
||||
<use xlink:href="#icon-play" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="pauseBtn" type="button" :aria-label="$t('list__pause')" @contextmenu.capture.stop @click.stop="handleClick('pause')">
|
||||
<svg v-once version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 277.338 277.338" space="preserve">
|
||||
<use xlink:href="#icon-pause" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="fileBtn" type="button" :aria-label="$t('list__file')" @contextmenu.capture.stop @click.stop="handleClick('file')">
|
||||
<svg v-once version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="-61 0 512 512" space="preserve">
|
||||
<use xlink:href="#icon-musicFile" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="searchBtn" type="button" :aria-label="$t('list__search')" @contextmenu.capture.stop @click.stop="handleClick('search')">
|
||||
<svg v-once version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 30.239 30.239" space="preserve">
|
||||
<use xlink:href="#icon-search" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-if="removeBtn" type="button" :aria-label="$t('list__remove')" @click.stop="handleClick('remove')">
|
||||
<svg v-once 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>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -72,8 +85,11 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
emits: ['btn-click'],
|
||||
setup() {
|
||||
return {
|
||||
appSetting,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick(action) {
|
||||
@@ -97,7 +113,7 @@ export default {
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
padding: 4px 7px;
|
||||
color: @color-btn;
|
||||
color: var(--color-button-font);
|
||||
outline: none;
|
||||
transition: background-color 0.2s ease;
|
||||
line-height: 0;
|
||||
@@ -110,27 +126,12 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.btns {
|
||||
button {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
<template lang="pug">
|
||||
teleport(:to="teleport")
|
||||
div(:class="$style.container" v-if="showModal")
|
||||
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
|
||||
div(:class="$style.modal" v-show="showContent" @click="bgClose && close()")
|
||||
transition(:enter-active-class="inClass" :leave-active-class="outClass" @after-enter="$emit('after-enter', $event)" @after-leave="handleAfterLeave")
|
||||
div(:class="$style.content" v-show="showContent" @click.stop :style="contentStyle")
|
||||
header(:class="$style.header")
|
||||
button(type="button" @click="close" v-if="closeBtn")
|
||||
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')
|
||||
slot
|
||||
<template>
|
||||
<teleport :to="teleport">
|
||||
<div v-if="showModal" ref="dom_container" :class="$style.container">
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div v-show="showContent" :class="[$style.modal, {[$style.filter]: teleport == '#root'}]" @click="bgClose && close()">
|
||||
<transition :enter-active-class="inClass" :leave-active-class="outClass" @after-enter="$emit('after-enter', $event)" @after-leave="handleAfterLeave">
|
||||
<div v-show="showContent" :class="$style.content" :style="contentStyle" @click.stop>
|
||||
<header :class="$style.header">
|
||||
<button v-if="closeBtn" type="button" @click="close">
|
||||
<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>
|
||||
</button>
|
||||
</header>
|
||||
<slot />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getRandom } from '@renderer/utils'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { nextTick } from '@renderer/utils/vueTools'
|
||||
import { getRandom } from '@common/utils/common'
|
||||
import { nextTick } from '@common/utils/vueTools'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
@@ -38,81 +48,125 @@ export default {
|
||||
type: String,
|
||||
default: '76%',
|
||||
},
|
||||
minWidth: {
|
||||
type: String,
|
||||
default: '280px',
|
||||
},
|
||||
maxHeight: {
|
||||
type: String,
|
||||
default: '80%',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto',
|
||||
},
|
||||
},
|
||||
emits: ['after-enter', 'after-leave', 'close'],
|
||||
data() {
|
||||
return {
|
||||
animateIn: [
|
||||
'flipInX',
|
||||
'flipInY',
|
||||
'fadeIn',
|
||||
'bounceIn',
|
||||
'lightSpeedIn',
|
||||
'rotateInDownLeft',
|
||||
'rotateInDownRight',
|
||||
'rotateInUpLeft',
|
||||
'rotateInUpRight',
|
||||
'rollIn',
|
||||
'zoomIn',
|
||||
'zoomInDown',
|
||||
'zoomInLeft',
|
||||
'zoomInRight',
|
||||
'zoomInUp',
|
||||
'slideInDown',
|
||||
'slideInLeft',
|
||||
'slideInRight',
|
||||
'slideInUp',
|
||||
'jackInTheBox',
|
||||
],
|
||||
animateOut: [
|
||||
'flipOutX',
|
||||
'flipOutY',
|
||||
'fadeOut',
|
||||
'bounceOut',
|
||||
'lightSpeedOut',
|
||||
'rotateOutDownLeft',
|
||||
'rotateOutDownRight',
|
||||
'rotateOutUpLeft',
|
||||
'rotateOutUpRight',
|
||||
'rollOut',
|
||||
'zoomOut',
|
||||
'zoomOutDown',
|
||||
'zoomOutLeft',
|
||||
'zoomOutRight',
|
||||
'zoomOutUp',
|
||||
'slideOutDown',
|
||||
'slideOutLeft',
|
||||
'slideOutRight',
|
||||
'slideOutUp',
|
||||
// 'hinge',
|
||||
animates: [
|
||||
[['jackInTheBox', 'flipInX', 'flipInY', 'lightSpeedIn'], ['flipOutX', 'flipOutY', 'lightSpeedOut']],
|
||||
// [['jackInTheBox', 'lightSpeedIn'], ['lightSpeedOut']],
|
||||
[['rotateInDownLeft', 'rotateInDownRight', 'rotateInUpLeft', 'rotateInUpRight'], ['rotateOutDownLeft', 'rotateOutDownRight', 'rotateOutUpLeft', 'rotateOutUpRight']],
|
||||
[['jackInTheBox', 'zoomInDown', 'zoomInUp'], ['zoomOutDown', 'zoomOutUp']],
|
||||
[['slideInDown', 'slideInLeft', 'slideInRight', 'slideInUp'], ['slideOutDown', 'slideOutLeft', 'slideOutRight', 'slideOutUp']],
|
||||
|
||||
// ['flipInX', 'flipOutX'],
|
||||
// ['flipInY', 'flipOutY'],
|
||||
// ['lightSpeedIn', 'lightSpeedOut'],
|
||||
// ['rotateInDownLeft', 'rotateOutDownLeft'],
|
||||
// ['rotateInDownRight', 'rotateOutDownRight'],
|
||||
// ['rotateInUpLeft', 'rotateOutUpLeft'],
|
||||
// ['rotateInUpRight', 'rotateOutUpRight'],
|
||||
// // ['rollIn', 'rollOut'],
|
||||
// // ['zoomIn', 'zoomOut'],
|
||||
// ['zoomInDown', 'zoomOutDown'],
|
||||
// // ['zoomInLeft', 'zoomOutLeft'],
|
||||
// // ['zoomInRight', 'zoomOutRight'],
|
||||
// ['zoomInUp', 'zoomOutUp'],
|
||||
// ['slideInDown', 'slideOutDown'],
|
||||
// ['slideInLeft', 'slideOutLeft'],
|
||||
// ['slideInRight', 'slideOutRight'],
|
||||
// ['slideInUp', 'slideOutUp'],
|
||||
// // ['jackInTheBox', 'hinge'],
|
||||
],
|
||||
// animateIn: [
|
||||
// 'flipInX',
|
||||
// 'flipInY',
|
||||
// // 'fadeIn',
|
||||
// // 'bounceIn',
|
||||
// 'lightSpeedIn',
|
||||
// 'rotateInDownLeft',
|
||||
// 'rotateInDownRight',
|
||||
// 'rotateInUpLeft',
|
||||
// 'rotateInUpRight',
|
||||
// 'rollIn',
|
||||
// 'zoomIn',
|
||||
// 'zoomInDown',
|
||||
// 'zoomInLeft',
|
||||
// 'zoomInRight',
|
||||
// 'zoomInUp',
|
||||
// 'slideInDown',
|
||||
// 'slideInLeft',
|
||||
// 'slideInRight',
|
||||
// 'slideInUp',
|
||||
// 'jackInTheBox',
|
||||
// ],
|
||||
// animateOut: [
|
||||
// 'flipOutX',
|
||||
// 'flipOutY',
|
||||
// // 'fadeOut',
|
||||
// // 'bounceOut',
|
||||
// 'lightSpeedOut',
|
||||
// 'rotateOutDownLeft',
|
||||
// 'rotateOutDownRight',
|
||||
// 'rotateOutUpLeft',
|
||||
// 'rotateOutUpRight',
|
||||
// 'rollOut',
|
||||
// 'zoomOut',
|
||||
// 'zoomOutDown',
|
||||
// 'zoomOutLeft',
|
||||
// 'zoomOutRight',
|
||||
// 'zoomOutUp',
|
||||
// 'slideOutDown',
|
||||
// 'slideOutLeft',
|
||||
// 'slideOutRight',
|
||||
// 'slideOutUp',
|
||||
// 'hinge',
|
||||
// ],
|
||||
inClass: 'animated jackInTheBox',
|
||||
outClass: 'animated flipOutX',
|
||||
unwatchFn: null,
|
||||
outClass: 'animated slideOutRight',
|
||||
showModal: false,
|
||||
showContent: false,
|
||||
// ai: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
contentStyle() {
|
||||
return {
|
||||
maxWidth: this.maxWidth,
|
||||
minWidth: this.minWidth,
|
||||
width: this.width,
|
||||
maxHeight: this.maxHeight,
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'setting.randomAnimate'(n) {
|
||||
n ? this.createWatch() : this.removeWatch()
|
||||
},
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
// const dom = document.getElementById(this.teleport)
|
||||
// if (dom) {
|
||||
// // dom.t
|
||||
// }
|
||||
this.setRandomAnimation()
|
||||
this.showModal = true
|
||||
nextTick(() => {
|
||||
this.$refs.dom_container.parentNode.classList.add('show-modal')
|
||||
this.showContent = true
|
||||
})
|
||||
} else {
|
||||
this.$refs.dom_container?.parentNode.classList.remove('show-modal')
|
||||
this.showContent = false
|
||||
}
|
||||
},
|
||||
@@ -120,37 +174,24 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.setting.randomAnimate) this.createWatch()
|
||||
this.setRandomAnimation()
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.removeWatch()
|
||||
this.$refs.dom_container?.parentNode.classList.remove('show-modal')
|
||||
},
|
||||
methods: {
|
||||
check(isShow) {
|
||||
if (isShow) {
|
||||
this.showModal = true
|
||||
nextTick(() => {
|
||||
this.showContent = true
|
||||
})
|
||||
} else {
|
||||
this.showContent = false
|
||||
setRandomAnimation() {
|
||||
if (appSetting['common.randomAnimate']) {
|
||||
const [animIn, animOut] = this.animates[getRandom(0, this.animates.length)]
|
||||
// const [animIn, animOut] = this.animates[this.ai]
|
||||
// if (++this.ai >= this.animates.length) this.ai = 0
|
||||
// console.log(animIn, animOut)
|
||||
// this.inClass = 'animated ' + animIn
|
||||
// this.outClass = 'animated ' + animOut
|
||||
this.inClass = 'animated ' + animIn[getRandom(0, animIn.length)]
|
||||
this.outClass = 'animated ' + animOut[getRandom(0, animOut.length)]
|
||||
}
|
||||
},
|
||||
createWatch() {
|
||||
this.removeWatch()
|
||||
this.unwatchFn = this.$watch('show', function(n) {
|
||||
if (!n) return
|
||||
this.inClass = 'animated ' + this.animateIn[getRandom(0, this.animateIn.length)]
|
||||
this.outClass = 'animated ' + this.animateOut[getRandom(0, this.animateOut.length)]
|
||||
})
|
||||
this.inClass = 'animated ' + this.animateIn[getRandom(0, this.animateIn.length)]
|
||||
this.outClass = 'animated ' + this.animateOut[getRandom(0, this.animateOut.length)]
|
||||
},
|
||||
removeWatch() {
|
||||
if (!this.unwatchFn) return
|
||||
this.unwatchFn()
|
||||
this.unwatchFn = null
|
||||
},
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
@@ -178,34 +219,50 @@ export default {
|
||||
.modal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, .3);
|
||||
// background-color: rgba(0, 0, 0, .2);
|
||||
// background-color: rgba(255, 255, 255, .6);
|
||||
// background-color: var(--color-primary-light-600-alpha-900);
|
||||
// backdrop-filter: blur(4px);
|
||||
// backdrop-filter: grayscale(70%);
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
// will-change: transform;
|
||||
|
||||
&.filter {
|
||||
backdrop-filter: grayscale(70%);
|
||||
}
|
||||
|
||||
// &:before {
|
||||
// .mixin-after;
|
||||
// position: absolute;
|
||||
// left: 0;
|
||||
// top: 0;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// background-color: var(--color-000);
|
||||
// opacity: .6;
|
||||
// }
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, .25);
|
||||
overflow: hidden;
|
||||
max-height: 80%;
|
||||
// max-height: 80%;
|
||||
// max-width: 76%;
|
||||
min-width: 220px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
z-index: 100;
|
||||
|
||||
> * {
|
||||
background-color: @color-theme_2-background_2;
|
||||
}
|
||||
background-color: var(--color-content-background);
|
||||
}
|
||||
|
||||
.header {
|
||||
flex: none;
|
||||
background-color: @color-theme;
|
||||
background-color: var(--color-primary-light-100-alpha-100);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
@@ -216,7 +273,7 @@ export default {
|
||||
cursor: pointer;
|
||||
padding: 4px 7px;
|
||||
background-color: transparent;
|
||||
color: @color-theme-font-label;
|
||||
color: var(--color-primary-dark-500-alpha-500);
|
||||
outline: none;
|
||||
transition: background-color 0.2s ease;
|
||||
line-height: 0;
|
||||
@@ -226,41 +283,12 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-theme-hover;
|
||||
background-color: var(--color-primary-dark-100-alpha-600);
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
background-color: var(--color-primary-dark-200-alpha-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.modal {
|
||||
background-color: rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
.content {
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
|
||||
> * {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: ~'@{color-@{value}-theme}';
|
||||
button {
|
||||
color: ~'@{color-@{value}-theme-font-label}';
|
||||
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,63 +1,114 @@
|
||||
<template lang="pug">
|
||||
div(:class="$style.songList")
|
||||
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||
div(:class="$style.list")
|
||||
div.thead(:class="$style.thead")
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th.nobreak.center(:class="$style.th" :style="{ width: rowWidth.r1 }") #
|
||||
th.nobreak(:class="$style.th" :style="{ width: rowWidth.r2 }") {{$t('music_name')}}
|
||||
th.nobreak(:class="$style.th" :style="{ width: rowWidth.r3 }") {{$t('music_singer')}}
|
||||
th.nobreak(:class="$style.th" :style="{ width: rowWidth.r4 }") {{$t('music_album')}}
|
||||
th.nobreak(:class="$style.th" :style="{ width: rowWidth.r5 }") {{$t('music_time')}}
|
||||
th.nobreak(:class="$style.th" :style="{ width: rowWidth.r6 }") {{$t('action')}}
|
||||
div(:class="$style.content")
|
||||
div(v-show="list.length" :class="$style.content" ref="dom_listContent")
|
||||
base-virtualized-list(:list="list" key-name="songmid" ref="listRef" :item-height="listItemHeight"
|
||||
containerClass="scroll" contentClass="list" @contextmenu.capture="handleListRightClick")
|
||||
template(#default="{ item, index }")
|
||||
div.list-item(@click="handleListItemClick($event, index)" @contextmenu="handleListItemRightClick($event, index)"
|
||||
:class="[{ selected: rightClickSelectedIndex == index }, { active: selectedList.includes(item) }]")
|
||||
div.list-item-cell.nobreak.center(:style="{ width: rowWidth.r1 }" style="padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
|
||||
div.list-item-cell.auto(:style="{ width: rowWidth.r2 }" :aria-label="item.name + (item._types.flac32bit ? ` - ${$t('tag__lossless_24bit')}` : (item._types.ape || item._types.flac || item._types.wav) ? ` - ${$t('tag__lossless')}` : item._types['320k'] ? ` - ${$t('tag__high_quality')}` : '')")
|
||||
span.select {{item.name}}
|
||||
span.badge.badge-theme-success(:class="[$style.labelQuality, $style.noSelect]" v-if="item._types.flac32bit") {{$t('tag__lossless_24bit')}}
|
||||
span.badge.badge-theme-success(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item._types.ape || item._types.flac || item._types.wav") {{$t('tag__lossless')}}
|
||||
span.badge.badge-theme-info(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item._types['320k']") {{$t('tag__high_quality')}}
|
||||
div.list-item-cell(:style="{ width: rowWidth.r3 }" :aria-label="item.singer")
|
||||
span.select {{item.singer}}
|
||||
div.list-item-cell(:style="{ width: rowWidth.r4 }" :aria-label="item.albumName")
|
||||
span.select {{item.albumName}}
|
||||
div.list-item-cell(:style="{ width: rowWidth.r5 }")
|
||||
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}
|
||||
div.list-item-cell(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
|
||||
material-list-buttons(:index="index" :class="$style.btns"
|
||||
:remove-btn="false" @btn-click="handleListBtnClick"
|
||||
:download-btn="assertApiSupport(item.source)")
|
||||
template(#footer)
|
||||
div(:class="$style.pagination")
|
||||
material-pagination(:count="total" :limit="limit" :page="page" @btn-click="$emit('togglePage', $event)")
|
||||
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
|
||||
div(v-show="!list.length" :class="$style.noitem")
|
||||
p(v-html="noItem")
|
||||
//- material-flow-btn(:show="isShowEditBtn && assertApiSupport(source)" :remove-btn="false" @btn-click="handleFlowBtnClick")
|
||||
common-download-modal(v-model:show="isShowDownload" :musicInfo="selectedDownloadMusicInfo" teleport="#view")
|
||||
common-download-multiple-modal(v-model:show="isShowDownloadMultiple" :list="selectedList" @confirm="removeAllSelect" teleport="#view")
|
||||
common-list-add-modal(v-model:show="isShowListAdd" :musicInfo="selectedAddMusicInfo" teleport="#view")
|
||||
common-list-add-multiple-modal(v-model:show="isShowListAddMultiple" :musicList="selectedList" @confirm="removeAllSelect" teleport="#view")
|
||||
base-menu(:menus="menus" :location="menuLocation" item-name="name" :isShow="isShowItemMenu" @menu-click="handleMenuClick")
|
||||
<template>
|
||||
<div :class="$style.songList">
|
||||
<transition enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut">
|
||||
<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: 24%;">{{ $t('music_singer') }}</th>
|
||||
<th class="nobreak" style="width: 27%;">{{ $t('music_album') }}</th>
|
||||
<th class="nobreak" style="width: 10%;">{{ $t('music_time') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div :class="$style.content">
|
||||
<div v-show="!noItem" ref="dom_listContent" :class="$style.content">
|
||||
<base-virtualized-list v-if="actionButtonsVisible" ref="listRef" :list="list" key-name="id" :item-height="listItemHeight" container-class="scroll" content-class="list" @contextmenu.capture="handleListRightClick">
|
||||
<template #default="{ item, index }">
|
||||
<div
|
||||
class="list-item" :class="[{ selected: rightClickSelectedIndex == index }, { active: selectedList.includes(item) }]"
|
||||
@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="item.meta._qualitys.flac24bit" class="no-select badge badge-theme-primary">{{ $t('tag__lossless_24bit') }}</span>
|
||||
<span v-else-if="item.meta._qualitys.ape || item.meta._qualitys.flac || item.meta._qualitys.wav" class="no-select badge badge-theme-primary">{{ $t('tag__lossless') }}</span>
|
||||
<span v-else-if="item.meta._qualitys['320k']" class="no-select badge badge-theme-secondary">{{ $t('tag__high_quality') }}</span>
|
||||
<span v-if="sourceTag" class="no-select badge badge-theme-tertiary">{{ 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" :remove-btn="false" :download-btn="assertApiSupport(item.source)" :play-btn="checkApiSource ? assertApiSupport(item.source) : true" @btn-click="handleListBtnClick" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style.pagination">
|
||||
<material-pagination :count="total" :limit="limit" :page="page" @btn-click="$emit('togglePage', $event)" />
|
||||
</div>
|
||||
</template>
|
||||
</base-virtualized-list>
|
||||
<base-virtualized-list v-else ref="listRef" :list="list" key-name="id" :item-height="listItemHeight" container-class="scroll" content-class="list" @contextmenu.capture="handleListRightClick">
|
||||
<template #default="{ item, index }">
|
||||
<div
|
||||
class="list-item" :class="[{ selected: rightClickSelectedIndex == index }, { active: selectedList.includes(item) }]"
|
||||
@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="item.meta._qualitys.flac24bit" class="no-select badge badge-theme-primary">{{ $t('tag__lossless_24bit') }}</span>
|
||||
<span v-else-if="item.meta._qualitys.ape || item.meta._qualitys.flac || item.meta._qualitys.wav" class="no-select badge badge-theme-primary">{{ $t('tag__lossless') }}</span>
|
||||
<span v-else-if="item.meta._qualitys['320k']" class="no-select badge badge-theme-secondary">{{ $t('tag__high_quality') }}</span>
|
||||
<span v-if="sourceTag" class="no-select badge badge-theme-tertiary">{{ item.source }}</span>
|
||||
</div>
|
||||
<div class="list-item-cell" style="flex: 0 0 24%;"><span class="select" :aria-label="item.singer">{{ item.singer }}</span></div>
|
||||
<div class="list-item-cell" style="flex: 0 0 27%;"><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>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div :class="$style.pagination">
|
||||
<material-pagination :count="total" :limit="limit" :page="page" @btn-click="$emit('togglePage', $event)" />
|
||||
</div>
|
||||
</template>
|
||||
</base-virtualized-list>
|
||||
</div>
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div v-show="noItem" :class="$style.noitem">
|
||||
<p v-text="noItem" />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<!-- <material-flow-btn :show="isShowEditBtn && assertApiSupport(source)" :remove-btn="false" @btn-click="handleFlowBtnClick" /> -->
|
||||
<!-- <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" /> -->
|
||||
<common-list-add-modal v-model:show="isShowListAdd" :music-info="selectedAddMusicInfo" teleport="#view" />
|
||||
<common-list-add-multiple-modal v-model:show="isShowListAddMultiple" :music-list="selectedList" 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" />
|
||||
<base-menu v-model="isShowItemMenu" :menus="menus" :xy="menuLocation" item-name="name" @menu-click="handleMenuClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { clipboardWriteText, assertApiSupport } from '@renderer/utils'
|
||||
import { ref, useCssModule, useRefGetter } from '@renderer/utils/vueTools'
|
||||
import { clipboardWriteText } from '@common/utils/electron'
|
||||
import { assertApiSupport } from '@renderer/store/utils'
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import useList from './useList'
|
||||
import useMenu from './useMenu'
|
||||
import usePlay from './usePlay'
|
||||
import useMusicDownload from './useMusicDownload'
|
||||
import useMusicAdd from './useMusicAdd'
|
||||
import useMusicActions from './useMusicActions'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
export default {
|
||||
name: 'MaterialOnlineList',
|
||||
props: {
|
||||
@@ -79,33 +130,26 @@ export default {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
rowWidth: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
r1: '5%',
|
||||
r2: 'auto',
|
||||
r3: '22%',
|
||||
r4: '22%',
|
||||
r5: '8%',
|
||||
r6: '13%',
|
||||
}
|
||||
},
|
||||
sourceTag: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
noItem: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
checkApiSource: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['show-menu', 'play-list', 'togglePage'],
|
||||
setup(props, { emit }) {
|
||||
const actionButtonsVisible = appSetting['list.actionButtonsVisible']
|
||||
const rightClickSelectedIndex = ref(-1)
|
||||
const dom_listContent = ref(null)
|
||||
const listRef = ref(null)
|
||||
|
||||
const styles = useCssModule()
|
||||
|
||||
const setting = useRefGetter('setting')
|
||||
|
||||
const {
|
||||
selectedList,
|
||||
listItemHeight,
|
||||
@@ -117,7 +161,7 @@ export default {
|
||||
handlePlayMusic,
|
||||
handlePlayMusicLater,
|
||||
doubleClickPlay,
|
||||
} = usePlay({ selectedList, props, removeAllSelect, setting, emit })
|
||||
} = usePlay({ selectedList, props, removeAllSelect, emit })
|
||||
|
||||
const {
|
||||
isShowListAdd,
|
||||
@@ -143,10 +187,9 @@ export default {
|
||||
menuLocation,
|
||||
isShowItemMenu,
|
||||
showMenu,
|
||||
hideMenu,
|
||||
menuClick,
|
||||
} = useMenu({
|
||||
listRef,
|
||||
props,
|
||||
assertApiSupport,
|
||||
emit,
|
||||
|
||||
@@ -176,10 +219,10 @@ export default {
|
||||
if (!event.target.classList.contains('select')) return
|
||||
event.stopImmediatePropagation()
|
||||
let classList = dom_listContent.value.classList
|
||||
classList.add(styles.copying)
|
||||
classList.add('copying')
|
||||
window.requestAnimationFrame(() => {
|
||||
let str = window.getSelection().toString()
|
||||
classList.remove(styles.copying)
|
||||
classList.remove('copying')
|
||||
str = str.split(/\n\n/).map(s => s.replace(/\n/g, ' ')).join('\n').trim()
|
||||
if (!str.length) return
|
||||
clipboardWriteText(str)
|
||||
@@ -220,7 +263,6 @@ export default {
|
||||
isShowItemMenu,
|
||||
menuLocation,
|
||||
handleMenuClick,
|
||||
hideMenu,
|
||||
|
||||
handleListRightClick,
|
||||
assertApiSupport,
|
||||
@@ -234,6 +276,7 @@ export default {
|
||||
selectedDownloadMusicInfo,
|
||||
|
||||
scrollToTop,
|
||||
actionButtonsVisible,
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -251,50 +294,23 @@ export default {
|
||||
}
|
||||
|
||||
.list {
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
height: 100%;
|
||||
}
|
||||
.thead {
|
||||
flex: none;
|
||||
}
|
||||
.th:first-child {
|
||||
color: @color-theme_2-font-label;
|
||||
// padding-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.copying {
|
||||
.no-select {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
:global(.list) {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
:global(.list-item-cell) {
|
||||
font-size: 12px !important;
|
||||
:global(.badge) {
|
||||
margin-left: 3px;
|
||||
}
|
||||
&:first-child {
|
||||
// padding-left: 10px;
|
||||
font-size: 11px !important;
|
||||
color: @color-theme_2-font-label !important;
|
||||
}
|
||||
}
|
||||
:global(.badge) {
|
||||
opacity: .85;
|
||||
}
|
||||
}
|
||||
.pagination {
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
@@ -311,30 +327,12 @@ export default {
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// background-color: var(--color-000);
|
||||
|
||||
p {
|
||||
font-size: 24px;
|
||||
color: @color-theme_2-font-label;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.th:first-child {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
:global(.list) {
|
||||
:global(.list-item-cell) {
|
||||
&:first-child {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}' !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.noitem {
|
||||
p {
|
||||
color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { computed, watch, ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { isFullscreen } from '@renderer/store'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { getFontSizeWithScreen } from '@renderer/utils'
|
||||
|
||||
const useKeyEvent = ({ handleSelectAllData }: {
|
||||
handleSelectAllData: () => void
|
||||
}) => {
|
||||
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 }: LX.KeyDownEevent) => {
|
||||
if (!event || (event.target as HTMLElement).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 ({ props }: {
|
||||
props: {
|
||||
list: LX.Music.MusicInfoOnline[]
|
||||
}
|
||||
}) => {
|
||||
const selectedList = ref<LX.Music.MusicInfoOnline[]>([])
|
||||
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 = [...props.list]
|
||||
}
|
||||
const keyEvent = useKeyEvent({ handleSelectAllData })
|
||||
|
||||
const handleSelectData = (clickIndex: number) => {
|
||||
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 = props.list.slice(lastSelectIndex, clickIndex + 1)
|
||||
if (isNeedReverse) selectedList.value.reverse()
|
||||
}
|
||||
} else {
|
||||
selectedList.value.push(props.list[clickIndex])
|
||||
lastSelectIndex = clickIndex
|
||||
}
|
||||
} else if (keyEvent.isModDown) {
|
||||
lastSelectIndex = clickIndex
|
||||
let item = props.list[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(() => props.list, removeAllSelect)
|
||||
|
||||
return {
|
||||
selectedList,
|
||||
listItemHeight,
|
||||
removeAllSelect,
|
||||
handleSelectData,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { computed, ref, reactive, useI18n, useCssModule, nextTick } from '@renderer/utils/vueTools'
|
||||
import musicSdk from '@renderer/utils/music'
|
||||
import { computed, ref, reactive, nextTick } from '@common/utils/vueTools'
|
||||
import musicSdk from '@renderer/utils/musicSdk'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
|
||||
export default ({
|
||||
listRef,
|
||||
props,
|
||||
assertApiSupport,
|
||||
emit,
|
||||
|
||||
@@ -21,9 +22,8 @@ export default ({
|
||||
search: true,
|
||||
sourceDetail: true,
|
||||
})
|
||||
const { t } = useI18n()
|
||||
const styles = useCssModule()
|
||||
const menuLocation = ref({ x: 0, y: 0 })
|
||||
const t = useI18n()
|
||||
const menuLocation = reactive({ x: 0, y: 0 })
|
||||
const isShowItemMenu = ref(false)
|
||||
|
||||
const menus = computed(() => {
|
||||
@@ -66,17 +66,17 @@ export default ({
|
||||
// this.listMenu.itemMenuControl.play =
|
||||
// this.listMenu.itemMenuControl.playLater =
|
||||
itemMenuControl.download = assertApiSupport(musicInfo.source)
|
||||
let dom_container = event.target.closest('.' + styles.songList)
|
||||
const getOffsetValue = (target, x = 0, y = 0) => {
|
||||
if (target === dom_container) return { x, y }
|
||||
if (!target) return { x: 0, y: 0 }
|
||||
x += target.offsetLeft
|
||||
y += target.offsetTop
|
||||
return getOffsetValue(target.offsetParent, x, y)
|
||||
|
||||
if (props.checkApiSource) {
|
||||
itemMenuControl.playLater =
|
||||
itemMenuControl.play =
|
||||
itemMenuControl.download
|
||||
}
|
||||
let { x, y } = getOffsetValue(event.target)
|
||||
menuLocation.value.x = x + event.offsetX
|
||||
menuLocation.value.y = y + event.offsetY - listRef.value.getScrollTop()
|
||||
|
||||
menuLocation.x = event.pageX
|
||||
menuLocation.y = event.pageY
|
||||
|
||||
if (isShowItemMenu.value) return
|
||||
emit('show-menu')
|
||||
nextTick(() => {
|
||||
isShowItemMenu.value = true
|
||||
@@ -119,6 +119,5 @@ export default ({
|
||||
isShowItemMenu,
|
||||
showMenu,
|
||||
menuClick,
|
||||
hideMenu,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useRouter } from '@renderer/utils/vueTools'
|
||||
import musicSdk from '@renderer/utils/music'
|
||||
import { openUrl } from '@renderer/utils'
|
||||
import { useRouter } from '@common/utils/vueRouter'
|
||||
import musicSdk from '@renderer/utils/musicSdk'
|
||||
import { openUrl } from '@common/utils/electron'
|
||||
import { toOldMusicInfo } from '@renderer/utils'
|
||||
|
||||
|
||||
export default ({ props }) => {
|
||||
@@ -9,7 +10,7 @@ export default ({ props }) => {
|
||||
const handleSearch = index => {
|
||||
const info = props.list[index]
|
||||
router.push({
|
||||
path: 'search',
|
||||
path: '/search',
|
||||
query: {
|
||||
text: `${info.name} ${info.singer}`,
|
||||
},
|
||||
@@ -18,7 +19,7 @@ export default ({ props }) => {
|
||||
|
||||
const handleOpenMusicDetail = index => {
|
||||
const minfo = props.list[index]
|
||||
const url = musicSdk[minfo.source].getMusicDetailPageUrl(minfo)
|
||||
const url = musicSdk[minfo.source].getMusicDetailPageUrl(toOldMusicInfo(minfo))
|
||||
if (!url) return
|
||||
openUrl(url)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref, nextTick } from '@renderer/utils/vueTools'
|
||||
import { ref, nextTick } from '@common/utils/vueTools'
|
||||
|
||||
export default ({ selectedList, props }) => {
|
||||
const isShowListAdd = ref(false)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref, nextTick } from '@renderer/utils/vueTools'
|
||||
import { ref, nextTick } from '@common/utils/vueTools'
|
||||
|
||||
export default ({ selectedList, props }) => {
|
||||
const isShowDownload = ref(false)
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { useCommit } from '@renderer/utils/vueTools'
|
||||
import { defaultList } from '@renderer/core/share/list'
|
||||
import { getList } from '@renderer/core/share/utils'
|
||||
|
||||
export default ({ selectedList, props, removeAllSelect, setting, emit }) => {
|
||||
let clickTime = 0
|
||||
let clickIndex = -1
|
||||
|
||||
const listAddMultiple = useCommit('list', 'listAddMultiple')
|
||||
const listAdd = useCommit('list', 'listAdd')
|
||||
const setList = useCommit('player', 'setList')
|
||||
const setTempPlayList = useCommit('player', 'setTempPlayList')
|
||||
|
||||
const handlePlayMusic = (index, single) => {
|
||||
let targetSong = props.list[index]
|
||||
const defaultListMusics = getList(defaultList.id)
|
||||
if (selectedList.value.length && !single) {
|
||||
listAddMultiple({ id: defaultList.id, list: [...selectedList.value] })
|
||||
removeAllSelect()
|
||||
} else {
|
||||
listAdd({ id: defaultList.id, musicInfo: targetSong })
|
||||
}
|
||||
let targetIndex = defaultListMusics.findIndex(s => s.songmid === targetSong.songmid)
|
||||
if (targetIndex > -1) {
|
||||
setList({
|
||||
listId: defaultList.id,
|
||||
index: targetIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handlePlayMusicLater = (index, single) => {
|
||||
if (selectedList.value.length && !single) {
|
||||
setTempPlayList(selectedList.value.map(s => ({ listId: '__temp__', musicInfo: s })))
|
||||
removeAllSelect()
|
||||
} else {
|
||||
setTempPlayList([{ listId: '__temp__', musicInfo: props.list[index] }])
|
||||
}
|
||||
}
|
||||
|
||||
const doubleClickPlay = index => {
|
||||
if (
|
||||
window.performance.now() - clickTime > 400 ||
|
||||
clickIndex !== index
|
||||
) {
|
||||
clickTime = window.performance.now()
|
||||
clickIndex = index
|
||||
return
|
||||
}
|
||||
if (setting.value.list.isClickPlayList) {
|
||||
emit('play-list', index)
|
||||
} else {
|
||||
handlePlayMusic(index, true)
|
||||
}
|
||||
clickTime = 0
|
||||
clickIndex = -1
|
||||
}
|
||||
|
||||
return {
|
||||
handlePlayMusic,
|
||||
handlePlayMusicLater,
|
||||
doubleClickPlay,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// import { useCommit } from '@common/utils/vueTools'
|
||||
import { defaultList } from '@renderer/store/list/state'
|
||||
import { getListMusics, addListMusics } from '@renderer/store/list/action'
|
||||
import { addTempPlayList } from '@renderer/store/player/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { Ref } from '@common/utils/vueTools'
|
||||
import { playList } from '@renderer/core/player'
|
||||
|
||||
export default ({ selectedList, props, removeAllSelect, emit }: {
|
||||
selectedList: Ref<LX.Music.MusicInfoOnline[]>
|
||||
props: {
|
||||
list: LX.Music.MusicInfoOnline[]
|
||||
}
|
||||
removeAllSelect: () => void
|
||||
emit: (event: 'show-menu' | 'play-list' | 'togglePage', ...args: any[]) => void
|
||||
}) => {
|
||||
let clickTime = 0
|
||||
let clickIndex = -1
|
||||
|
||||
const handlePlayMusic = async(index: number, single: boolean) => {
|
||||
let targetSong = props.list[index]
|
||||
const defaultListMusics = await getListMusics(defaultList.id)
|
||||
if (selectedList.value.length && !single) {
|
||||
await addListMusics(defaultList.id, [...selectedList.value])
|
||||
removeAllSelect()
|
||||
} else {
|
||||
await addListMusics(defaultList.id, [targetSong])
|
||||
}
|
||||
let targetIndex = defaultListMusics.findIndex(s => s.id === targetSong.id)
|
||||
if (targetIndex > -1) {
|
||||
playList(defaultList.id, targetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePlayMusicLater = (index: number, single: boolean) => {
|
||||
if (selectedList.value.length && !single) {
|
||||
addTempPlayList(selectedList.value.map(s => ({ listId: '__temp__', musicInfo: s })))
|
||||
removeAllSelect()
|
||||
} else {
|
||||
addTempPlayList([{ listId: '__temp__', musicInfo: props.list[index] }])
|
||||
}
|
||||
}
|
||||
|
||||
const doubleClickPlay = (index: number) => {
|
||||
if (
|
||||
window.performance.now() - clickTime > 400 ||
|
||||
clickIndex !== index
|
||||
) {
|
||||
clickTime = window.performance.now()
|
||||
clickIndex = index
|
||||
return
|
||||
}
|
||||
if (appSetting['list.isClickPlayList']) {
|
||||
emit('play-list', index)
|
||||
} else {
|
||||
void handlePlayMusic(index, true)
|
||||
}
|
||||
clickTime = 0
|
||||
clickIndex = -1
|
||||
}
|
||||
|
||||
return {
|
||||
handlePlayMusic,
|
||||
handlePlayMusicLater,
|
||||
doubleClickPlay,
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,56 @@
|
||||
<template>
|
||||
<div :class="$style.pagination" v-if="allPage > 1">
|
||||
<ul>
|
||||
<li v-if="page===1" :class="$style.disabled"><span>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-left"></use>
|
||||
</svg></span></li>
|
||||
<li v-else>
|
||||
<button type="button" @click="handleClick(page - 1)" :aria-label="$t('pagination__prev')">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-left"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="allPage > btnLength && page > pageEvg+1" :class="$style.first">
|
||||
<button type="button" @click="handleClick(1)" :aria-label="$t('pagination__page', { num: 1 })">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-first"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li v-for="(p, index) in pages" :key="index" :class="{[$style.active] : p == page}"><span v-if="p === page" v-text="page"></span>
|
||||
<button v-else type="button" @click="handleClick(p)" v-text="p" :aria-label="$t('pagination__page', { num: p })"></button>
|
||||
</li>
|
||||
<li v-if="allPage > btnLength && allPage - page > pageEvg" :class="$style.last">
|
||||
<button type="button" @click="handleClick(allPage)" :aria-label="$t('pagination__page', { num: allPage })">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-last"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="page===allPage" :class="$style.disabled"><span>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-right"></use>
|
||||
</svg></span></li>
|
||||
<li v-else>
|
||||
<button type="button" @click="handleClick(page + 1)" :aria-label="$t('pagination__next')">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-right"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="maxPage > 1" :class="$style.pagination">
|
||||
<ul>
|
||||
<li v-if="page == 1" :class="$style.disabled">
|
||||
<span>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-left" />
|
||||
</svg>
|
||||
</span>
|
||||
</li>
|
||||
<li v-else>
|
||||
<button type="button" :aria-label="$t('pagination__prev')" @click="handleClick(page - 1)">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-left" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="maxPage > btnLength && page > pageEvg+1" :class="$style.first">
|
||||
<button type="button" :aria-label="$t('pagination__page', { num: 1 })" @click="handleClick(1)">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-first" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li v-for="p in pages" :key="p" :class="{[$style.active] : p == page}">
|
||||
<span v-if="p === page" v-text="page" />
|
||||
<button v-else type="button" :aria-label="$t('pagination__page', { num: p })" @click="handleClick(p)" v-text="p" />
|
||||
</li>
|
||||
<li v-if="maxPage > btnLength && maxPage - page > pageEvg" :class="$style.last">
|
||||
<button type="button" :aria-label="$t('pagination__page', { num: maxPage })" @click="handleClick(maxPage)">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-last" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="page == maxPage" :class="$style.disabled">
|
||||
<span>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-right" />
|
||||
</svg></span>
|
||||
</li>
|
||||
<li v-else>
|
||||
<button type="button" :aria-label="$t('pagination__next')" @click="handleClick(page + 1)">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.846 451.847" space="preserve">
|
||||
<use xlink:href="#icon-right" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -66,61 +70,31 @@ export default {
|
||||
type: Number,
|
||||
default: 7,
|
||||
},
|
||||
maxPage: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageArr: [],
|
||||
}
|
||||
},
|
||||
emits: ['btn-click'],
|
||||
computed: {
|
||||
...mapGetters(['userInfo']),
|
||||
allPage() {
|
||||
return this.maxPage == null ? Math.ceil(this.count / this.limit) || 1 : this.maxPage
|
||||
maxPage() {
|
||||
return Math.ceil(this.count / this.limit) || 1
|
||||
},
|
||||
pageEvg() {
|
||||
return Math.floor(this.btnLength / 2)
|
||||
},
|
||||
pages() {
|
||||
if (this.allPage <= this.btnLength) return this.pageArr
|
||||
if (this.maxPage <= this.btnLength) return Array.from({ length: this.maxPage }, (_, i) => i + 1)
|
||||
let start =
|
||||
this.page - this.pageEvg > 1
|
||||
? this.allPage - this.page < this.pageEvg + 1
|
||||
? this.allPage - (this.btnLength - 1)
|
||||
? this.maxPage - this.page < this.pageEvg + 1
|
||||
? this.maxPage - (this.btnLength - 1)
|
||||
: this.page - this.pageEvg
|
||||
: 1
|
||||
let end =
|
||||
this.page + this.pageEvg <= this.btnLength
|
||||
? this.btnLength
|
||||
: this.page + this.pageEvg <= this.allPage
|
||||
? this.page + this.pageEvg
|
||||
: this.allPage
|
||||
// console.log(start-1);
|
||||
// console.log(end);
|
||||
// console.log(this.pageArr.slice(start-1, end-1));
|
||||
return this.pageArr.slice(start - 1, end)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
allPage() {
|
||||
this.initPageArr()
|
||||
return Array.from({ length: this.btnLength }, (_, i) => start + i)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
initPageArr() {
|
||||
this.pageArr = []
|
||||
for (let i = 1; i <= this.allPage; i++) this.pageArr.push(i)
|
||||
},
|
||||
handleClick(page) {
|
||||
this.$emit('btn-click', page)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initPageArr()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -130,7 +104,7 @@ export default {
|
||||
|
||||
.pagination {
|
||||
display: inline-block;
|
||||
background-color: @color-pagination-background;
|
||||
background-color: var(--color-button-background);
|
||||
// border-top-left-radius: 8px;
|
||||
border-radius: @radius-border;
|
||||
ul {
|
||||
@@ -140,7 +114,7 @@ export default {
|
||||
// border-radius: .3125rem;
|
||||
li {
|
||||
// margin-right: @padding;
|
||||
color: @color-theme;
|
||||
// color: var(--color-button-font);
|
||||
// border: .0625rem solid @theme_line;
|
||||
// border-radius: .3125rem;
|
||||
transition: 0.4s ease;
|
||||
@@ -156,12 +130,12 @@ export default {
|
||||
display: block;
|
||||
padding: 7px 12px;
|
||||
line-height: 1;
|
||||
color: @color-theme;
|
||||
color: var(--color-button-font);
|
||||
font-size: 13px;
|
||||
}
|
||||
&.active {
|
||||
span {
|
||||
background-color: @color-pagination-select;
|
||||
background-color: var(--color-button-background-selected);
|
||||
}
|
||||
}
|
||||
button {
|
||||
@@ -171,10 +145,10 @@ export default {
|
||||
outline: none;
|
||||
transition: background-color .3s ease;
|
||||
&:hover {
|
||||
background-color: @color-pagination-hover;
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-pagination-active;
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
@@ -207,35 +181,4 @@ export default {
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
|
||||
.pagination {
|
||||
background-color: ~'@{color-@{value}-pagination-background}';
|
||||
ul {
|
||||
li {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
span,
|
||||
button {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
&.active {
|
||||
span {
|
||||
background-color: ~'@{color-@{value}-pagination-select}';
|
||||
}
|
||||
}
|
||||
button {
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-pagination-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-pagination-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,50 +1,57 @@
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<div :class="[$style.search, {[$style.active]: focus}, {[$style.big]: big}, {[$style.small]: small}]">
|
||||
<div :class="$style.form">
|
||||
<input :placeholder="placeholder"
|
||||
v-model.trim="text"
|
||||
ref="dom_input"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@input="$emit('update:modelValue', text)"
|
||||
@change="sendEvent('change')"
|
||||
@keyup.enter="handleSearch"
|
||||
@keyup.arrow-down.prevent="handleKeyDown"
|
||||
@keyup.arrow-up.prevent="handleKeyUp"
|
||||
@contextmenu="handleContextMenu" />
|
||||
<transition enter-active-class="animated zoomIn" leave-active-class="animated zoomOut">
|
||||
<button type="button" @click="handleClearList" v-show="text">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-window-close"></use>
|
||||
</svg>
|
||||
<div :class="$style.container">
|
||||
<div :class="[$style.search, {[$style.active]: focus}, {[$style.big]: big}, {[$style.small]: small}]">
|
||||
<div :class="$style.form">
|
||||
<input
|
||||
ref="dom_input"
|
||||
v-model.trim="text"
|
||||
:placeholder="placeholder"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@input="$emit('update:modelValue', text)"
|
||||
@change="sendEvent('change')"
|
||||
@keyup.enter="handleSearch"
|
||||
@keyup.arrow-down.prevent="handleKeyDown"
|
||||
@keyup.arrow-up.prevent="handleKeyUp"
|
||||
@contextmenu="handleContextMenu"
|
||||
>
|
||||
<transition enter-active-class="animated zoomIn" leave-active-class="animated zoomOut">
|
||||
<button v-show="text" type="button" @click="handleClearList">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-window-close" />
|
||||
</svg>
|
||||
</button>
|
||||
</transition>
|
||||
<button type="button" @click="handleSearch">
|
||||
<slot>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 30.239 30.239" space="preserve">
|
||||
<use xlink:href="#icon-search" />
|
||||
</svg>
|
||||
</slot>
|
||||
</button>
|
||||
</transition>
|
||||
<button type="button" @click="handleSearch">
|
||||
<slot>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 30.239 30.239" space="preserve">
|
||||
<use xlink:href="#icon-search"></use>
|
||||
</svg>
|
||||
</slot>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="list" :class="$style.list" :style="listStyle">
|
||||
<ul ref="dom_list">
|
||||
<li v-for="(item, index) in list"
|
||||
:key="item"
|
||||
:class="{[$style.select]: selectIndex === index }"
|
||||
@mouseenter="selectIndex = index"
|
||||
@click="handleTemplistClick(index)"
|
||||
><span>{{item}}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="list" :class="$style.list" :style="listStyle">
|
||||
<ul ref="dom_list">
|
||||
<li
|
||||
v-for="(item, index) in list"
|
||||
:key="item"
|
||||
:class="{[$style.select]: selectIndex === index }"
|
||||
@mouseenter="selectIndex = index"
|
||||
@click="handleTemplistClick(index)"
|
||||
>
|
||||
<span>{{ item }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { clipboardReadText } from '@renderer/utils'
|
||||
import { common as eventCommonNames } from '@common/hotKey'
|
||||
import { clipboardReadText } from '@common/utils/electron'
|
||||
import { HOTKEY_COMMON } from '@common/hotKey'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
placeholder: {
|
||||
@@ -53,6 +60,9 @@ export default {
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
visibleList: {
|
||||
type: Boolean,
|
||||
@@ -99,7 +109,7 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.$store.getters.setting.search.isFocusSearchBox) this.handleFocusInput()
|
||||
if (appSetting['search.isFocusSearchBox']) this.handleFocusInput()
|
||||
this.handleRegisterEvent('on')
|
||||
},
|
||||
beforeUnmount() {
|
||||
@@ -107,9 +117,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
handleRegisterEvent(action) {
|
||||
let eventHub = window.eventHub
|
||||
let eventHub = window.key_event
|
||||
let name = action == 'on' ? 'on' : 'off'
|
||||
eventHub[name](eventCommonNames.focusSearchInput.action, this.handleFocusInput)
|
||||
eventHub[name](HOTKEY_COMMON.focusSearchInput.action, this.handleFocusInput)
|
||||
},
|
||||
handleFocusInput() {
|
||||
this.$refs.dom_input.focus()
|
||||
@@ -162,7 +172,7 @@ export default {
|
||||
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)}`
|
||||
this.text = this.text.substring(0, dom_input.selectionStart) + str + this.text.substring(dom_input.selectionEnd, this.text.length)
|
||||
this.$emit('update:modelValue', this.text)
|
||||
},
|
||||
handleClearList() {
|
||||
@@ -189,10 +199,10 @@ export default {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
border-radius: @form-radius;
|
||||
transition: box-shadow .4s ease, background-color @transition-theme;
|
||||
transition: box-shadow .4s ease, background-color @transition-normal;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
background-color: @color-search-form-background;
|
||||
background-color: var(--color-primary-light-600-alpha-100);
|
||||
|
||||
&.active {
|
||||
box-shadow: 0 1px 5px 0 rgba(0,0,0,.2);
|
||||
@@ -216,8 +226,8 @@ export default {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
background-color: transparent;
|
||||
// border-bottom: 2px solid @color-theme;
|
||||
// border-color: @color-theme;
|
||||
// border-bottom: 2px solid var(--color-primary);
|
||||
// border-color: var(--color-primary);
|
||||
border: none;
|
||||
|
||||
outline: none;
|
||||
@@ -227,7 +237,7 @@ export default {
|
||||
font-size: 13.5px;
|
||||
line-height: @height-toolbar * 0.52 + 5px;
|
||||
&::placeholder {
|
||||
color: @color-btn;
|
||||
color: var(--color-button-font);
|
||||
}
|
||||
}
|
||||
button {
|
||||
@@ -239,7 +249,7 @@ export default {
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
padding: 6px 7px;
|
||||
color: @color-btn;
|
||||
color: var(--color-button-font);
|
||||
transition: background-color .2s ease;
|
||||
|
||||
&:last-child {
|
||||
@@ -248,10 +258,10 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-theme-hover;
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme-active;
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,7 +282,7 @@ export default {
|
||||
}
|
||||
|
||||
&.select {
|
||||
background-color: @color-search-list-hover;
|
||||
background-color: var(--color-primary-dark-100-alpha-700);
|
||||
}
|
||||
&:last-child {
|
||||
border-bottom-left-radius: 3px;
|
||||
@@ -295,41 +305,5 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
|
||||
.search {
|
||||
background-color: ~'@{color-@{value}-search-form-background}';
|
||||
&.active {
|
||||
box-shadow: 0 1px 5px 0 rgba(0,0,0,.2);
|
||||
}
|
||||
|
||||
.form {
|
||||
input {
|
||||
&::placeholder {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
}
|
||||
}
|
||||
button {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
.list {
|
||||
li {
|
||||
&.select {
|
||||
background-color: ~'@{color-@{value}-search-list-hover}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div :class="$style.container" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.container {
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
import { apiSource, qualityList, userApi } from '@renderer/store'
|
||||
import { appSetting, setApiSource } from '@renderer/store/setting'
|
||||
import { setUserApi as setUserApiAction } from '@renderer/utils/ipc'
|
||||
import musicSdk from '@renderer/utils/musicSdk'
|
||||
import apiSourceInfo from '@renderer/utils/musicSdk/api-source-info'
|
||||
|
||||
|
||||
export const setUserApi = (apiId: string) => {
|
||||
if (/^user_api/.test(apiId)) {
|
||||
qualityList.value = {}
|
||||
userApi.status = false
|
||||
userApi.message = 'initing'
|
||||
|
||||
setUserApiAction(apiId).then(() => {
|
||||
apiSource.value = apiId
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
let api = apiSourceInfo.find(api => !api.disabled)
|
||||
if (!api) return
|
||||
apiSource.value = api.id
|
||||
if (api.id != appSetting['common.apiSource']) setApiSource(api.id)
|
||||
})
|
||||
} else {
|
||||
// @ts-expect-error
|
||||
qualityList.value = musicSdk.supportQuality[apiId] as LX.QualityList
|
||||
apiSource.value = apiId
|
||||
void setUserApiAction(apiId)
|
||||
}
|
||||
|
||||
if (apiId != appSetting['common.apiSource']) setApiSource(apiId)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// import defaultSetting from '@common/defaultSetting'
|
||||
import createWorkers from '@renderer/worker'
|
||||
|
||||
window.lx = {
|
||||
// appSetting: defaultSetting,
|
||||
isEditingHotKey: false,
|
||||
isPlayedStop: false,
|
||||
appHotKeyConfig: {
|
||||
local: {
|
||||
enable: false,
|
||||
keys: {},
|
||||
},
|
||||
global: {
|
||||
enable: false,
|
||||
keys: {},
|
||||
},
|
||||
},
|
||||
songListInfo: {
|
||||
fromName: '',
|
||||
searchKey: '',
|
||||
searchPosition: 0,
|
||||
songlistKey: '',
|
||||
songlistPosition: 0,
|
||||
},
|
||||
restorePlayInfo: null,
|
||||
worker: createWorkers(),
|
||||
isDev: process.env.NODE_ENV == 'production',
|
||||
}
|
||||
|
||||
window.lxData = {}
|
||||
|
||||
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
|
||||
@@ -0,0 +1,214 @@
|
||||
import Lyric from '@common/utils/lyric-font-player'
|
||||
import { getAnalyser, getCurrentTime as getPlayerCurrentTime } from '@renderer/plugins/player'
|
||||
import { lyric, setLines, setOffset, setTempOffset, setText } from '@renderer/store/player/lyric'
|
||||
import { isPlay, musicInfo } from '@renderer/store/player/state'
|
||||
import { setStatusText } from '@renderer/store/player/action'
|
||||
import { markRawList } from '@common/utils/vueTools'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { onNewDesktopLyricProcess } from '@renderer/utils/ipc'
|
||||
|
||||
const getCurrentTime = () => {
|
||||
return getPlayerCurrentTime() * 1000
|
||||
}
|
||||
|
||||
let lrc: Lyric
|
||||
let desktopLyricPort: Electron.IpcRendererEvent['ports'][0] | null = null
|
||||
const analyserTools: {
|
||||
dataArray: Uint8Array
|
||||
bufferLength: number
|
||||
analyser: AnalyserNode | null
|
||||
sendDataArray: () => void
|
||||
} = {
|
||||
dataArray: new Uint8Array(),
|
||||
bufferLength: 0,
|
||||
analyser: null,
|
||||
sendDataArray() {
|
||||
if (this.analyser == null) {
|
||||
this.analyser = getAnalyser()
|
||||
// console.log(this.analyser)
|
||||
if (!this.analyser) return
|
||||
this.bufferLength = this.analyser.frequencyBinCount
|
||||
}
|
||||
const dataArray = new Uint8Array(this.bufferLength)
|
||||
this.analyser.getByteFrequencyData(dataArray)
|
||||
sendDesktopLyricInfo({
|
||||
action: 'send_analyser_data_array',
|
||||
data: dataArray,
|
||||
}, [dataArray.buffer])
|
||||
},
|
||||
}
|
||||
|
||||
export const sendDesktopLyricInfo = (info: LX.DesktopLyric.LyricActions, transferList?: Transferable[]) => {
|
||||
if (desktopLyricPort == null) return
|
||||
if (transferList) desktopLyricPort.postMessage(info, transferList)
|
||||
else desktopLyricPort.postMessage(info)
|
||||
}
|
||||
const handleDesktopLyricMessage = (action: LX.DesktopLyric.WinMainActions) => {
|
||||
switch (action) {
|
||||
case 'get_info':
|
||||
sendDesktopLyricInfo({
|
||||
action: 'set_info',
|
||||
data: {
|
||||
id: musicInfo.id,
|
||||
singer: musicInfo.singer,
|
||||
name: musicInfo.name,
|
||||
album: musicInfo.album,
|
||||
lrc: musicInfo.lrc,
|
||||
tlrc: musicInfo.tlrc,
|
||||
rlrc: musicInfo.rlrc,
|
||||
lxlrc: musicInfo.lxlrc,
|
||||
// pic: musicInfo.pic,
|
||||
isPlay: isPlay.value,
|
||||
line: lyric.line,
|
||||
played_time: getCurrentTime(),
|
||||
},
|
||||
})
|
||||
break
|
||||
case 'get_status':
|
||||
sendDesktopLyricInfo({
|
||||
action: 'set_status',
|
||||
data: {
|
||||
isPlay: isPlay.value,
|
||||
line: lyric.line,
|
||||
played_time: getCurrentTime(),
|
||||
},
|
||||
})
|
||||
break
|
||||
case 'get_analyser_data_array':
|
||||
analyserTools.sendDataArray()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
export const init = () => {
|
||||
lrc = new Lyric({
|
||||
shadowContent: false,
|
||||
onPlay(line, text) {
|
||||
setText(text, line)
|
||||
setStatusText(text)
|
||||
// console.log(line, text)
|
||||
},
|
||||
onSetLyric(lines, offset) { // listening lyrics seting event
|
||||
// console.log(lines) // lines is array of all lyric text
|
||||
setLines(markRawList([...lines]))
|
||||
setText(lines[0] ?? '', 0)
|
||||
setOffset(offset) // 歌词延迟
|
||||
setTempOffset(0) // 重置临时延迟
|
||||
},
|
||||
onUpdateLyric(lines) {
|
||||
setLines(markRawList([...lines]))
|
||||
setText(lines[0] ?? '', 0)
|
||||
},
|
||||
// offset: 80,
|
||||
})
|
||||
|
||||
onNewDesktopLyricProcess(({ event }) => {
|
||||
console.log('onNewDesktopLyricProcess')
|
||||
const [port] = event.ports
|
||||
desktopLyricPort = port
|
||||
|
||||
port.onmessage = ({ data }) => {
|
||||
handleDesktopLyricMessage(data.action)
|
||||
// The event data can be any serializable object (and the event could even
|
||||
// carry other MessagePorts with it!)
|
||||
// const result = doWork(event.data)
|
||||
// port.postMessage(result)
|
||||
}
|
||||
|
||||
port.onmessageerror = (event) => {
|
||||
console.log('onmessageerror', event)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const setLyricOffset = (offset: number) => {
|
||||
const tempOffset = offset - lyric.offset
|
||||
setTempOffset(tempOffset)
|
||||
lrc.setOffset(tempOffset)
|
||||
sendDesktopLyricInfo({
|
||||
action: 'set_offset',
|
||||
data: tempOffset,
|
||||
})
|
||||
|
||||
if (isPlay.value) {
|
||||
setTimeout(() => {
|
||||
const time = getCurrentTime()
|
||||
sendDesktopLyricInfo({
|
||||
action: 'set_play',
|
||||
data: time,
|
||||
})
|
||||
lrc.play(time)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const setLyric = () => {
|
||||
if (!musicInfo.id) return
|
||||
if (musicInfo.lrc) {
|
||||
const extendedLyrics = []
|
||||
if (appSetting['player.isShowLyricTranslation'] && musicInfo.tlrc) extendedLyrics.push(musicInfo.tlrc)
|
||||
if (appSetting['player.isShowLyricRoma'] && musicInfo.rlrc) extendedLyrics.push(musicInfo.rlrc)
|
||||
lrc.setLyric(
|
||||
appSetting['player.isPlayLxlrc'] && musicInfo.lxlrc ? musicInfo.lxlrc : musicInfo.lrc,
|
||||
extendedLyrics,
|
||||
)
|
||||
sendDesktopLyricInfo({
|
||||
action: 'set_lyric',
|
||||
data: {
|
||||
lrc: musicInfo.lrc,
|
||||
tlrc: musicInfo.tlrc,
|
||||
rlrc: musicInfo.rlrc,
|
||||
lxlrc: musicInfo.lxlrc,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (isPlay.value) {
|
||||
setTimeout(() => {
|
||||
const time = getCurrentTime()
|
||||
sendDesktopLyricInfo({ action: 'set_play', data: time })
|
||||
lrc.play(time)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const play = () => {
|
||||
// if (!musicInfo.lrc) return
|
||||
const currentTime = getCurrentTime()
|
||||
lrc.play(currentTime)
|
||||
sendDesktopLyricInfo({ action: 'set_play', data: currentTime })
|
||||
}
|
||||
|
||||
export const pause = () => {
|
||||
lrc.pause()
|
||||
sendDesktopLyricInfo({ action: 'set_pause' })
|
||||
}
|
||||
|
||||
export const stop = () => {
|
||||
lrc.setLyric('')
|
||||
sendDesktopLyricInfo({ action: 'set_stop' })
|
||||
// setLines([])
|
||||
setText('', 0)
|
||||
}
|
||||
|
||||
export const sendInfo = () => {
|
||||
sendDesktopLyricInfo({
|
||||
action: 'set_info',
|
||||
data: {
|
||||
id: musicInfo.id,
|
||||
singer: musicInfo.singer,
|
||||
name: musicInfo.name,
|
||||
album: musicInfo.album,
|
||||
lrc: musicInfo.lrc,
|
||||
tlrc: musicInfo.tlrc,
|
||||
rlrc: musicInfo.rlrc,
|
||||
lxlrc: musicInfo.lxlrc,
|
||||
// pic: musicInfo.pic,
|
||||
isPlay: isPlay.value,
|
||||
line: lyric.line,
|
||||
played_time: getCurrentTime(),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { getDownloadFilePath } from '@renderer/utils/music'
|
||||
|
||||
import {
|
||||
getMusicUrl as getOnlineMusicUrl,
|
||||
getPicUrl as getOnlinePicUrl,
|
||||
getLyricInfo as getOnlineLyricInfo,
|
||||
} from './online'
|
||||
import { buildLyricInfo, getCachedLyricInfo } from './utils'
|
||||
|
||||
export const getMusicUrl = async({ musicInfo, isRefresh, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Download.ListItem
|
||||
isRefresh: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<string> => {
|
||||
if (!isRefresh) {
|
||||
const path = await getDownloadFilePath(musicInfo, appSetting['download.savePath'])
|
||||
if (path) return path
|
||||
}
|
||||
|
||||
return await getOnlineMusicUrl({ musicInfo: musicInfo.metadata.musicInfo, isRefresh, onToggleSource })
|
||||
}
|
||||
|
||||
export const getPicUrl = async({ musicInfo, isRefresh, listId, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Download.ListItem
|
||||
isRefresh: boolean
|
||||
listId?: string | null
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<string> => {
|
||||
if (!isRefresh) {
|
||||
const onlineMusicInfo = musicInfo.metadata.musicInfo
|
||||
if (onlineMusicInfo.meta.picUrl && !isRefresh) return onlineMusicInfo.meta.picUrl
|
||||
|
||||
const path = await getDownloadFilePath(musicInfo, appSetting['download.savePath'])
|
||||
if (path) {
|
||||
const pic = await window.lx.worker.main.getMusicFilePic(path)
|
||||
if (pic) return pic
|
||||
}
|
||||
}
|
||||
|
||||
return await getOnlinePicUrl({ musicInfo: musicInfo.metadata.musicInfo, isRefresh, onToggleSource }).then((url) => {
|
||||
// TODO: when listId required save url (update downloadInfo)
|
||||
|
||||
return url
|
||||
})
|
||||
}
|
||||
|
||||
export const getLyricInfo = async({ musicInfo, isRefresh, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Download.ListItem
|
||||
isRefresh: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<LX.Player.LyricInfo> => {
|
||||
if (!isRefresh) {
|
||||
const lyricInfo = await getCachedLyricInfo(musicInfo.metadata.musicInfo)
|
||||
if (lyricInfo) return await buildLyricInfo(lyricInfo)
|
||||
}
|
||||
|
||||
return getOnlineLyricInfo({
|
||||
musicInfo: musicInfo.metadata.musicInfo,
|
||||
isRefresh,
|
||||
onToggleSource,
|
||||
}).catch(async() => {
|
||||
// 尝试读取文件内歌词
|
||||
const path = await getDownloadFilePath(musicInfo, appSetting['download.savePath'])
|
||||
if (path) {
|
||||
const rawlrcInfo = await window.lx.worker.main.getMusicFileLyric(path)
|
||||
if (rawlrcInfo) return buildLyricInfo(rawlrcInfo)
|
||||
}
|
||||
|
||||
throw new Error('failed')
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// if (targetSong.key) { // 如果是已下载的歌曲
|
||||
// const filePath = path.join(appSetting['download.savePath'], targetSong.metadata.fileName)
|
||||
// // console.log(filePath)
|
||||
|
||||
import {
|
||||
getMusicUrl as getOnlineMusicUrl,
|
||||
getPicUrl as getOnlinePicUrl,
|
||||
getLyricInfo as getOnlineLyricInfo,
|
||||
} from './online'
|
||||
import {
|
||||
getMusicUrl as getDownloadMusicUrl,
|
||||
getPicUrl as getDownloadPicUrl,
|
||||
getLyricInfo as getDownloadLyricInfo,
|
||||
} from './download'
|
||||
import {
|
||||
getMusicUrl as getLocalMusicUrl,
|
||||
getPicUrl as getLocalPicUrl,
|
||||
getLyricInfo as getLocalLyricInfo,
|
||||
} from './local'
|
||||
|
||||
|
||||
export const getMusicUrl = async({
|
||||
musicInfo,
|
||||
quality,
|
||||
isRefresh = false,
|
||||
onToggleSource,
|
||||
}: {
|
||||
musicInfo: LX.Music.MusicInfo | LX.Download.ListItem
|
||||
isRefresh?: boolean
|
||||
quality?: LX.Quality
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<string> => {
|
||||
if ('progress' in musicInfo) {
|
||||
return await getDownloadMusicUrl({ musicInfo, isRefresh, onToggleSource })
|
||||
} else if (musicInfo.source == 'local') {
|
||||
return await getLocalMusicUrl({ musicInfo, isRefresh, onToggleSource })
|
||||
} else {
|
||||
return await getOnlineMusicUrl({ musicInfo, isRefresh, quality, onToggleSource })
|
||||
}
|
||||
}
|
||||
|
||||
export const getPicPath = async({
|
||||
musicInfo,
|
||||
isRefresh = false,
|
||||
listId,
|
||||
onToggleSource,
|
||||
}: {
|
||||
musicInfo: LX.Music.MusicInfo | LX.Download.ListItem
|
||||
listId?: string | null
|
||||
isRefresh?: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<string> => {
|
||||
if ('progress' in musicInfo) {
|
||||
return await getDownloadPicUrl({ musicInfo, isRefresh, listId, onToggleSource })
|
||||
} else if (musicInfo.source == 'local') {
|
||||
return await getLocalPicUrl({ musicInfo, isRefresh, listId, onToggleSource })
|
||||
} else {
|
||||
return await getOnlinePicUrl({ musicInfo, isRefresh, listId, onToggleSource })
|
||||
}
|
||||
}
|
||||
|
||||
export const getLyricInfo = async({
|
||||
musicInfo,
|
||||
isRefresh = false,
|
||||
onToggleSource,
|
||||
}: {
|
||||
musicInfo: LX.Music.MusicInfo | LX.Download.ListItem
|
||||
isRefresh?: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<LX.Player.LyricInfo> => {
|
||||
if ('progress' in musicInfo) {
|
||||
return await getDownloadLyricInfo({ musicInfo, isRefresh, onToggleSource })
|
||||
} else if (musicInfo.source == 'local') {
|
||||
return await getLocalLyricInfo({ musicInfo, isRefresh, onToggleSource })
|
||||
} else {
|
||||
return await getOnlineLyricInfo({ musicInfo, isRefresh, onToggleSource })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { updateListMusics } from '@renderer/store/list/action'
|
||||
import { saveLyric, saveMusicUrl } from '@renderer/utils/ipc'
|
||||
import { getLocalFilePath } from '@renderer/utils/music'
|
||||
|
||||
import {
|
||||
buildLyricInfo,
|
||||
getCachedLyricInfo,
|
||||
getOnlineOtherSourceLyricInfo,
|
||||
getOnlineOtherSourceMusicUrl,
|
||||
getOnlineOtherSourcePicUrl,
|
||||
getOtherSource,
|
||||
} from './utils'
|
||||
|
||||
|
||||
export const getMusicUrl = async({ musicInfo, isRefresh, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Music.MusicInfoLocal
|
||||
isRefresh: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<string> => {
|
||||
if (!isRefresh) {
|
||||
const path = await getLocalFilePath(musicInfo)
|
||||
if (path) return path
|
||||
}
|
||||
onToggleSource()
|
||||
const otherSource = await getOtherSource(musicInfo)
|
||||
if (!otherSource.length) throw new Error('source not found')
|
||||
return await getOnlineOtherSourceMusicUrl({ musicInfos: [...otherSource], onToggleSource, isRefresh }).then(({ url, quality: targetQuality, musicInfo: targetMusicInfo, isFromCache }) => {
|
||||
// saveLyric(musicInfo, data.lyricInfo)
|
||||
if (!isFromCache) void saveMusicUrl(targetMusicInfo, targetQuality, url)
|
||||
|
||||
// TODO: save url ?
|
||||
return url
|
||||
})
|
||||
}
|
||||
|
||||
export const getPicUrl = async({ musicInfo, listId, isRefresh, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Music.MusicInfoLocal
|
||||
listId?: string | null
|
||||
isRefresh: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<string> => {
|
||||
if (!isRefresh) {
|
||||
if (musicInfo.meta.picUrl) return musicInfo.meta.picUrl
|
||||
|
||||
const pic = await window.lx.worker.main.getMusicFilePic(musicInfo.meta.filePath)
|
||||
if (pic) return pic
|
||||
}
|
||||
|
||||
onToggleSource()
|
||||
const otherSource = await getOtherSource(musicInfo)
|
||||
if (!otherSource.length) throw new Error('source not found')
|
||||
return await getOnlineOtherSourcePicUrl({ musicInfos: [...otherSource], onToggleSource, isRefresh }).then(({ url, musicInfo: targetMusicInfo, isFromCache }) => {
|
||||
if (listId) {
|
||||
musicInfo.meta.picUrl = url
|
||||
void updateListMusics([{ id: listId, musicInfo }])
|
||||
}
|
||||
|
||||
return url
|
||||
})
|
||||
}
|
||||
|
||||
export const getLyricInfo = async({ musicInfo, isRefresh, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Music.MusicInfoLocal
|
||||
isRefresh: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<LX.Player.LyricInfo> => {
|
||||
if (!isRefresh) {
|
||||
const lyricInfo = await getCachedLyricInfo(musicInfo)
|
||||
if (lyricInfo) {
|
||||
// 存在已编辑、原始歌词
|
||||
if (lyricInfo.rawlrcInfo.lyric) return await buildLyricInfo(lyricInfo)
|
||||
}
|
||||
|
||||
// 尝试读取文件内歌词
|
||||
const rawlrcInfo = await window.lx.worker.main.getMusicFileLyric(musicInfo.meta.filePath)
|
||||
if (rawlrcInfo) return buildLyricInfo(lyricInfo ? { ...lyricInfo, rawlrcInfo } : rawlrcInfo)
|
||||
}
|
||||
|
||||
onToggleSource()
|
||||
const otherSource = await getOtherSource(musicInfo)
|
||||
if (!otherSource.length) throw new Error('source not found')
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return await getOnlineOtherSourceLyricInfo({ musicInfos: [...otherSource], onToggleSource, isRefresh }).then(({ lyricInfo, musicInfo: targetMusicInfo, isFromCache }) => {
|
||||
void saveLyric(musicInfo, lyricInfo)
|
||||
|
||||
if (isFromCache) return buildLyricInfo(lyricInfo)
|
||||
void saveLyric(targetMusicInfo, lyricInfo)
|
||||
|
||||
return buildLyricInfo(lyricInfo)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { updateListMusics } from '@renderer/store/list/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import {
|
||||
saveLyric,
|
||||
saveMusicUrl,
|
||||
getMusicUrl as getStoreMusicUrl,
|
||||
} from '@renderer/utils/ipc'
|
||||
import {
|
||||
buildLyricInfo,
|
||||
getPlayQuality,
|
||||
handleGetOnlineLyricInfo,
|
||||
handleGetOnlineMusicUrl,
|
||||
handleGetOnlinePicUrl,
|
||||
getCachedLyricInfo,
|
||||
} from './utils'
|
||||
|
||||
/* export const setMusicUrl = ({ musicInfo, type, url }: {
|
||||
musicInfo: LX.Music.MusicInfo
|
||||
type: LX.Quality
|
||||
url: string
|
||||
}) => {
|
||||
saveMusicUrl(musicInfo, type, url)
|
||||
}
|
||||
|
||||
export const setPic = (datas: {
|
||||
listId: string
|
||||
musicInfo: LX.Music.MusicInfo
|
||||
url: string
|
||||
}) => {
|
||||
datas.musicInfo.img = datas.url
|
||||
updateMusicInfo({
|
||||
listId: datas.listId,
|
||||
id: datas.musicInfo.songmid,
|
||||
data: { img: datas.url },
|
||||
musicInfo: datas.musicInfo,
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
export const getMusicUrl = async({ musicInfo, quality, isRefresh, allowToggleSource = true, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
quality?: LX.Quality
|
||||
isRefresh: boolean
|
||||
allowToggleSource?: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<string> => {
|
||||
// if (!musicInfo._types[type]) {
|
||||
// // 兼容旧版酷我源搜索列表过滤128k音质的bug
|
||||
// if (!(musicInfo.source == 'kw' && type == '128k')) throw new Error('该歌曲没有可播放的音频')
|
||||
|
||||
// // return Promise.reject(new Error('该歌曲没有可播放的音频'))
|
||||
// }
|
||||
const targetQuality = quality ?? getPlayQuality(appSetting['player.highQuality'], musicInfo)
|
||||
const cachedUrl = await getStoreMusicUrl(musicInfo, targetQuality)
|
||||
if (cachedUrl && !isRefresh) return cachedUrl
|
||||
|
||||
return await handleGetOnlineMusicUrl({ musicInfo, quality, onToggleSource, isRefresh, allowToggleSource }).then(({ url, quality: targetQuality, musicInfo: targetMusicInfo, isFromCache }) => {
|
||||
if (targetMusicInfo.id != musicInfo.id && !isFromCache) void saveMusicUrl(targetMusicInfo, targetQuality, url)
|
||||
void saveMusicUrl(musicInfo, targetQuality, url)
|
||||
return url
|
||||
})
|
||||
}
|
||||
|
||||
export const getPicUrl = async({ musicInfo, listId, isRefresh, allowToggleSource = true, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
listId?: string | null
|
||||
isRefresh: boolean
|
||||
allowToggleSource?: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<string> => {
|
||||
if (musicInfo.meta.picUrl && !isRefresh) return musicInfo.meta.picUrl
|
||||
return await handleGetOnlinePicUrl({ musicInfo, onToggleSource, isRefresh, allowToggleSource }).then(({ url, musicInfo: targetMusicInfo, isFromCache }) => {
|
||||
// picRequest = null
|
||||
if (listId) {
|
||||
musicInfo.meta.picUrl = url
|
||||
void updateListMusics([{ id: listId, musicInfo }])
|
||||
}
|
||||
// savePic({ musicInfo, url, listId })
|
||||
return url
|
||||
})
|
||||
}
|
||||
export const getLyricInfo = async({ musicInfo, isRefresh, allowToggleSource = true, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
isRefresh: boolean
|
||||
allowToggleSource?: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<LX.Player.LyricInfo> => {
|
||||
if (!isRefresh) {
|
||||
const lyricInfo = await getCachedLyricInfo(musicInfo)
|
||||
if (lyricInfo) return await buildLyricInfo(lyricInfo)
|
||||
}
|
||||
|
||||
// lrcRequest = music[musicInfo.source].getLyric(musicInfo)
|
||||
return await handleGetOnlineLyricInfo({ musicInfo, onToggleSource, isRefresh, allowToggleSource }).then(async({ lyricInfo, musicInfo: targetMusicInfo, isFromCache }) => {
|
||||
// lrcRequest = null
|
||||
if (isFromCache) return await buildLyricInfo(lyricInfo)
|
||||
if (targetMusicInfo.id == musicInfo.id) void saveLyric(musicInfo, lyricInfo)
|
||||
else void saveLyric(targetMusicInfo, lyricInfo)
|
||||
|
||||
return await buildLyricInfo(lyricInfo)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
import { qualityList } from '@renderer/store'
|
||||
import { assertApiSupport } from '@renderer/store/utils'
|
||||
import musicSdk from '@renderer/utils/musicSdk'
|
||||
import {
|
||||
getOtherSource as getOtherSourceFromStore,
|
||||
saveOtherSource as saveOtherSourceFromStore,
|
||||
getMusicUrl as getStoreMusicUrl,
|
||||
getPlayerLyric as getStoreLyric,
|
||||
} from '@renderer/utils/ipc'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { langS2T, toNewMusicInfo, toOldMusicInfo } from '@renderer/utils'
|
||||
|
||||
|
||||
const getOtherSourcePromises = new Map()
|
||||
export const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
||||
|
||||
export const getOtherSource = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, isRefresh = false): Promise<LX.Music.MusicInfoOnline[]> => {
|
||||
if (!isRefresh) {
|
||||
const cachedInfo = await getOtherSourceFromStore(musicInfo.id)
|
||||
if (cachedInfo.length) return cachedInfo
|
||||
}
|
||||
let key: string
|
||||
let searchMusicInfo: {
|
||||
name: string
|
||||
singer: string
|
||||
source: string
|
||||
albumName: string
|
||||
interval: string
|
||||
}
|
||||
if ('progress' in musicInfo) {
|
||||
key = `local_${musicInfo.id}`
|
||||
searchMusicInfo = {
|
||||
name: musicInfo.metadata.musicInfo.name,
|
||||
singer: musicInfo.metadata.musicInfo.singer,
|
||||
source: musicInfo.metadata.musicInfo.source,
|
||||
albumName: musicInfo.metadata.musicInfo.meta.albumName,
|
||||
interval: musicInfo.metadata.musicInfo.interval ?? '',
|
||||
}
|
||||
} else {
|
||||
key = `${musicInfo.source}_${musicInfo.id}`
|
||||
searchMusicInfo = {
|
||||
name: musicInfo.name,
|
||||
singer: musicInfo.singer,
|
||||
source: musicInfo.source,
|
||||
albumName: musicInfo.meta.albumName,
|
||||
interval: musicInfo.interval ?? '',
|
||||
}
|
||||
}
|
||||
if (getOtherSourcePromises.has(key)) return getOtherSourcePromises.get(key)
|
||||
|
||||
const promise = musicSdk.findMusic(searchMusicInfo).then((otherSource) => {
|
||||
const sources: LX.Music.MusicInfoOnline[] = otherSource.map(toNewMusicInfo)
|
||||
if (sources.length) void saveOtherSourceFromStore(musicInfo.id, sources)
|
||||
return sources
|
||||
}).finally(() => {
|
||||
if (getOtherSourcePromises.has(key)) getOtherSourcePromises.delete(key)
|
||||
})
|
||||
getOtherSourcePromises.set(key, promise)
|
||||
return promise
|
||||
}
|
||||
|
||||
|
||||
export const buildLyricInfo = async(lyricInfo: MakeOptional<LX.Player.LyricInfo, 'rawlrcInfo'>): Promise<LX.Player.LyricInfo> => {
|
||||
if (!appSetting['player.isS2t']) {
|
||||
// @ts-expect-error
|
||||
if (lyricInfo.rawlrcInfo) return lyricInfo
|
||||
return { ...lyricInfo, rawlrcInfo: { ...lyricInfo } }
|
||||
}
|
||||
|
||||
if (appSetting['player.isS2t']) {
|
||||
const tasks = [
|
||||
lyricInfo.lyric ? window.lx.worker.main.langS2t(lyricInfo.lyric) : Promise.resolve(''),
|
||||
lyricInfo.tlyric ? langS2T(lyricInfo.tlyric) : Promise.resolve(''),
|
||||
lyricInfo.rlyric ? langS2T(lyricInfo.rlyric) : Promise.resolve(''),
|
||||
lyricInfo.lxlyric ? langS2T(lyricInfo.lxlyric) : Promise.resolve(''),
|
||||
]
|
||||
if (lyricInfo.rawlrcInfo) {
|
||||
tasks.push(lyricInfo.lyric ? window.lx.worker.main.langS2t(lyricInfo.lyric) : Promise.resolve(''))
|
||||
tasks.push(lyricInfo.tlyric ? window.lx.worker.main.langS2t(lyricInfo.tlyric) : Promise.resolve(''))
|
||||
tasks.push(lyricInfo.rlyric ? window.lx.worker.main.langS2t(lyricInfo.rlyric) : Promise.resolve(''))
|
||||
tasks.push(lyricInfo.lxlyric ? window.lx.worker.main.langS2t(lyricInfo.lxlyric) : Promise.resolve(''))
|
||||
}
|
||||
return await Promise.all(tasks).then(([lyric, tlyric, rlyric, lxlyric, lyric_raw, tlyric_raw, rlyric_raw, lxlyric_raw]) => {
|
||||
const rawlrcInfo = lyric_raw ? {
|
||||
lyric: lyric_raw,
|
||||
tlyric: tlyric_raw,
|
||||
rlyric: rlyric_raw,
|
||||
lxlyric: lxlyric_raw,
|
||||
} : {
|
||||
lyric,
|
||||
tlyric,
|
||||
rlyric,
|
||||
lxlyric,
|
||||
}
|
||||
return {
|
||||
lyric,
|
||||
tlyric,
|
||||
rlyric,
|
||||
lxlyric,
|
||||
rawlrcInfo,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
return lyricInfo.rawlrcInfo ? lyricInfo : { ...lyricInfo, rawlrcInfo: { ...lyricInfo } }
|
||||
}
|
||||
|
||||
export const getCachedLyricInfo = async(musicInfo: LX.Music.MusicInfo): Promise<LX.Player.LyricInfo | null> => {
|
||||
let lrcInfo = await getStoreLyric(musicInfo)
|
||||
// lrcInfo = {}
|
||||
if (existTimeExp.test(lrcInfo.lyric) && lrcInfo.tlyric != null) {
|
||||
// if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) {
|
||||
// let str = musicInfo.lrc.replace('\ufeff[id:$00000000]\n', '')
|
||||
// commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
|
||||
// } else if (musicInfo.lrc.startsWith('[id:$00000000]')) {
|
||||
// let str = musicInfo.lrc.replace('[id:$00000000]\n', '')
|
||||
// commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
|
||||
// }
|
||||
|
||||
if (lrcInfo.lxlyric == null) {
|
||||
switch (musicInfo.source) {
|
||||
case 'kg':
|
||||
case 'kw':
|
||||
case 'mg':
|
||||
break
|
||||
default:
|
||||
return lrcInfo
|
||||
}
|
||||
} else if (lrcInfo.rlyric == null) {
|
||||
if (!['wy', 'kg'].includes(musicInfo.source)) return lrcInfo
|
||||
} else return lrcInfo
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export const getPlayQuality = (highQuality: boolean, musicInfo: LX.Music.MusicInfoOnline): LX.Quality => {
|
||||
let type: LX.Quality = '128k'
|
||||
let list = qualityList.value[musicInfo.source]
|
||||
if (highQuality && musicInfo.meta._qualitys['320k'] && list && list.includes('320k')) type = '320k'
|
||||
return type
|
||||
}
|
||||
|
||||
export const getOnlineOtherSourceMusicUrl = async({ musicInfos, quality, onToggleSource, isRefresh, retryedSource = [] }: {
|
||||
musicInfos: LX.Music.MusicInfoOnline[]
|
||||
quality?: LX.Quality
|
||||
onToggleSource: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
isRefresh: boolean
|
||||
retryedSource?: LX.OnlineSource[]
|
||||
}): Promise<{
|
||||
url: string
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
quality: LX.Quality
|
||||
isFromCache: boolean
|
||||
}> => {
|
||||
let musicInfo: LX.Music.MusicInfoOnline | null = null
|
||||
let itemQuality: LX.Quality | null = null
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (musicInfo = (musicInfos.shift() as LX.Music.MusicInfoOnline)) {
|
||||
if (retryedSource.includes(musicInfo.source)) continue
|
||||
retryedSource.push(musicInfo.source)
|
||||
if (!assertApiSupport(musicInfo.source)) continue
|
||||
itemQuality = quality ?? getPlayQuality(appSetting['player.highQuality'], musicInfo)
|
||||
if (!musicInfo.meta._qualitys[itemQuality]) continue
|
||||
|
||||
console.log('try toggle to: ', musicInfo.source, musicInfo.name, musicInfo.singer, musicInfo.interval)
|
||||
onToggleSource(musicInfo)
|
||||
break
|
||||
}
|
||||
if (!musicInfo || !itemQuality) throw new Error(window.i18n.t('toggle_source_failed'))
|
||||
|
||||
const cachedUrl = await getStoreMusicUrl(musicInfo, itemQuality)
|
||||
if (cachedUrl && !isRefresh) return { url: cachedUrl, musicInfo, quality: itemQuality, isFromCache: true }
|
||||
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = musicSdk[musicInfo.source].getMusicUrl(toOldMusicInfo(musicInfo), itemQuality).promise
|
||||
} catch (err: any) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
retryedSource.includes(musicInfo.source)
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return reqPromise.then(({ url, type }: { url: string, type: LX.Quality }) => {
|
||||
return { musicInfo, url, quality: type, isFromCache: false }
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
}).catch((err: any) => {
|
||||
console.log(err)
|
||||
return getOnlineOtherSourceMusicUrl({ musicInfos, quality, onToggleSource, isRefresh, retryedSource })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线音乐URL
|
||||
*/
|
||||
export const handleGetOnlineMusicUrl = async({ musicInfo, quality, onToggleSource, isRefresh, allowToggleSource }: {
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
quality?: LX.Quality
|
||||
isRefresh: boolean
|
||||
allowToggleSource: boolean
|
||||
onToggleSource: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<{
|
||||
url: string
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
quality: LX.Quality
|
||||
isFromCache: boolean
|
||||
}> => {
|
||||
// console.log(musicInfo.source)
|
||||
const targetQuality = quality ?? getPlayQuality(appSetting['player.highQuality'], musicInfo)
|
||||
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = musicSdk[musicInfo.source].getMusicUrl(toOldMusicInfo(musicInfo), targetQuality).promise
|
||||
} catch (err: any) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.then(({ url, type }: { url: string, type: LX.Quality }) => {
|
||||
return { musicInfo, url, quality: type, isFromCache: false }
|
||||
}).catch(async(err: any) => {
|
||||
console.log(err)
|
||||
if (!allowToggleSource) throw err
|
||||
onToggleSource()
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return await getOtherSource(musicInfo).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
return getOnlineOtherSourceMusicUrl({
|
||||
musicInfos: [...otherSource],
|
||||
onToggleSource,
|
||||
quality,
|
||||
isRefresh,
|
||||
retryedSource: [musicInfo.source],
|
||||
})
|
||||
}
|
||||
throw err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const getOnlineOtherSourcePicUrl = async({ musicInfos, onToggleSource, isRefresh, retryedSource = [] }: {
|
||||
musicInfos: LX.Music.MusicInfoOnline[]
|
||||
onToggleSource: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
isRefresh: boolean
|
||||
retryedSource?: LX.OnlineSource[]
|
||||
}): Promise<{
|
||||
url: string
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
isFromCache: boolean
|
||||
}> => {
|
||||
let musicInfo: LX.Music.MusicInfoOnline | null = null
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (musicInfo = (musicInfos.shift() as LX.Music.MusicInfoOnline)) {
|
||||
if (retryedSource.includes(musicInfo.source)) continue
|
||||
retryedSource.push(musicInfo.source)
|
||||
// if (!assertApiSupport(musicInfo.source)) continue
|
||||
console.log('try toggle to: ', musicInfo.source, musicInfo.name, musicInfo.singer, musicInfo.interval)
|
||||
onToggleSource(musicInfo)
|
||||
break
|
||||
}
|
||||
if (!musicInfo) throw new Error(window.i18n.t('toggle_source_failed'))
|
||||
|
||||
if (musicInfo.meta.picUrl && !isRefresh) return { musicInfo, url: musicInfo.meta.picUrl, isFromCache: true }
|
||||
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = musicSdk[musicInfo.source].getPic(toOldMusicInfo(musicInfo)).promise
|
||||
} catch (err: any) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
retryedSource.includes(musicInfo.source)
|
||||
return reqPromise.then((url: string) => {
|
||||
return { musicInfo, url, isFromCache: false }
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
}).catch((err: any) => {
|
||||
console.log(err)
|
||||
return getOnlineOtherSourcePicUrl({ musicInfos, onToggleSource, isRefresh, retryedSource })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线歌曲封面
|
||||
*/
|
||||
export const handleGetOnlinePicUrl = async({ musicInfo, isRefresh, onToggleSource, allowToggleSource }: {
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
onToggleSource: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
isRefresh: boolean
|
||||
allowToggleSource: boolean
|
||||
}): Promise<{
|
||||
url: string
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
isFromCache: boolean
|
||||
}> => {
|
||||
// console.log(musicInfo.source)
|
||||
let reqPromise
|
||||
try {
|
||||
reqPromise = musicSdk[musicInfo.source].getPic(toOldMusicInfo(musicInfo)).promise
|
||||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.then((url: string) => {
|
||||
return { musicInfo, url, isFromCache: false }
|
||||
}).catch(async(err: any) => {
|
||||
console.log(err)
|
||||
if (!allowToggleSource) throw err
|
||||
onToggleSource()
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return await getOtherSource(musicInfo).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
return getOnlineOtherSourcePicUrl({
|
||||
musicInfos: [...otherSource],
|
||||
onToggleSource,
|
||||
isRefresh,
|
||||
retryedSource: [musicInfo.source],
|
||||
})
|
||||
}
|
||||
throw err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const getOnlineOtherSourceLyricInfo = async({ musicInfos, onToggleSource, isRefresh, retryedSource = [] }: {
|
||||
musicInfos: LX.Music.MusicInfoOnline[]
|
||||
onToggleSource: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
isRefresh: boolean
|
||||
retryedSource?: LX.OnlineSource[]
|
||||
}): Promise<{
|
||||
lyricInfo: LX.Music.LyricInfo | LX.Player.LyricInfo
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
isFromCache: boolean
|
||||
}> => {
|
||||
let musicInfo: LX.Music.MusicInfoOnline | null = null
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (musicInfo = (musicInfos.shift() as LX.Music.MusicInfoOnline)) {
|
||||
if (retryedSource.includes(musicInfo.source)) continue
|
||||
retryedSource.push(musicInfo.source)
|
||||
// if (!assertApiSupport(musicInfo.source)) continue
|
||||
console.log('try toggle to: ', musicInfo.source, musicInfo.name, musicInfo.singer, musicInfo.interval)
|
||||
onToggleSource(musicInfo)
|
||||
break
|
||||
}
|
||||
if (!musicInfo) throw new Error(window.i18n.t('toggle_source_failed'))
|
||||
|
||||
if (!isRefresh) {
|
||||
const lyricInfo = await getCachedLyricInfo(musicInfo)
|
||||
if (lyricInfo) return { musicInfo, lyricInfo, isFromCache: true }
|
||||
}
|
||||
|
||||
let reqPromise
|
||||
try {
|
||||
// TODO: remove any type
|
||||
reqPromise = (musicSdk[musicInfo.source].getLyric(toOldMusicInfo(musicInfo)) as any).promise
|
||||
} catch (err: any) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
retryedSource.includes(musicInfo.source)
|
||||
return reqPromise.then((lyricInfo: LX.Music.LyricInfo) => {
|
||||
return existTimeExp.test(lyricInfo.lyric) ? {
|
||||
lyricInfo,
|
||||
musicInfo,
|
||||
isFromCache: false,
|
||||
} : Promise.reject(new Error('failed'))
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
}).catch((err: any) => {
|
||||
console.log(err)
|
||||
return getOnlineOtherSourceLyricInfo({ musicInfos, onToggleSource, isRefresh, retryedSource })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线歌词信息
|
||||
*/
|
||||
export const handleGetOnlineLyricInfo = async({ musicInfo, onToggleSource, isRefresh, allowToggleSource }: {
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
onToggleSource: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
isRefresh: boolean
|
||||
allowToggleSource: boolean
|
||||
}): Promise<{
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
lyricInfo: LX.Music.LyricInfo | LX.Player.LyricInfo
|
||||
isFromCache: boolean
|
||||
}> => {
|
||||
// console.log(musicInfo.source)
|
||||
let reqPromise
|
||||
try {
|
||||
// TODO: remove any type
|
||||
reqPromise = (musicSdk[musicInfo.source].getLyric(toOldMusicInfo(musicInfo)) as any).promise
|
||||
} catch (err) {
|
||||
reqPromise = Promise.reject(err)
|
||||
}
|
||||
return reqPromise.then((lyricInfo: LX.Music.LyricInfo) => {
|
||||
return existTimeExp.test(lyricInfo.lyric) ? {
|
||||
musicInfo,
|
||||
lyricInfo,
|
||||
isFromCache: false,
|
||||
} : Promise.reject(new Error('failed'))
|
||||
}).catch(async(err: any) => {
|
||||
console.log(err)
|
||||
if (!allowToggleSource) throw err
|
||||
|
||||
onToggleSource()
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
return await getOtherSource(musicInfo).then(otherSource => {
|
||||
console.log('find otherSource', otherSource)
|
||||
if (otherSource.length) {
|
||||
return getOnlineOtherSourceLyricInfo({
|
||||
musicInfos: [...otherSource],
|
||||
onToggleSource,
|
||||
isRefresh,
|
||||
retryedSource: [musicInfo.source],
|
||||
})
|
||||
}
|
||||
throw err
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
import { isEmpty, setPause, setPlay, setResource, setStop } from '@renderer/plugins/player'
|
||||
import { isPlay, playedList, playInfo, playMusicInfo, tempPlayList } from '@renderer/store/player/state'
|
||||
import {
|
||||
getList,
|
||||
clearPlayedList,
|
||||
clearTempPlayeList,
|
||||
setPlayMusicInfo,
|
||||
addPlayedList,
|
||||
setMusicInfo,
|
||||
setAllStatus,
|
||||
removeTempPlayList,
|
||||
setPlayListId,
|
||||
removePlayedList,
|
||||
} from '@renderer/store/player/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { getMusicUrl, getPicPath, getLyricInfo } from '../music/index'
|
||||
import { filterList } from './utils'
|
||||
import { requestMsg } from '@renderer/utils/message'
|
||||
import { getRandom } from '@renderer/utils/index'
|
||||
// import { checkMusicFileAvailable } from '@renderer/utils/music'
|
||||
|
||||
let isGettingUrl = false
|
||||
const createDelayNextTimeout = (delay: number) => {
|
||||
let timeout: NodeJS.Timeout | null
|
||||
const clearDelayNextTimeout = () => {
|
||||
// console.log(this.timeout)
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
timeout = null
|
||||
}
|
||||
}
|
||||
|
||||
const addDelayNextTimeout = () => {
|
||||
clearDelayNextTimeout()
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null
|
||||
if (window.lx.isPlayedStop) return
|
||||
console.warn('delay next timeout timeout', delay)
|
||||
void playNext(true)
|
||||
}, delay)
|
||||
}
|
||||
|
||||
return {
|
||||
clearDelayNextTimeout,
|
||||
addDelayNextTimeout,
|
||||
}
|
||||
}
|
||||
const { addDelayNextTimeout, clearDelayNextTimeout } = createDelayNextTimeout(5000)
|
||||
const { addDelayNextTimeout: addLoadTimeout, clearDelayNextTimeout: clearLoadTimeout } = createDelayNextTimeout(100000)
|
||||
|
||||
/**
|
||||
* 检查音乐信息是否已更改
|
||||
*/
|
||||
const diffCurrentMusicInfo = (curMusicInfo: LX.Music.MusicInfo | LX.Download.ListItem): boolean => {
|
||||
return curMusicInfo !== playMusicInfo.musicInfo || isPlay.value
|
||||
}
|
||||
|
||||
const getMusicPlayUrl = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, isRefresh = false, isRetryed = false): Promise<string | null> => {
|
||||
// this.musicInfo.url = await getMusicPlayUrl(targetSong, type)
|
||||
setAllStatus(window.i18n.t('player__geting_url'))
|
||||
|
||||
// const type = getPlayType(appSetting['player.highQuality'], musicInfo)
|
||||
|
||||
return getMusicUrl({
|
||||
musicInfo,
|
||||
isRefresh,
|
||||
onToggleSource(mInfo) {
|
||||
if (diffCurrentMusicInfo(musicInfo)) return
|
||||
setAllStatus(window.i18n.t('toggle_source_try'))
|
||||
},
|
||||
}).then(url => {
|
||||
if (window.lx.isPlayedStop || diffCurrentMusicInfo(musicInfo)) return null
|
||||
|
||||
return url
|
||||
}).catch(err => {
|
||||
// console.log('err', err.message)
|
||||
if (window.lx.isPlayedStop ||
|
||||
diffCurrentMusicInfo(musicInfo) ||
|
||||
err.message == requestMsg.cancelRequest) return null
|
||||
|
||||
if (!isRetryed) return getMusicPlayUrl(musicInfo, isRefresh, true)
|
||||
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
export const setMusicUrl = (musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, isRefresh?: boolean) => {
|
||||
if (appSetting['player.autoSkipOnError']) addLoadTimeout()
|
||||
isGettingUrl = true
|
||||
void getMusicPlayUrl(musicInfo, isRefresh).then((url) => {
|
||||
if (!url) return
|
||||
setResource(url)
|
||||
}).catch((err: any) => {
|
||||
console.log(err)
|
||||
setAllStatus(err.message)
|
||||
window.app_event.error()
|
||||
if (appSetting['player.autoSkipOnError']) addDelayNextTimeout()
|
||||
}).finally(() => {
|
||||
clearLoadTimeout()
|
||||
if (musicInfo === playMusicInfo.musicInfo) isGettingUrl = false
|
||||
})
|
||||
}
|
||||
|
||||
// 恢复上次播放的状态
|
||||
const handleRestorePlay = async(restorePlayInfo: LX.Player.SavedPlayInfo) => {
|
||||
const musicInfo = playMusicInfo.musicInfo
|
||||
if (!musicInfo) return
|
||||
|
||||
setImmediate(() => {
|
||||
window.app_event.setProgress(appSetting['player.isSavePlayTime'] ? restorePlayInfo.time : 0, restorePlayInfo.maxTime)
|
||||
})
|
||||
|
||||
|
||||
void getPicPath({ musicInfo, listId: playMusicInfo.isTempPlay ? null : playMusicInfo.listId }).then((url: string) => {
|
||||
if (musicInfo.id != playMusicInfo.musicInfo?.id) return
|
||||
setMusicInfo({ pic: url })
|
||||
window.app_event.picUpdated()
|
||||
})
|
||||
|
||||
void getLyricInfo({ musicInfo }).then((lyricInfo) => {
|
||||
if (musicInfo.id != playMusicInfo.musicInfo?.id) return
|
||||
setMusicInfo({
|
||||
lrc: lyricInfo.lyric,
|
||||
tlrc: lyricInfo.tlyric,
|
||||
lxlrc: lyricInfo.lxlyric,
|
||||
rlrc: lyricInfo.rlyric,
|
||||
rawlrc: lyricInfo.rawlrcInfo.lyric,
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
if (musicInfo.id != playMusicInfo.musicInfo?.id) return
|
||||
setAllStatus(window.i18n.t('lyric__load_error'))
|
||||
}).finally(() => {
|
||||
window.app_event.lyricUpdated()
|
||||
})
|
||||
|
||||
if (appSetting['player.togglePlayMethod'] == 'random') addPlayedList({ ...playMusicInfo as LX.Player.PlayMusicInfo })
|
||||
}
|
||||
|
||||
|
||||
// 处理音乐播放
|
||||
const handlePlay = () => {
|
||||
if (window.lx.isPlayedStop) window.lx.isPlayedStop = false
|
||||
if (isGettingUrl) isGettingUrl = false
|
||||
|
||||
if (window.lx.restorePlayInfo) {
|
||||
void handleRestorePlay(window.lx.restorePlayInfo)
|
||||
window.lx.restorePlayInfo = null
|
||||
return
|
||||
}
|
||||
|
||||
setStop()
|
||||
window.app_event.pause()
|
||||
|
||||
clearDelayNextTimeout()
|
||||
clearLoadTimeout()
|
||||
|
||||
const musicInfo = playMusicInfo.musicInfo
|
||||
|
||||
if (!musicInfo) return
|
||||
|
||||
if (appSetting['player.togglePlayMethod'] == 'random' && !playMusicInfo.isTempPlay) addPlayedList({ ...(playMusicInfo as LX.Player.PlayMusicInfo) })
|
||||
|
||||
void setMusicUrl(musicInfo)
|
||||
|
||||
void getPicPath({ musicInfo, listId: playMusicInfo.isTempPlay ? null : playMusicInfo.listId }).then((url: string) => {
|
||||
if (musicInfo.id != playMusicInfo.musicInfo?.id) return
|
||||
setMusicInfo({ pic: url })
|
||||
window.app_event.picUpdated()
|
||||
})
|
||||
|
||||
void getLyricInfo({ musicInfo }).then((lyricInfo) => {
|
||||
if (musicInfo.id != playMusicInfo.musicInfo?.id) return
|
||||
setMusicInfo({
|
||||
lrc: lyricInfo.lyric,
|
||||
tlrc: lyricInfo.tlyric,
|
||||
lxlrc: lyricInfo.lxlyric,
|
||||
rlrc: lyricInfo.rlyric,
|
||||
rawlrc: lyricInfo.rawlrcInfo.lyric,
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
if (musicInfo.id != playMusicInfo.musicInfo?.id) return
|
||||
setAllStatus(window.i18n.t('lyric__load_error'))
|
||||
}).finally(() => {
|
||||
window.app_event.lyricUpdated()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放列表内歌曲
|
||||
* @param listId 列表id
|
||||
* @param index 播放的歌曲位置
|
||||
*/
|
||||
export const playList = (listId: string, index: number) => {
|
||||
setPlayListId(listId)
|
||||
setPlayMusicInfo(listId, getList(listId)[index])
|
||||
clearPlayedList()
|
||||
clearTempPlayeList()
|
||||
handlePlay()
|
||||
}
|
||||
|
||||
const handleToggleStop = () => {
|
||||
stop()
|
||||
setTimeout(() => {
|
||||
setPlayMusicInfo(null, null)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一曲
|
||||
* @param isAutoToggle 是否自动切换
|
||||
* @returns
|
||||
*/
|
||||
export const playNext = async(isAutoToggle = false): Promise<void> => {
|
||||
if (tempPlayList.length) { // 如果稍后播放列表存在歌曲则直接播放改列表的歌曲
|
||||
const playMusicInfo = tempPlayList[0]
|
||||
removeTempPlayList(0)
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
handlePlay()
|
||||
return
|
||||
}
|
||||
|
||||
if (playMusicInfo.musicInfo == null) return handleToggleStop()
|
||||
|
||||
// console.log(playInfo.playerListId)
|
||||
const currentListId = playInfo.playerListId
|
||||
if (!currentListId) return handleToggleStop()
|
||||
const currentList = getList(currentListId)
|
||||
|
||||
if (playedList.length) { // 移除已播放列表内不存在原列表的歌曲
|
||||
let currentId: string
|
||||
if (playMusicInfo.isTempPlay) {
|
||||
const musicInfo = currentList[playInfo.playerPlayIndex]
|
||||
if (musicInfo) currentId = musicInfo.id
|
||||
} else {
|
||||
currentId = playMusicInfo.musicInfo.id
|
||||
}
|
||||
// 从已播放列表移除播放列表已删除的歌曲
|
||||
let index
|
||||
for (index = playedList.findIndex(m => m.musicInfo.id === currentId) + 1; index < playedList.length; index++) {
|
||||
const playMusicInfo = playedList[index]
|
||||
const currentId = playMusicInfo.musicInfo.id
|
||||
if (playMusicInfo.listId == currentListId && !currentList.some(m => m.id === currentId)) {
|
||||
removePlayedList(index)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (index < playedList.length) {
|
||||
const playMusicInfo = playedList[index]
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
handlePlay()
|
||||
return
|
||||
}
|
||||
}
|
||||
// const isCheckFile = findNum > 2 // 针对下载列表,如果超过两次都碰到无效歌曲,则过滤整个列表内的无效歌曲
|
||||
let { filteredList, playerIndex } = await filterList({ // 过滤已播放歌曲
|
||||
listId: currentListId,
|
||||
list: currentList,
|
||||
playedList,
|
||||
playerMusicInfo: currentList[playInfo.playerPlayIndex],
|
||||
})
|
||||
|
||||
if (!filteredList.length) return handleToggleStop()
|
||||
// let currentIndex: number = filteredList.indexOf(currentList[playInfo.playerPlayIndex])
|
||||
if (playerIndex == -1 && filteredList.length) playerIndex = 0
|
||||
let nextIndex = playerIndex
|
||||
|
||||
let togglePlayMethod = appSetting['player.togglePlayMethod']
|
||||
if (!isAutoToggle) {
|
||||
switch (togglePlayMethod) {
|
||||
case 'list':
|
||||
case 'singleLoop':
|
||||
case 'none':
|
||||
togglePlayMethod = 'listLoop'
|
||||
}
|
||||
}
|
||||
switch (togglePlayMethod) {
|
||||
case 'listLoop':
|
||||
nextIndex = playerIndex === filteredList.length - 1 ? 0 : playerIndex + 1
|
||||
break
|
||||
case 'random':
|
||||
nextIndex = getRandom(0, filteredList.length)
|
||||
break
|
||||
case 'list':
|
||||
nextIndex = playerIndex === filteredList.length - 1 ? -1 : playerIndex + 1
|
||||
break
|
||||
case 'singleLoop':
|
||||
break
|
||||
default:
|
||||
nextIndex = -1
|
||||
return
|
||||
}
|
||||
if (nextIndex < 0) return
|
||||
|
||||
const nextPlayMusicInfo = {
|
||||
musicInfo: filteredList[nextIndex],
|
||||
listId: currentListId,
|
||||
isTempPlay: false,
|
||||
}
|
||||
|
||||
setPlayMusicInfo(nextPlayMusicInfo.listId, nextPlayMusicInfo.musicInfo)
|
||||
handlePlay()
|
||||
}
|
||||
|
||||
/**
|
||||
* 上一曲
|
||||
*/
|
||||
export const playPrev = async(isAutoToggle = false): Promise<void> => {
|
||||
if (playMusicInfo.musicInfo == null) return handleToggleStop()
|
||||
|
||||
const currentListId = playInfo.playerListId
|
||||
if (!currentListId) return handleToggleStop()
|
||||
const currentList = getList(currentListId)
|
||||
|
||||
if (playedList.length) {
|
||||
let currentId: string
|
||||
if (playMusicInfo.isTempPlay) {
|
||||
const musicInfo = currentList[playInfo.playerPlayIndex]
|
||||
if (musicInfo) currentId = musicInfo.id
|
||||
} else {
|
||||
currentId = playMusicInfo.musicInfo.id
|
||||
}
|
||||
// 从已播放列表移除播放列表已删除的歌曲
|
||||
let index
|
||||
for (index = playedList.findIndex(m => m.musicInfo.id === currentId) - 1; index > -1; index--) {
|
||||
const playMusicInfo = playedList[index]
|
||||
const currentId = playMusicInfo.musicInfo.id
|
||||
if (playMusicInfo.listId == currentListId && !currentList.some(m => m.id === currentId)) {
|
||||
removePlayedList(index)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (index > -1) {
|
||||
const playMusicInfo = playedList[index]
|
||||
setPlayMusicInfo(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
handlePlay()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// const isCheckFile = findNum > 2
|
||||
let { filteredList, playerIndex } = await filterList({ // 过滤已播放歌曲
|
||||
listId: currentListId,
|
||||
list: currentList,
|
||||
playedList,
|
||||
playerMusicInfo: currentList[playInfo.playerPlayIndex],
|
||||
})
|
||||
if (!filteredList.length) return handleToggleStop()
|
||||
|
||||
// let currentIndex = filteredList.indexOf(currentList[playInfo.playerPlayIndex])
|
||||
if (playerIndex == -1 && filteredList.length) playerIndex = 0
|
||||
let nextIndex = playerIndex
|
||||
if (!playMusicInfo.isTempPlay) {
|
||||
let togglePlayMethod = appSetting['player.togglePlayMethod']
|
||||
if (!isAutoToggle) {
|
||||
switch (togglePlayMethod) {
|
||||
case 'list':
|
||||
case 'singleLoop':
|
||||
case 'none':
|
||||
togglePlayMethod = 'listLoop'
|
||||
}
|
||||
}
|
||||
switch (togglePlayMethod) {
|
||||
case 'random':
|
||||
nextIndex = getRandom(0, filteredList.length)
|
||||
break
|
||||
case 'listLoop':
|
||||
case 'list':
|
||||
nextIndex = playerIndex === 0 ? filteredList.length - 1 : playerIndex - 1
|
||||
break
|
||||
case 'singleLoop':
|
||||
break
|
||||
default:
|
||||
nextIndex = -1
|
||||
return
|
||||
}
|
||||
if (nextIndex < 0) return
|
||||
}
|
||||
|
||||
const nextPlayMusicInfo = {
|
||||
musicInfo: filteredList[nextIndex],
|
||||
listId: currentListId,
|
||||
isTempPlay: false,
|
||||
}
|
||||
|
||||
setPlayMusicInfo(nextPlayMusicInfo.listId, nextPlayMusicInfo.musicInfo)
|
||||
handlePlay()
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复播放
|
||||
*/
|
||||
export const play = () => {
|
||||
if (playMusicInfo.musicInfo == null) return
|
||||
if (isEmpty()) {
|
||||
void setMusicUrl(playMusicInfo.musicInfo)
|
||||
return
|
||||
}
|
||||
setPlay()
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停播放
|
||||
*/
|
||||
export const pause = () => {
|
||||
setPause()
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止播放
|
||||
*/
|
||||
export const stop = () => {
|
||||
setStop()
|
||||
setTimeout(() => {
|
||||
window.app_event.stop()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放、暂停播放切换
|
||||
*/
|
||||
export const togglePlay = () => {
|
||||
if (window.lx.isPlayedStop) window.lx.isPlayedStop = false
|
||||
if (isPlay.value) {
|
||||
pause()
|
||||
} else {
|
||||
play()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './action'
|
||||
export * from './timeoutStop'
|
||||
@@ -0,0 +1,99 @@
|
||||
import { ref, computed, ComputedRef } from '@common/utils/vueTools'
|
||||
import { isPlay } from '@renderer/store/player/state'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
// import { interval, intervalCancel } from '@renderer/utils/ipc'
|
||||
import { stop } from './action'
|
||||
|
||||
const time = ref(-1)
|
||||
|
||||
|
||||
const timeoutTools: {
|
||||
isRunning: boolean
|
||||
// time: number
|
||||
interval: null | number
|
||||
timeout: NodeJS.Timeout | null
|
||||
endTime: number
|
||||
exit: () => void
|
||||
clearTimeout: () => void
|
||||
start: (_time: number) => void
|
||||
} = {
|
||||
isRunning: false,
|
||||
timeout: null,
|
||||
// time: -1,
|
||||
endTime: 0,
|
||||
interval: null,
|
||||
exit() {
|
||||
window.lx.isPlayedStop = true
|
||||
if (!appSetting['player.waitPlayEndStop'] && isPlay.value) {
|
||||
stop()
|
||||
}
|
||||
},
|
||||
clearTimeout() {
|
||||
if (this.interval) {
|
||||
window.clearInterval(this.interval)
|
||||
this.interval = null
|
||||
}
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = null
|
||||
}
|
||||
|
||||
if (!this.isRunning) return
|
||||
// this.time = -1
|
||||
time.value = -1
|
||||
this.isRunning = false
|
||||
},
|
||||
start(_time: number) {
|
||||
this.clearTimeout()
|
||||
// this.time = _time
|
||||
time.value = _time
|
||||
this.isRunning = true
|
||||
this.endTime = performance.now() + _time * 1000
|
||||
|
||||
this.interval = window.setInterval(() => {
|
||||
// this.endTime = performance.now()
|
||||
// if (this.time > 0) {
|
||||
// this.time--
|
||||
// }
|
||||
time.value = Math.max(0, Math.round((this.endTime - performance.now()) / 1000))
|
||||
}, 1000)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.timeout = null
|
||||
time.value = -1
|
||||
this.clearTimeout()
|
||||
this.exit()
|
||||
}, _time * 1000)
|
||||
},
|
||||
}
|
||||
|
||||
export const startTimeoutStop = (time: number) => {
|
||||
if (window.lx.isPlayedStop) window.lx.isPlayedStop = false
|
||||
timeoutTools.start(time)
|
||||
}
|
||||
export const stopTimeoutStop = () => {
|
||||
console.warn('stopTimeoutStop')
|
||||
if (window.lx.isPlayedStop) window.lx.isPlayedStop = false
|
||||
timeoutTools.clearTimeout()
|
||||
}
|
||||
|
||||
const formatTime = (time: number): string => {
|
||||
// let d = parseInt(time / 86400)
|
||||
// d = d ? d.toString() + ':' : ''
|
||||
// time = time % 86400
|
||||
let h: number | string = Math.trunc(time / 3600)
|
||||
h = h ? h.toString() + ':' : ''
|
||||
time = time % 3600
|
||||
const m = Math.trunc(time / 60).toString().padStart(2, '0')
|
||||
const s = Math.trunc(time % 60).toString().padStart(2, '0')
|
||||
return `${h}${m}:${s}`
|
||||
}
|
||||
export const useTimeout = () => {
|
||||
const timeLabel: ComputedRef<string> = computed(() => {
|
||||
return time.value > 0 ? formatTime(time.value) : ''
|
||||
})
|
||||
|
||||
return {
|
||||
time,
|
||||
timeLabel,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { toRaw, markRawList } from '@common/utils/vueTools'
|
||||
import { qualityList } from '@renderer/store'
|
||||
import { clearPlayedList } from '@renderer/store/player/action'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
export const getPlayType = (highQuality: boolean, musicInfo: LX.Music.MusicInfo | LX.Download.ListItem): LX.Quality | null => {
|
||||
if ('progress' in musicInfo || musicInfo.source == 'local') return null
|
||||
let type: LX.Quality = '128k'
|
||||
let list = qualityList.value[musicInfo.source]
|
||||
if (highQuality && musicInfo.meta._qualitys['320k'] && list && list.includes('320k')) type = '320k'
|
||||
return type
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤列表中已播放的歌曲
|
||||
*/
|
||||
export const filterList = async({ playedList, listId, list, playerMusicInfo }: {
|
||||
playedList: LX.Player.PlayMusicInfo[]
|
||||
listId: string
|
||||
list: Array<LX.Music.MusicInfo | LX.Download.ListItem>
|
||||
playerMusicInfo?: LX.Music.MusicInfo | LX.Download.ListItem
|
||||
}) => {
|
||||
// if (this.list.listName === null) return
|
||||
// console.log(isCheckFile)
|
||||
let { filteredList, canPlayList, playerIndex } = await window.lx.worker.main.filterMusicList({
|
||||
listId,
|
||||
list: list.map(m => toRaw(m)),
|
||||
playedList: toRaw(playedList),
|
||||
savePath: appSetting['download.savePath'],
|
||||
playerMusicInfo: toRaw(playerMusicInfo),
|
||||
})
|
||||
|
||||
if (!filteredList.length && playedList.length) {
|
||||
clearPlayedList()
|
||||
return { filteredList: markRawList(canPlayList), playerIndex }
|
||||
}
|
||||
return { filteredList: markRawList(filteredList), playerIndex }
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { reactive, ref, markRaw } from '@renderer/utils/vueTools'
|
||||
|
||||
export const isInitedList = ref(false)
|
||||
|
||||
export const setInited = () => {
|
||||
isInitedList.value = true
|
||||
}
|
||||
|
||||
export const downloadList = reactive([])
|
||||
export const downloadListMap = new Map()
|
||||
|
||||
export const initDownloadList = list => {
|
||||
downloadList.splice(0, downloadList.length, ...list)
|
||||
downloadListMap.clear()
|
||||
for (const task of list) {
|
||||
downloadListMap.set(task.key, task)
|
||||
}
|
||||
}
|
||||
|
||||
export const downloadStatus = markRaw({
|
||||
RUN: 'run',
|
||||
WAITING: 'waiting',
|
||||
PAUSE: 'pause',
|
||||
ERROR: 'error',
|
||||
COMPLETED: 'completed',
|
||||
})
|
||||
@@ -1,51 +0,0 @@
|
||||
import { ref, reactive, shallowRef, markRaw } from '@renderer/utils/vueTools'
|
||||
import { windowSizeList as configWindowSizeList, themes as configThemes, themeLights as themeLightIds, themeDarks as themeDarkIds } from '@common/config'
|
||||
import { version } from '../../../../package.json'
|
||||
process.versions.app = version
|
||||
|
||||
export const apiSource = ref(null)
|
||||
export const isShowPact = window.isShowPact = ref(false)
|
||||
export const proxy = reactive({})
|
||||
export const versionInfo = window.versionInfo = reactive({
|
||||
version,
|
||||
newVersion: null,
|
||||
showModal: false,
|
||||
isError: false,
|
||||
isTimeOut: false,
|
||||
isUnknow: false,
|
||||
isDownloaded: false,
|
||||
isDownloading: false,
|
||||
isLatestVer: false,
|
||||
downloadProgress: null,
|
||||
})
|
||||
export const userApi = reactive({
|
||||
list: [],
|
||||
status: false,
|
||||
message: 'initing',
|
||||
apis: {},
|
||||
})
|
||||
export const sync = window.sync = reactive({
|
||||
enable: false,
|
||||
isShowSyncMode: false,
|
||||
deviceName: '',
|
||||
status: {
|
||||
status: false,
|
||||
message: '',
|
||||
address: [],
|
||||
code: '',
|
||||
devices: [],
|
||||
},
|
||||
})
|
||||
export const isFullscreen = ref(false)
|
||||
|
||||
export const windowSizeList = markRaw(configWindowSizeList)
|
||||
export const themes = markRaw(configThemes)
|
||||
export const themeLights = markRaw(configThemes.filter(t => themeLightIds.includes(t.id)))
|
||||
export const themeDarks = markRaw(configThemes.filter(t => themeDarkIds.includes(t.id)))
|
||||
export const themeShouldUseDarkColors = ref(window.shouldUseDarkColors)
|
||||
delete window.shouldUseDarkColors
|
||||
|
||||
export const qualityList = shallowRef({})
|
||||
export const setQualityList = _qualityList => {
|
||||
qualityList.value = _qualityList
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
import { reactive, ref, markRaw } from '@renderer/utils/vueTools'
|
||||
|
||||
// const TEMP_LIST = 'TEMP_LIST'
|
||||
|
||||
export const isInitedList = ref(false)
|
||||
|
||||
export const setInited = () => {
|
||||
isInitedList.value = true
|
||||
}
|
||||
|
||||
export const allList = window.allList = markRaw({})
|
||||
|
||||
export const allListInit = (newLists) => {
|
||||
const newIds = [defaultList.id, loveList.id, tempList.id, ...newLists.userList.map(l => l.id)]
|
||||
for (const id of Object.keys(allList)) {
|
||||
if (newIds.includes(id)) continue
|
||||
delete allList[id]
|
||||
}
|
||||
allListUpdate(defaultList.id, newLists.defaultList.list)
|
||||
allListUpdate(loveList.id, newLists.loveList.list)
|
||||
if (newLists.tempList) {
|
||||
allListUpdate(tempList.id, newLists.tempList.list)
|
||||
tempList.meta = newLists.tempList.meta ?? {}
|
||||
}
|
||||
userLists.splice(0, userLists.length)
|
||||
for (const { list, ...listInfo } of newLists.userList) {
|
||||
allListUpdate(listInfo.id, list)
|
||||
userLists.push(listInfo)
|
||||
}
|
||||
}
|
||||
export const allListUpdate = (id, list) => {
|
||||
if (allList[id]) {
|
||||
allList[id].splice(0, allList[id].length, ...list)
|
||||
} else {
|
||||
allList[id] = reactive(list)
|
||||
}
|
||||
}
|
||||
export const allListRemove = id => {
|
||||
delete allList[id]
|
||||
}
|
||||
|
||||
export const defaultList = reactive({
|
||||
id: 'default',
|
||||
name: '试听列表',
|
||||
})
|
||||
|
||||
export const loveList = reactive({
|
||||
id: 'love',
|
||||
name: '我的收藏',
|
||||
})
|
||||
|
||||
export const tempList = reactive({
|
||||
id: 'temp',
|
||||
name: '临时列表',
|
||||
meta: {},
|
||||
})
|
||||
|
||||
export const userLists = window.userLists = reactive([])
|
||||
|
||||
export const addUserList = ({
|
||||
name,
|
||||
id,
|
||||
list,
|
||||
source,
|
||||
sourceListId,
|
||||
position,
|
||||
locationUpdateTime,
|
||||
}) => {
|
||||
if (position == null) {
|
||||
userLists.push({
|
||||
name,
|
||||
id,
|
||||
source,
|
||||
sourceListId,
|
||||
locationUpdateTime,
|
||||
})
|
||||
} else {
|
||||
userLists.splice(position + 1, 0, {
|
||||
name,
|
||||
id,
|
||||
source,
|
||||
sourceListId,
|
||||
locationUpdateTime,
|
||||
})
|
||||
}
|
||||
allListUpdate(id, list)
|
||||
}
|
||||
|
||||
export const updateList = ({
|
||||
name,
|
||||
id,
|
||||
list,
|
||||
source,
|
||||
sourceListId,
|
||||
meta,
|
||||
locationUpdateTime,
|
||||
}) => {
|
||||
let targetList
|
||||
switch (id) {
|
||||
case defaultList.id:
|
||||
case loveList.id:
|
||||
break
|
||||
case tempList.id:
|
||||
tempList.meta = meta || {}
|
||||
break
|
||||
default:
|
||||
targetList = userLists.find(l => l.id == id)
|
||||
targetList.name = name
|
||||
targetList.source = source
|
||||
targetList.sourceListId = sourceListId
|
||||
if (locationUpdateTime) targetList.locationUpdateTime = locationUpdateTime
|
||||
break
|
||||
}
|
||||
allListUpdate(id, list)
|
||||
}
|
||||
|
||||
export const removeUserList = id => {
|
||||
const index = userLists.findIndex(l => l.id == id)
|
||||
if (index < 0) return
|
||||
userLists.splice(index, 1)
|
||||
allListRemove(id)
|
||||
}
|
||||
|
||||
export const getList = id => {
|
||||
return allList[id] ?? []
|
||||
}
|
||||
|
||||
export const fetchingListStatus = reactive({})
|
||||
export const setFetchingListStatus = (id, status) => {
|
||||
fetchingListStatus[id] = status
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { reactive } from '@renderer/utils/vueTools'
|
||||
|
||||
export const lyric = reactive({
|
||||
lines: [],
|
||||
text: '',
|
||||
line: 0,
|
||||
offset: 0, // 歌词延迟
|
||||
tempOffset: 0, // 歌词临时延迟
|
||||
})
|
||||
|
||||
export const setLines = lines => {
|
||||
lyric.lines = lines
|
||||
}
|
||||
export const setText = (text, line) => {
|
||||
lyric.text = text
|
||||
lyric.line = line
|
||||
}
|
||||
export const setOffset = offset => {
|
||||
lyric.offset = offset
|
||||
}
|
||||
export const setTempOffset = offset => {
|
||||
lyric.tempOffset = offset
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
import { reactive, ref, shallowRef } from '@renderer/utils/vueTools'
|
||||
import { getList } from '@renderer/core/share/utils'
|
||||
|
||||
export const musicInfo = window.musicInfo = reactive({
|
||||
songmid: null,
|
||||
img: null,
|
||||
lrc: null,
|
||||
tlrc: null,
|
||||
rlrc: null,
|
||||
lxlrc: null,
|
||||
rawlrc: null,
|
||||
url: null,
|
||||
name: '',
|
||||
singer: '',
|
||||
album: '',
|
||||
})
|
||||
const musicInfoKeys = Object.keys(musicInfo)
|
||||
export const setMusicInfo = _musicInfo => {
|
||||
for (const key of musicInfoKeys) {
|
||||
if (_musicInfo[key] !== undefined) {
|
||||
musicInfo[key] = _musicInfo[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const musicInfoItem = shallowRef({})
|
||||
export const setMusicInfoItem = musicInfo => {
|
||||
musicInfoItem.value = musicInfo.key ? musicInfo.metadata.musicInfo : musicInfo
|
||||
}
|
||||
|
||||
|
||||
export const isPlay = ref(false)
|
||||
export const setPlay = val => {
|
||||
isPlay.value = val
|
||||
}
|
||||
|
||||
export const status = ref('')
|
||||
export const setStatus = val => {
|
||||
status.value = val
|
||||
}
|
||||
|
||||
|
||||
export const statusText = ref('')
|
||||
export const setStatusText = val => {
|
||||
statusText.value = val
|
||||
}
|
||||
|
||||
export const setAllStatus = val => {
|
||||
status.value = val
|
||||
statusText.value = val
|
||||
}
|
||||
|
||||
|
||||
export const isShowPlayerDetail = ref(false)
|
||||
export const setShowPlayerDetail = val => {
|
||||
isShowPlayerDetail.value = val
|
||||
}
|
||||
|
||||
export const isShowPlayComment = ref(false)
|
||||
export const setShowPlayComment = val => {
|
||||
isShowPlayComment.value = val
|
||||
}
|
||||
|
||||
export const isShowLrcSelectContent = ref(false)
|
||||
export const setShowPlayLrcSelectContentLrc = val => {
|
||||
isShowLrcSelectContent.value = val
|
||||
}
|
||||
|
||||
|
||||
export const playMusicInfo = reactive({
|
||||
listId: null, // 当前播放歌曲的列表 id
|
||||
musicInfo: null, // 当前播放歌曲的歌曲信息
|
||||
isTempPlay: false, // 是否属于 “稍后播放”
|
||||
})
|
||||
export const playInfo = reactive({
|
||||
playIndex: -1, // 当前正在播放歌曲 index
|
||||
playListId: null, // 播放器的播放列表 id
|
||||
listPlayIndex: -1, // 播放器播放歌曲 index
|
||||
})
|
||||
|
||||
// 设置播放器的播放列表
|
||||
export const setPlayList = listId => {
|
||||
playInfo.playListId = listId
|
||||
}
|
||||
|
||||
// 更新播放位置
|
||||
export const updatePlayIndex = () => {
|
||||
const indexInfo = getPlayIndex(playMusicInfo.listId, playMusicInfo.musicInfo, playMusicInfo.isTempPlay)
|
||||
// console.log(indexInfo)
|
||||
playInfo.playIndex = indexInfo.playIndex
|
||||
playInfo.listPlayIndex = indexInfo.listPlayIndex
|
||||
|
||||
return indexInfo
|
||||
}
|
||||
|
||||
export const getPlayIndex = (listId, musicInfo, isTempPlay) => {
|
||||
const playerList = getList(playInfo.playListId)
|
||||
|
||||
// if (listIndex < 0) throw new Error('music info not found')
|
||||
// playInfo.playIndex = listIndex
|
||||
|
||||
let playIndex = -1
|
||||
let listPlayIndex = -1
|
||||
if (playerList?.length) {
|
||||
listPlayIndex = Math.min(playInfo.listPlayIndex, playerList.length - 1)
|
||||
}
|
||||
|
||||
const list = getList(listId)
|
||||
if (list?.length && musicInfo) {
|
||||
if (musicInfo.key) { // 已下载的歌曲
|
||||
const currentKey = musicInfo.key
|
||||
playIndex = list.findIndex(m => m.key == currentKey)
|
||||
} else {
|
||||
const currentSongmid = musicInfo.songmid
|
||||
playIndex = list.findIndex(m => m.songmid == currentSongmid)
|
||||
}
|
||||
if (!isTempPlay) {
|
||||
if (playIndex < 0) {
|
||||
listPlayIndex = listPlayIndex < 1 ? (list.length - 1) : (listPlayIndex - 1)
|
||||
} else {
|
||||
listPlayIndex = playIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
playIndex,
|
||||
listPlayIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// 设置当前播放歌曲的信息
|
||||
export const setPlayMusicInfo = (listId, musicInfo, isTempPlay = false) => {
|
||||
playMusicInfo.listId = listId
|
||||
playMusicInfo.musicInfo = musicInfo
|
||||
playMusicInfo.isTempPlay = isTempPlay
|
||||
if (musicInfo == null) {
|
||||
playInfo.playIndex = -1
|
||||
playInfo.playListId = null
|
||||
playInfo.listPlayIndex = -1
|
||||
return
|
||||
}
|
||||
|
||||
const { playIndex, listPlayIndex } = getPlayIndex(listId, musicInfo, isTempPlay)
|
||||
|
||||
playInfo.playIndex = playIndex
|
||||
playInfo.listPlayIndex = listPlayIndex
|
||||
|
||||
setMusicInfoItem(musicInfo)
|
||||
// console.log(playInfo)
|
||||
}
|
||||
|
||||
export const playedList = window.playedList = reactive([])
|
||||
export const addPlayedList = (item) => {
|
||||
if (playedList.some(m => m.musicInfo === item.musicInfo)) return
|
||||
playedList.push(item)
|
||||
}
|
||||
export const removePlayedList = (index) => {
|
||||
playedList.splice(index, 1)
|
||||
}
|
||||
export const clearPlayedList = () => {
|
||||
playedList.splice(0, playedList.length)
|
||||
}
|
||||
|
||||
export const tempPlayList = reactive([])
|
||||
export const addTempPlayList = (list) => {
|
||||
const topList = []
|
||||
const bottomList = list.filter(({ isTop, ...musicInfo }) => {
|
||||
if (isTop) {
|
||||
topList.push(musicInfo)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if (topList.length) tempPlayList.unshift(...topList.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true })))
|
||||
if (bottomList.length) tempPlayList.push(...bottomList.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true })))
|
||||
}
|
||||
export const removeTempPlayList = (index) => {
|
||||
tempPlayList.splice(index, 1)
|
||||
}
|
||||
export const clearTempPlayeList = () => {
|
||||
tempPlayList.splice(0, tempPlayList.length)
|
||||
}
|
||||
|
||||
window.playInfo = playInfo
|
||||
window.playMusicInfo = playMusicInfo
|
||||
@@ -1,7 +0,0 @@
|
||||
import { getList as getMyList } from './list'
|
||||
import { downloadList } from './download'
|
||||
|
||||
|
||||
export const getList = listId => {
|
||||
return listId == 'download' ? downloadList : getMyList(listId)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { ref } from '@renderer/utils/vueTools'
|
||||
|
||||
|
||||
export const volume = ref(0)
|
||||
export const isMute = ref(false)
|
||||
|
||||
export const setVolume = num => {
|
||||
volume.value = num
|
||||
}
|
||||
|
||||
export const setMute = flag => {
|
||||
isMute.value = flag
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { useAction, useCommit } from '@renderer/utils/vueTools'
|
||||
import { tempList } from '@renderer/core/share/list'
|
||||
|
||||
const getListPlayIndex = (list, index) => {
|
||||
if (index == null) {
|
||||
index = 1
|
||||
} else {
|
||||
index = parseInt(index)
|
||||
if (Number.isNaN(index)) {
|
||||
index = 1
|
||||
} else {
|
||||
if (index < 1) index = 1
|
||||
else if (index > list.length) index = list.length
|
||||
}
|
||||
}
|
||||
return index - 1
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const getListDetail = useAction('songList', 'getListDetail')
|
||||
const getListDetailAll = useAction('songList', 'getListDetailAll')
|
||||
const setTempList = useCommit('player', 'setTempList')
|
||||
const updateTempList = useCommit('player', 'updateTempList')
|
||||
|
||||
const playSongListDetail = async(source, link, playIndex) => {
|
||||
console.log(source, link, playIndex)
|
||||
if (link == null) return
|
||||
let isPlayingList = false
|
||||
const id = decodeURIComponent(link)
|
||||
const playListId = `${source}__${decodeURIComponent(link)}`
|
||||
let list
|
||||
try {
|
||||
list = await getListDetail({ source, id, page: 1 })
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
if (list.length > playIndex) {
|
||||
isPlayingList = true
|
||||
setTempList({
|
||||
list,
|
||||
index: getListPlayIndex(list, playIndex),
|
||||
id: playListId,
|
||||
})
|
||||
}
|
||||
getListDetailAll({ source, id }).then(list => {
|
||||
if (isPlayingList) {
|
||||
if (tempList.meta.id == id) {
|
||||
updateTempList({
|
||||
list,
|
||||
id: playListId,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
setTempList({
|
||||
list,
|
||||
index: getListPlayIndex(list, playIndex),
|
||||
id: playListId,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (source, link, playIndex) => {
|
||||
playSongListDetail(source, link, playIndex)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { playList } from '@renderer/core/player'
|
||||
import { setTempList } from '@renderer/store/list/action'
|
||||
import { tempList, tempListMeta } from '@renderer/store/list/state'
|
||||
import { getListDetail, getListDetailAll } from '@renderer/store/songList/action'
|
||||
|
||||
const getListPlayIndex = (list: LX.Music.MusicInfoOnline[], index?: number) => {
|
||||
if (index == null) {
|
||||
index = 1
|
||||
} else {
|
||||
if (index < 1) index = 1
|
||||
else if (index > list.length) index = list.length
|
||||
}
|
||||
return index - 1
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const playSongListDetail = async(source: LX.OnlineSource, link: string, playIndex?: number) => {
|
||||
// console.log(source, link, playIndex)
|
||||
if (link == null) return
|
||||
let isPlayingList = false
|
||||
const id = decodeURIComponent(link)
|
||||
const playListId = `${source}__${decodeURIComponent(link)}`
|
||||
let list = (await getListDetail(id, source, 1)).list
|
||||
if (playIndex != null && list.length > playIndex) {
|
||||
isPlayingList = true
|
||||
await setTempList(playListId, list)
|
||||
playList(tempList.id, getListPlayIndex(list, playIndex))
|
||||
}
|
||||
list = await getListDetailAll(id, source)
|
||||
if (isPlayingList) {
|
||||
if (tempListMeta.id == id) await setTempList(playListId, list)
|
||||
} else {
|
||||
await setTempList(playListId, list)
|
||||
playList(tempList.id, getListPlayIndex(list, playIndex))
|
||||
}
|
||||
}
|
||||
|
||||
return async(source: LX.OnlineSource, link: string, playIndex?: number) => {
|
||||
try {
|
||||
await playSongListDetail(source, link, playIndex)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
throw new Error('Get play list failed.')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import { isLinux } from '@common/utils'
|
||||
import { getEnvParams, setIgnoreMouseEvents } from '@renderer/utils/tools'
|
||||
|
||||
import { useRefGetter } from '@renderer/utils/vueTools'
|
||||
import { sync, apiSource, proxy, isFullscreen } from '@renderer/core/share'
|
||||
|
||||
import useSync from './useSync'
|
||||
import useUpdate from './useUpdate'
|
||||
import useDataInit from './useDataInit'
|
||||
import useHandleEnvParams from './useHandleEnvParams'
|
||||
import useEventListener from './useEventListener'
|
||||
import useDeepLink from './useDeepLink'
|
||||
import usePlayer from './usePlayer'
|
||||
import useListAutoUpdate from './useListAutoUpdate'
|
||||
|
||||
|
||||
export default () => {
|
||||
const isProd = process.env.NODE_ENV === 'production'
|
||||
|
||||
const setting = useRefGetter('setting')
|
||||
|
||||
sync.enable = setting.value.sync.enable
|
||||
apiSource.value = setting.value.apiSource
|
||||
Object.assign(proxy, setting.value.network.proxy)
|
||||
isFullscreen.value = setting.value.startInFullscreen
|
||||
|
||||
const dieableIgnoreMouseEvents = () => {
|
||||
if (window.dt) return
|
||||
setIgnoreMouseEvents(false)
|
||||
}
|
||||
const enableIgnoreMouseEvents = () => {
|
||||
if (window.dt) return
|
||||
setIgnoreMouseEvents(true)
|
||||
}
|
||||
|
||||
useUpdate(setting)
|
||||
useSync()
|
||||
useEventListener({
|
||||
dieableIgnoreMouseEvents,
|
||||
enableIgnoreMouseEvents,
|
||||
setting,
|
||||
isProd,
|
||||
isLinux,
|
||||
})
|
||||
const initPlayer = usePlayer({ setting })
|
||||
const handleEnvParams = useHandleEnvParams()
|
||||
const initData = useDataInit({
|
||||
setting,
|
||||
})
|
||||
const initDeepLink = useDeepLink()
|
||||
const handleListAutoUpdate = useListAutoUpdate()
|
||||
|
||||
|
||||
getEnvParams().then(envParams => {
|
||||
// 移除代理相关的环境变量设置,防止请求库自动应用它们
|
||||
// eslint-disable-next-line no-undef
|
||||
const processEnv = ENVIRONMENT
|
||||
for (const key of Object.keys(processEnv)) {
|
||||
if (/^(?:http_proxy|https_proxy|NO_PROXY)$/i.test(key)) delete processEnv[key]
|
||||
}
|
||||
const envProxy = envParams.cmdParams['proxy-server']
|
||||
if (envProxy && typeof envProxy == 'string') {
|
||||
const [host, port = ''] = envProxy.split(':')
|
||||
proxy.envProxy = {
|
||||
host,
|
||||
port,
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化我的列表、下载列表等数据
|
||||
initData().then(() => {
|
||||
initPlayer()
|
||||
handleEnvParams(envParams) // 处理传入的启动参数
|
||||
initDeepLink(envParams)
|
||||
handleListAutoUpdate()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { checkUpdate, getEnvParams, getViewPrevState, sendInited } from '@renderer/utils/ipc'
|
||||
|
||||
import { proxy, isFullscreen, themeId } from '@renderer/store'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
import useSync from './useSync'
|
||||
import useUpdate from './useUpdate'
|
||||
import useDataInit from './useDataInit'
|
||||
// import useHandleEnvParams from './useHandleEnvParams'
|
||||
import useEventListener from './useEventListener'
|
||||
import useDeeplink from './useDeeplink'
|
||||
import usePlayer from './usePlayer'
|
||||
import handleListAutoUpdate from './listAutoUpdate'
|
||||
import { useRouter } from '@common/utils/vueRouter'
|
||||
import useSettingSync from './useSettingSync'
|
||||
|
||||
|
||||
export default () => {
|
||||
// apiSource.value = appSetting['common.apiSource']
|
||||
proxy.enable = appSetting['network.proxy.enable']
|
||||
proxy.host = appSetting['network.proxy.host']
|
||||
proxy.port = appSetting['network.proxy.port']
|
||||
proxy.username = appSetting['network.proxy.username']
|
||||
proxy.password = appSetting['network.proxy.password']
|
||||
isFullscreen.value = appSetting['common.startInFullscreen']
|
||||
themeId.value = appSetting['theme.id']
|
||||
|
||||
const router = useRouter()
|
||||
const initSyncService = useSync()
|
||||
useEventListener()
|
||||
const initPlayer = usePlayer()
|
||||
// const handleEnvParams = useHandleEnvParams()
|
||||
const initData = useDataInit()
|
||||
const initDeeplink = useDeeplink()
|
||||
// const handleListAutoUpdate = useListAutoUpdate()
|
||||
|
||||
useUpdate()
|
||||
useSettingSync()
|
||||
|
||||
void getEnvParams().then(envParams => {
|
||||
// 移除代理相关的环境变量设置,防止请求库自动应用它们
|
||||
// eslint-disable-next-line no-undef
|
||||
// const processEnv = ENVIRONMENT
|
||||
// for (const key of Object.keys(processEnv)) {
|
||||
// // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
// if (/^(?:http_proxy|https_proxy|NO_PROXY)$/i.test(key)) delete processEnv[key]
|
||||
// }
|
||||
const envProxy = envParams.cmdParams['proxy-server']
|
||||
if (envProxy && typeof envProxy == 'string') {
|
||||
const [host, port = ''] = envProxy.split(':')
|
||||
proxy.envProxy = {
|
||||
host,
|
||||
port,
|
||||
}
|
||||
}
|
||||
|
||||
void getViewPrevState().then(state => {
|
||||
void router.push({ path: state.url, query: state.query })
|
||||
})
|
||||
|
||||
// 初始化我的列表、下载列表等数据
|
||||
void initData().then(() => {
|
||||
initPlayer()
|
||||
// handleEnvParams(envParams) // 处理传入的启动参数
|
||||
void initDeeplink(envParams)
|
||||
void initSyncService()
|
||||
handleListAutoUpdate()
|
||||
if (window.lx.isDev) checkUpdate()
|
||||
|
||||
sendInited()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { getListUpdateInfo } from '@renderer/utils/data'
|
||||
import { userLists } from '@renderer/store/list/state'
|
||||
import syncSourceList from '@renderer/store/list/syncSourceList'
|
||||
|
||||
const handleSyncSourceList = async(waitUpdateLists: LX.List.UserListInfo[]) => {
|
||||
if (!waitUpdateLists.length) return
|
||||
const targetListInfo = waitUpdateLists.shift() as LX.List.UserListInfo
|
||||
// console.log(targetListInfo)
|
||||
try {
|
||||
await syncSourceList(targetListInfo)
|
||||
} catch {}
|
||||
void handleSyncSourceList(waitUpdateLists)
|
||||
}
|
||||
|
||||
export default () => {
|
||||
void getListUpdateInfo().then(listUpdateInfo => {
|
||||
const waitUpdateLists = Object.entries(listUpdateInfo)
|
||||
.map(([id, info]) => info.isAutoUpdate && userLists.find(l => l.id == id))
|
||||
.filter(_ => _) as LX.List.UserListInfo[]
|
||||
for (let i = 2; i > 0; i--) {
|
||||
void handleSyncSourceList(waitUpdateLists)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
import { useCommit, useRefGetter } from '@renderer/utils/vueTools'
|
||||
import { getPlayList } from '@renderer/utils'
|
||||
import { getPlayInfo, getSearchHistoryList } from '@renderer/utils/tools'
|
||||
import { initListPosition, initListPrevSelectId, initListUpdateInfo } from '@renderer/utils/data'
|
||||
import music from '@renderer/utils/music'
|
||||
import { log } from '@common/utils'
|
||||
import {
|
||||
defaultList as stateDefaultList,
|
||||
loveList as stateloveList,
|
||||
userLists as stateUserLists,
|
||||
tempList as stateTempList,
|
||||
} from '@renderer/core/share/list'
|
||||
import { getList } from '@renderer/core/share/utils'
|
||||
import { downloadStatus, downloadList } from '@renderer/core/share/download'
|
||||
|
||||
|
||||
import useSaveData from './useSaveData'
|
||||
import useInitUserApi from './useInitUserApi'
|
||||
|
||||
const useListInit = ({
|
||||
saveMyListThrottle,
|
||||
}) => {
|
||||
const initList = useCommit('list', 'initList')
|
||||
const updateDownloadList = useCommit('download', 'initDownloadList')
|
||||
|
||||
const initMyList = ({ defaultList, loveList, userList, tempList }) => {
|
||||
if (!defaultList) defaultList = { ...stateDefaultList }
|
||||
if (!loveList) loveList = { ...stateloveList }
|
||||
if (!tempList) tempList = { ...stateTempList }
|
||||
if (userList) {
|
||||
let needSave = false
|
||||
const getListId = id => id.includes('.') ? getListId(id.substring(0, id.lastIndexOf('_'))) : id
|
||||
userList.forEach(l => {
|
||||
if (!l.id.includes('__') || l.source) return
|
||||
let [source, id] = l.id.split('__')
|
||||
id = getListId(id)
|
||||
l.source = source
|
||||
l.sourceListId = id
|
||||
if (!needSave) needSave = true
|
||||
})
|
||||
if (needSave) saveMyListThrottle({ userList })
|
||||
} else {
|
||||
userList = [...stateUserLists]
|
||||
}
|
||||
|
||||
if (!defaultList.list) defaultList.list = []
|
||||
if (!loveList.list) loveList.list = []
|
||||
if (!tempList.list) tempList.list = []
|
||||
initList({ defaultList, loveList, userList, tempList })
|
||||
}
|
||||
|
||||
const initDownloadList = list => {
|
||||
if (list) {
|
||||
list = list.filter(item => item?.key)
|
||||
for (const item of list) {
|
||||
if (item.status == downloadStatus.RUN || item.status == downloadStatus.WAITING) {
|
||||
item.status = downloadStatus.PAUSE
|
||||
item.statusText = '暂停下载'
|
||||
}
|
||||
|
||||
if (!item.metadata) { // 转换v1.15.3及以前的任务信息
|
||||
if (item.name == null) {
|
||||
item.name = `${item.musicInfo.name} - ${item.musicInfo.singer}`
|
||||
item.songmid = item.musicInfo.songmid
|
||||
}
|
||||
item.metadata = {
|
||||
musicInfo: item.musicInfo,
|
||||
url: item.url,
|
||||
type: item.type,
|
||||
ext: item.ext,
|
||||
fileName: item.fileName,
|
||||
filePath: item.filePath,
|
||||
}
|
||||
delete item.musicInfo
|
||||
delete item.url
|
||||
delete item.type
|
||||
delete item.ext
|
||||
delete item.fileName
|
||||
delete item.filePath
|
||||
}
|
||||
}
|
||||
updateDownloadList(list)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
return getPlayList().then(({ defaultList, loveList, userList, tempList, downloadList }) => {
|
||||
initMyList({ defaultList, loveList, userList, tempList })
|
||||
initDownloadList(downloadList) // 初始化下载列表
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const usePlayInfoInit = () => {
|
||||
const setPlayList = useCommit('player', 'setList')
|
||||
|
||||
return () => {
|
||||
return getPlayInfo().then(info => {
|
||||
window.restorePlayInfo = null
|
||||
if (!info?.listId || info.index < 0) return
|
||||
const list = getList(info.listId)
|
||||
if (!list || !list[info.index]) return
|
||||
window.restorePlayInfo = info
|
||||
setPlayList({
|
||||
listId: info.listId,
|
||||
index: info.index,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const useSearchHistoryInit = () => {
|
||||
const setSearchHistoryList = useCommit('search', 'setHistory')
|
||||
return saveSearchHistoryListThrottle => {
|
||||
return getSearchHistoryList().then(historyList => {
|
||||
if (historyList == null) {
|
||||
historyList = []
|
||||
saveSearchHistoryListThrottle(historyList)
|
||||
} else {
|
||||
setSearchHistoryList(historyList)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ({
|
||||
setting,
|
||||
}) => {
|
||||
const searchHistoryList = useRefGetter('search', 'historyList')
|
||||
|
||||
// 数据保存初始化
|
||||
const { saveMyListThrottle, saveSearchHistoryListThrottle } = useSaveData({
|
||||
setting,
|
||||
searchHistoryList,
|
||||
})
|
||||
|
||||
const initUserApi = useInitUserApi({ setting })
|
||||
|
||||
// 列表初始化
|
||||
const initList = useListInit({
|
||||
saveMyListThrottle,
|
||||
})
|
||||
|
||||
|
||||
// 播放信息初始化
|
||||
const initPlayInfo = usePlayInfoInit()
|
||||
|
||||
// 搜索历史初始化
|
||||
const initSearchHistory = useSearchHistoryInit()
|
||||
|
||||
return async() => {
|
||||
await Promise.all([
|
||||
initListPosition(), // 列表位置记录
|
||||
initListPrevSelectId(), // 上次选中的列表记录
|
||||
initListUpdateInfo(), // 列表更新设置
|
||||
initUserApi(), // 自定义API
|
||||
]).catch(err => log.error(err))
|
||||
music.init() // 初始化音乐sdk
|
||||
await initList().catch(err => log.error(err)) // 初始化列表
|
||||
await initPlayInfo(downloadList).catch(err => log.error(err)) // 初始化上次的歌曲播放信息
|
||||
await initSearchHistory(saveSearchHistoryListThrottle).catch(err => log.error(err)) // 初始化搜索历史记录
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { getPlayInfo } from '@renderer/utils/ipc'
|
||||
import music from '@renderer/utils/musicSdk'
|
||||
import { log } from '@common/utils'
|
||||
import { getListMusics, getUserLists, registerAction } from '@renderer/store/list/action'
|
||||
|
||||
|
||||
import useInitUserApi from './useInitUserApi'
|
||||
import { play, playList } from '@renderer/core/player'
|
||||
import { onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
const initPrevPlayInfo = async() => {
|
||||
const info = await getPlayInfo()
|
||||
window.lx.restorePlayInfo = null
|
||||
if (!info?.listId || info.index < 0) return
|
||||
const list = await getListMusics(info.listId)
|
||||
if (!list || !list[info.index]) return
|
||||
window.lx.restorePlayInfo = info
|
||||
playList(info.listId, info.index)
|
||||
|
||||
if (appSetting['player.startupAutoPlay']) setTimeout(play)
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const initUserApi = useInitUserApi()
|
||||
|
||||
let unregister: null | (() => void) = null
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (unregister) unregister()
|
||||
})
|
||||
|
||||
return async() => {
|
||||
await Promise.all([
|
||||
initUserApi(), // 自定义API
|
||||
]).catch(err => log.error(err))
|
||||
void music.init() // 初始化音乐sdk
|
||||
unregister = registerAction((ids) => {
|
||||
window.app_event.myListUpdate(ids)
|
||||
})
|
||||
window.lxData.userLists = await getUserLists() // 获取用户列表
|
||||
await initPrevPlayInfo().catch(err => log.error(err)) // 初始化上次的歌曲播放信息
|
||||
}
|
||||
}
|
||||
+28
-26
@@ -1,6 +1,5 @@
|
||||
import { onBeforeUnmount } from '@renderer/utils/vueTools'
|
||||
import { base as eventBaseName } from '@renderer/event/names'
|
||||
import { getEnvParams, clearEnvParamsDeeplink } from '@renderer/utils/tools'
|
||||
import { onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { clearEnvParamsDeeplink, focusWindow, onDeeplink } from '@renderer/utils/ipc'
|
||||
|
||||
import { useDialog } from './utils'
|
||||
import useMusicAction from './useMusicAction'
|
||||
@@ -15,11 +14,17 @@ export default () => {
|
||||
const handleSonglistAction = useSonglistAction()
|
||||
|
||||
|
||||
const handleLinkAction = link => {
|
||||
const handleLinkAction = async(link: string) => {
|
||||
// console.log(link)
|
||||
const [url, search] = link.split('?')
|
||||
const [type, action, ...paths] = url.replace('lxmusic://', '').split('/')
|
||||
const params = {}
|
||||
const params: {
|
||||
paths: string[]
|
||||
data?: any
|
||||
[key: string]: any
|
||||
} = {
|
||||
paths: [],
|
||||
}
|
||||
if (search) {
|
||||
for (const param of search.split('&')) {
|
||||
const [key, value] = param.split('=')
|
||||
@@ -34,39 +39,36 @@ export default () => {
|
||||
handleMusicAction(action, params)
|
||||
break
|
||||
case 'songlist':
|
||||
handleSonglistAction(action, params)
|
||||
await handleSonglistAction(action, params)
|
||||
break
|
||||
default: throw new Error('Unknown type: ' + type)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFocus = () => {
|
||||
const rDeeplink = onDeeplink(async({ params: link }) => {
|
||||
console.log(link)
|
||||
if (!isInited) return
|
||||
|
||||
getEnvParams().then(envParams => {
|
||||
if (!envParams.deeplink) return
|
||||
clearEnvParamsDeeplink()
|
||||
try {
|
||||
handleLinkAction(envParams.deeplink)
|
||||
} catch (err) {
|
||||
showErrorDialog(err.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.eventHub.on(eventBaseName.focus, handleFocus)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.eventHub.off(eventBaseName.focus, handleFocus)
|
||||
clearEnvParamsDeeplink()
|
||||
try {
|
||||
await handleLinkAction(link)
|
||||
} catch (err: any) {
|
||||
showErrorDialog(err.message)
|
||||
focusWindow()
|
||||
}
|
||||
})
|
||||
|
||||
return envParams => {
|
||||
onBeforeUnmount(() => {
|
||||
rDeeplink()
|
||||
})
|
||||
|
||||
return async(envParams: LX.EnvParams) => {
|
||||
if (envParams.deeplink) {
|
||||
clearEnvParamsDeeplink()
|
||||
try {
|
||||
handleLinkAction(envParams.deeplink)
|
||||
} catch (err) {
|
||||
await handleLinkAction(envParams.deeplink)
|
||||
} catch (err: any) {
|
||||
showErrorDialog(err.message)
|
||||
focusWindow()
|
||||
}
|
||||
}
|
||||
isInited = true
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user