fix path
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<template lang="pug">
|
||||
material-modal(:show="modelValue" bg-close @close="handleCloseModal" @after-enter="$refs.dom_input.focus()" teleport="#view")
|
||||
main(:class="$style.main")
|
||||
h2 {{$t('play_timeout')}}
|
||||
div(:class="$style.content")
|
||||
div(:class="[$style.row, $style.inputGroup]")
|
||||
base-input(:class="$style.input" ref="dom_input" v-model="time" type="number")
|
||||
p(:class="$style.inputLabel") {{$t('play_timeout_unit')}}
|
||||
div(:class="$style.row")
|
||||
base-checkbox(id="play_timeout_end" :modelValue="appSetting['player.waitPlayEndStop']" @update:modelValue="updateSetting({'player.waitPlayEndStop': $event})" :label="$t('play_timeout_end')")
|
||||
div(:class="[$style.row, $style.tip, { [$style.show]: !!timeLabel }]")
|
||||
p {{$t('play_timeout_tip', { time: timeLabel })}}
|
||||
div(:class="$style.footer")
|
||||
base-btn(:class="$style.footerBtn" @click="handleCancel") {{$t(timeLabel ? 'play_timeout_stop' : 'play_timeout_close')}}
|
||||
base-btn(:class="$style.footerBtn" @click="handleConfirm") {{$t(timeLabel ? 'play_timeout_update' : 'play_timeout_confirm')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useTimeout, startTimeoutStop, stopTimeoutStop } from '@renderer/core/player/timeoutStop'
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
const MAX_MIN = 1440
|
||||
|
||||
const rxp = /([1-9]\d*)/
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const { timeLabel } = useTimeout()
|
||||
const time = ref(appSetting['player.waitPlayEndStopTime'])
|
||||
|
||||
const handleCloseModal = () => {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
const handleCancel = () => {
|
||||
if (timeLabel.value) {
|
||||
stopTimeoutStop()
|
||||
}
|
||||
handleCloseModal()
|
||||
}
|
||||
const verify = () => {
|
||||
const orgText = time.value
|
||||
let text = time.value
|
||||
|
||||
if (rxp.test(text)) {
|
||||
text = RegExp.$1
|
||||
if (parseInt(text) > MAX_MIN) {
|
||||
text = text.substring(0, text.length - 1)
|
||||
}
|
||||
} else {
|
||||
text = ''
|
||||
}
|
||||
time.value = text
|
||||
return text && orgText == text ? parseInt(text) : ''
|
||||
}
|
||||
const handleConfirm = () => {
|
||||
let time = verify()
|
||||
if (time == '') return
|
||||
if (appSetting['player.waitPlayEndStopTime'] != time) updateSetting({ 'player.waitPlayEndStopTime': time })
|
||||
startTimeoutStop(time * 60)
|
||||
handleCloseModal()
|
||||
}
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
timeLabel,
|
||||
time,
|
||||
handleCloseModal,
|
||||
handleCancel,
|
||||
handleConfirm,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 15px;
|
||||
max-width: 530px;
|
||||
min-width: 280px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding-top: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.row {
|
||||
padding-top: 5px;
|
||||
}
|
||||
.inputGroup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.input {
|
||||
flex: auto;
|
||||
}
|
||||
.inputLabel {
|
||||
flex: none;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.tip {
|
||||
visibility: hidden;
|
||||
|
||||
&.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
.footerBtn {
|
||||
flex: auto;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
padding: 0 10px !important;
|
||||
width: 150px;
|
||||
.mixin-ellipsis-1;
|
||||
+ .footerBtn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
.ruleLink {
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
<template lang="pug">
|
||||
dt#about {{$t('setting__about')}}
|
||||
dd
|
||||
p.small
|
||||
| 本软件完全免费,代码已开源,开源地址:
|
||||
span.hover.underline(:aria-label="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop#readme')") https://github.com/lyswhut/lx-music-desktop
|
||||
p.small
|
||||
| 最新版网盘下载地址(网盘内有Windows、MAC版):
|
||||
span.hover.underline(:aria-label="$t('setting__click_open')" @click="openUrl('https://www.lanzoui.com/b0bf2cfa/')") 网盘地址
|
||||
| 密码:
|
||||
span.hover(:aria-label="$t('setting__click_copy')" @click="clipboardWriteText('glqw')") glqw
|
||||
p.small
|
||||
| 软件的常见问题可转至:
|
||||
span.hover.underline(:aria-label="$t('setting__click_open')" @click="openUrl('https://lyswhut.github.io/lx-music-doc/desktop/faq')") 常见问题
|
||||
p.small
|
||||
strong 本软件没有客服
|
||||
| ,但我们整理了一些常见的使用问题,
|
||||
strong 仔细 仔细 仔细
|
||||
| 地阅读常见问题后,
|
||||
p.small
|
||||
| 仍有问题可加企鹅群
|
||||
span.hover(:aria-label="$t('setting__click_open')" @click="openUrl('https://jq.qq.com/?_wv=1027&k=51ECeq2')") 830125506
|
||||
| 反馈
|
||||
strong (为免满人,无事勿加,入群先看群公告)
|
||||
| ,或到 GitHub 提交
|
||||
span.hover.underline(:aria-label="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop/issues')") issue
|
||||
|
||||
br
|
||||
p.small
|
||||
| 如果你喜欢并经常使用洛雪音乐,并想要第一时间尝鲜洛雪的新功能
|
||||
span(style="text-decoration: line-through;") (当小白鼠)
|
||||
| ,
|
||||
p
|
||||
| 可以加入测试企鹅群
|
||||
span.hover(:aria-label="$t('setting__click_open')" @click="openUrl('https://qm.qq.com/cgi-bin/qm/qr?k=zR6aYosQoKb07g4FGFZdO9n9zL1dhFpE&jump_from=webapi')") 768786588
|
||||
| ,注意:测试版的功能可能会不稳定,
|
||||
strong 打算潜水的勿加
|
||||
|
||||
br
|
||||
p.small 由于软件开发的初衷仅是为了对新技术的学习与研究,因此软件直至停止维护都将会一直保持纯净。
|
||||
|
||||
p.small
|
||||
| 你已签署本软件的
|
||||
base-btn(min @click="handleShowPact") 许可协议
|
||||
| ,协议的在线版本在
|
||||
strong.hover.underline(:aria-label="$t('setting__click_open')" @click="openUrl('https://github.com/lyswhut/lx-music-desktop#%E9%A1%B9%E7%9B%AE%E5%8D%8F%E8%AE%AE')") 这里
|
||||
| 。
|
||||
br
|
||||
|
||||
p
|
||||
small By:
|
||||
| 落雪无痕
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { isShowPact } from '@renderer/store'
|
||||
import { openUrl, clipboardWriteText } from '@common/utils/electron'
|
||||
|
||||
export default {
|
||||
name: 'SettingAbout',
|
||||
setup() {
|
||||
const handleShowPact = () => {
|
||||
isShowPact.value = true
|
||||
}
|
||||
return {
|
||||
openUrl,
|
||||
clipboardWriteText,
|
||||
handleShowPact,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,380 @@
|
||||
<template lang="pug">
|
||||
dt#backup {{$t('setting__backup')}}
|
||||
dd
|
||||
h3#backup_part {{$t('setting__backup_part')}}
|
||||
div
|
||||
base-btn.btn.gap-left(min @click="handleImportPlayList") {{$t('setting__backup_part_import_list')}}
|
||||
base-btn.btn.gap-left(min @click="handleExportPlayList") {{$t('setting__backup_part_export_list')}}
|
||||
base-btn.btn.gap-left(min @click="handleImportSetting") {{$t('setting__backup_part_import_setting')}}
|
||||
base-btn.btn.gap-left(min @click="handleExportSetting") {{$t('setting__backup_part_export_setting')}}
|
||||
dd
|
||||
h3#backup_all {{$t('setting__backup_all')}}
|
||||
div
|
||||
base-btn.btn.gap-left(min @click="handleImportAllData") {{$t('setting__backup_all_import')}}
|
||||
base-btn.btn.gap-left(min @click="handleExportAllData") {{$t('setting__backup_all_export')}}
|
||||
dd
|
||||
h3#backup_other {{$t('setting__backup_other')}}
|
||||
div
|
||||
base-btn.btn.gap-left(min @click="handleExportPlayListToText") {{$t('setting__backup_other_export_list_text')}}
|
||||
base-btn.btn.gap-left(min @click="handleExportPlayListToCsv") {{$t('setting__backup_other_export_list_csv')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { toRaw } from '@common/utils/vueTools'
|
||||
// import { mergeSetting } from '@common/utils'
|
||||
// import { base as eventBaseName } from '@renderer/event/names'
|
||||
// import { defaultList, loveList, userLists } from '@renderer/core/share/list'
|
||||
import {
|
||||
toNewMusicInfo,
|
||||
// toOldMusicInfo,
|
||||
fixNewMusicInfoQuality,
|
||||
} from '@renderer/utils'
|
||||
import {
|
||||
showSelectDialog,
|
||||
openSaveDir,
|
||||
} from '@renderer/utils/ipc'
|
||||
// import { currentStting } from '../setting'
|
||||
import { dialog } from '@renderer/plugins/Dialog'
|
||||
import useImportTip from '@renderer/utils/compositions/useImportTip'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
import { createUserList, getListMusics, overwriteListMusics } from '@renderer/store/list/action'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
import { defaultList, loveList, userLists } from '@renderer/store/list/state'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
import migrateSetting from '@common/utils/migrateSetting'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'SettingUpdate',
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
// const setting = useRefGetter('setting')
|
||||
// const settingVersion = useRefGetter('settingVersion')
|
||||
// const setSettingVersion = useCommit('setSettingVersion')
|
||||
// const setList = useCommit('list', 'setList')
|
||||
const showImportTip = useImportTip()
|
||||
|
||||
const getAllLists = async() => {
|
||||
const lists = []
|
||||
lists.push(await getListMusics(defaultList.id).then(musics => ({ ...defaultList, list: toRaw(musics) })))
|
||||
lists.push(await getListMusics(loveList.id).then(musics => ({ ...loveList, list: toRaw(musics) })))
|
||||
|
||||
for await (const list of userLists) {
|
||||
lists.push(await getListMusics(list.id).then(musics => ({ ...toRaw(list), list: toRaw(musics) })))
|
||||
}
|
||||
|
||||
return lists
|
||||
}
|
||||
|
||||
const importOldListData = async(lists) => {
|
||||
for await (const list of lists) {
|
||||
try {
|
||||
if (userLists.some(l => l.id == list.id) || list.id == LIST_IDS.DEFAULT || list.id == LIST_IDS.LOVE) {
|
||||
await overwriteListMusics({ listId: list.id, musicInfos: list.list.map(m => toNewMusicInfo(m)) })
|
||||
} else {
|
||||
await createUserList({
|
||||
name: list.name,
|
||||
id: list.id,
|
||||
list: list.list.map(m => toNewMusicInfo(m)),
|
||||
position: list.position,
|
||||
source: list.source,
|
||||
sourceListId: list.sourceListId,
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
const importNewListData = async(lists) => {
|
||||
for await (const list of lists) {
|
||||
try {
|
||||
if (userLists.some(l => l.id == list.id) || list.id == LIST_IDS.DEFAULT || list.id == LIST_IDS.LOVE) {
|
||||
await overwriteListMusics({ listId: list.id, musicInfos: list.list.map(m => fixNewMusicInfoQuality(m)) })
|
||||
} else {
|
||||
await createUserList({
|
||||
name: list.name,
|
||||
id: list.id,
|
||||
list: list.list.map(m => fixNewMusicInfoQuality(m)),
|
||||
position: list.position,
|
||||
source: list.source,
|
||||
sourceListId: list.sourceListId,
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
const importOldSettingData = (setting) => {
|
||||
console.log(setting)
|
||||
setting = migrateSetting(setting)
|
||||
setting['common.isAgreePact'] = false
|
||||
updateSetting(setting)
|
||||
}
|
||||
const importNewSettingData = (setting) => {
|
||||
setting['common.isAgreePact'] = false
|
||||
updateSetting(setting)
|
||||
}
|
||||
|
||||
|
||||
const importAllData = async(path) => {
|
||||
let allData
|
||||
try {
|
||||
allData = await window.lx.worker.main.readLxConfigFile(path)
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (allData.type) {
|
||||
case 'allData':
|
||||
// 兼容0.6.2及以前版本的列表数据
|
||||
if (allData.defaultList) await overwriteListMusics({ listId: LIST_IDS.DEFAULT, musicInfos: allData.defaultList.list.map(m => toNewMusicInfo(m)) })
|
||||
else await importOldListData(allData.playList)
|
||||
importOldSettingData(allData.setting)
|
||||
break
|
||||
case 'allData_v2':
|
||||
await importNewListData(allData.playList)
|
||||
importNewSettingData(allData.setting)
|
||||
break
|
||||
default: return showImportTip(allData.type)
|
||||
}
|
||||
}
|
||||
const handleImportAllData = () => {
|
||||
showSelectDialog({
|
||||
title: t('setting__backup_all_import_desc'),
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'Setting', extensions: ['json', 'lxmc'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
],
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
dialog.confirm({
|
||||
message: t('setting__backup_part_import_list_confirm'),
|
||||
cancelButtonText: t('cancel_button_text'),
|
||||
confirmButtonText: t('confirm_button_text'),
|
||||
}).then(confirm => {
|
||||
if (!confirm) return
|
||||
importAllData(result.filePaths[0])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const exportAllData = async(path) => {
|
||||
let allData = {
|
||||
type: 'allData_v2',
|
||||
setting: { ...appSetting },
|
||||
playList: await getAllLists(),
|
||||
}
|
||||
window.lx.worker.main.saveLxConfigFile(path, allData)
|
||||
}
|
||||
const handleExportAllData = () => {
|
||||
openSaveDir({
|
||||
title: t('setting__backup_all_export_desc'),
|
||||
defaultPath: 'lx_datas_v2.lxmc',
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
exportAllData(result.filePath)
|
||||
})
|
||||
}
|
||||
|
||||
const exportSetting = (path) => {
|
||||
const data = {
|
||||
type: 'setting_v2',
|
||||
data: { ...appSetting },
|
||||
}
|
||||
window.lx.worker.main.saveLxConfigFile(path, data)
|
||||
}
|
||||
const handleExportSetting = () => {
|
||||
openSaveDir({
|
||||
title: t('setting__backup_part_export_setting_desc'),
|
||||
defaultPath: 'lx_setting_v2.lxmc',
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
exportSetting(result.filePath)
|
||||
})
|
||||
}
|
||||
|
||||
const importSetting = async(path) => {
|
||||
let settingData
|
||||
try {
|
||||
settingData = await window.lx.worker.main.readLxConfigFile(path)
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (settingData.type) {
|
||||
case 'setting':
|
||||
importOldSettingData(settingData.data)
|
||||
break
|
||||
case 'setting_v2':
|
||||
importNewSettingData(settingData.data)
|
||||
break
|
||||
default: return showImportTip(settingData.type)
|
||||
}
|
||||
}
|
||||
const handleImportSetting = () => {
|
||||
showSelectDialog({
|
||||
title: t('setting__backup_part_import_setting_desc'),
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'Setting', extensions: ['json', 'lxmc'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
],
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
importSetting(result.filePaths[0])
|
||||
})
|
||||
}
|
||||
|
||||
const exportPlayList = async(path) => {
|
||||
const data = {
|
||||
type: 'playList_v2',
|
||||
data: await getAllLists(),
|
||||
}
|
||||
window.lx.worker.main.saveLxConfigFile(path, data)
|
||||
}
|
||||
const handleExportPlayList = () => {
|
||||
openSaveDir({
|
||||
title: t('setting__backup_part_export_list_desc'),
|
||||
defaultPath: 'lx_list.lxmc',
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
exportPlayList(result.filePath)
|
||||
})
|
||||
}
|
||||
|
||||
const importPlayList = async(path) => {
|
||||
let listData
|
||||
try {
|
||||
listData = await window.lx.worker.main.readLxConfigFile(path)
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
console.log(listData.type)
|
||||
|
||||
switch (listData.type) {
|
||||
case 'defautlList': // 兼容0.6.2及以前版本的列表数据
|
||||
await overwriteListMusics({ listId: LIST_IDS.DEFAULT, musicInfos: listData.data.list.map(m => toNewMusicInfo(m)) })
|
||||
break
|
||||
case 'playList':
|
||||
await importOldListData(listData.data)
|
||||
break
|
||||
case 'playList_v2':
|
||||
await importNewListData(listData.data)
|
||||
break
|
||||
default: return showImportTip(listData.type)
|
||||
}
|
||||
}
|
||||
const handleImportPlayList = () => {
|
||||
showSelectDialog({
|
||||
title: t('setting__backup_part_import_list_desc'),
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'Play List', extensions: ['json', 'lxmc'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
],
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
dialog.confirm({
|
||||
message: t('setting__backup_part_import_list_confirm'),
|
||||
cancelButtonText: t('cancel_button_text'),
|
||||
confirmButtonText: t('confirm_button_text'),
|
||||
}).then(confirm => {
|
||||
if (!confirm) return
|
||||
importPlayList(result.filePaths[0])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const exportPlayListToText = async(savePath, isMerge) => {
|
||||
const lists = await getAllLists()
|
||||
await window.lx.worker.main.exportPlayListToText(savePath, lists, isMerge)
|
||||
}
|
||||
const handleExportPlayListToText = async() => {
|
||||
const confirm = await dialog.confirm({
|
||||
message: t('setting__backup_other_export_list_text_confirm'),
|
||||
cancelButtonText: t('cancel_button_text'),
|
||||
confirmButtonText: t('confirm_button_text'),
|
||||
})
|
||||
if (confirm) {
|
||||
openSaveDir({
|
||||
title: t('setting__backup_other_export_dir'),
|
||||
defaultPath: 'lx_list_all.txt',
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
let path = result.filePath
|
||||
if (!path.endsWith('.txt')) path += '.txt'
|
||||
exportPlayListToText(path, true)
|
||||
})
|
||||
} else {
|
||||
showSelectDialog({
|
||||
title: t('setting__backup_other_export_dir'),
|
||||
// defaultPath: currentStting.value.download.savePath,
|
||||
properties: ['openDirectory'],
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
exportPlayListToText(result.filePaths[0], false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const exportPlayListToCsv = async(savePath, isMerge) => {
|
||||
const lists = await getAllLists()
|
||||
await window.lx.worker.main.exportPlayListToCSV(savePath, lists, isMerge, `${t('music_name')},${t('music_singer')},${t('music_album')}\n`)
|
||||
}
|
||||
const handleExportPlayListToCsv = async() => {
|
||||
const confirm = await dialog.confirm({
|
||||
message: t('setting__backup_other_export_list_text_confirm'),
|
||||
cancelButtonText: t('cancel_button_text'),
|
||||
confirmButtonText: t('confirm_button_text'),
|
||||
})
|
||||
if (confirm) {
|
||||
openSaveDir({
|
||||
title: t('setting__backup_other_export_dir'),
|
||||
defaultPath: 'lx_list_all.csv',
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
let path = result.filePath
|
||||
if (!path.endsWith('.csv')) path += '.csv'
|
||||
exportPlayListToCsv(path, true)
|
||||
})
|
||||
} else {
|
||||
showSelectDialog({
|
||||
title: t('setting__backup_other_export_dir'),
|
||||
// defaultPath: currentStting.value.download.savePath,
|
||||
properties: ['openDirectory'],
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
exportPlayListToCsv(result.filePaths[0], false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// window.eventHub.on(eventBaseName.set_config, handleUpdateSetting)
|
||||
|
||||
// onBeforeUnmount(() => {
|
||||
// window.eventHub.off(eventBaseName.set_config, handleUpdateSetting)
|
||||
// })
|
||||
|
||||
return {
|
||||
// currentStting,
|
||||
handleExportPlayList,
|
||||
handleImportPlayList,
|
||||
handleExportSetting,
|
||||
handleImportSetting,
|
||||
handleExportAllData,
|
||||
handleImportAllData,
|
||||
handleExportPlayListToText,
|
||||
handleExportPlayListToCsv,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
.save-path {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,471 @@
|
||||
<template lang="pug">
|
||||
dt#basic {{$t('setting__basic')}}
|
||||
dd
|
||||
h3#basic_theme {{$t('setting__basic_theme')}}
|
||||
div
|
||||
ul(:class="$style.theme")
|
||||
li(v-for="theme in defaultThemes" :key="theme.id" :aria-label="$t('theme_' + theme.id)" @click="toggleTheme(theme)" :style="theme.styles" :class="[$style.themeItem, {[$style.active]: themeId == theme.id}]")
|
||||
div(:class="$style.bg")
|
||||
span(:class="$style.label") {{$t('theme_' + theme.id)}}
|
||||
li(v-for="theme in userThemes" :key="theme.id" :aria-label="theme.name" @click="toggleTheme(theme)" @contextmenu="handleEditTheme(theme)" :style="theme.styles" :class="[$style.themeItem, {[$style.active]: themeId == theme.id}]")
|
||||
div(:class="$style.bg")
|
||||
span(:class="$style.label") {{theme.name}}
|
||||
li(:aria-label="$t('theme_auto_tip')" @click="handleSetThemeAuto" @contextmenu="isShowThemeSelectorModal = true" :style="autoTheme" :class="[$style.themeItem, $style.auto, {[$style.active]: themeId == 'auto'}]")
|
||||
div(:class="$style.bg")
|
||||
div(:class="$style.bgContent")
|
||||
div(:class="$style.light")
|
||||
div(:class="$style.dark")
|
||||
span(:class="$style.label") {{$t('theme_auto')}}
|
||||
li(:aria-label="$t('theme_add')" @click="handleEditTheme()" :class="[$style.themeItem, $style.add]")
|
||||
div(:class="$style.bg")
|
||||
div(:class="$style.bgContent")
|
||||
svg-icon(:class="$style.icon" name="plus")
|
||||
span(:class="$style.label") {{$t('theme_add')}}
|
||||
|
||||
dd
|
||||
div
|
||||
.gap-top.top
|
||||
base-checkbox(id="setting_show_animate" :modelValue="appSetting['common.isShowAnimation']" @update:modelValue="updateSetting({'common.isShowAnimation': $event})" :label="$t('setting__basic_show_animation')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_animate" :modelValue="appSetting['common.randomAnimate']" @update:modelValue="updateSetting({'common.randomAnimate': $event})" :label="$t('setting__basic_animation')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_start_in_fullscreen" :modelValue="appSetting['common.startInFullscreen']" @update:modelValue="updateSetting({'common.startInFullscreen': $event})" :label="$t('setting__basic_start_in_fullscreen')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_to_tray" :modelValue="appSetting['tray.enable']" @update:modelValue="updateSetting({'tray.enable': $event})" :label="$t('setting__basic_to_tray')")
|
||||
p.gap-top
|
||||
base-btn.btn(min @click="isShowPlayTimeoutModal = true") {{$t('setting__play_timeout')}} {{ timeLabel ? ` (${timeLabel})` : '' }}
|
||||
|
||||
dd
|
||||
h3#basic_source {{$t('setting__basic_source')}}
|
||||
div
|
||||
.gap-top(v-for="item in apiSources" :key="item.id")
|
||||
base-checkbox(:id="`setting_api_source_${item.id}`" name="setting_api_source"
|
||||
need :modelValue="appSetting['common.apiSource']" @update:modelValue="updateSetting({'common.apiSource': $event})" :disabled="item.disabled" :value="item.id" :label="item.label")
|
||||
p.gap-top
|
||||
base-btn.btn(min @click="isShowUserApiModal = true") {{$t('setting__basic_source_user_api_btn')}}
|
||||
|
||||
dd
|
||||
h3#basic_window_size {{$t('setting__basic_window_size')}}
|
||||
div
|
||||
base-checkbox.gap-left(v-for="(item, index) in windowSizeList" :id="`setting_window_size_${item.id}`" name="setting_window_size"
|
||||
need :modelValue="appSetting['common.windowSizeId']" @update:modelValue="updateSetting({'common.windowSizeId': $event})" :disabled="isFullscreen" :value="item.id" :label="$t('setting__basic_window_size_' + item.name)" :key="item.id")
|
||||
|
||||
dd
|
||||
h3#basic_font_size {{$t('setting__basic_font_size')}}
|
||||
div
|
||||
//- base-selection.gap-teft(:list="fontSizeList" :modelValue="appSetting['common.fontSize']" @update:modelValue="updateSetting({'common.fontSize': $event})")
|
||||
base-checkbox.gap-left(v-for="item in fontSizeList" :key="item.id" :id="`setting_basic_font_size_${item.id}`"
|
||||
name="setting_basic_font_size" need :modelValue="appSetting['common.fontSize']" @update:modelValue="updateSetting({'common.fontSize': $event})"
|
||||
:value="item.id" :label="item.label" :disabled="isFullscreen")
|
||||
|
||||
dd
|
||||
h3#basic_font {{$t('setting__basic_font')}}
|
||||
div
|
||||
base-selection.gap-teft(:list="fontList" :modelValue="appSetting['common.font']" @update:modelValue="updateSetting({'common.font': $event})" item-key="id" item-name="label")
|
||||
|
||||
dd
|
||||
h3#basic_lang {{$t('setting__basic_lang')}}
|
||||
div
|
||||
base-checkbox.gap-left(v-for="item in langList" :key="item.locale" :id="`setting_lang_${item.locale}`" name="setting_lang"
|
||||
need :modelValue="appSetting['common.langId']" @update:modelValue="updateSetting({'common.langId': $event})" :value="item.locale" :label="item.name")
|
||||
|
||||
dd
|
||||
h3#basic_sourcename {{$t('setting__basic_sourcename')}}
|
||||
div
|
||||
base-checkbox.gap-left(v-for="item in sourceNameTypes" :key="item.id" :id="`setting_abasic_sourcename_${item.id}`"
|
||||
name="setting_basic_sourcename" need :modelValue="appSetting['common.sourceNameType']" @update:modelValue="updateSetting({'common.sourceNameType': $event})" :value="item.id" :label="item.label")
|
||||
dd
|
||||
h3#basic_control_btn_position {{$t('setting__basic_control_btn_position')}}
|
||||
div
|
||||
base-checkbox.gap-left(v-for="item in controlBtnPositionList" :key="item.id" :id="`setting_basic_control_btn_position_${item.id}`"
|
||||
name="setting_basic_control_btn_position" need :modelValue="appSetting['common.controlBtnPosition']" @update:modelValue="updateSetting({'common.controlBtnPosition': $event})" :value="item.id" :label="item.name")
|
||||
|
||||
ThemeSelectorModal(v-model="isShowThemeSelectorModal")
|
||||
ThemeEditModal(v-model="isShowThemeEditModal" :theme-id="editThemeId" @submit="handleRefreshTheme")
|
||||
play-timeout-modal(v-model="isShowPlayTimeoutModal")
|
||||
user-api-modal(v-model="isShowUserApiModal")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, ref, watch, reactive, shallowReactive } from '@common/utils/vueTools'
|
||||
import { windowSizeList, userApi, isFullscreen, themeId } from '@renderer/store'
|
||||
import { langList, useI18n } from '@/lang'
|
||||
import { getSystemFonts } from '@renderer/utils/ipc'
|
||||
import apiSourceInfo from '@renderer/utils/musicSdk/api-source-info'
|
||||
import { useTimeout } from '@renderer/core/player/timeoutStop'
|
||||
import { dialog } from '@renderer/plugins/Dialog'
|
||||
|
||||
import ThemeSelectorModal from './ThemeSelectorModal'
|
||||
import ThemeEditModal from './ThemeEditModal'
|
||||
import PlayTimeoutModal from './PlayTimeoutModal'
|
||||
import UserApiModal from './UserApiModal'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
import { getThemes, applyTheme, findTheme } from '@renderer/store/utils'
|
||||
import { joinPath } from '@common/utils/nodejs'
|
||||
|
||||
export default {
|
||||
name: 'SettingBasic',
|
||||
components: {
|
||||
ThemeSelectorModal,
|
||||
ThemeEditModal,
|
||||
PlayTimeoutModal,
|
||||
UserApiModal,
|
||||
},
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
|
||||
const defaultThemes = shallowReactive([])
|
||||
const userThemes = shallowReactive([])
|
||||
const autoTheme = reactive({})
|
||||
const updateAutoTheme = (info) => {
|
||||
let light = findTheme(info, appSetting['theme.lightId'])
|
||||
if (!light) light = info.themes.find(theme => theme.id == 'green')
|
||||
let dark = findTheme(info, appSetting['theme.darkId'])
|
||||
if (!dark) dark = info.themes.find(theme => theme.id == 'black')
|
||||
autoTheme['--color-primary-theme-light'] = light.config.themeColors['--color-theme']
|
||||
autoTheme['--background-image-theme-light'] = light.isCustom
|
||||
? light.config.extInfo['--background-image'] == 'none'
|
||||
? 'none'
|
||||
: `url(file:///${joinPath(info.dataPath, light.config.extInfo['--background-image']).replaceAll('\\', '/')})`
|
||||
: light.config.extInfo['--background-image']
|
||||
autoTheme['--color-primary-theme-dark'] = dark.config.themeColors['--color-theme']
|
||||
autoTheme['--background-image-theme-dark'] = dark.isCustom
|
||||
? dark.config.extInfo['--background-image'] == 'none'
|
||||
? 'none'
|
||||
: `url(file:///${joinPath(info.dataPath, dark.config.extInfo['--background-image']).replaceAll('\\', '/')})`
|
||||
: dark.config.extInfo['--background-image']
|
||||
}
|
||||
|
||||
let dataPath = ''
|
||||
const init = () => {
|
||||
getThemes((info) => {
|
||||
// console.log(info)
|
||||
dataPath = info.dataPath
|
||||
defaultThemes.splice(0, defaultThemes.length, ...info.themes.map(t => {
|
||||
return {
|
||||
id: t.id,
|
||||
styles: {
|
||||
'--color-primary-theme': t.config.themeColors['--color-theme'],
|
||||
'--background-image-theme': t.config.extInfo['--background-image'],
|
||||
},
|
||||
}
|
||||
}))
|
||||
userThemes.splice(0, userThemes.length, ...info.userThemes.map(t => {
|
||||
return {
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
styles: {
|
||||
'--color-primary-theme': t.config.themeColors['--color-theme'],
|
||||
'--background-image-theme': t.config.extInfo['--background-image'] == 'none'
|
||||
? 'none'
|
||||
: `url(file:///${joinPath(info.dataPath, t.config.extInfo['--background-image']).replaceAll('\\', '/')})`,
|
||||
},
|
||||
}
|
||||
}))
|
||||
updateAutoTheme(info)
|
||||
})
|
||||
}
|
||||
const editThemeId = ref('')
|
||||
const handleEditTheme = (theme) => {
|
||||
// console.log(theme)
|
||||
if (!theme && userThemes.length >= 10) {
|
||||
dialog({
|
||||
message: t('theme_max_tip'),
|
||||
confirmButtonText: t('alert_button_text'),
|
||||
})
|
||||
return
|
||||
}
|
||||
editThemeId.value = theme ? theme.id : ''
|
||||
isShowThemeEditModal.value = true
|
||||
}
|
||||
const handleRefreshTheme = () => {
|
||||
init()
|
||||
}
|
||||
init()
|
||||
const toggleTheme = (theme) => {
|
||||
if (themeId.value == theme.id) return
|
||||
themeId.value = theme.id
|
||||
applyTheme(theme.id, appSetting['theme.lightId'], appSetting['theme.darkId'], dataPath)
|
||||
updateSetting({ 'theme.id': theme.id })
|
||||
}
|
||||
|
||||
watch(() => [appSetting['theme.lightId'], appSetting['theme.darkId']], () => {
|
||||
getThemes(updateAutoTheme)
|
||||
})
|
||||
const isShowThemeSelectorModal = ref(false)
|
||||
const handleSetThemeAuto = () => {
|
||||
if (themeId.value == 'auto') return
|
||||
if (window.localStorage.getItem('theme-auto-tip') != 'true') {
|
||||
window.localStorage.setItem('theme-auto-tip', 'true')
|
||||
dialog({
|
||||
message: t('setting__basic_theme_auto_tip'),
|
||||
confirmButtonText: t('ok'),
|
||||
})
|
||||
}
|
||||
toggleTheme({ id: 'auto' })
|
||||
}
|
||||
const isShowThemeEditModal = ref(false)
|
||||
|
||||
const isShowPlayTimeoutModal = ref(false)
|
||||
const { timeLabel } = useTimeout()
|
||||
|
||||
const isShowUserApiModal = ref(false)
|
||||
const getApiStatus = () => {
|
||||
let status
|
||||
if (userApi.status) status = t('setting__basic_source_status_success')
|
||||
else if (userApi.message == 'initing') status = t('setting__basic_source_status_initing')
|
||||
else status = `${t('setting__basic_source_status_failed')} - ${userApi.message}`
|
||||
|
||||
return status
|
||||
}
|
||||
const apiSources = computed(() => {
|
||||
return [
|
||||
...apiSourceInfo.map(api => ({
|
||||
id: api.id,
|
||||
label: t('setting__basic_source_' + api.id) || api.name,
|
||||
disabled: api.disabled,
|
||||
})),
|
||||
...userApi.list.map(api => ({
|
||||
id: api.id,
|
||||
label: `${api.name}${api.description ? `(${api.description})` : ''}${api.id == appSetting['common.apiSource'] ? `[${getApiStatus()}]` : ''}`,
|
||||
status: api.status,
|
||||
message: api.message,
|
||||
disabled: false,
|
||||
})),
|
||||
]
|
||||
})
|
||||
|
||||
const sourceNameTypes = computed(() => {
|
||||
return [
|
||||
{ id: 'real', label: t('setting__basic_sourcename_real') },
|
||||
{ id: 'alias', label: t('setting__basic_sourcename_alias') },
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
const controlBtnPositionList = computed(() => {
|
||||
return [
|
||||
{ id: 'left', name: t('setting__basic_control_btn_position_left') },
|
||||
{ id: 'right', name: t('setting__basic_control_btn_position_right') },
|
||||
]
|
||||
})
|
||||
|
||||
const systemFontList = ref([])
|
||||
const fontList = computed(() => {
|
||||
return [{ id: '', label: t('setting__desktop_lyric_font_default') }, ...systemFontList.value]
|
||||
})
|
||||
getSystemFonts().then(fonts => {
|
||||
systemFontList.value = fonts.map(f => ({ id: f, label: f.replace(/(^"|"$)/g, '') }))
|
||||
})
|
||||
|
||||
const fontSizeList = computed(() => {
|
||||
return [
|
||||
{ id: 14, label: t('setting__basic_font_size_14px') },
|
||||
{ id: 15, label: t('setting__basic_font_size_15px') },
|
||||
{ id: 16, label: t('setting__basic_font_size_16px') },
|
||||
{ id: 17, label: t('setting__basic_font_size_17px') },
|
||||
{ id: 18, label: t('setting__basic_font_size_18px') },
|
||||
{ id: 19, label: t('setting__basic_font_size_19px') },
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
defaultThemes,
|
||||
userThemes,
|
||||
autoTheme,
|
||||
// currentStting,
|
||||
// themes,
|
||||
// themeClassName,
|
||||
isShowThemeSelectorModal,
|
||||
isShowThemeEditModal,
|
||||
handleSetThemeAuto,
|
||||
isShowPlayTimeoutModal,
|
||||
timeLabel,
|
||||
apiSources,
|
||||
isShowUserApiModal,
|
||||
windowSizeList,
|
||||
langList,
|
||||
sourceNameTypes,
|
||||
controlBtnPositionList,
|
||||
fontList,
|
||||
isFullscreen,
|
||||
toggleTheme,
|
||||
themeId,
|
||||
handleRefreshTheme,
|
||||
editThemeId,
|
||||
handleEditTheme,
|
||||
fontSizeList,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.theme {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
// padding: 0 15px;
|
||||
margin-bottom: -20px;
|
||||
|
||||
.themeItem {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
// color: var(--color-primary);
|
||||
margin-right: 30px;
|
||||
transition: .3s ease;
|
||||
transition-property: color, opacity;
|
||||
margin-bottom: 18px;
|
||||
width: 56px;
|
||||
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--color-primary-font-active);
|
||||
.bg {
|
||||
border-color: var(--color-primary-font-active);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.bg {
|
||||
display: block;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-bottom: 5px;
|
||||
border: 2Px solid transparent;
|
||||
padding: 2Px;
|
||||
transition: border-color .3s ease;
|
||||
border-radius: 5px;
|
||||
&:after {
|
||||
display: block;
|
||||
content: ' ';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: @radius-border;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-color: var(--color-primary-theme);
|
||||
background-image: var(--background-image-theme);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
height: 1.2em;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
|
||||
&.auto {
|
||||
|
||||
&.active {
|
||||
color: var(--color-primary-font-active);
|
||||
.bg {
|
||||
border-color: var(--color-primary-font-active);
|
||||
}
|
||||
}
|
||||
|
||||
>.bg {
|
||||
&:after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
.bgContent {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.light, .dark {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&:after {
|
||||
display: block;
|
||||
content: ' ';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
.light {
|
||||
&:after {
|
||||
clip-path: polygon(0 0, 100% 0, 0 100%);
|
||||
}
|
||||
svg {
|
||||
fill: var(--color-primary-theme-light);
|
||||
}
|
||||
&:after {
|
||||
background-color: var(--color-primary-theme-light);
|
||||
background-image: var(--background-image-theme-light);
|
||||
}
|
||||
}
|
||||
.dark {
|
||||
&:after {
|
||||
clip-path: polygon(0 100%, 100% 0, 100% 100%);
|
||||
}
|
||||
svg {
|
||||
fill: var(--color-primary-theme-dark);
|
||||
}
|
||||
&:after {
|
||||
background-color: var(--color-primary-theme-dark);
|
||||
background-image: var(--background-image-theme-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.add {
|
||||
>.bg {
|
||||
&:after {
|
||||
content: none;
|
||||
}
|
||||
.bgContent {
|
||||
transition: .3s ease;
|
||||
transition-property: border, color;
|
||||
box-sizing: border-box;
|
||||
border: 1Px dashed var(--color-primary-light-100-alpha-300);
|
||||
color: var(--color-primary-light-100-alpha-300);
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.icon {
|
||||
// position: absolute;
|
||||
// font-size: 16px;
|
||||
width: 66%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
.label {
|
||||
color: var(--color-primary-dark-100-alpha-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,270 @@
|
||||
<template lang="pug">
|
||||
dt#desktop_lyric {{$t('setting__desktop_lyric')}}
|
||||
dd
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_enable" :modelValue="appSetting['desktopLyric.enable']" @update:modelValue="updateSetting({ 'desktopLyric.enable': $event })" :label="$t('setting__desktop_lyric_enable')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_lock" :modelValue="appSetting['desktopLyric.isLock']" @update:modelValue="updateSetting({ 'desktopLyric.isLock': $event })" :label="$t('setting__desktop_lyric_lock')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_audio_visualization" :modelValue="appSetting['desktopLyric.audioVisualization']" @update:modelValue="updateSetting({ 'desktopLyric.audioVisualization': $event })" :label="$t('setting__desktop_lyric_audio_visualization')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_delayScroll" :modelValue="appSetting['desktopLyric.isDelayScroll']" @update:modelValue="updateSetting({ 'desktopLyric.isDelayScroll': $event })" :label="$t('setting__desktop_lyric_delay_scroll')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_alwaysOnTop" :modelValue="appSetting['desktopLyric.isAlwaysOnTop']" @update:modelValue="updateSetting({ 'desktopLyric.isAlwaysOnTop': $event })" :label="$t('setting__desktop_lyric_always_on_top')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_alwaysOnTopLoop" :modelValue="appSetting['desktopLyric.isAlwaysOnTopLoop']" @update:modelValue="updateSetting({ 'desktopLyric.isAlwaysOnTopLoop': $event })" :label="$t('setting__desktop_lyric_always_on_top_loop')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_lockScreen" :modelValue="appSetting['desktopLyric.isLockScreen']" @update:modelValue="updateSetting({ 'desktopLyric.isLockScreen': $event })" :label="$t('setting__desktop_lyric_lock_screen')")
|
||||
.gap-top(v-if="!isLinux")
|
||||
base-checkbox(id="setting_desktop_lyric_hoverHide" :modelValue="appSetting['desktopLyric.isHoverHide']" @update:modelValue="updateSetting({ 'desktopLyric.isHoverHide': $event })" :label="$t('setting__desktop_lyric_hover_hide')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_ellipsis" :modelValue="appSetting['desktopLyric.style.ellipsis']" @update:modelValue="updateSetting({ 'desktopLyric.style.ellipsis': $event })" :label="$t('setting__desktop_lyric_ellipsis')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_zoom" :modelValue="appSetting['desktopLyric.style.isZoomActiveLrc']" @update:modelValue="updateSetting({ 'desktopLyric.style.isZoomActiveLrc': $event })" :label="$t('desktop_lyric__lrc_active_zoom_on')")
|
||||
//- .gap-top
|
||||
base-checkbox(id="setting_desktop_lyric_fontWeight" :modelValue="appSetting['desktopLyric.style.fontWeight']" @update:modelValue="updateSetting({ 'desktopLyric.style.fontWeight': $event })" :label="$t('setting__desktop_lyric_font_weight')")
|
||||
|
||||
dd
|
||||
h3#desktop_lyric_direction {{$t('setting__desktop_lyric_direction')}}
|
||||
div
|
||||
base-checkbox.gap-left(id="setting_desktop_lyric_direction_horizontal" :modelValue="appSetting['desktopLyric.direction']" @update:modelValue="updateSetting({ 'desktopLyric.direction': $event })" need value="horizontal" :label="$t('setting__desktop_lyric_direction_horizontal')")
|
||||
base-checkbox.gap-left(id="setting_desktop_lyric_direction_vertical" :modelValue="appSetting['desktopLyric.direction']" @update:modelValue="updateSetting({ 'desktopLyric.direction': $event })" need value="vertical" :label="$t('setting__desktop_lyric_direction_vertical')")
|
||||
|
||||
dd
|
||||
h3#desktop_lyric_align {{$t('setting__desktop_lyric_align')}}
|
||||
div
|
||||
base-checkbox.gap-left(id="setting_desktop_lyric_align_left" :modelValue="appSetting['desktopLyric.style.align']" @update:modelValue="updateSetting({ 'desktopLyric.style.align': $event })" need value="left" :label="$t('setting__desktop_lyric_align_left')")
|
||||
base-checkbox.gap-left(id="setting_desktop_lyric_align_center" :modelValue="appSetting['desktopLyric.style.align']" @update:modelValue="updateSetting({ 'desktopLyric.style.align': $event })" need value="center" :label="$t('setting__desktop_lyric_align_center')")
|
||||
base-checkbox.gap-left(id="setting_desktop_lyric_align_right" :modelValue="appSetting['desktopLyric.style.align']" @update:modelValue="updateSetting({ 'desktopLyric.style.align': $event })" need value="right" :label="$t('setting__desktop_lyric_align_right')")
|
||||
|
||||
dd
|
||||
h3#desktop_lyric_color {{$t('setting__desktop_lyric_color')}}
|
||||
div
|
||||
p.gap-top
|
||||
div(:class="$style.groupContent")
|
||||
div(:class="$style.item")
|
||||
div(ref="lyric_unplay_color_ref", :class="$style.color")
|
||||
div(:class="$style.label") {{ $t('setting__desktop_lyric_unplay_color') }}
|
||||
div(:class="$style.item")
|
||||
div(ref="lyric_played_color_ref", :class="$style.color")
|
||||
div(:class="$style.label") {{ $t('setting__desktop_lyric_played_color') }}
|
||||
div(:class="$style.item")
|
||||
div(ref="lyric_shadow_color_ref", :class="$style.color")
|
||||
div(:class="$style.label") {{ $t('setting__desktop_lyric_shadow_color') }}
|
||||
p.gap-top
|
||||
base-btn.btn(min @click="resetColor") {{$t('setting__desktop_lyric_color_reset')}}
|
||||
dd
|
||||
h3#desktop_lyric_font {{$t('setting__desktop_lyric_font')}}
|
||||
div
|
||||
base-selection.gap-teft(:list="fontList" :modelValue="appSetting['desktopLyric.style.font']" @update:modelValue="updateSetting({ 'desktopLyric.style.font': $event })" item-key="id" item-name="label")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { getSystemFonts } from '@renderer/utils/ipc'
|
||||
import { isLinux } from '@common/utils'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
import { pickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
const useLyricUnplayColor = () => {
|
||||
const lyric_unplay_color_ref = ref(null)
|
||||
let tools
|
||||
|
||||
const initLyricUnplayColor = (color, changed, reset) => {
|
||||
if (!lyric_unplay_color_ref.value) return
|
||||
tools = pickrTools.create(lyric_unplay_color_ref.value, color, null, changed, reset)
|
||||
}
|
||||
const destroyLyricUnplayColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setLyricUnplayColor = (color) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
lyric_unplay_color_ref,
|
||||
initLyricUnplayColor,
|
||||
destroyLyricUnplayColor,
|
||||
setLyricUnplayColor,
|
||||
}
|
||||
}
|
||||
|
||||
const useLyricPlayedColor = () => {
|
||||
const lyric_played_color_ref = ref(null)
|
||||
let tools
|
||||
|
||||
const initLyricPlayedColor = (color, changed, reset) => {
|
||||
if (!lyric_played_color_ref.value) return
|
||||
tools = pickrTools.create(lyric_played_color_ref.value, color, [
|
||||
'rgba(7, 197, 86, 1)',
|
||||
'rgba(255, 250, 18, 1)',
|
||||
'rgba(25, 181, 254, 1)',
|
||||
'rgba(255, 18, 34, 1)',
|
||||
], changed, reset)
|
||||
}
|
||||
const destroyLyricPlayedColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setLyricPlayedColor = (color) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
lyric_played_color_ref,
|
||||
initLyricPlayedColor,
|
||||
destroyLyricPlayedColor,
|
||||
setLyricPlayedColor,
|
||||
}
|
||||
}
|
||||
|
||||
const useLyricShadowColor = () => {
|
||||
const lyric_shadow_color_ref = ref(null)
|
||||
let tools
|
||||
|
||||
const initLyricShadowColor = (color, changed, reset) => {
|
||||
if (!lyric_shadow_color_ref.value) return
|
||||
tools = pickrTools.create(lyric_shadow_color_ref.value, color, null, changed, reset)
|
||||
}
|
||||
const destroyLyricShadowColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setLyricShadowColor = (color) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
lyric_shadow_color_ref,
|
||||
initLyricShadowColor,
|
||||
destroyLyricShadowColor,
|
||||
setLyricShadowColor,
|
||||
}
|
||||
}
|
||||
|
||||
const useLyricColor = () => {
|
||||
const { lyric_unplay_color_ref, initLyricUnplayColor, destroyLyricUnplayColor, setLyricUnplayColor } = useLyricUnplayColor()
|
||||
const { lyric_played_color_ref, initLyricPlayedColor, destroyLyricPlayedColor, setLyricPlayedColor } = useLyricPlayedColor()
|
||||
const { lyric_shadow_color_ref, initLyricShadowColor, destroyLyricShadowColor, setLyricShadowColor } = useLyricShadowColor()
|
||||
|
||||
const initColors = () => {
|
||||
initLyricUnplayColor(appSetting['desktopLyric.style.lyricUnplayColor'], (color) => {
|
||||
updateSetting({ 'desktopLyric.style.lyricUnplayColor': color })
|
||||
})
|
||||
initLyricPlayedColor(appSetting['desktopLyric.style.lyricPlayedColor'], (color) => {
|
||||
updateSetting({ 'desktopLyric.style.lyricPlayedColor': color })
|
||||
})
|
||||
initLyricShadowColor(appSetting['desktopLyric.style.lyricShadowColor'], (color) => {
|
||||
updateSetting({ 'desktopLyric.style.lyricShadowColor': color })
|
||||
})
|
||||
}
|
||||
|
||||
const destroyColors = () => {
|
||||
destroyLyricUnplayColor()
|
||||
destroyLyricPlayedColor()
|
||||
destroyLyricShadowColor()
|
||||
}
|
||||
|
||||
const resetColor = () => {
|
||||
const defaultSetting = {
|
||||
'desktopLyric.style.lyricUnplayColor': 'rgba(255, 255, 255, 1)',
|
||||
'desktopLyric.style.lyricPlayedColor': 'rgba(7, 197, 86, 1)',
|
||||
'desktopLyric.style.lyricShadowColor': 'rgba(0, 0, 0, 0.14)',
|
||||
}
|
||||
updateSetting(defaultSetting)
|
||||
setLyricUnplayColor(defaultSetting['desktopLyric.style.lyricUnplayColor'])
|
||||
setLyricPlayedColor(defaultSetting['desktopLyric.style.lyricPlayedColor'])
|
||||
setLyricShadowColor(defaultSetting['desktopLyric.style.lyricShadowColor'])
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initColors()
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
destroyColors()
|
||||
})
|
||||
|
||||
return {
|
||||
lyric_unplay_color_ref,
|
||||
lyric_played_color_ref,
|
||||
lyric_shadow_color_ref,
|
||||
resetColor,
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'SettingDesktopLyric',
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
|
||||
|
||||
const {
|
||||
lyric_unplay_color_ref,
|
||||
lyric_played_color_ref,
|
||||
lyric_shadow_color_ref,
|
||||
resetColor,
|
||||
} = useLyricColor()
|
||||
|
||||
const systemFontList = ref([])
|
||||
const fontList = computed(() => {
|
||||
return [{ id: '', label: t('setting__desktop_lyric_font_default') }, ...systemFontList.value]
|
||||
})
|
||||
getSystemFonts().then(fonts => {
|
||||
systemFontList.value = fonts.map(f => ({ id: f, label: f.replace(/(^"|"$)/g, '') }))
|
||||
})
|
||||
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
lyric_unplay_color_ref,
|
||||
lyric_played_color_ref,
|
||||
lyric_shadow_color_ref,
|
||||
resetColor,
|
||||
|
||||
fontList,
|
||||
isLinux,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.groupContent {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
.item {
|
||||
padding-right: 40px;
|
||||
width: 70px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
.color {
|
||||
width: 80%;
|
||||
aspect-ratio: 1 / 1;
|
||||
background-color: var(--pcr-color);
|
||||
border-radius: @radius-border;
|
||||
cursor: pointer;
|
||||
transition: @transition-fast !important;
|
||||
transition-property: background-color, opacity !important;
|
||||
box-shadow: 0 0 3px var(--color-primary-light-100-alpha-300);
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
.label {
|
||||
.mixin-ellipsis-2;
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template lang="pug">
|
||||
dt#download {{$t('setting__download')}}
|
||||
dd
|
||||
base-checkbox(id="setting_download_enable" :modelValue="appSetting['download.enable']" @update:modelValue="updateSetting({'download.enable': $event})" :label="$t('setting__download_enable')")
|
||||
dd(:aria-label="$t('setting__download_path_title')")
|
||||
h3#download_path {{$t('setting__download_path')}}
|
||||
div
|
||||
p
|
||||
| {{$t('setting__download_path_label')}}
|
||||
span.auto-hidden.hover(:class="$style.savePath" @click="openDirInExplorer(appSetting['download.savePath'])" :aria-label="$t('setting__download_path_open_label')") {{appSetting['download.savePath']}}
|
||||
p
|
||||
base-btn.btn(min @click="handleChangeSavePath") {{$t('setting__download_path_change_btn')}}
|
||||
dd
|
||||
h3#download_use_other_source
|
||||
| {{$t('setting__download_use_other_source')}}
|
||||
svg-icon(class="help-icon" name="help-circle-outline" :aria-label="$t('setting__download_use_other_source_tip')")
|
||||
div
|
||||
base-checkbox(id="setting_download_isUseOtherSource" :modelValue="appSetting['download.isUseOtherSource']" @update:modelValue="updateSetting({'download.isUseOtherSource': $event})" :label="$t('setting__is_enable')")
|
||||
div
|
||||
dd(:aria-label="$t('setting__download_name_title')")
|
||||
h3#download_name {{$t('setting__download_name')}}
|
||||
div
|
||||
base-checkbox.gap-left(:id="`setting_download_musicName_${item.value}`" name="setting_download_musicName" :value="item.value" :key="item.value" need
|
||||
:modelValue="appSetting['download.fileName']" @update:modelValue="updateSetting({'download.fileName': $event})" v-for="item in musicNames" :label="item.name")
|
||||
dd
|
||||
h3#download_data_embed {{$t('setting__download_data_embed')}}
|
||||
.gap-top
|
||||
base-checkbox(id="setting_download_isEmbedPic" :modelValue="appSetting['download.isEmbedPic']" @update:modelValue="updateSetting({'download.isEmbedPic': $event})" :label="$t('setting__download_embed_pic')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_download_isEmbedLyric" :modelValue="appSetting['download.isEmbedLyric']" @update:modelValue="updateSetting({'download.isEmbedLyric': $event})" :label="$t('setting__download_embed_lyric')")
|
||||
dd(:aria-label="$t('setting__download_lyric_title')")
|
||||
h3#download_lyric {{$t('setting__download_lyric')}}
|
||||
.gap-top
|
||||
base-checkbox(id="setting_download_isDownloadLrc" :modelValue="appSetting['download.isDownloadLrc']" @update:modelValue="updateSetting({'download.isDownloadLrc': $event})" :label="$t('setting__is_enable')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_download_isDownloadTLrc" :disabled="!appSetting['download.isDownloadLrc']" :modelValue="appSetting['download.isDownloadTLrc']" @update:modelValue="updateSetting({'download.isDownloadTLrc': $event})" :label="$t('setting__download_tlyric')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_download_isDownloadRLrc" :disabled="!appSetting['download.isDownloadLrc']" :modelValue="appSetting['download.isDownloadRLrc']" @update:modelValue="updateSetting({'download.isDownloadRLrc': $event})" :label="$t('setting__download_rlyric')")
|
||||
dd
|
||||
h3#download_lyric_format {{$t('setting__download_lyric_format')}}
|
||||
div
|
||||
base-checkbox.gap-left(v-for="item in lrcFormatList" :key="item.id" :id="`setting_download_lrcFormat_${item.id}`"
|
||||
name="setting_download_lrcFormat" need :modelValue="appSetting['download.lrcFormat']" @update:modelValue="updateSetting({'download.lrcFormat': $event})" :value="item.id" :label="item.name")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from '@common/utils/vueTools'
|
||||
// import { getSystemFonts } from '@renderer/utils/tools'
|
||||
import { openDirInExplorer } from '@common/utils/electron'
|
||||
import { showSelectDialog } from '@renderer/utils/ipc'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
name: 'SettingDownload',
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
|
||||
const handleChangeSavePath = () => {
|
||||
showSelectDialog({
|
||||
title: t('setting__download_select_save_path'),
|
||||
defaultPath: appSetting['download.savePath'],
|
||||
properties: ['openDirectory'],
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
updateSetting({ 'download.savePath': result.filePaths[0] })
|
||||
})
|
||||
}
|
||||
|
||||
const musicNames = computed(() => {
|
||||
return [
|
||||
{ value: '歌名 - 歌手', name: t('setting__download_name1') },
|
||||
{ value: '歌手 - 歌名', name: t('setting__download_name2') },
|
||||
{ value: '歌名', name: t('setting__download_name3') },
|
||||
]
|
||||
})
|
||||
|
||||
const lrcFormatList = computed(() => {
|
||||
return [
|
||||
{ id: 'utf8', name: t('setting__download_lyric_format_utf8') },
|
||||
{ id: 'gbk', name: t('setting__download_lyric_format_gbk') },
|
||||
]
|
||||
})
|
||||
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
openDirInExplorer,
|
||||
handleChangeSavePath,
|
||||
musicNames,
|
||||
lrcFormatList,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
.save-path {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,305 @@
|
||||
<template lang="pug">
|
||||
dt#hot_key {{$t('setting__hot_key')}}
|
||||
dd
|
||||
h3#hot_key_local_title {{$t('setting__hot_key_local_title')}}
|
||||
div
|
||||
base-checkbox(id="setting_download_hotKeyLocal" v-model="current_hot_key.local.enable" :label="$t('setting__is_enable')" @change="handleHotKeySaveConfig")
|
||||
div(:class="$style.hotKeyContainer" :style="{ opacity: current_hot_key.local.enable ? 1 : .6 }")
|
||||
div(:class="$style.hotKeyItem" v-for="item in allHotKeys.local")
|
||||
h4(:class="$style.hotKeyItemTitle") {{$t('setting__hot_key_' + item.name)}}
|
||||
base-input(:class="$style.hotKeyItemInput" readonly @keyup.prevent :placeholder="$t('setting__hot_key_unset_input')"
|
||||
:value="hotKeyConfig.local[item.name] && formatHotKeyName(hotKeyConfig.local[item.name].key)"
|
||||
@focus="handleHotKeyFocus($event, item, 'local')"
|
||||
@blur="handleHotKeyBlur($event, item, 'local')")
|
||||
dd
|
||||
h3#hot_key_global_title {{$t('setting__hot_key_global_title')}}
|
||||
div
|
||||
base-checkbox(id="setting_download_hotKeyGlobal" v-model="current_hot_key.global.enable" :label="$t('setting__is_enable')" @change="handleEnableHotKey")
|
||||
div(:class="$style.hotKeyContainer" :style="{ opacity: current_hot_key.global.enable ? 1 : .6 }")
|
||||
div(:class="$style.hotKeyItem" v-for="item in allHotKeys.global")
|
||||
h4(:class="$style.hotKeyItemTitle") {{$t('setting__hot_key_' + item.name)}}
|
||||
base-input(:class="[$style.hotKeyItemInput, hotKeyConfig.global[item.name] && hotKeyStatus[hotKeyConfig.global[item.name].key] && hotKeyStatus[hotKeyConfig.global[item.name].key].status === false ? $style.hotKeyFailed : null]"
|
||||
:value="hotKeyConfig.global[item.name] && formatHotKeyName(hotKeyConfig.global[item.name].key)" @input.prevent readonly :placeholder="$t('setting__hot_key_unset_input')"
|
||||
@focus="handleHotKeyFocus($event, item, 'global')"
|
||||
@blur="handleHotKeyBlur($event, item, 'global')")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onBeforeUnmount, toRaw, shallowReactive } from '@common/utils/vueTools'
|
||||
import { allHotKeys, hotKeySetEnable, hotKeySetConfig, hotKeyGetStatus } from '@renderer/utils/ipc'
|
||||
import { isMac } from '@common/utils'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
|
||||
|
||||
const formatHotKeyName = (name) => {
|
||||
if (name.includes('arrow')) {
|
||||
name = name.replace(/arrow(left|right|up|down)/, s => {
|
||||
switch (s) {
|
||||
case 'arrowleft': return '←'
|
||||
case 'arrowright': return '→'
|
||||
case 'arrowup': return '↑'
|
||||
case 'arrowdown': return '↓'
|
||||
}
|
||||
})
|
||||
}
|
||||
if (name.includes('mod')) name = name.replace('mod', isMac ? 'Command' : 'Ctrl')
|
||||
name = name.replace(/(\+|^)[a-z]/g, l => l.toUpperCase())
|
||||
if (name.length > 1) name = name.replace(/\+/g, ' + ')
|
||||
return name
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'SettingHotKey',
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
const current_hot_key = ref({
|
||||
local: {
|
||||
enable: false,
|
||||
keys: {},
|
||||
},
|
||||
global: {
|
||||
enable: false,
|
||||
keys: {},
|
||||
},
|
||||
})
|
||||
|
||||
const hotKeyConfig = ref({
|
||||
local: {},
|
||||
global: {},
|
||||
})
|
||||
|
||||
const hotKeyStatus = ref({})
|
||||
let isEditHotKey = false
|
||||
let hotKeyTargetInput
|
||||
let newHotKey
|
||||
let tip
|
||||
|
||||
const initHotKeyConfig = () => {
|
||||
let config = {}
|
||||
for (const [type, typeInfo] of Object.entries(current_hot_key.value)) {
|
||||
let configInfo = config[type] = {}
|
||||
for (const [key, info] of Object.entries(typeInfo.keys)) {
|
||||
if (!info.name) continue
|
||||
configInfo[info.name] = shallowReactive({
|
||||
key,
|
||||
info,
|
||||
})
|
||||
}
|
||||
}
|
||||
hotKeyConfig.value = config
|
||||
}
|
||||
|
||||
const handleHotKeyFocus = (event, info, type) => {
|
||||
setTimeout(async() => {
|
||||
await hotKeySetEnable(false)
|
||||
window.lx.isEditingHotKey = true
|
||||
isEditHotKey = true
|
||||
let config = hotKeyConfig.value[type][info.name]
|
||||
newHotKey = config?.key
|
||||
hotKeyTargetInput = event.target
|
||||
event.target.value = tip = t('setting__hot_key_tip_input')
|
||||
})
|
||||
}
|
||||
|
||||
const handleHotKeyBlur = (event, info, type) => {
|
||||
setTimeout(async() => {
|
||||
await hotKeySetEnable(true)
|
||||
window.lx.isEditingHotKey = false
|
||||
isEditHotKey = false
|
||||
const prevInput = hotKeyTargetInput
|
||||
hotKeyTargetInput = null
|
||||
if (prevInput?.value == tip) {
|
||||
prevInput.value = newHotKey ? formatHotKeyName(newHotKey) : ''
|
||||
return
|
||||
}
|
||||
let config = hotKeyConfig.value[type][info.name]
|
||||
let originKey
|
||||
if (type == 'global' && newHotKey && current_hot_key.value.global.enable) {
|
||||
try {
|
||||
await hotKeySetConfig({
|
||||
action: 'register',
|
||||
data: {
|
||||
key: newHotKey,
|
||||
info,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (config) {
|
||||
if (config.key == newHotKey) return
|
||||
originKey = config.key
|
||||
delete current_hot_key.value[type].keys[config.key]
|
||||
} else if (!newHotKey) return
|
||||
|
||||
if (newHotKey) {
|
||||
for (const [tempType, tempInfo] of Object.entries(current_hot_key.value)) {
|
||||
if (tempType == type) continue
|
||||
config = tempInfo.keys[newHotKey]
|
||||
if (config) {
|
||||
console.log(newHotKey, info, config, info.name, config.name)
|
||||
delete current_hot_key.value[tempType].keys[newHotKey]
|
||||
break
|
||||
}
|
||||
}
|
||||
current_hot_key.value[type].keys[newHotKey] = info
|
||||
}
|
||||
|
||||
initHotKeyConfig()
|
||||
// console.log(this.current_hot_key.global.keys)
|
||||
if (originKey && current_hot_key.value.global.enable) {
|
||||
try {
|
||||
await hotKeySetConfig({
|
||||
action: 'unregister',
|
||||
data: originKey,
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
await handleHotKeySaveConfig()
|
||||
await getHotKeyStatus()
|
||||
})
|
||||
}
|
||||
|
||||
const handleKeyDown = ({ event, keys, key, type }) => {
|
||||
// if (!event || event.repeat) return
|
||||
if (!event || event.repeat || type == 'up' || !isEditHotKey) return
|
||||
event.preventDefault()
|
||||
// console.log(event, key)
|
||||
switch (key) {
|
||||
case 'delete':
|
||||
case 'backspace':
|
||||
key = ''
|
||||
break
|
||||
}
|
||||
hotKeyTargetInput.value = formatHotKeyName(key)
|
||||
// console.log(keys, key, type)
|
||||
newHotKey = key
|
||||
}
|
||||
// const handleUpdateHotKeyConfig = (config) => {
|
||||
// // console.log(config)
|
||||
// for (const [type, info] of Object.entries(config)) {
|
||||
// current_hot_key.value[type] = info
|
||||
// }
|
||||
// }
|
||||
const handleHotKeySaveConfig = async() => {
|
||||
// console.log(this.current_hot_key)
|
||||
await hotKeySetConfig({
|
||||
action: 'config',
|
||||
data: toRaw(current_hot_key.value),
|
||||
})
|
||||
}
|
||||
const handleEnableHotKey = async() => {
|
||||
await hotKeySetConfig({
|
||||
action: 'enable',
|
||||
data: current_hot_key.value.global.enable,
|
||||
})
|
||||
await handleHotKeySaveConfig()
|
||||
await getHotKeyStatus()
|
||||
}
|
||||
const getHotKeyStatus = () => {
|
||||
return hotKeyGetStatus().then(status => {
|
||||
// console.log(status)
|
||||
hotKeyStatus.value = status
|
||||
return status
|
||||
})
|
||||
}
|
||||
|
||||
current_hot_key.value = window.lx.appHotKeyConfig
|
||||
initHotKeyConfig()
|
||||
getHotKeyStatus()
|
||||
|
||||
window.app_event.on('keyDown', handleKeyDown)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.app_event.off('keyDown', handleKeyDown)
|
||||
})
|
||||
|
||||
return {
|
||||
// appSetting,
|
||||
allHotKeys,
|
||||
current_hot_key,
|
||||
hotKeyConfig,
|
||||
hotKeyStatus,
|
||||
handleHotKeyFocus,
|
||||
handleHotKeyBlur,
|
||||
handleEnableHotKey,
|
||||
formatHotKeyName,
|
||||
handleHotKeySaveConfig,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.hotKeyContainer {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
// margin-top: -15px;
|
||||
margin-bottom: 15px;
|
||||
transition: opacity @transition-normal;
|
||||
}
|
||||
.hotKeyItem {
|
||||
width: 30%;
|
||||
padding-right: 35px;
|
||||
margin-top: 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.hotKeyItemTitle {
|
||||
.mixin-ellipsis-1;
|
||||
padding-bottom: 5px;
|
||||
color: var(--color-font-label);
|
||||
font-size: 12px;
|
||||
}
|
||||
.hotKeyItemInput {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
// font-family: monospace;
|
||||
&:focus {
|
||||
background-color: var(--color-primary-background-active);
|
||||
text-decoration: none;
|
||||
}
|
||||
&::placeholder {
|
||||
color: rgba(197, 197, 197, 0.7)!important;
|
||||
}
|
||||
}
|
||||
.hotKeyFailed {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.del-line {
|
||||
position: relative;
|
||||
&:before {
|
||||
display: block;
|
||||
height: 1px;
|
||||
position: absolute;
|
||||
width: 110%;
|
||||
content: ' ';
|
||||
left: 0;
|
||||
background-color: #000;
|
||||
transform: rotate(-24deg);
|
||||
transform-origin: 0;
|
||||
top: 83%;
|
||||
z-index: 1;
|
||||
}
|
||||
&:after {
|
||||
display: block;
|
||||
height: 1px;
|
||||
position: absolute;
|
||||
width: 110%;
|
||||
content: ' ';
|
||||
left: 0;
|
||||
background-color: #000;
|
||||
transform: rotate(23deg);
|
||||
transform-origin: 0px;
|
||||
top: 2px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template lang="pug">
|
||||
dt#list {{$t('setting__list')}}
|
||||
dd
|
||||
.gap-top
|
||||
base-checkbox(id="setting_list_actionButtonsVisible_enable" :modelValue="appSetting['list.actionButtonsVisible']" @update:modelValue="updateSetting({'list.actionButtonsVisible': $event})" :label="$t('setting__list_action_btn')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_list_showSource_enable" :modelValue="appSetting['list.isShowSource']" @update:modelValue="updateSetting({'list.isShowSource': $event})" :label="$t('setting__list_source')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_list_scroll_enable" :modelValue="appSetting['list.isSaveScrollLocation']" @update:modelValue="updateSetting({'list.isSaveScrollLocation': $event})" :label="$t('setting__list_scroll')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_list_clickAction_enable" :modelValue="appSetting['list.isClickPlayList']" @update:modelValue="updateSetting({'list.isClickPlayList': $event})" :label="$t('setting__list_click_action')")
|
||||
dd(:aria-label="$t('setting__basic_sourcename_title')")
|
||||
h3#list_addMusicLocationType {{$t('setting__list_add_music_location_type')}}
|
||||
div
|
||||
base-checkbox.gap-left(id="setting_list_add_music_location_type_top"
|
||||
name="setting_list_add_music_location_type" need :modelValue="appSetting['list.addMusicLocationType']" @update:modelValue="updateSetting({'list.addMusicLocationType': $event})" value="top" :label="$t('setting__list_add_music_location_type_top')")
|
||||
base-checkbox.gap-left(id="setting_list_add_music_location_type_bottom"
|
||||
name="setting_list_add_music_location_type" need :modelValue="appSetting['list.addMusicLocationType']" @update:modelValue="updateSetting({'list.addMusicLocationType': $event})" value="bottom" :label="$t('setting__list_add_music_location_type_bottom')")
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
name: 'SettingList',
|
||||
setup() {
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template lang="pug">
|
||||
dt#network {{$t('setting__network')}}
|
||||
dd
|
||||
h3#network_proxy_title {{$t('setting__network_proxy_title')}}
|
||||
div
|
||||
p
|
||||
base-checkbox(id="setting_network_proxy_enable" :modelValue="appSetting['network.proxy.enable']" @update:modelValue="updateSetting({'network.proxy.enable': $event})" :label="$t('setting__is_enable')")
|
||||
p
|
||||
base-input.gap-left(:modelValue="appSetting['network.proxy.host']" @update:modelValue="setHost" :placeholder="proxy.envProxy ? proxy.envProxy.host : $t('setting__network_proxy_host')")
|
||||
base-input.gap-left(:modelValue="appSetting['network.proxy.port']" @update:modelValue="setPort" :placeholder="proxy.envProxy ? proxy.envProxy.port : $t('setting__network_proxy_port')")
|
||||
p
|
||||
base-input.gap-left(:modelValue="appSetting['network.proxy.username']" @update:modelValue="setUserName" :placeholder="$t('setting__network_proxy_username')")
|
||||
base-input.gap-left(:modelValue="appSetting['network.proxy.password']" @update:modelValue="setPassword" type="password" :placeholder="$t('setting__network_proxy_password')")
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { proxy } from '@renderer/store'
|
||||
import { debounce } from '@common/utils'
|
||||
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
name: 'SettingNetwork',
|
||||
setup() {
|
||||
const setHost = debounce(host => {
|
||||
updateSetting({ 'network.proxy.host': host.trim() })
|
||||
}, 500)
|
||||
const setPort = debounce(port => {
|
||||
updateSetting({ 'network.proxy.port': port.trim() })
|
||||
}, 500)
|
||||
const setUserName = debounce(username => {
|
||||
updateSetting({ 'network.proxy.username': username.trim() })
|
||||
}, 500)
|
||||
const setPassword = debounce(password => {
|
||||
updateSetting({ 'network.proxy.password': password.trim() })
|
||||
}, 500)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (appSetting['network.proxy.enable'] && !appSetting['network.proxy.host']) proxy.enable = false
|
||||
})
|
||||
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
setHost,
|
||||
setPort,
|
||||
setUserName,
|
||||
setPassword,
|
||||
proxy,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,23 @@
|
||||
<template lang="pug">
|
||||
dt#odc {{$t('setting__odc')}}
|
||||
dd
|
||||
.gap-top
|
||||
base-checkbox(id="setting_odc_isAutoClearSearchInput" :modelValue="appSetting['odc.isAutoClearSearchInput']" @update:modelValue="updateSetting({'odc.isAutoClearSearchInput': $event})" :label="$t('setting__odc_clear_search_input')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_odc_isAutoClearSearchList" :modelValue="appSetting['odc.isAutoClearSearchList']" @update:modelValue="updateSetting({'odc.isAutoClearSearchList': $event})" :label="$t('setting__odc_clear_search_list')")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
name: 'SettingOdc',
|
||||
setup() {
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,195 @@
|
||||
<template lang="pug">
|
||||
dt#other {{$t('setting__other')}}
|
||||
dd
|
||||
h3#other_tray_theme {{$t('setting__other_tray_theme')}}
|
||||
div
|
||||
base-checkbox.gap-left(:id="'setting_tray_theme_' + item.id" :modelValue="appSetting['tray.themeId']" @update:modelValue="updateSetting({'tray.themeId': $event})" name="setting_tray_theme" need
|
||||
:label="item.label" :key="item.id" :value="item.id" v-for="item in trayThemeList")
|
||||
dd
|
||||
h3#other_resource_cache
|
||||
| {{$t('setting__other_resource_cache')}}
|
||||
svg-icon(class="help-icon" name="help-circle-outline" :aria-label="$t('setting__other_resource_cache_tip')")
|
||||
div
|
||||
p
|
||||
| {{$t('setting__other_resource_cache_label')}}
|
||||
span.auto-hidden {{cacheSize}}
|
||||
p
|
||||
base-btn.btn(min :disabled="isDisabledResourceCacheClear" @click="clearResourceCache") {{$t('setting__other_resource_cache_clear_btn')}}
|
||||
|
||||
dd
|
||||
h3#other_other_source {{$t('setting__other_other_cache')}}
|
||||
div
|
||||
p
|
||||
| {{$t('setting__other_other_source_label')}}
|
||||
span.auto-hidden {{otherSourceCount}}
|
||||
p
|
||||
| {{$t('setting__other_music_url_label')}}
|
||||
span.auto-hidden {{musicUrlCount}}
|
||||
p
|
||||
| {{$t('setting__other_lyric_raw_label')}}
|
||||
span.auto-hidden {{lyricRawCount}}
|
||||
p
|
||||
base-btn.btn(min :disabled="isDisabledOtherSourceCacheClear" @click="handleClearOtherSourceCache") {{$t('setting__other_other_source_clear_btn')}}
|
||||
base-btn.btn(min :disabled="isDisabledMusicUrlCacheClear" @click="handleClearMusicUrlCache") {{$t('setting__other_music_url_clear_btn')}}
|
||||
base-btn.btn(min :disabled="isDisabledLyricRawCacheClear" @click="handleClearLyricRawCache") {{$t('setting__other_lyric_raw_clear_btn')}}
|
||||
|
||||
dd
|
||||
h3#other_lyric_edited {{$t('setting__other_lyric_edited_cache')}}
|
||||
div
|
||||
p
|
||||
| {{$t('setting__other_lyric_edited_label')}}
|
||||
span.auto-hidden {{lyricEditedCount}}
|
||||
p
|
||||
base-btn.btn(min :disabled="isDisabledLyricEditedCacheClear" @click="handleClearLyricEditedCache") {{$t('setting__other_lyric_edited_clear_btn')}}
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed } from '@common/utils/vueTools'
|
||||
import {
|
||||
clearCache, getCacheSize,
|
||||
getOtherSourceCount, clearOtherSource,
|
||||
getMusicUrlCount, clearMusicUrl,
|
||||
getLyricRawCount, clearLyricRaw,
|
||||
getLyricEditedCount, clearLyricEdited,
|
||||
} from '@renderer/utils/ipc'
|
||||
import { sizeFormate } from '@common/utils/common'
|
||||
import { dialog } from '@renderer/plugins/Dialog'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
name: 'SettingOther',
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
|
||||
const trayThemeList = computed(() => {
|
||||
return [
|
||||
{ id: 0, name: 'native', label: t('setting__other_tray_theme_native') },
|
||||
{ id: 2, name: 'black', label: t('setting__other_tray_theme_black') },
|
||||
{ id: 1, name: 'origin', label: t('setting__other_tray_theme_origin') },
|
||||
]
|
||||
})
|
||||
|
||||
const cacheSize = ref('0 B')
|
||||
const isDisabledResourceCacheClear = ref(false)
|
||||
// const isDisabledListCacheClear = ref(false)
|
||||
const refreshCacheSize = () => {
|
||||
getCacheSize().then(size => {
|
||||
cacheSize.value = sizeFormate(size)
|
||||
})
|
||||
}
|
||||
const clearResourceCache = async() => {
|
||||
if (!await dialog.confirm({
|
||||
message: t('setting__other_resource_cache_tip_confirm'),
|
||||
cancelButtonText: t('cancel_button_text'),
|
||||
confirmButtonText: t('setting__other_resource_cache_confirm'),
|
||||
})) return
|
||||
isDisabledResourceCacheClear.value = true
|
||||
clearCache().then(() => {
|
||||
refreshCacheSize()
|
||||
isDisabledResourceCacheClear.value = false
|
||||
})
|
||||
}
|
||||
refreshCacheSize()
|
||||
|
||||
|
||||
const otherSourceCount = ref(0)
|
||||
const isDisabledOtherSourceCacheClear = ref(false)
|
||||
const refreshOtherSourceCount = () => {
|
||||
getOtherSourceCount().then(count => {
|
||||
otherSourceCount.value = count
|
||||
})
|
||||
}
|
||||
const handleClearOtherSourceCache = async() => {
|
||||
isDisabledOtherSourceCacheClear.value = true
|
||||
clearOtherSource().then(() => {
|
||||
refreshOtherSourceCount()
|
||||
isDisabledOtherSourceCacheClear.value = false
|
||||
})
|
||||
}
|
||||
refreshOtherSourceCount()
|
||||
|
||||
|
||||
const musicUrlCount = ref(0)
|
||||
const isDisabledMusicUrlCacheClear = ref(false)
|
||||
const refreshMusicUrlCount = () => {
|
||||
getMusicUrlCount().then(count => {
|
||||
musicUrlCount.value = count
|
||||
})
|
||||
}
|
||||
const handleClearMusicUrlCache = async() => {
|
||||
isDisabledMusicUrlCacheClear.value = true
|
||||
clearMusicUrl().then(() => {
|
||||
refreshMusicUrlCount()
|
||||
isDisabledMusicUrlCacheClear.value = false
|
||||
})
|
||||
}
|
||||
refreshMusicUrlCount()
|
||||
|
||||
|
||||
const lyricRawCount = ref(0)
|
||||
const isDisabledLyricRawCacheClear = ref(false)
|
||||
const refreshLyricRawCount = () => {
|
||||
getLyricRawCount().then(count => {
|
||||
lyricRawCount.value = count
|
||||
})
|
||||
}
|
||||
const handleClearLyricRawCache = async() => {
|
||||
isDisabledLyricRawCacheClear.value = true
|
||||
clearLyricRaw().then(() => {
|
||||
refreshLyricRawCount()
|
||||
isDisabledLyricRawCacheClear.value = false
|
||||
})
|
||||
}
|
||||
refreshLyricRawCount()
|
||||
|
||||
|
||||
const lyricEditedCount = ref(0)
|
||||
const isDisabledLyricEditedCacheClear = ref(false)
|
||||
const refreshLyricEditedCount = () => {
|
||||
getLyricEditedCount().then(count => {
|
||||
lyricEditedCount.value = count
|
||||
})
|
||||
}
|
||||
const handleClearLyricEditedCache = async() => {
|
||||
if (!await dialog.confirm({
|
||||
message: t('setting__other_lyric_edited_clear_tip_confirm'),
|
||||
cancelButtonText: t('cancel_button_text'),
|
||||
confirmButtonText: t('setting__other_resource_cache_confirm'),
|
||||
})) return
|
||||
isDisabledLyricEditedCacheClear.value = true
|
||||
clearLyricEdited().then(() => {
|
||||
refreshLyricEditedCount()
|
||||
isDisabledLyricEditedCacheClear.value = false
|
||||
})
|
||||
}
|
||||
refreshLyricEditedCount()
|
||||
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
trayThemeList,
|
||||
cacheSize,
|
||||
isDisabledResourceCacheClear,
|
||||
clearResourceCache,
|
||||
|
||||
otherSourceCount,
|
||||
isDisabledOtherSourceCacheClear,
|
||||
handleClearOtherSourceCache,
|
||||
|
||||
musicUrlCount,
|
||||
isDisabledMusicUrlCacheClear,
|
||||
handleClearMusicUrlCache,
|
||||
|
||||
lyricRawCount,
|
||||
isDisabledLyricRawCacheClear,
|
||||
handleClearLyricRawCache,
|
||||
|
||||
lyricEditedCount,
|
||||
isDisabledLyricEditedCacheClear,
|
||||
handleClearLyricEditedCache,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,95 @@
|
||||
<template lang="pug">
|
||||
dt#play {{$t('setting__play')}}
|
||||
dd
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_startup_auto_play" :modelValue="appSetting['player.startupAutoPlay']" @update:modelValue="updateSetting({'player.startupAutoPlay': $event})" :label="$t('setting__play_startup_auto_play')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_save_play_time" :modelValue="appSetting['player.isSavePlayTime']" @update:modelValue="updateSetting({'player.isSavePlayTime': $event})" :label="$t('setting__play_save_play_time')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_lyric_transition" :modelValue="appSetting['player.isShowLyricTranslation']" @update:modelValue="updateSetting({'player.isShowLyricTranslation': $event})" :label="$t('setting__play_lyric_transition')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_lyric_roma" :modelValue="appSetting['player.isShowLyricRoma']" @update:modelValue="updateSetting({'player.isShowLyricRoma': $event})" :label="$t('setting__play_lyric_roma')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_auto_skip_on_error" :modelValue="appSetting['player.autoSkipOnError']" @update:modelValue="updateSetting({'player.autoSkipOnError': $event})" :label="$t('setting__play_auto_skip_on_error')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_lyric_s2t" :modelValue="appSetting['player.isS2t']" @update:modelValue="updateSetting({'player.isS2t': $event})" :label="$t('setting__play_lyric_s2t')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_lyric_play_lxlrc" :modelValue="appSetting['player.isPlayLxlrc']" @update:modelValue="updateSetting({'player.isPlayLxlrc': $event})" :label="$t('setting__play_lyric_lxlrc')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_highQuality" :modelValue="appSetting['player.highQuality']" @update:modelValue="updateSetting({'player.highQuality': $event})" :label="$t('setting__play_quality')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_showTaskProgess" :modelValue="appSetting['player.isShowTaskProgess']" @update:modelValue="updateSetting({'player.isShowTaskProgess': $event})" :label="$t('setting__play_task_bar')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_player_isMediaDeviceRemovedStopPlay" :modelValue="appSetting['player.isMediaDeviceRemovedStopPlay']" @update:modelValue="updateSetting({'player.isMediaDeviceRemovedStopPlay': $event})" :label="$t('setting__play_mediaDevice_remove_stop_play')")
|
||||
dd(:aria-label="$t('setting__play_mediaDevice_title')")
|
||||
h3#play_mediaDevice {{$t('setting__play_mediaDevice')}}
|
||||
div
|
||||
base-selection.gap-left(:list="mediaDevices" v-model="mediaDeviceId" @change="handleMediaDeviceIdChnage" item-key="deviceId" item-name="label")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onBeforeUnmount, watch } from '@common/utils/vueTools'
|
||||
import { hasInitedAnalyser } from '@renderer/plugins/player'
|
||||
import { dialog } from '@renderer/plugins/Dialog'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'SettingPlay',
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
|
||||
const mediaDevices = ref([])
|
||||
const getMediaDevice = async() => {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices()
|
||||
let audioDevices = devices.filter(device => device.kind === 'audiooutput')
|
||||
mediaDevices.value = audioDevices
|
||||
// console.log(this.mediaDevices)
|
||||
}
|
||||
getMediaDevice()
|
||||
|
||||
navigator.mediaDevices.addEventListener('devicechange', getMediaDevice)
|
||||
onBeforeUnmount(() => {
|
||||
navigator.mediaDevices.removeEventListener('devicechange', getMediaDevice)
|
||||
})
|
||||
|
||||
const mediaDeviceId = ref(appSetting['player.mediaDeviceId'])
|
||||
const handleMediaDeviceIdChnage = async() => {
|
||||
if (hasInitedAnalyser()) {
|
||||
await dialog({
|
||||
message: t('setting__play_media_device_error_tip'),
|
||||
confirmButtonText: t('alert_button_text'),
|
||||
})
|
||||
mediaDeviceId.value = appSetting['player.mediaDeviceId']
|
||||
} else if (appSetting['player.audioVisualization']) {
|
||||
const confirm = await dialog.confirm({
|
||||
message: t('setting__play_media_device_tip'),
|
||||
cancelButtonText: t('cancel_button_text'),
|
||||
confirmButtonText: t('confirm_button_text'),
|
||||
})
|
||||
if (confirm) {
|
||||
appSetting['player.audioVisualization'] = false
|
||||
appSetting['player.mediaDeviceId'] = mediaDeviceId.value
|
||||
} else {
|
||||
mediaDeviceId.value = appSetting['player.mediaDeviceId']
|
||||
}
|
||||
} else {
|
||||
appSetting['player.mediaDeviceId'] = mediaDeviceId.value
|
||||
}
|
||||
}
|
||||
watch(() => appSetting['player.mediaDeviceId'], val => {
|
||||
mediaDeviceId.value = val
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
mediaDevices,
|
||||
mediaDeviceId,
|
||||
handleMediaDeviceIdChnage,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,31 @@
|
||||
<template lang="pug">
|
||||
dt#play_detail {{$t('setting__play_detail')}}
|
||||
dd
|
||||
.gap-top
|
||||
base-checkbox(id="setting_play_detail_font_zoom_enable" :modelValue="appSetting['playDetail.isZoomActiveLrc']" @update:modelValue="updateSetting({'playDetail.isZoomActiveLrc': $event})" :label="$t('setting__play_detail_font_zoom')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_play_detail_lyric_progress_enable" :modelValue="appSetting['playDetail.isShowLyricProgressSetting']" @update:modelValue="updateSetting({'playDetail.isShowLyricProgressSetting': $event})" :label="$t('setting__play_detail_lyric_progress')")
|
||||
|
||||
dd
|
||||
h3#play_detail_align {{$t('setting__play_detail_align')}}
|
||||
div
|
||||
base-checkbox.gap-left(id="setting_play_detail_align_left" :modelValue="appSetting['playDetail.style.align']" @update:modelValue="updateSetting({ 'playDetail.style.align': $event })" need value="left" :label="$t('setting__play_detail_align_left')")
|
||||
base-checkbox.gap-left(id="setting_play_detail_align_center" :modelValue="appSetting['playDetail.style.align']" @update:modelValue="updateSetting({ 'playDetail.style.align': $event })" need value="center" :label="$t('setting__play_detail_align_center')")
|
||||
base-checkbox.gap-left(id="setting_play_detail_align_right" :modelValue="appSetting['playDetail.style.align']" @update:modelValue="updateSetting({ 'playDetail.style.align': $event })" need value="right" :label="$t('setting__play_detail_align_right')")
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
name: 'SettingPlayDetail',
|
||||
setup() {
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template lang="pug">
|
||||
dt#search {{$t('setting__search')}}
|
||||
dd
|
||||
.gap-top
|
||||
base-checkbox(id="setting_search_showHot_enable" :modelValue="appSetting['search.isShowHotSearch']" @update:modelValue="updateSetting({'search.isShowHotSearch': $event})" :label="$t('setting__search_hot')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_search_showHistory_enable" :modelValue="appSetting['search.isShowHistorySearch']" @update:modelValue="updateSetting({'search.isShowHistorySearch': $event})" :label="$t('setting__search_history')")
|
||||
.gap-top
|
||||
base-checkbox(id="setting_search_focusSearchBox_enable" :modelValue="appSetting['search.isFocusSearchBox']" @update:modelValue="updateSetting({'search.isFocusSearchBox': $event})" :label="$t('setting__search_focus_search_box')")
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
name: 'SettingSearch',
|
||||
setup() {
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template lang="pug">
|
||||
dt#sync
|
||||
| {{$t('setting__sync')}}
|
||||
button(class="help-btn" @click="openUrl('https://lyswhut.github.io/lx-music-doc/desktop/faq/sync')" :aria-label="$t('setting__sync_tip')")
|
||||
svg-icon(name="help-circle-outline")
|
||||
dd
|
||||
base-checkbox(id="setting_sync_enable" :modelValue="appSetting['sync.enable']" @update:modelValue="updateSetting({ 'sync.enable': $event })" :label="syncEnableTitle")
|
||||
div
|
||||
p.small {{$t('setting__sync_auth_code', { code: sync.status.code || '' })}}
|
||||
p.small {{$t('setting__sync_address', { address: sync.status.address.join(', ') || '' })}}
|
||||
p.small {{$t('setting__sync_device', { devices: syncDevices })}}
|
||||
p
|
||||
base-btn.btn(min :disabled="!sync.status.status" @click="refreshSyncCode") {{$t('setting__sync_refresh_code')}}
|
||||
dd
|
||||
h3#sync_port {{$t('setting__sync_port')}}
|
||||
div
|
||||
p
|
||||
base-input.gap-left(:modelValue="appSetting['sync.port']" @update:modelValue="setSyncPort" :placeholder="$t('setting__sync_port_tip')")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from '@common/utils/vueTools'
|
||||
import { sync } from '@renderer/store'
|
||||
import { sendSyncAction } from '@renderer/utils/ipc'
|
||||
import { openUrl } from '@common/utils/electron'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
import { debounce } from '@common/utils/common'
|
||||
|
||||
export default {
|
||||
name: 'SettingSync',
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
|
||||
|
||||
const syncEnableTitle = computed(() => {
|
||||
let title = t('setting__sync_enable')
|
||||
if (sync.status.message) {
|
||||
title += ` [${sync.status.message}]`
|
||||
}
|
||||
// else if (this.sync.status.address.length) {
|
||||
// // title += ` [${this.sync.status.address.join(', ')}]`
|
||||
// }
|
||||
return title
|
||||
})
|
||||
const syncDevices = computed(() => {
|
||||
return sync.status.devices.length
|
||||
? sync.status.devices.map(d => `${d.deviceName} (${d.clientId.substring(0, 5)})`).join(', ')
|
||||
: ''
|
||||
})
|
||||
|
||||
const refreshSyncCode = () => {
|
||||
sendSyncAction({ action: 'generate_code' })
|
||||
}
|
||||
|
||||
const setSyncPort = debounce(port => {
|
||||
updateSetting({ 'sync.port': port.trim() })
|
||||
}, 500)
|
||||
|
||||
|
||||
return {
|
||||
appSetting,
|
||||
updateSetting,
|
||||
sync,
|
||||
syncEnableTitle,
|
||||
setSyncPort,
|
||||
syncDevices,
|
||||
refreshSyncCode,
|
||||
openUrl,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
.save-path {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template lang="pug">
|
||||
dt#update {{$t('setting__update')}}
|
||||
dd
|
||||
p.small
|
||||
| {{$t('setting__update_latest_label')}}{{versionInfo.newVersion ? versionInfo.newVersion.version : $t('setting__update_unknown')}}
|
||||
p.small(@click="handleOPenDevTools") {{$t('setting__update_current_label')}}{{versionInfo.version}}
|
||||
p.small(v-if="versionInfo.downloadProgress" style="line-height: 1.5;")
|
||||
| {{$t('setting__update_downloading')}}
|
||||
br
|
||||
| {{$t('setting__update_progress')}}{{downloadProgress}}
|
||||
p(v-if="versionInfo.newVersion")
|
||||
span(v-if="versionInfo.isLatestVer") {{$t('setting__update_latest')}}
|
||||
base-btn.btn.gap-left(v-else min @click="showUpdateModal") {{$t('setting__update_open_version_modal_btn')}}
|
||||
p.small(v-else) {{$t('setting__update_checking')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from '@common/utils/vueTools'
|
||||
import { versionInfo } from '@renderer/store'
|
||||
import { sizeFormate } from '@common/utils/common'
|
||||
// import { openDirInExplorer, selectDir } from '@renderer/utils'
|
||||
import { openDevTools } from '@renderer/utils/ipc'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
|
||||
export default {
|
||||
name: 'SettingUpdate',
|
||||
setup() {
|
||||
let lastClickTime = 0
|
||||
let clickNum = 0
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const handleOPenDevTools = () => {
|
||||
if (window.performance.now() - lastClickTime > 1000) {
|
||||
if (clickNum > 0) clickNum = 0
|
||||
} else {
|
||||
if (clickNum > 4) {
|
||||
openDevTools()
|
||||
clickNum = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
clickNum++
|
||||
lastClickTime = window.performance.now()
|
||||
}
|
||||
|
||||
const downloadProgress = computed(() => {
|
||||
return versionInfo.downloadProgress
|
||||
? `${versionInfo.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(versionInfo.downloadProgress.transferred)}/${sizeFormate(versionInfo.downloadProgress.total)} - ${sizeFormate(versionInfo.downloadProgress.bytesPerSecond)}/s`
|
||||
: t('setting__update_init')
|
||||
})
|
||||
|
||||
const showUpdateModal = () => {
|
||||
versionInfo.showModal = true
|
||||
}
|
||||
|
||||
return {
|
||||
versionInfo,
|
||||
downloadProgress,
|
||||
handleOPenDevTools,
|
||||
showUpdateModal,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
.save-path {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,768 @@
|
||||
<template>
|
||||
<material-modal :show="modelValue" max-height="90%" teleport="#view" @close="handleCancel">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ $t('theme_edit_modal__title') }}</h2>
|
||||
<div class="scroll" :class="$style.content">
|
||||
<div :class="[$style.group, $style.base]">
|
||||
<div :class="$style.groupContent">
|
||||
<div :class="$style.item">
|
||||
<div ref="primary_color_ref" :class="$style.color" />
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__primary') }}</div>
|
||||
</div>
|
||||
<div :class="$style.item">
|
||||
<div ref="app_bg_color_ref" :class="$style.color" />
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__app_bg') }}</div>
|
||||
</div>
|
||||
<div :class="$style.item">
|
||||
<div ref="aside_font_color_ref" :class="$style.color" />
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__aside_color') }}</div>
|
||||
</div>
|
||||
<div :class="$style.item">
|
||||
<div ref="main_bg_color_ref" :class="$style.color" />
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__main_bg') }}</div>
|
||||
</div>
|
||||
<div :class="[$style.item, $style.bg]">
|
||||
<div :class="[$style.bgImg, {[$style.hasBg]: !!bgImg}]" @click="selectBgImg">
|
||||
<img v-if="bgImg" loading="lazy" decoding="async" :class="$style.img" :src="bgImg" alt="Background Image">
|
||||
<svg-icon v-else :class="$style.icon" name="plus" />
|
||||
<button :class="$style.removeBtn" type="button" @click.stop="removeBgImg">
|
||||
<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>
|
||||
</div>
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__bg_image') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.groupContent">
|
||||
<div :class="$style.group">
|
||||
<div :class="$style.groupTitle">
|
||||
<span :class="$style.title">{{ $t('theme_edit_modal__badge') }}</span>
|
||||
<span class="badge badge-theme-primary">{{ $t('tag__lossless') }}</span>
|
||||
<span class="badge badge-theme-secondary">{{ $t('tag__high_quality') }}</span>
|
||||
<span class="badge badge-theme-tertiary">kw</span>
|
||||
</div>
|
||||
<div :class="$style.groupContent">
|
||||
<div :class="$style.item">
|
||||
<div ref="badge_primary_color_ref" :class="$style.color" />
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__badge_primary') }}</div>
|
||||
</div>
|
||||
<div :class="$style.item">
|
||||
<div ref="badge_secondary_color_ref" :class="$style.color" />
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__badge_secondary') }}</div>
|
||||
</div>
|
||||
<div :class="$style.item">
|
||||
<div ref="badge_tertiary_color_ref" :class="$style.color" />
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__badge_tertiary') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.group">
|
||||
<div :class="$style.groupTitle">
|
||||
<span>{{ $t('theme_edit_modal__control_btn') }}</span>
|
||||
<div :class="$style.controlBtn">
|
||||
<button type="button" :class="$style.hide">
|
||||
<svg :class="$style.controlBtnIcon" 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">
|
||||
<use xlink:href="#icon-window-hide" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" :class="$style.min">
|
||||
<svg :class="$style.controlBtnIcon" 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>
|
||||
<button type="button" :class="$style.close">
|
||||
<svg :class="$style.controlBtnIcon" 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>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.groupContent">
|
||||
<div :class="$style.item">
|
||||
<div ref="close_btn_color_ref" :class="$style.color" />
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__close_btn') }}</div>
|
||||
</div>
|
||||
<div :class="$style.item">
|
||||
<div ref="min_btn_color_ref" :class="$style.color" />
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__min_btn') }}</div>
|
||||
</div>
|
||||
<div :class="$style.item">
|
||||
<div ref="hide_btn_color_ref" :class="$style.color" />
|
||||
<div :class="$style.label">{{ $t('theme_edit_modal__hide_btn') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div :class="$style.note">
|
||||
<p>{{ $t('theme_selector_modal__title_tip') }}</p>
|
||||
</div> -->
|
||||
<div :class="$style.footer">
|
||||
<div :class="$style.subContent">
|
||||
<base-input v-model="themeName" :class="$style.input" :placeholder="$t('theme_selector_modal__theme_name')" />
|
||||
<base-checkbox id="theme_edit_modal__dark" v-model="isDark" :class="$style.checkbox" :label="$t('theme_edit_modal__dark')" @change="handleDark" />
|
||||
<base-checkbox id="theme_edit_modal__preview" v-model="preview" :class="$style.checkbox" :label="$t('theme_edit_modal__preview')" @change="handlePreview" />
|
||||
</div>
|
||||
<div :class="$style.subContent">
|
||||
<base-btn v-if="themeId" :class="$style.btn" @click="handleRemove">{{ $t('theme_edit_modal__remove') }}</base-btn>
|
||||
<base-btn v-if="themeId" :class="$style.btn" @click="handleSaveNew">{{ $t('theme_edit_modal__save_new') }}</base-btn>
|
||||
<!-- <base-btn :class="$style.btn" @click="handleCancel">{{ $t('btn_cancel') }}</base-btn> -->
|
||||
<base-btn :class="$style.btn" @click="handleSubmit">{{ $t('btn_save') }}</base-btn>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { joinPath, extname, copyFile, checkPath, createDir, removeFile, moveFile, basename } from '@common/utils/nodejs'
|
||||
import { nextTick, ref, watch } from '@common/utils/vueTools'
|
||||
import { applyTheme, buildThemeColors, getThemes, copyTheme } from '@renderer/store/utils'
|
||||
// import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
// import { applyTheme, getThemes } from '@renderer/store/utils'
|
||||
import { createThemeColors } from '@common/theme/utils'
|
||||
import useMainCoolor from './useMainCoolor'
|
||||
import useAppBgColor from './useAppBgColor'
|
||||
import useMainBgColor from './useMainBgColor'
|
||||
import useAsideFontColor from './useAsideFontColor'
|
||||
import useBadgePrimaryColor from './useBadgePrimaryColor'
|
||||
import useBadgeSecondaryColor from './useBadgeSecondaryColor'
|
||||
import useBadgeTertiaryColor from './useBadgeTertiaryColor'
|
||||
import useCloseBtnColor from './useCloseBtnColor'
|
||||
import useMinBtnColor from './useMinBtnColor'
|
||||
import useHideBtnColor from './useHideBtnColor'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
import { removeTheme, saveTheme, showSelectDialog } from '@renderer/utils/ipc'
|
||||
import { dialog } from '@renderer/plugins/Dialog'
|
||||
import { themeInfo } from '@renderer/store'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'ThemeSelectorModal',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
themeId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'submit'],
|
||||
setup(props, { emit }) {
|
||||
const themeName = ref('')
|
||||
const isDark = ref(false)
|
||||
const preview = ref(false)
|
||||
const bgImg = ref('')
|
||||
let originBgName = ''
|
||||
let currentBgPath = ''
|
||||
|
||||
let theme
|
||||
|
||||
const getColor = (color, theme) => {
|
||||
return color.startsWith('var')
|
||||
? theme.config.themeColors[color.replace(/var\((.+)\)/, '$1')]
|
||||
: color
|
||||
}
|
||||
|
||||
const createPreview = () => {
|
||||
if (!preview.value) return
|
||||
window.setTheme(buildThemeColors(theme, themeInfo.dataPath))
|
||||
}
|
||||
|
||||
// '--color-app-background': string
|
||||
// '--color-main-background': string
|
||||
// '--color-nav-font': string
|
||||
// '--background-image': string
|
||||
// '--background-image-position': string
|
||||
// '--background-image-size': string
|
||||
|
||||
// // 关闭按钮颜色
|
||||
// '--color-btn-hide': string
|
||||
// '--color-btn-min': string
|
||||
// '--color-btn-close': string
|
||||
|
||||
// // 徽章颜色
|
||||
// '--color-badge-primary': string
|
||||
// '--color-badge-secondary': string
|
||||
// '--color-badge-tertiary': string
|
||||
const { primary_color_ref, initMainColor, destroyMainColor } = useMainCoolor()
|
||||
const { app_bg_color_ref, initAppBgColor, destroyAppBgColor, setAppBgColor } = useAppBgColor()
|
||||
const { aside_font_color_ref, initAsideFontColor, destroyAsideFontColor, setAsideFontColor } = useAsideFontColor()
|
||||
const { main_bg_color_ref, initMainBgColor, destroyMainBgColor, setMainBgColor } = useMainBgColor()
|
||||
const { badge_primary_color_ref, initBadgePrimaryColor, destroyBadgePrimaryColor, setBadgePrimaryColor } = useBadgePrimaryColor()
|
||||
const { badge_secondary_color_ref, initBadgeSecondaryColor, destroyBadgeSecondaryColor, setBadgeSecondaryColor } = useBadgeSecondaryColor()
|
||||
const { badge_tertiary_color_ref, initBadgeTertiaryColor, destroyBadgeTertiaryColor, setBadgeTertiaryColor } = useBadgeTertiaryColor()
|
||||
const { close_btn_color_ref, initCloseBtnColor, destroyCloseBtnColor, setCloseBtnColor } = useCloseBtnColor()
|
||||
const { min_btn_color_ref, initMinBtnColor, destroyMinBtnColor, setMinBtnColor } = useMinBtnColor()
|
||||
const { hide_btn_color_ref, initHideBtnColor, destroyHideBtnColor, setHideBtnColor } = useHideBtnColor()
|
||||
|
||||
let appBgColorOrigin
|
||||
let appBgColor
|
||||
let asideFontColorOrigin
|
||||
let asideFontColor
|
||||
let mainBgColorOrigin
|
||||
let mainBgColor
|
||||
let badgePrimaryColorOrigin
|
||||
let badgePrimaryColor
|
||||
let badgeSecondaryColorOrigin
|
||||
let badgeSecondaryColor
|
||||
let badgeTertiaryColorOrigin
|
||||
let badgeTertiaryColor
|
||||
let closeBtnColorOrigin
|
||||
let closeBtnColor
|
||||
let minBtnColorOrigin
|
||||
let minBtnColor
|
||||
let hideBtnColorOrigin
|
||||
let hideBtnColor
|
||||
|
||||
const applyPrimaryColor = (color, isDark) => {
|
||||
theme.config.themeColors = createThemeColors(color, isDark)
|
||||
if (theme.config.extInfo['--color-app-background'].startsWith('var')) setAppBgColor(getColor(appBgColorOrigin, theme))
|
||||
if (theme.config.extInfo['--color-nav-font'].startsWith('var')) setAsideFontColor(getColor(asideFontColorOrigin, theme))
|
||||
if (theme.config.extInfo['--color-main-background'].startsWith('var')) setMainBgColor(getColor(mainBgColorOrigin, theme))
|
||||
if (theme.config.extInfo['--color-badge-primary'].startsWith('var')) setBadgePrimaryColor(getColor(badgePrimaryColorOrigin, theme))
|
||||
if (theme.config.extInfo['--color-badge-secondary'].startsWith('var')) setBadgeSecondaryColor(getColor(badgeSecondaryColorOrigin, theme))
|
||||
if (theme.config.extInfo['--color-badge-tertiary'].startsWith('var')) setBadgeTertiaryColor(getColor(badgeTertiaryColorOrigin, theme))
|
||||
if (theme.config.extInfo['--color-btn-close'].startsWith('var')) setCloseBtnColor(getColor(closeBtnColorOrigin, theme))
|
||||
if (theme.config.extInfo['--color-btn-min'].startsWith('var')) setMinBtnColor(getColor(minBtnColorOrigin, theme))
|
||||
if (theme.config.extInfo['--color-btn-hide'].startsWith('var')) setHideBtnColor(getColor(hideBtnColorOrigin, theme))
|
||||
|
||||
createPreview()
|
||||
}
|
||||
|
||||
const initColors = (_theme) => {
|
||||
theme = _theme
|
||||
// console.log(theme)
|
||||
themeName.value = theme.name
|
||||
isDark.value = theme.isDark
|
||||
currentBgPath = ''
|
||||
if (theme.config.extInfo['--background-image'] == 'none') {
|
||||
bgImg.value = ''
|
||||
originBgName = ''
|
||||
} else {
|
||||
bgImg.value = joinPath(themeInfo.dataPath, theme.config.extInfo['--background-image'])
|
||||
originBgName = theme.config.extInfo['--background-image']
|
||||
}
|
||||
appBgColorOrigin = theme.config.extInfo['--color-app-background']
|
||||
appBgColor = getColor(appBgColorOrigin, theme)
|
||||
asideFontColorOrigin = theme.config.extInfo['--color-nav-font']
|
||||
asideFontColor = getColor(asideFontColorOrigin, theme)
|
||||
mainBgColorOrigin = theme.config.extInfo['--color-main-background']
|
||||
mainBgColor = getColor(mainBgColorOrigin, theme)
|
||||
badgePrimaryColorOrigin = theme.config.extInfo['--color-badge-primary']
|
||||
badgePrimaryColor = getColor(badgePrimaryColorOrigin, theme)
|
||||
badgeSecondaryColorOrigin = theme.config.extInfo['--color-badge-secondary']
|
||||
badgeSecondaryColor = getColor(badgeSecondaryColorOrigin, theme)
|
||||
badgeTertiaryColorOrigin = theme.config.extInfo['--color-badge-tertiary']
|
||||
badgeTertiaryColor = getColor(badgeTertiaryColorOrigin, theme)
|
||||
closeBtnColorOrigin = theme.config.extInfo['--color-btn-close']
|
||||
closeBtnColor = getColor(closeBtnColorOrigin, theme)
|
||||
minBtnColorOrigin = theme.config.extInfo['--color-btn-min']
|
||||
minBtnColor = getColor(minBtnColorOrigin, theme)
|
||||
hideBtnColorOrigin = theme.config.extInfo['--color-btn-hide']
|
||||
hideBtnColor = getColor(hideBtnColorOrigin, theme)
|
||||
|
||||
initMainColor(theme.config.themeColors['--color-primary'], (color) => {
|
||||
// console.log(color)
|
||||
applyPrimaryColor(color, theme.isDark)
|
||||
})
|
||||
initAppBgColor(appBgColor, (color) => {
|
||||
// console.log('appBgColor', color)
|
||||
theme.config.extInfo['--color-app-background'] = color == appBgColor ? appBgColorOrigin : color
|
||||
createPreview()
|
||||
}, () => setAppBgColor(getColor(appBgColorOrigin, theme)))
|
||||
initAsideFontColor(asideFontColor, (color) => {
|
||||
theme.config.extInfo['--color-nav-font'] = color == asideFontColor ? asideFontColorOrigin : color
|
||||
createPreview()
|
||||
}, () => setAsideFontColor(getColor(asideFontColorOrigin, theme)))
|
||||
initMainBgColor(mainBgColor, (color) => {
|
||||
theme.config.extInfo['--color-main-background'] = color == mainBgColor ? mainBgColorOrigin : color
|
||||
createPreview()
|
||||
}, () => setMainBgColor(getColor(mainBgColorOrigin, theme)))
|
||||
initBadgePrimaryColor(badgePrimaryColor, (color) => {
|
||||
theme.config.extInfo['--color-badge-primary'] = color == badgePrimaryColor ? badgePrimaryColorOrigin : color
|
||||
createPreview()
|
||||
}, () => setBadgePrimaryColor(getColor(badgePrimaryColorOrigin, theme)))
|
||||
initBadgeSecondaryColor(badgeSecondaryColor, (color) => {
|
||||
theme.config.extInfo['--color-badge-secondary'] = color == badgeSecondaryColor ? badgeSecondaryColorOrigin : color
|
||||
createPreview()
|
||||
}, () => setBadgeSecondaryColor(getColor(badgeSecondaryColorOrigin, theme)))
|
||||
initBadgeTertiaryColor(badgeTertiaryColor, (color) => {
|
||||
theme.config.extInfo['--color-badge-tertiary'] = color == badgeTertiaryColor ? badgeTertiaryColorOrigin : color
|
||||
createPreview()
|
||||
}, () => setBadgeTertiaryColor(getColor(badgeTertiaryColorOrigin, theme)))
|
||||
initCloseBtnColor(closeBtnColor, (color) => {
|
||||
theme.config.extInfo['--color-btn-close'] = color == closeBtnColor ? closeBtnColorOrigin : color
|
||||
createPreview()
|
||||
}, () => setCloseBtnColor(getColor(closeBtnColorOrigin, theme)))
|
||||
initMinBtnColor(minBtnColor, (color) => {
|
||||
theme.config.extInfo['--color-btn-min'] = color == minBtnColor ? minBtnColorOrigin : color
|
||||
createPreview()
|
||||
}, () => setMinBtnColor(getColor(minBtnColorOrigin, theme)))
|
||||
initHideBtnColor(hideBtnColor, (color) => {
|
||||
theme.config.extInfo['--color-btn-hide'] = color == hideBtnColor ? hideBtnColorOrigin : color
|
||||
createPreview()
|
||||
}, () => setHideBtnColor(getColor(hideBtnColorOrigin, theme)))
|
||||
|
||||
createPreview()
|
||||
}
|
||||
const destroyColors = () => {
|
||||
destroyMainColor()
|
||||
destroyAppBgColor()
|
||||
destroyAsideFontColor()
|
||||
destroyMainBgColor()
|
||||
destroyBadgePrimaryColor()
|
||||
destroyBadgeSecondaryColor()
|
||||
destroyBadgeTertiaryColor()
|
||||
destroyCloseBtnColor()
|
||||
destroyMinBtnColor()
|
||||
destroyHideBtnColor()
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (visible) => {
|
||||
nextTick(() => {
|
||||
getThemes(({ themes, userThemes }) => {
|
||||
if (visible) {
|
||||
if (props.themeId) {
|
||||
const theme = userThemes.find(t => t.id == props.themeId)
|
||||
if (theme) {
|
||||
initColors(copyTheme(theme))
|
||||
return
|
||||
}
|
||||
}
|
||||
const theme = copyTheme(themes[0])
|
||||
theme.id = 'user_theme_' + Date.now()
|
||||
theme.name = ''
|
||||
theme.isCustom = true
|
||||
initColors(theme)
|
||||
} else {
|
||||
destroyColors()
|
||||
// 移除临时保存的背景
|
||||
if (currentBgPath) removeFile(currentBgPath).catch(_ => _)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const selectBgImg = async() => {
|
||||
const result = await showSelectDialog({
|
||||
title: window.i18n.t('theme_edit_modal__select_bg_file'),
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{
|
||||
name: 'Image File',
|
||||
extensions: [
|
||||
'jpg', 'jpeg', 'jfif', 'pjpeg',
|
||||
'pjp', 'png', 'apng', 'avif', 'gif', 'svg',
|
||||
'webp', 'bmp'],
|
||||
},
|
||||
],
|
||||
})
|
||||
if (result.canceled) return
|
||||
const path = result.filePaths[0]
|
||||
const fileName = `${theme.id}_${Date.now()}${extname(path)}`
|
||||
const tempDir = joinPath(themeInfo.dataPath, 'temp')
|
||||
const bgPath = joinPath(tempDir, fileName)
|
||||
if (!await checkPath(tempDir)) await createDir(tempDir)
|
||||
await copyFile(path, bgPath)
|
||||
currentBgPath = bgImg.value = bgPath
|
||||
theme.config.extInfo['--background-image'] = 'temp/' + fileName
|
||||
|
||||
createPreview()
|
||||
}
|
||||
const removeBgImg = async() => {
|
||||
if (currentBgPath) {
|
||||
removeFile(currentBgPath)
|
||||
currentBgPath = ''
|
||||
}
|
||||
bgImg.value = ''
|
||||
theme.config.extInfo['--background-image'] = 'none'
|
||||
createPreview()
|
||||
}
|
||||
const handleDark = (val) => {
|
||||
theme.isDark = val
|
||||
applyPrimaryColor(theme.config.themeColors['--color-primary'], theme.isDark)
|
||||
}
|
||||
const handlePreview = (val) => {
|
||||
if (val) {
|
||||
createPreview()
|
||||
} else {
|
||||
applyTheme(appSetting['theme.id'], appSetting['theme.lightId'], appSetting['theme.darkId'], themeInfo.dataPath)
|
||||
}
|
||||
}
|
||||
const handleCancel = () => {
|
||||
handlePreview(false)
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
const handleSubmit = async() => {
|
||||
if (!themeName.value) return
|
||||
theme.name = themeName.value.substring(0, 20)
|
||||
// 保存新背景
|
||||
if (currentBgPath) {
|
||||
const name = basename(currentBgPath)
|
||||
await moveFile(currentBgPath, joinPath(themeInfo.dataPath, name))
|
||||
theme.config.extInfo['--background-image'] = name
|
||||
}
|
||||
// 移除旧背景
|
||||
if (originBgName && theme.config.extInfo['--background-image'] != originBgName) removeFile(joinPath(themeInfo.dataPath, originBgName))
|
||||
if (props.themeId) {
|
||||
const index = themeInfo.userThemes.findIndex(t => t.id == theme.id)
|
||||
if (index > -1) themeInfo.userThemes.splice(index, 1, theme)
|
||||
} else themeInfo.userThemes.push(theme)
|
||||
handlePreview(false)
|
||||
await saveTheme(theme)
|
||||
emit('submit')
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
const handleRemove = async() => {
|
||||
const confirm = await dialog.confirm({
|
||||
message: window.i18n.t('theme_edit_modal__remove_tip'),
|
||||
cancelButtonText: window.i18n.t('cancel_button_text'),
|
||||
confirmButtonText: window.i18n.t('confirm_button_text'),
|
||||
})
|
||||
if (!confirm) return
|
||||
let isRequireUpdateSetting = false
|
||||
const newSetting = {}
|
||||
if (appSetting['theme.id'] == props.themeId) {
|
||||
newSetting['theme.id'] = 'green'
|
||||
isRequireUpdateSetting = true
|
||||
}
|
||||
if (theme.isDark) {
|
||||
if (appSetting['theme.darkId'] == props.themeId) {
|
||||
newSetting['theme.darkId'] = 'black'
|
||||
isRequireUpdateSetting = true
|
||||
}
|
||||
} else {
|
||||
if (appSetting['theme.lightId'] == props.themeId) {
|
||||
newSetting['theme.lightId'] = 'green'
|
||||
isRequireUpdateSetting = true
|
||||
}
|
||||
}
|
||||
if (isRequireUpdateSetting) updateSetting(newSetting)
|
||||
if (originBgName) removeFile(joinPath(themeInfo.dataPath, originBgName))
|
||||
await removeTheme(props.themeId)
|
||||
const index = themeInfo.userThemes.findIndex(t => t.id == theme.id)
|
||||
console.log(index)
|
||||
if (index > -1) themeInfo.userThemes.splice(index, 1)
|
||||
handlePreview(false)
|
||||
emit('submit')
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
const handleSaveNew = async() => {
|
||||
if (!themeName.value) return
|
||||
theme.name = themeName.value.substring(0, 20)
|
||||
theme.id = 'user_theme_' + Date.now()
|
||||
// 保存新背景
|
||||
if (currentBgPath) {
|
||||
const name = basename(currentBgPath)
|
||||
await moveFile(currentBgPath, joinPath(themeInfo.dataPath, name))
|
||||
theme.config.extInfo['--background-image'] = name
|
||||
} else if (bgImg.value) {
|
||||
const fileName = `${theme.id}_${Date.now()}${extname(bgImg.value)}`
|
||||
await copyFile(bgImg.value, joinPath(themeInfo.dataPath, fileName))
|
||||
theme.config.extInfo['--background-image'] = fileName
|
||||
}
|
||||
themeInfo.userThemes.push(theme)
|
||||
handlePreview(false)
|
||||
await saveTheme(theme)
|
||||
emit('submit')
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
return {
|
||||
themeName,
|
||||
bgImg,
|
||||
isDark,
|
||||
handleDark,
|
||||
preview,
|
||||
handlePreview,
|
||||
handleCancel,
|
||||
handleSubmit,
|
||||
handleRemove,
|
||||
handleSaveNew,
|
||||
selectBgImg,
|
||||
removeBgImg,
|
||||
|
||||
primary_color_ref,
|
||||
app_bg_color_ref,
|
||||
main_bg_color_ref,
|
||||
aside_font_color_ref,
|
||||
badge_primary_color_ref,
|
||||
badge_secondary_color_ref,
|
||||
badge_tertiary_color_ref,
|
||||
close_btn_color_ref,
|
||||
min_btn_color_ref,
|
||||
hide_btn_color_ref,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
.main {
|
||||
// padding: 15px;
|
||||
// max-width: 400px;
|
||||
min-width: 300px;
|
||||
min-height: 0;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
h2 {
|
||||
flex: none;
|
||||
font-size: 16px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
padding-bottom: 15px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
flex: auto;
|
||||
padding: 15px 0;
|
||||
font-size: 14px;
|
||||
gap: 5px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
.group {
|
||||
|
||||
}
|
||||
.groupTitle {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
.title {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.groupContent {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
.item {
|
||||
padding: 0 20px;
|
||||
width: 60px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
}
|
||||
.base {
|
||||
.color {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.color {
|
||||
width: 80%;
|
||||
aspect-ratio: 1 / 1;
|
||||
background-color: var(--pcr-color);
|
||||
border-radius: @radius-border;
|
||||
cursor: pointer;
|
||||
transition: @transition-fast !important;
|
||||
transition-property: background-color, opacity !important;
|
||||
box-shadow: 0 0 3px var(--color-primary-light-100-alpha-300);
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
.label {
|
||||
.mixin-ellipsis-2;
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.bg {
|
||||
width: 0;
|
||||
flex: 1 1 auto;
|
||||
// width: 180px;
|
||||
min-width: 60px;
|
||||
max-width: 200px;
|
||||
}
|
||||
.bgImg {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
border: 1Px dashed var(--color-primary-light-100-alpha-300);
|
||||
transition: .3s ease;
|
||||
transition-property: border, color;
|
||||
box-sizing: border-box;
|
||||
border: 1px dashed var(--color-primary-light-100-alpha-300);
|
||||
color: var(--color-primary-light-100-alpha-300);
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: @transition-fast !important;
|
||||
transition-property: background-color, opacity !important;
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
&.hasBg {
|
||||
border: none;
|
||||
|
||||
.removeBtn {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.removeBtn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 6px 8px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
outline: none;
|
||||
transition: background-color 0.2s ease;
|
||||
line-height: 0;
|
||||
display: none;
|
||||
|
||||
svg {
|
||||
height: .7em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
&:active {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
// position: absolute;
|
||||
// font-size: 16px;
|
||||
width: auto;
|
||||
height: 66%;
|
||||
}
|
||||
}
|
||||
|
||||
@control-btn-width: @height-toolbar * .26;
|
||||
.controlBtn {
|
||||
display: flex;
|
||||
-webkit-app-region: no-drag;
|
||||
align-items: center;
|
||||
padding: 0 @control-btn-width;
|
||||
flex-direction: row-reverse;
|
||||
// height: @height-toolbar * .7;
|
||||
transition: opacity @transition-normal;
|
||||
opacity: .5;
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
.controlBtnIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
position: relative;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 1px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: @control-btn-width;
|
||||
height: @control-btn-width;
|
||||
border-radius: 50%;
|
||||
color: var(--color-font);
|
||||
+ button {
|
||||
margin-right: (@control-btn-width / 2);
|
||||
}
|
||||
|
||||
&.hide {
|
||||
background-color: var(--color-btn-hide);
|
||||
}
|
||||
&.min, &.fullscreenExit {
|
||||
background-color: var(--color-btn-min);
|
||||
}
|
||||
// &.max {
|
||||
// background-color: var(--color-btn-max);
|
||||
// }
|
||||
&.close {
|
||||
background-color: var(--color-btn-close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controlBtnIcon {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.note {
|
||||
padding: 8px 15px;
|
||||
font-size: 13px;
|
||||
line-height: 1.25;
|
||||
color: var(--color-font);
|
||||
// p {
|
||||
// + p {
|
||||
// margin-top: 5px;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
.footer {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 15px;
|
||||
font-size: 14px;
|
||||
.subContent {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
flex: none;
|
||||
}
|
||||
.input {
|
||||
min-width: 0;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.btn {
|
||||
// box-sizing: border-box;
|
||||
// margin-left: 15px;
|
||||
// margin-bottom: 15px;
|
||||
// height: 36px;
|
||||
// line-height: 36px;
|
||||
// padding: 0 10px !important;
|
||||
min-width: 70px;
|
||||
// .mixin-ellipsis-1;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { pickrTools, PickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
export default () => {
|
||||
const app_bg_color_ref = ref(null)
|
||||
let tools: PickrTools | null
|
||||
|
||||
const initAppBgColor = (color: string, changed: (color: string) => void, reset: () => void) => {
|
||||
if (!app_bg_color_ref.value) return
|
||||
tools = pickrTools.create(app_bg_color_ref.value, color, [
|
||||
'rgba(255, 255, 255, 0)',
|
||||
'rgba(255, 255, 255, 0.15)',
|
||||
'rgba(255, 255, 255, 0.15)',
|
||||
'rgba(21.34, 18.92, 44.61, 0.81)',
|
||||
], changed, reset)
|
||||
}
|
||||
const destroyAppBgColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setAppBgColor = (color: string) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
app_bg_color_ref,
|
||||
initAppBgColor,
|
||||
destroyAppBgColor,
|
||||
setAppBgColor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { pickrTools, PickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
export default () => {
|
||||
const aside_font_color_ref = ref(null)
|
||||
let tools: PickrTools | null
|
||||
|
||||
const initAsideFontColor = (color: string, changed: (color: string) => void, reset: () => void) => {
|
||||
if (!aside_font_color_ref.value) return
|
||||
tools = pickrTools.create(aside_font_color_ref.value, color, null, changed, reset)
|
||||
}
|
||||
const destroyAsideFontColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setAsideFontColor = (color: string) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
aside_font_color_ref,
|
||||
initAsideFontColor,
|
||||
destroyAsideFontColor,
|
||||
setAsideFontColor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { pickrTools, PickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
export default () => {
|
||||
const badge_primary_color_ref = ref(null)
|
||||
let tools: PickrTools | null
|
||||
|
||||
const initBadgePrimaryColor = (color: string, changed: (color: string) => void, reset: () => void) => {
|
||||
if (!badge_primary_color_ref.value) return
|
||||
tools = pickrTools.create(badge_primary_color_ref.value, color, null, changed, reset)
|
||||
}
|
||||
const destroyBadgePrimaryColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setBadgePrimaryColor = (color: string) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
badge_primary_color_ref,
|
||||
initBadgePrimaryColor,
|
||||
destroyBadgePrimaryColor,
|
||||
setBadgePrimaryColor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { pickrTools, PickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
export default () => {
|
||||
const badge_secondary_color_ref = ref(null)
|
||||
let tools: PickrTools | null
|
||||
|
||||
const initBadgeSecondaryColor = (color: string, changed: (color: string) => void, reset: () => void) => {
|
||||
if (!badge_secondary_color_ref.value) return
|
||||
tools = pickrTools.create(badge_secondary_color_ref.value, color, [
|
||||
'rgba(75, 174, 213, 1)',
|
||||
'rgba(92, 191, 155, 1)',
|
||||
'rgba(66, 6, 150, 7, 171, 1)',
|
||||
'rgba(158, 212, 88, 1)',
|
||||
'rgba(223, 187, 107, 1)',
|
||||
'rgba(245, 182, 132, 1)',
|
||||
'rgba(229, 163, 159, 1)',
|
||||
'rgba(177, 155, 159, 1)',
|
||||
'rgba(99, 118, 162, 1)',
|
||||
'rgba(176, 128, 219, 1)',
|
||||
'rgba(175, 148, 121, 1)',
|
||||
'rgba(223, 187, 107, 1)',
|
||||
'rgba(53.08, 107.67, 129.18, 1)',
|
||||
], changed, reset)
|
||||
}
|
||||
const destroyBadgeSecondaryColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setBadgeSecondaryColor = (color: string) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
badge_secondary_color_ref,
|
||||
initBadgeSecondaryColor,
|
||||
destroyBadgeSecondaryColor,
|
||||
setBadgeSecondaryColor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { pickrTools, PickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
export default () => {
|
||||
const badge_tertiary_color_ref = ref(null)
|
||||
let tools: PickrTools | null
|
||||
|
||||
const initBadgeTertiaryColor = (color: string, changed: (color: string) => void, reset: () => void) => {
|
||||
if (!badge_tertiary_color_ref.value) return
|
||||
tools = pickrTools.create(badge_tertiary_color_ref.value, color, [
|
||||
'rgba(231, 170, 54, 1)',
|
||||
'rgba(92, 191, 155, 1)',
|
||||
'rgba(54, 196, 231, 1)',
|
||||
'rgba(158, 212, 88, 1)',
|
||||
'rgba(223, 187, 107, 1)',
|
||||
'rgba(245, 182, 132, 1)',
|
||||
'rgba(229, 163, 159, 1)',
|
||||
'rgba(177, 155, 159, 1)',
|
||||
'rgba(99, 118, 162, 1)',
|
||||
'rgba(176, 128, 219, 1)',
|
||||
'rgba(175, 148, 121, 1)',
|
||||
'rgba(223, 187, 107, 1)',
|
||||
'rgba(49.11, 161.05, 135.64, 1)',
|
||||
], changed, reset)
|
||||
}
|
||||
const destroyBadgeTertiaryColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setBadgeTertiaryColor = (color: string) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
badge_tertiary_color_ref,
|
||||
initBadgeTertiaryColor,
|
||||
destroyBadgeTertiaryColor,
|
||||
setBadgeTertiaryColor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { pickrTools, PickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
export default () => {
|
||||
const close_btn_color_ref = ref(null)
|
||||
let tools: PickrTools | null
|
||||
|
||||
const initCloseBtnColor = (color: string, changed: (color: string) => void, reset: () => void) => {
|
||||
if (!close_btn_color_ref.value) return
|
||||
tools = pickrTools.create(close_btn_color_ref.value, color, [
|
||||
'rgba(59, 194, 178, 1)',
|
||||
'rgba(133, 196, 59, 1)',
|
||||
'rgba(250, 180, 160, 1)',
|
||||
'rgba(104.51, 72.55, 107.98, 1)',
|
||||
], changed, reset)
|
||||
}
|
||||
const destroyCloseBtnColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setCloseBtnColor = (color: string) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
close_btn_color_ref,
|
||||
initCloseBtnColor,
|
||||
destroyCloseBtnColor,
|
||||
setCloseBtnColor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { pickrTools, PickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
export default () => {
|
||||
const hide_btn_color_ref = ref(null)
|
||||
let tools: PickrTools | null
|
||||
|
||||
const initHideBtnColor = (color: string, changed: (color: string) => void, reset: () => void) => {
|
||||
if (!hide_btn_color_ref.value) return
|
||||
tools = pickrTools.create(hide_btn_color_ref.value, color, [
|
||||
'rgba(59, 194, 178, 1)',
|
||||
'rgba(133, 196, 59, 1)',
|
||||
'rgba(250, 180, 160, 1)',
|
||||
'rgba(77.46, 103.73, 151.81, 1)',
|
||||
], changed, reset)
|
||||
}
|
||||
const destroyHideBtnColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setHideBtnColor = (color: string) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
hide_btn_color_ref,
|
||||
initHideBtnColor,
|
||||
destroyHideBtnColor,
|
||||
setHideBtnColor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { pickrTools, PickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
export default () => {
|
||||
const main_bg_color_ref = ref(null)
|
||||
let tools: PickrTools | null
|
||||
|
||||
const initMainBgColor = (color: string, changed: (color: string) => void, reset: () => void) => {
|
||||
if (!main_bg_color_ref.value) return
|
||||
tools = pickrTools.create(main_bg_color_ref.value, color, [
|
||||
'rgba(255, 255, 255, 1)',
|
||||
'rgba(19, 19, 19, 0.9)',
|
||||
'rgba(255, 255, 255, 0.9)',
|
||||
'rgba(255, 255, 255, 0.8)',
|
||||
'rgba(25.82, 23.65, 46.6, 0.54)',
|
||||
], changed, reset)
|
||||
}
|
||||
const destroyMainBgColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setMainBgColor = (color: string) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
main_bg_color_ref,
|
||||
initMainBgColor,
|
||||
destroyMainBgColor,
|
||||
setMainBgColor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { pickrTools, PickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
export default () => {
|
||||
const primary_color_ref = ref(null)
|
||||
let tools: PickrTools | null
|
||||
|
||||
const initMainColor = (color: string, changed: (color: string) => void) => {
|
||||
if (!primary_color_ref.value) return
|
||||
tools = pickrTools.create(primary_color_ref.value, color, [
|
||||
'rgba(77, 175, 124, 1)',
|
||||
'rgba(52, 152, 219, 1)',
|
||||
'rgba(77, 131, 175, 1)',
|
||||
'rgba(245, 171, 53, 1)',
|
||||
'rgba(214, 69, 65, 1)',
|
||||
'rgba(241, 130, 141, 1)',
|
||||
'rgba(155, 89, 182, 1)',
|
||||
'rgba(108, 122, 137, 1)',
|
||||
'rgba(51, 110, 123, 1)',
|
||||
'rgba(79, 98, 208, 1)',
|
||||
'rgba(150, 150, 150, 1)',
|
||||
'rgba(74, 55, 82, 1)',
|
||||
'rgba(87, 144, 167, 1)',
|
||||
'rgba(192, 57, 43, 1)',
|
||||
'rgba(113.52, 107.21, 166.13, 1)',
|
||||
], changed, () => {})
|
||||
}
|
||||
const destroyMainColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
|
||||
return {
|
||||
primary_color_ref,
|
||||
initMainColor,
|
||||
destroyMainColor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ref } from '@common/utils/vueTools'
|
||||
import { pickrTools, PickrTools } from '@renderer/utils/pickrTools'
|
||||
|
||||
export default () => {
|
||||
const min_btn_color_ref = ref(null)
|
||||
let tools: PickrTools | null
|
||||
|
||||
const initMinBtnColor = (color: string, changed: (color: string) => void, reset: () => void) => {
|
||||
if (!min_btn_color_ref.value) return
|
||||
tools = pickrTools.create(min_btn_color_ref.value, color, [
|
||||
'rgba(59, 194, 178, 1)',
|
||||
'rgba(133, 196, 59, 1)',
|
||||
'rgba(250, 180, 160, 1)',
|
||||
'rgba(116, 87, 152, 1)',
|
||||
], changed, reset)
|
||||
}
|
||||
const destroyMinBtnColor = () => {
|
||||
if (!tools) return
|
||||
tools.destroy()
|
||||
tools = null
|
||||
}
|
||||
const setMinBtnColor = (color: string) => {
|
||||
tools?.setColor(color)
|
||||
}
|
||||
|
||||
return {
|
||||
min_btn_color_ref,
|
||||
initMinBtnColor,
|
||||
destroyMinBtnColor,
|
||||
setMinBtnColor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<material-modal :show="modelValue" bg-close="bg-close" teleport="#view" @close="$emit('update:modelValue', false)">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ $t('theme_selector_modal__title') }}</h2>
|
||||
<div class="scroll" :class="$style.content">
|
||||
<div>
|
||||
<h3>{{ $t('theme_selector_modal__light_title') }}</h3>
|
||||
<ul :class="$style.theme">
|
||||
<li
|
||||
v-for="theme in themeInfo.themeLights" :key="theme.id"
|
||||
:style="theme.styles" :aria-label="theme.name"
|
||||
:class="[{[$style.active]: appSetting['theme.lightId'] == theme.id}]" @click="setLightId(theme.id)"
|
||||
>
|
||||
<span :class="$style.bg" />
|
||||
<label>{{ theme.name }}</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>{{ $t('theme_selector_modal__dark_title') }}</h3>
|
||||
<ul :class="$style.theme">
|
||||
<li
|
||||
v-for="theme in themeInfo.themeDarks" :key="theme.id"
|
||||
:style="theme.styles" :aria-label="theme.name"
|
||||
:class="[{[$style.active]: appSetting['theme.darkId'] == theme.id}]" @click="setDarkId(theme.id)"
|
||||
>
|
||||
<span :class="$style.bg" />
|
||||
<label>{{ theme.name }}</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.note">
|
||||
<p>{{ $t('theme_selector_modal__title_tip') }}</p>
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { joinPath } from '@common/utils/nodejs'
|
||||
import { markRaw, reactive, watch } from '@common/utils/vueTools'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
import { applyTheme, getThemes } from '@renderer/store/utils'
|
||||
|
||||
export default {
|
||||
name: 'ThemeSelectorModal',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props) {
|
||||
const themeInfo = reactive({
|
||||
themeLights: [],
|
||||
themeDarks: [],
|
||||
})
|
||||
let dataPath = ''
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (!val) return
|
||||
getThemes((info) => {
|
||||
// console.log(info)
|
||||
const themes = [...info.themes, ...info.userThemes]
|
||||
const lights = []
|
||||
const darks = themes.filter(t => {
|
||||
if (t.isDark) return true
|
||||
lights.push(t)
|
||||
return false
|
||||
})
|
||||
dataPath = info.dataPath
|
||||
themeInfo.themeLights = lights.map(t => {
|
||||
return {
|
||||
id: t.id,
|
||||
// @ts-expect-error
|
||||
name: t.isCustom ? t.name : window.i18n.t('theme_' + t.id),
|
||||
styles: {
|
||||
'--color-primary-theme': t.config.themeColors['--color-theme'],
|
||||
'--background-image-theme': t.isCustom
|
||||
? t.config.extInfo['--background-image'] == 'none'
|
||||
? 'none'
|
||||
: `url(file:///${joinPath(info.dataPath, t.config.extInfo['--background-image']).replaceAll('\\', '/')})`
|
||||
: t.config.extInfo['--background-image'],
|
||||
},
|
||||
}
|
||||
})
|
||||
themeInfo.themeDarks = markRaw(darks.map(t => {
|
||||
return {
|
||||
id: t.id,
|
||||
// @ts-expect-error
|
||||
name: t.isCustom ? t.name : window.i18n.t('theme_' + t.id),
|
||||
styles: {
|
||||
'--color-primary-theme': t.config.themeColors['--color-theme'],
|
||||
'--background-image-theme': t.isCustom
|
||||
? t.config.extInfo['--background-image'] == 'none'
|
||||
? 'none'
|
||||
: `url(file:///${joinPath(info.dataPath, t.config.extInfo['--background-image']).replaceAll('\\', '/')})`
|
||||
: t.config.extInfo['--background-image'],
|
||||
},
|
||||
}
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
const setLightId = (id) => {
|
||||
if (appSetting['theme.lightId'] == id) return
|
||||
updateSetting({ 'theme.lightId': id })
|
||||
if (appSetting['theme.id'] == 'auto') applyTheme('auto', id, appSetting['theme.darkId'], dataPath)
|
||||
}
|
||||
const setDarkId = (id) => {
|
||||
if (appSetting['theme.darkId'] == id) return
|
||||
updateSetting({ 'theme.darkId': id })
|
||||
if (appSetting['theme.id'] == 'auto') applyTheme('auto', appSetting['theme.lightId'], id, dataPath)
|
||||
}
|
||||
return {
|
||||
appSetting,
|
||||
themeInfo,
|
||||
setLightId,
|
||||
setDarkId,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
.main {
|
||||
// padding: 15px;
|
||||
// max-width: 400px;
|
||||
min-width: 300px;
|
||||
min-height: 0;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
h2 {
|
||||
flex: none;
|
||||
font-size: 16px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
padding-bottom: 15px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
flex: auto;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 15px;
|
||||
}
|
||||
.theme {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
// padding: 0 15px;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
// color: var(--color-primary);
|
||||
margin-right: 30px;
|
||||
transition: color .3s ease;
|
||||
margin-bottom: 15px;
|
||||
width: 56px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--color-primary-theme);
|
||||
.bg {
|
||||
border-color: var(--color-primary-theme);
|
||||
}
|
||||
}
|
||||
|
||||
.bg {
|
||||
display: block;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-bottom: 5px;
|
||||
border: 2px solid transparent;
|
||||
padding: 2px;
|
||||
transition: border-color .3s ease;
|
||||
border-radius: 5px;
|
||||
&:after {
|
||||
display: block;
|
||||
content: ' ';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: @radius-border;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-color: var(--color-primary-theme);
|
||||
background-image: var(--background-image-theme);
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
height: 1.2em;
|
||||
font-size: 14px;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.note {
|
||||
padding: 8px 15px;
|
||||
font-size: 13px;
|
||||
line-height: 1.25;
|
||||
color: var(--color-font);
|
||||
// p {
|
||||
// + p {
|
||||
// margin-top: 5px;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,225 @@
|
||||
<template lang="pug">
|
||||
material-modal(:show="modelValue" bg-close @close="handleClose" teleport="#view")
|
||||
main(:class="$style.main")
|
||||
h2 {{$t('user_api__title')}}
|
||||
ul.scroll(v-if="apiList.length" :class="$style.content")
|
||||
li(:class="[$style.listItem, {[$style.active]: appSetting['common.apiSource'] == api.id}]" v-for="(api, index) in apiList" :key="api.id")
|
||||
div(:class="$style.listLeft")
|
||||
h3 {{api.name}}
|
||||
p {{api.description}}
|
||||
div
|
||||
base-checkbox(:class="$style.checkbox" :id="`user_api_${api.id}`" v-model="api.allowShowUpdateAlert" @change="handleChangeAllowUpdateAlert(api, $event)" :label="$t('user_api__allow_show_update_alert')")
|
||||
base-btn(:class="$style.listBtn" outline :aria-label="$t('user_api__btn_remove')" @click.stop="handleRemove(index)")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 212.982 212.982' space='preserve' v-once)
|
||||
use(xlink:href='#icon-delete')
|
||||
div(v-else :class="$style.content")
|
||||
div(:class="$style.noitem") {{$t('user_api__noitem')}}
|
||||
div(:class="$style.note")
|
||||
p(:class="[$style.ruleLink]")
|
||||
| {{$t('user_api__readme')}}
|
||||
span.hover.underline(@click="handleOpenUrl('https://lyswhut.github.io/lx-music-doc/desktop/custom-source')" aria-label="https://lxmusic.toside.cn/desktop/custom-source") FAQ
|
||||
p {{$t('user_api__note')}}
|
||||
div(:class="$style.footer")
|
||||
base-btn(:class="$style.footerBtn" @click="handleImport") {{$t('user_api__btn_import')}}
|
||||
//- base-btn(:class="$style.footerBtn" @click="handleExport") {{$t('user_api__btn_export')}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { importUserApi, removeUserApi, showSelectDialog, setAllowShowUserApiUpdateAlert } from '@renderer/utils/ipc'
|
||||
import { readFile } from '@common/utils/nodejs'
|
||||
import { openUrl } from '@common/utils/electron'
|
||||
import apiSourceInfo from '@renderer/utils/musicSdk/api-source-info'
|
||||
import { userApi } from '@renderer/store'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup() {
|
||||
return {
|
||||
userApi,
|
||||
appSetting,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
apiList() {
|
||||
return this.userApi.list
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleImport() {
|
||||
if (this.userApi.list.length > 20) {
|
||||
this.$dialog({
|
||||
message: this.$t('user_api__max_tip'),
|
||||
confirmButtonText: this.$t('ok'),
|
||||
})
|
||||
return
|
||||
}
|
||||
showSelectDialog({
|
||||
title: this.$t('user_api__import_file'),
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'LX API File', extensions: ['js'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
],
|
||||
}).then(result => {
|
||||
if (result.canceled) return
|
||||
return readFile(result.filePaths[0]).then(data => {
|
||||
return importUserApi(data.toString()).then(({ apiList }) => {
|
||||
userApi.list = apiList
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
handleExport() {
|
||||
|
||||
},
|
||||
async handleRemove(index) {
|
||||
const api = this.apiList[index]
|
||||
if (!api) return
|
||||
if (appSetting['common.apiSource'] == api.id) {
|
||||
let backApi = apiSourceInfo.find(api => !api.disabled)
|
||||
if (backApi) updateSetting({ 'common.apiSource': backApi.id })
|
||||
}
|
||||
userApi.list = await removeUserApi([api.id])
|
||||
},
|
||||
handleClose() {
|
||||
this.$emit('update:modelValue', false)
|
||||
},
|
||||
handleOpenUrl(url) {
|
||||
openUrl(url)
|
||||
},
|
||||
handleChangeAllowUpdateAlert(api, enable) {
|
||||
setAllowShowUserApiUpdateAlert(api.id, enable)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 15px;
|
||||
max-width: 400px;
|
||||
min-width: 300px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-top: 3px;
|
||||
font-size: 14px;
|
||||
opacity: .86;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
min-height: 100px;
|
||||
max-height: 100%;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.listItem {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
transition: background-color 0.2s ease;
|
||||
padding: 10px;
|
||||
border-radius: @radius-border;
|
||||
&:hover {
|
||||
background-color: var(--color-primary-background-hover);
|
||||
}
|
||||
&.active {
|
||||
background-color: var(--color-primary-background-active);
|
||||
}
|
||||
h3 {
|
||||
font-size: 15px;
|
||||
color: var(--color-font);
|
||||
word-break: break-all;
|
||||
}
|
||||
p {
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
color: var(--color-font-label);
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.noitem {
|
||||
height: 100px;
|
||||
font-size: 18px;
|
||||
color: var(--color-font-label);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.listLeft {
|
||||
flex: auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
}
|
||||
.listBtn {
|
||||
flex: none;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
svg {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
.note {
|
||||
margin-top: 15px;
|
||||
font-size: 12px;
|
||||
line-height: 1.25;
|
||||
color: var(--color-font);
|
||||
p {
|
||||
+ p {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
.footerBtn {
|
||||
flex: auto;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
padding: 0 10px !important;
|
||||
width: 150px;
|
||||
.mixin-ellipsis-1;
|
||||
+ .footerBtn {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
.ruleLink {
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<div :class="$style.main">
|
||||
<div class="scroll" :class="$style.toc">
|
||||
<ul :class="$style.tocList" role="toolbar">
|
||||
<li v-for="h2 in tocList" :key="h2.id" :class="$style.tocListItem" role="presentation">
|
||||
<h2
|
||||
:class="[$style.tocH2, {[$style.active]: avtiveComponentName == h2.id }]"
|
||||
role="tab" :aria-selected="avtiveComponentName == h2.id"
|
||||
:aria-label="h2.title" ignore-tip @click="toggleTab(h2.id)"
|
||||
>
|
||||
{{ h2.title }}
|
||||
</h2>
|
||||
<!-- <ul v-if="h2.children.length" :class="$style.tocList">
|
||||
<li v-for="h3 in h2.children" :key="h3.id" :class="$style.tocSubListItem">
|
||||
<h3 :class="[$style.tocH3, toc.activeId == h3.id ? $style.active : null]" :aria-label="h3.title">
|
||||
<a :href="'#' + h3.id" @click.stop="toc.activeId = h3.id">{{ h3.title }}</a>
|
||||
</h3>
|
||||
</li>
|
||||
</ul> -->
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div ref="dom_content_ref" class="scroll" :class="$style.setting">
|
||||
<dl>
|
||||
<component :is="avtiveComponentName" />
|
||||
<!-- <SettingBasic />
|
||||
<SettingPlay />
|
||||
<SettingPlayDetail />
|
||||
<SettingDesktopLyric />
|
||||
<SettingSearch />
|
||||
<SettingList />
|
||||
<SettingDownload />
|
||||
<SettingSync />
|
||||
<SettingHotKey />
|
||||
<SettingNetwork />
|
||||
<SettingOdc />
|
||||
<SettingBackup />
|
||||
<SettingOther />
|
||||
<SettingUpdate />
|
||||
<SettingAbout /> -->
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, nextTick } from '@common/utils/vueTools'
|
||||
// import { currentStting } from './setting'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
import { useRoute } from '@common/utils/vueRouter'
|
||||
|
||||
import SettingBasic from './components/SettingBasic'
|
||||
import SettingPlay from './components/SettingPlay'
|
||||
import SettingPlayDetail from './components/SettingPlayDetail'
|
||||
import SettingDesktopLyric from './components/SettingDesktopLyric'
|
||||
import SettingSearch from './components/SettingSearch'
|
||||
import SettingList from './components/SettingList'
|
||||
import SettingDownload from './components/SettingDownload'
|
||||
import SettingSync from './components/SettingSync'
|
||||
import SettingHotKey from './components/SettingHotKey'
|
||||
import SettingNetwork from './components/SettingNetwork'
|
||||
import SettingOdc from './components/SettingOdc'
|
||||
import SettingBackup from './components/SettingBackup'
|
||||
import SettingOther from './components/SettingOther'
|
||||
import SettingUpdate from './components/SettingUpdate'
|
||||
import SettingAbout from './components/SettingAbout'
|
||||
|
||||
export default {
|
||||
name: 'Setting',
|
||||
components: {
|
||||
SettingBasic,
|
||||
SettingPlay,
|
||||
SettingPlayDetail,
|
||||
SettingDesktopLyric,
|
||||
SettingSearch,
|
||||
SettingList,
|
||||
SettingDownload,
|
||||
SettingSync,
|
||||
SettingHotKey,
|
||||
SettingNetwork,
|
||||
SettingOdc,
|
||||
SettingBackup,
|
||||
SettingOther,
|
||||
SettingUpdate,
|
||||
SettingAbout,
|
||||
},
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
const route = useRoute()
|
||||
|
||||
const dom_content_ref = ref(null)
|
||||
|
||||
const tocList = computed(() => {
|
||||
return [
|
||||
{ id: 'SettingBasic', title: t('setting__basic') },
|
||||
{ id: 'SettingPlay', title: t('setting__play') },
|
||||
{ id: 'SettingPlayDetail', title: t('setting__play_detail') },
|
||||
{ id: 'SettingDesktopLyric', title: t('setting__desktop_lyric') },
|
||||
{ id: 'SettingSearch', title: t('setting__search') },
|
||||
{ id: 'SettingList', title: t('setting__list') },
|
||||
{ id: 'SettingDownload', title: t('setting__download') },
|
||||
{ id: 'SettingSync', title: t('setting__sync') },
|
||||
{ id: 'SettingHotKey', title: t('setting__hot_key') },
|
||||
{ id: 'SettingNetwork', title: t('setting__network') },
|
||||
{ id: 'SettingOdc', title: t('setting__odc') },
|
||||
{ id: 'SettingBackup', title: t('setting__backup') },
|
||||
{ id: 'SettingOther', title: t('setting__other') },
|
||||
{ id: 'SettingUpdate', title: t('setting__update') },
|
||||
{ id: 'SettingAbout', title: t('setting__about') },
|
||||
]
|
||||
})
|
||||
|
||||
const avtiveComponentName = ref(route.query.name && tocList.value.some(t => t.id == route.query.name)
|
||||
? route.query.name
|
||||
: tocList.value[0].id)
|
||||
|
||||
const toggleTab = id => {
|
||||
avtiveComponentName.value = id
|
||||
nextTick(() => {
|
||||
dom_content_ref.value?.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
tocList,
|
||||
avtiveComponentName,
|
||||
dom_content_ref,
|
||||
toggleTab,
|
||||
}
|
||||
},
|
||||
// mounted() {
|
||||
// this.initTOC()
|
||||
// },
|
||||
// methods: {
|
||||
// initTOC() {
|
||||
// const list = this.$refs.dom_setting_list.children
|
||||
// const toc = []
|
||||
// let prevTitle
|
||||
// for (const item of list) {
|
||||
// if (item.tagName == 'DT') {
|
||||
// prevTitle = {
|
||||
// title: item.innerText.replace(/[((].+?[))]/, ''),
|
||||
// id: item.getAttribute('id'),
|
||||
// dom: item,
|
||||
// children: [],
|
||||
// }
|
||||
// toc.push(prevTitle)
|
||||
// continue
|
||||
// }
|
||||
// const h3 = item.querySelector('h3')
|
||||
// if (h3) {
|
||||
// prevTitle.children.push({
|
||||
// title: h3.innerText.replace(/[((].+?[))]/, ''),
|
||||
// id: h3.getAttribute('id'),
|
||||
// dom: h3,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// console.log(toc)
|
||||
// this.toc.list = toc
|
||||
// },
|
||||
// handleListScroll(event) {
|
||||
// // console.log(event.target.scrollTop)
|
||||
// },
|
||||
// },
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
height: 100%;
|
||||
border-top: var(--color-list-header-border-bottom);
|
||||
}
|
||||
|
||||
.toc {
|
||||
flex: 0 0 16%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.tocH2 {
|
||||
line-height: 1.5;
|
||||
.mixin-ellipsis-1;
|
||||
font-size: 13px;
|
||||
color: var(--color-font);
|
||||
padding: 8px 10px;
|
||||
transition: @transition-fast;
|
||||
transition-property: background-color, color;
|
||||
|
||||
&:not(.active) {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
// .tocH3 {
|
||||
// font-size: 13px;
|
||||
// opacity: .8;
|
||||
// }
|
||||
|
||||
// .tocList {
|
||||
// .tocList {
|
||||
// // padding-left: 15px;
|
||||
// }
|
||||
// }
|
||||
// .tocSubListItem {
|
||||
// padding-top: 10px;
|
||||
// }
|
||||
|
||||
.setting {
|
||||
padding: 0 15px 15px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
dt {
|
||||
border-left: 5px solid var(--color-primary-alpha-700);
|
||||
padding: 3px 7px;
|
||||
margin: 15px 0;
|
||||
|
||||
+ dd h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
dd {
|
||||
// margin-left: 15px;
|
||||
// font-size: 13px;
|
||||
> div {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
}
|
||||
h3 {
|
||||
font-size: 12px;
|
||||
margin: 25px 0 15px;
|
||||
}
|
||||
p {
|
||||
padding: 3px 0;
|
||||
line-height: 1.3;
|
||||
.btn {
|
||||
+ .btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help-btn {
|
||||
padding: 0;
|
||||
margin: 0 0.4em;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--color-button-font);
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease;
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
.help-icon {
|
||||
margin: 0 0.4em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .btn-content {
|
||||
// display: inline-block;
|
||||
// transition: @transition-theme;
|
||||
// transition-property: opacity, transform;
|
||||
// opacity: 1;
|
||||
// transform: scale(1);
|
||||
|
||||
// &.hide {
|
||||
// opacity: 0;
|
||||
// transform: scale(0);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// :global(dt):target, :global(h3):target {
|
||||
// animation: highlight 1s ease;
|
||||
// }
|
||||
|
||||
// @keyframes highlight {
|
||||
// from { background: yellow; }
|
||||
// to { background: transparent; }
|
||||
// }
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user