v2.0.0
This commit is contained in:
@@ -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,309 +0,0 @@
|
||||
<template>
|
||||
<teleport to="#root">
|
||||
<div :class="$style.container" :style="menuStyles" ref="dom_menu" :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]: playDetailSetting.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]: playDetailSetting.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: playDetailSetting.style.fontSize })}}</div>
|
||||
<button :class="[$style.btn, $style.titleBtn]" :disabled="playDetailSetting.style.fontSize == 100" @click="fontSizeReset" ignore-tip :aria-label="$t('lyric_menu__size_reset')">{{$t('lyric_menu__size_reset')}}</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" @click="fontSizeUp(5)" @contextmenu="fontSizeUp(1)" :aria-label="$t('lyric_menu__size_add')">
|
||||
<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"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button :class="$style.btn" @click="fontSizeDown(5)" @contextmenu="fontSizeDown(1)" :aria-label="$t('lyric_menu__size_dec')">
|
||||
<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"></use>
|
||||
</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" @click="setOffset(10)" ignore-tip :aria-label="$t('lyric_menu__offset_add_10')">+ 10ms</button>
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(-10)" ignore-tip :aria-label="$t('lyric_menu__offset_dec_10')">- 10ms</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(100)" ignore-tip :aria-label="$t('lyric_menu__offset_add_100')">+ 100ms</button>
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(-100)" ignore-tip :aria-label="$t('lyric_menu__offset_dec_100')">- 100ms</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, useRefGetter, ref, useCommit, watch } from '@renderer/utils/vueTools'
|
||||
import useMenuLocation from '@renderer/utils/compositions/useMenuLocation'
|
||||
import { setLyricEdited, removeLyricEdited, debounce } from '@renderer/utils'
|
||||
|
||||
const offsetTagRxp = /(?:^|\n)\s*\[offset:\s*(\S+(?:\d+)*)\s*\]/
|
||||
const offsetTagAllRxp = /(?:^|\n)\s*\[offset:\s*(\S+(?:\d+)*)\s*\]/g
|
||||
|
||||
const saveLyric = debounce((musicInfo, lyricInfo) => {
|
||||
setLyricEdited(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: Object,
|
||||
lyricInfo: Object,
|
||||
},
|
||||
emits: ['updateLyric', 'update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const setting = useRefGetter('setting')
|
||||
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 musicInfo = computed(() => props.lyricInfo.musicInfo)
|
||||
const location = computed(() => props.xy)
|
||||
|
||||
const onHide = () => {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
const setFontAlign = val => {
|
||||
if (playDetailSetting.value.style.align == val) return
|
||||
setPlayDetailLyricAlign(val)
|
||||
}
|
||||
|
||||
const fontSizeUp = step => {
|
||||
if (setting.value.playDetail.style.fontSize >= 200) return
|
||||
setPlayDetailLyricFont(Math.min(setting.value.playDetail.style.fontSize + step, 200))
|
||||
}
|
||||
const fontSizeDown = step => {
|
||||
if (setting.value.playDetail.style.fontSize <= 70) return
|
||||
setPlayDetailLyricFont(Math.max(setting.value.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, `[offset:${offset}]`)
|
||||
if (tlyric) tlyric = tlyric.replace(offsetTagAllRxp, `[offset:${offset}]`)
|
||||
if (lxlyric) lxlyric = lxlyric.replace(offsetTagAllRxp, `[offset:${offset}]`)
|
||||
if (rlyric) rlyric = rlyric.replace(offsetTagAllRxp, `[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
|
||||
}
|
||||
|
||||
if (offset == originOffset.value) {
|
||||
removeLyric(props.lyricInfo.musicInfo)
|
||||
} else {
|
||||
saveLyric(props.lyricInfo.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(musicInfo, () => {
|
||||
if (!props.modelValue) return
|
||||
parseLrcOffset()
|
||||
})
|
||||
watch(visible, val => {
|
||||
if (!val) return
|
||||
parseLrcOffset()
|
||||
})
|
||||
|
||||
return {
|
||||
dom_menu,
|
||||
menuStyles,
|
||||
playDetailSetting,
|
||||
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: .25s ease;
|
||||
transition-property: transform, opacity;
|
||||
border-radius: @radius-border;
|
||||
background-color: @color-theme_2-background_2;
|
||||
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: @color-theme_2-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-theme;
|
||||
transition-property: background-color, opacity;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
background-color: @color-theme_2-background_2;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
&.active {
|
||||
background-color: @color-theme_2-background_2;
|
||||
color: @color-btn-active;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.container {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
}
|
||||
.btn {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
&.active {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
color: ~'@{color-@{value}-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>
|
||||
|
||||
+26
-20
@@ -1,8 +1,11 @@
|
||||
import { computed, useRefGetter, watch, ref, onBeforeUnmount } from '@renderer/utils/vueTools'
|
||||
import { windowSizeList, isFullscreen } from '@renderer/core/share'
|
||||
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 }) => {
|
||||
const useKeyEvent = ({ handleSelectAllData }: {
|
||||
handleSelectAllData: () => void
|
||||
}) => {
|
||||
const keyEvent = {
|
||||
isShiftDown: false,
|
||||
isModDown: false,
|
||||
@@ -20,8 +23,8 @@ const useKeyEvent = ({ handleSelectAllData }) => {
|
||||
const handle_key_mod_up = () => {
|
||||
if (keyEvent.isModDown) keyEvent.isModDown = false
|
||||
}
|
||||
const handle_key_mod_a_down = ({ event }) => {
|
||||
if (event.target.tagName == 'INPUT') return
|
||||
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
|
||||
@@ -29,28 +32,31 @@ const useKeyEvent = ({ handleSelectAllData }) => {
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.eventHub.off('key_shift_down', handle_key_shift_down)
|
||||
window.eventHub.off('key_shift_up', handle_key_shift_up)
|
||||
window.eventHub.off('key_mod_down', handle_key_mod_down)
|
||||
window.eventHub.off('key_mod_up', handle_key_mod_up)
|
||||
window.eventHub.off('key_mod+a_down', handle_key_mod_a_down)
|
||||
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.eventHub.on('key_shift_down', handle_key_shift_down)
|
||||
window.eventHub.on('key_shift_up', handle_key_shift_up)
|
||||
window.eventHub.on('key_mod_down', handle_key_mod_down)
|
||||
window.eventHub.on('key_mod_up', handle_key_mod_up)
|
||||
window.eventHub.on('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 }) => {
|
||||
const selectedList = ref([])
|
||||
const setting = useRefGetter('setting')
|
||||
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() : parseInt(windowSizeList.find(item => item.id == setting.value.windowSizeId).fontSize)) * 2.3)
|
||||
return Math.ceil((isFullscreen.value ? getFontSizeWithScreen() : appSetting['common.fontSize']) * 2.3)
|
||||
})
|
||||
|
||||
const removeAllSelect = () => {
|
||||
@@ -62,7 +68,7 @@ export default ({ props }) => {
|
||||
}
|
||||
const keyEvent = useKeyEvent({ handleSelectAllData })
|
||||
|
||||
const handleSelectData = clickIndex => {
|
||||
const handleSelectData = (clickIndex: number) => {
|
||||
if (keyEvent.isShiftDown) {
|
||||
if (selectedList.value.length) {
|
||||
removeAllSelect()
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user