v2.0.0
@@ -1,130 +0,0 @@
|
||||
module.exports = {
|
||||
windowSizeList: [
|
||||
{
|
||||
id: 0,
|
||||
name: 'smaller',
|
||||
width: 828,
|
||||
height: 530,
|
||||
fontSize: '14px',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'small',
|
||||
width: 920,
|
||||
height: 590,
|
||||
fontSize: '16px',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'medium',
|
||||
width: 1018,
|
||||
height: 650,
|
||||
fontSize: '16px',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'big',
|
||||
width: 1114,
|
||||
height: 708,
|
||||
fontSize: '17px',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'larger',
|
||||
width: 1202,
|
||||
height: 766,
|
||||
fontSize: '17px',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'oversized',
|
||||
width: 1382,
|
||||
height: 886,
|
||||
fontSize: '18px',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'huge',
|
||||
width: 1686,
|
||||
height: 1062,
|
||||
fontSize: '19px',
|
||||
},
|
||||
],
|
||||
navigationUrlWhiteList: [
|
||||
|
||||
],
|
||||
themes: [
|
||||
{
|
||||
id: 0,
|
||||
name: '绿意盎然',
|
||||
className: 'green',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: '蓝田生玉',
|
||||
className: 'blue',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '信口雌黄',
|
||||
className: 'yellow',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '橙黄橘绿',
|
||||
className: 'orange',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '热情似火',
|
||||
className: 'red',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: '粉装玉琢',
|
||||
className: 'pink',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '重斤球紫',
|
||||
className: 'purple',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: '灰常美丽',
|
||||
className: 'grey',
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: '青出于黑',
|
||||
className: 'ming',
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: '青出于黑',
|
||||
className: 'blue2',
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: '黑灯瞎火',
|
||||
className: 'black',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: '月里嫦娥',
|
||||
className: 'mid_autumn',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: '木叶之村',
|
||||
className: 'naruto',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: '新年快乐',
|
||||
className: 'happy_new_year',
|
||||
},
|
||||
],
|
||||
themeLights: [0, 1, 2, 3, 4, 10, 5, 6, 11, 12, 7, 8, 9],
|
||||
themeDarks: [13, 7],
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
export interface WindowSize {
|
||||
id: number
|
||||
name: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export const windowSizeList: WindowSize[] = [
|
||||
{
|
||||
id: 0,
|
||||
name: 'smaller',
|
||||
width: 828,
|
||||
height: 540,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'small',
|
||||
width: 920,
|
||||
height: 600,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'medium',
|
||||
width: 1020,
|
||||
height: 660,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'big',
|
||||
width: 1114,
|
||||
height: 718,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'larger',
|
||||
width: 1202,
|
||||
height: 776,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'oversized',
|
||||
width: 1385,
|
||||
height: 896,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'huge',
|
||||
width: 1700,
|
||||
height: 1070,
|
||||
},
|
||||
]
|
||||
|
||||
export const navigationUrlWhiteList: RegExp[] = []
|
||||
|
||||
// 基础黑白色
|
||||
export const commonColorNames = [
|
||||
'--color-000', '--color-050', '--color-100', '--color-200', '--color-300', '--color-400',
|
||||
'--color-500', '--color-600', '--color-700', '--color-800', '--color-900',
|
||||
] as const
|
||||
export const commonLightColorValues = [
|
||||
'rgb(255, 255, 255)',
|
||||
'rgb(217,217,217)',
|
||||
'rgb(184,184,184)',
|
||||
'rgb(156,156,156)',
|
||||
'rgb(133,133,133)',
|
||||
'rgb(113,113,113)',
|
||||
'rgb(96,96,96)',
|
||||
'rgb(82,82,82)',
|
||||
'rgb(70,70,70)',
|
||||
'rgb(60,60,60)',
|
||||
'rgb(51,51,51)',
|
||||
] as const
|
||||
export const commonDarkColorValues = [
|
||||
'rgb(11, 11, 11)',
|
||||
'rgb(60,60,60)',
|
||||
'rgb(99,99,99)',
|
||||
'rgb(130,130,130)',
|
||||
'rgb(155,155,155)',
|
||||
'rgb(175,175,175)',
|
||||
'rgb(191,191,191)',
|
||||
'rgb(204,204,204)',
|
||||
'rgb(214,214,214)',
|
||||
'rgb(222,222,222)',
|
||||
'rgb(229,229,229)',
|
||||
] as const
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
export const URL_SCHEME_RXP = /^lxmusic:\/\//
|
||||
|
||||
|
||||
export const STORE_NAMES = {
|
||||
APP_SETTINGS: 'config_v2',
|
||||
DATA: 'data',
|
||||
SYNC: 'sync',
|
||||
HOTKEY: 'hot_key',
|
||||
USER_API: 'user_api',
|
||||
LRC_RAW: 'lyrics',
|
||||
LRC_EDITED: 'lyrics_edited',
|
||||
THEME: 'theme',
|
||||
} as const
|
||||
|
||||
export const APP_EVENT_NAMES = {
|
||||
winMainName: 'win_main',
|
||||
winLyricName: 'win_lyric',
|
||||
trayName: 'tray',
|
||||
} as const
|
||||
|
||||
export const LIST_IDS = {
|
||||
DEFAULT: 'default',
|
||||
LOVE: 'love',
|
||||
TEMP: 'temp',
|
||||
DOWNLOAD: 'download',
|
||||
} as const
|
||||
|
||||
export const DATA_KEYS = {
|
||||
viewPrevState: 'viewPrevState',
|
||||
playInfo: 'playInfo',
|
||||
searchHistoryList: 'searchHistoryList',
|
||||
listScrollPosition: 'listScrollPosition',
|
||||
listPrevSelectId: 'listPrevSelectId',
|
||||
listUpdateInfo: 'listUpdateInfo',
|
||||
ignoreVersion: 'ignoreVersion',
|
||||
|
||||
leaderboardSetting: 'leaderboardSetting',
|
||||
songListSetting: 'songListSetting',
|
||||
searchSetting: 'searchSetting',
|
||||
} as const
|
||||
|
||||
export const DEFAULT_SETTING = {
|
||||
leaderboard: {
|
||||
source: 'kw',
|
||||
boardId: 'kw__16',
|
||||
},
|
||||
|
||||
songList: {
|
||||
source: 'kg',
|
||||
sortId: '5',
|
||||
tagId: '',
|
||||
},
|
||||
|
||||
search: {
|
||||
temp_source: 'kw',
|
||||
source: 'all',
|
||||
type: 'music',
|
||||
},
|
||||
|
||||
viewPrevState: {
|
||||
url: '/search',
|
||||
query: {},
|
||||
},
|
||||
}
|
||||
|
||||
export const DOWNLOAD_STATUS = {
|
||||
RUN: 'run',
|
||||
WAITING: 'waiting',
|
||||
PAUSE: 'pause',
|
||||
ERROR: 'error',
|
||||
COMPLETED: 'completed',
|
||||
} as const
|
||||
|
||||
export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const
|
||||
@@ -1,89 +0,0 @@
|
||||
const { player: hotKeyPlayer, common: hotKeyCommon, desktop_lyric: hotKeyDesktopLyric } = require('./hotKey')
|
||||
|
||||
module.exports = {
|
||||
local: {
|
||||
enable: true,
|
||||
keys: {
|
||||
'mod+f5': {
|
||||
type: hotKeyPlayer.toggle_play.type,
|
||||
name: hotKeyPlayer.toggle_play.name,
|
||||
action: hotKeyPlayer.toggle_play.action,
|
||||
},
|
||||
'mod+arrowleft': {
|
||||
type: hotKeyPlayer.prev.type,
|
||||
name: hotKeyPlayer.prev.name,
|
||||
action: hotKeyPlayer.prev.action,
|
||||
},
|
||||
'mod+arrowright': {
|
||||
type: hotKeyPlayer.next.type,
|
||||
name: hotKeyPlayer.next.name,
|
||||
action: hotKeyPlayer.next.action,
|
||||
},
|
||||
f1: {
|
||||
type: hotKeyCommon.focusSearchInput.type,
|
||||
name: hotKeyCommon.focusSearchInput.name,
|
||||
action: hotKeyCommon.focusSearchInput.action,
|
||||
},
|
||||
},
|
||||
},
|
||||
global: {
|
||||
enable: false,
|
||||
keys: {
|
||||
MediaPlayPause: {
|
||||
type: hotKeyPlayer.toggle_play.type,
|
||||
name: null,
|
||||
action: hotKeyPlayer.toggle_play.action,
|
||||
},
|
||||
MediaPreviousTrack: {
|
||||
type: hotKeyPlayer.prev.type,
|
||||
name: null,
|
||||
action: hotKeyPlayer.prev.action,
|
||||
},
|
||||
MediaNextTrack: {
|
||||
type: hotKeyPlayer.next.type,
|
||||
name: null,
|
||||
action: hotKeyPlayer.next.action,
|
||||
},
|
||||
'mod+alt+f5': {
|
||||
type: hotKeyPlayer.toggle_play.type,
|
||||
name: hotKeyPlayer.toggle_play.name,
|
||||
action: hotKeyPlayer.toggle_play.action,
|
||||
},
|
||||
'mod+alt+arrowleft': {
|
||||
type: hotKeyPlayer.prev.type,
|
||||
name: hotKeyPlayer.prev.name,
|
||||
action: hotKeyPlayer.prev.action,
|
||||
},
|
||||
'mod+alt+arrowright': {
|
||||
type: hotKeyPlayer.next.type,
|
||||
name: hotKeyPlayer.next.name,
|
||||
action: hotKeyPlayer.next.action,
|
||||
},
|
||||
'mod+alt+arrowup': {
|
||||
type: hotKeyPlayer.volume_up.type,
|
||||
name: hotKeyPlayer.volume_up.name,
|
||||
action: hotKeyPlayer.volume_up.action,
|
||||
},
|
||||
'mod+alt+arrowdown': {
|
||||
type: hotKeyPlayer.volume_down.type,
|
||||
name: hotKeyPlayer.volume_down.name,
|
||||
action: hotKeyPlayer.volume_down.action,
|
||||
},
|
||||
'mod+alt+0': {
|
||||
type: hotKeyDesktopLyric.toggle_visible.type,
|
||||
name: hotKeyDesktopLyric.toggle_visible.name,
|
||||
action: hotKeyDesktopLyric.toggle_visible.action,
|
||||
},
|
||||
'mod+alt+-': {
|
||||
type: hotKeyDesktopLyric.toggle_lock.type,
|
||||
name: hotKeyDesktopLyric.toggle_lock.name,
|
||||
action: hotKeyDesktopLyric.toggle_lock.action,
|
||||
},
|
||||
'mod+alt+=': {
|
||||
type: hotKeyDesktopLyric.toggle_always_top.type,
|
||||
name: hotKeyDesktopLyric.toggle_always_top.name,
|
||||
action: hotKeyDesktopLyric.toggle_always_top.action,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { HOTKEY_PLAYER, HOTKEY_COMMON, HOTKEY_DESKTOP_LYRIC } from './hotKey'
|
||||
|
||||
const local: LX.HotKeyConfig = {
|
||||
enable: true,
|
||||
keys: {
|
||||
'mod+f5': {
|
||||
type: HOTKEY_PLAYER.toggle_play.type,
|
||||
name: HOTKEY_PLAYER.toggle_play.name,
|
||||
action: HOTKEY_PLAYER.toggle_play.action,
|
||||
},
|
||||
'mod+arrowleft': {
|
||||
type: HOTKEY_PLAYER.prev.type,
|
||||
name: HOTKEY_PLAYER.prev.name,
|
||||
action: HOTKEY_PLAYER.prev.action,
|
||||
},
|
||||
'mod+arrowright': {
|
||||
type: HOTKEY_PLAYER.next.type,
|
||||
name: HOTKEY_PLAYER.next.name,
|
||||
action: HOTKEY_PLAYER.next.action,
|
||||
},
|
||||
f1: {
|
||||
type: HOTKEY_COMMON.focusSearchInput.type,
|
||||
name: HOTKEY_COMMON.focusSearchInput.name,
|
||||
action: HOTKEY_COMMON.focusSearchInput.action,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const global: LX.HotKeyConfig = {
|
||||
enable: false,
|
||||
keys: {
|
||||
MediaPlayPause: {
|
||||
type: HOTKEY_PLAYER.toggle_play.type,
|
||||
name: '',
|
||||
action: HOTKEY_PLAYER.toggle_play.action,
|
||||
},
|
||||
MediaPreviousTrack: {
|
||||
type: HOTKEY_PLAYER.prev.type,
|
||||
name: '',
|
||||
action: HOTKEY_PLAYER.prev.action,
|
||||
},
|
||||
MediaNextTrack: {
|
||||
type: HOTKEY_PLAYER.next.type,
|
||||
name: '',
|
||||
action: HOTKEY_PLAYER.next.action,
|
||||
},
|
||||
'mod+alt+f5': {
|
||||
type: HOTKEY_PLAYER.toggle_play.type,
|
||||
name: HOTKEY_PLAYER.toggle_play.name,
|
||||
action: HOTKEY_PLAYER.toggle_play.action,
|
||||
},
|
||||
'mod+alt+arrowleft': {
|
||||
type: HOTKEY_PLAYER.prev.type,
|
||||
name: HOTKEY_PLAYER.prev.name,
|
||||
action: HOTKEY_PLAYER.prev.action,
|
||||
},
|
||||
'mod+alt+arrowright': {
|
||||
type: HOTKEY_PLAYER.next.type,
|
||||
name: HOTKEY_PLAYER.next.name,
|
||||
action: HOTKEY_PLAYER.next.action,
|
||||
},
|
||||
'mod+alt+arrowup': {
|
||||
type: HOTKEY_PLAYER.volume_up.type,
|
||||
name: HOTKEY_PLAYER.volume_up.name,
|
||||
action: HOTKEY_PLAYER.volume_up.action,
|
||||
},
|
||||
'mod+alt+arrowdown': {
|
||||
type: HOTKEY_PLAYER.volume_down.type,
|
||||
name: HOTKEY_PLAYER.volume_down.name,
|
||||
action: HOTKEY_PLAYER.volume_down.action,
|
||||
},
|
||||
'mod+alt+0': {
|
||||
type: HOTKEY_DESKTOP_LYRIC.toggle_visible.type,
|
||||
name: HOTKEY_DESKTOP_LYRIC.toggle_visible.name,
|
||||
action: HOTKEY_DESKTOP_LYRIC.toggle_visible.action,
|
||||
},
|
||||
'mod+alt+-': {
|
||||
type: HOTKEY_DESKTOP_LYRIC.toggle_lock.type,
|
||||
name: HOTKEY_DESKTOP_LYRIC.toggle_lock.name,
|
||||
action: HOTKEY_DESKTOP_LYRIC.toggle_lock.action,
|
||||
},
|
||||
'mod+alt+=': {
|
||||
type: HOTKEY_DESKTOP_LYRIC.toggle_always_top.type,
|
||||
name: HOTKEY_DESKTOP_LYRIC.toggle_always_top.name,
|
||||
action: HOTKEY_DESKTOP_LYRIC.toggle_always_top.action,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
local,
|
||||
global,
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
const defaultSetting = {
|
||||
version: '1.0.59',
|
||||
player: {
|
||||
togglePlayMethod: 'listLoop',
|
||||
highQuality: false,
|
||||
isShowTaskProgess: true,
|
||||
volume: 1,
|
||||
isMute: false,
|
||||
mediaDeviceId: 'default',
|
||||
isMediaDeviceRemovedStopPlay: false,
|
||||
isShowLyricTranslation: false,
|
||||
isShowLyricRoma: false,
|
||||
isS2t: false, // 是否将歌词从简体转换为繁体
|
||||
isPlayLxlrc: true,
|
||||
isSavePlayTime: false,
|
||||
audioVisualization: false,
|
||||
waitPlayEndStop: true,
|
||||
waitPlayEndStopTime: '',
|
||||
autoSkipOnError: true,
|
||||
},
|
||||
playDetail: {
|
||||
isZoomActiveLrc: true,
|
||||
isShowLyricProgressSetting: false,
|
||||
style: {
|
||||
fontSize: 100,
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
desktopLyric: {
|
||||
enable: false,
|
||||
isLock: false,
|
||||
isAlwaysOnTop: false,
|
||||
isAlwaysOnTopLoop: false,
|
||||
width: 450,
|
||||
height: 300,
|
||||
x: null,
|
||||
y: null,
|
||||
theme: 0,
|
||||
isLockScreen: true,
|
||||
isDelayScroll: true,
|
||||
isHoverHide: false,
|
||||
style: {
|
||||
font: '',
|
||||
fontSize: 120,
|
||||
opacity: 95,
|
||||
isZoomActiveLrc: true,
|
||||
},
|
||||
},
|
||||
list: {
|
||||
isClickPlayList: false,
|
||||
isShowAlbumName: true,
|
||||
isShowSource: true,
|
||||
isSaveScrollLocation: true,
|
||||
addMusicLocationType: 'top',
|
||||
},
|
||||
download: {
|
||||
enable: false,
|
||||
savePath: path.join(os.homedir(), 'Desktop'),
|
||||
fileName: '歌名 - 歌手',
|
||||
maxDownloadNum: 3,
|
||||
isDownloadLrc: false,
|
||||
lrcFormat: 'utf8',
|
||||
isEmbedPic: true,
|
||||
isEmbedLyric: false,
|
||||
isUseOtherSource: false,
|
||||
},
|
||||
leaderboard: {
|
||||
source: 'kw',
|
||||
tabId: 'kw__16',
|
||||
},
|
||||
songList: {
|
||||
source: 'kg',
|
||||
sortId: '5',
|
||||
tagInfo: {
|
||||
name: '默认',
|
||||
id: null,
|
||||
},
|
||||
},
|
||||
odc: {
|
||||
isAutoClearSearchInput: false,
|
||||
isAutoClearSearchList: false,
|
||||
},
|
||||
search: {
|
||||
searchSource: 'all',
|
||||
tempSearchSource: 'kw',
|
||||
isShowHotSearch: false,
|
||||
isShowHistorySearch: false,
|
||||
isFocusSearchBox: false,
|
||||
},
|
||||
network: {
|
||||
proxy: {
|
||||
enable: false,
|
||||
host: '',
|
||||
port: '',
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
},
|
||||
tray: {
|
||||
isShow: false,
|
||||
isToTray: false,
|
||||
themeId: 0,
|
||||
},
|
||||
sync: {
|
||||
enable: false,
|
||||
port: '23332',
|
||||
},
|
||||
windowSizeId: 2,
|
||||
startInFullscreen: false,
|
||||
theme: {
|
||||
id: 0,
|
||||
lightId: 0,
|
||||
darkId: 13,
|
||||
},
|
||||
langId: null,
|
||||
sourceId: 'kw',
|
||||
apiSource: 'temp',
|
||||
sourceNameType: 'alias',
|
||||
font: '',
|
||||
isShowAnimation: true,
|
||||
randomAnimate: true,
|
||||
ignoreVersion: null,
|
||||
isAgreePact: false,
|
||||
controlBtnPosition: process.platform === 'darwin' ? 'left' : 'right',
|
||||
}
|
||||
|
||||
const overwriteSetting = {
|
||||
|
||||
}
|
||||
|
||||
// 使用新年皮肤
|
||||
if (new Date().getMonth() < 2) {
|
||||
defaultSetting.theme.id = 9
|
||||
defaultSetting.desktopLyric.theme = 3
|
||||
}
|
||||
|
||||
|
||||
exports.defaultSetting = defaultSetting
|
||||
exports.overwriteSetting = overwriteSetting
|
||||
@@ -0,0 +1,120 @@
|
||||
import { join } from 'path'
|
||||
import { homedir } from 'os'
|
||||
|
||||
const defaultSetting: LX.AppSetting = {
|
||||
version: '2.0.0',
|
||||
|
||||
'common.windowSizeId': 2,
|
||||
'common.fontSize': 16,
|
||||
'common.startInFullscreen': false,
|
||||
'common.langId': null,
|
||||
'common.apiSource': 'temp',
|
||||
'common.sourceNameType': 'alias',
|
||||
'common.font': '',
|
||||
'common.isShowAnimation': true,
|
||||
'common.randomAnimate': true,
|
||||
'common.isAgreePact': false,
|
||||
'common.controlBtnPosition': process.platform === 'darwin' ? 'left' : 'right',
|
||||
|
||||
'player.startupAutoPlay': false,
|
||||
'player.togglePlayMethod': 'listLoop',
|
||||
'player.highQuality': false,
|
||||
'player.isShowTaskProgess': true,
|
||||
'player.volume': 1,
|
||||
'player.isMute': false,
|
||||
'player.mediaDeviceId': 'default',
|
||||
'player.isMediaDeviceRemovedStopPlay': false,
|
||||
'player.isShowLyricTranslation': false,
|
||||
'player.isShowLyricRoma': false,
|
||||
'player.isS2t': false,
|
||||
'player.isPlayLxlrc': false,
|
||||
'player.isSavePlayTime': false,
|
||||
'player.audioVisualization': false,
|
||||
'player.waitPlayEndStop': true,
|
||||
'player.waitPlayEndStopTime': '',
|
||||
'player.autoSkipOnError': true,
|
||||
|
||||
'playDetail.isZoomActiveLrc': false,
|
||||
'playDetail.isShowLyricProgressSetting': false,
|
||||
'playDetail.style.fontSize': 100,
|
||||
'playDetail.style.align': 'center',
|
||||
|
||||
'desktopLyric.enable': false,
|
||||
'desktopLyric.isLock': false,
|
||||
'desktopLyric.isAlwaysOnTop': false,
|
||||
'desktopLyric.isAlwaysOnTopLoop': false,
|
||||
'desktopLyric.audioVisualization': false,
|
||||
'desktopLyric.width': 450,
|
||||
'desktopLyric.height': 300,
|
||||
'desktopLyric.x': null,
|
||||
'desktopLyric.y': null,
|
||||
'desktopLyric.isLockScreen': true,
|
||||
'desktopLyric.isDelayScroll': true,
|
||||
'desktopLyric.isHoverHide': false,
|
||||
'desktopLyric.direction': 'horizontal',
|
||||
'desktopLyric.style.align': 'center',
|
||||
'desktopLyric.style.font': '',
|
||||
'desktopLyric.style.fontSize': 20,
|
||||
'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)',
|
||||
// 'desktopLyric.style.fontWeight': false,
|
||||
'desktopLyric.style.opacity': 95,
|
||||
'desktopLyric.style.ellipsis': false,
|
||||
'desktopLyric.style.isZoomActiveLrc': true,
|
||||
|
||||
'list.isClickPlayList': false,
|
||||
'list.isShowSource': true,
|
||||
'list.isSaveScrollLocation': true,
|
||||
'list.addMusicLocationType': 'top',
|
||||
'list.actionButtonsVisible': false,
|
||||
|
||||
'download.enable': false,
|
||||
'download.savePath': join(homedir(), 'Desktop'),
|
||||
'download.fileName': '歌名 - 歌手',
|
||||
'download.maxDownloadNum': 3,
|
||||
'download.isDownloadLrc': false,
|
||||
'download.isDownloadTLrc': false,
|
||||
'download.isDownloadRLrc': false,
|
||||
'download.lrcFormat': 'utf8',
|
||||
'download.isEmbedPic': true,
|
||||
'download.isEmbedLyric': false,
|
||||
'download.isUseOtherSource': false,
|
||||
|
||||
'search.isShowHotSearch': false,
|
||||
'search.isShowHistorySearch': false,
|
||||
'search.isFocusSearchBox': false,
|
||||
|
||||
'network.proxy.enable': false,
|
||||
'network.proxy.host': '',
|
||||
'network.proxy.port': '',
|
||||
'network.proxy.username': '',
|
||||
'network.proxy.password': '',
|
||||
|
||||
'tray.enable': false,
|
||||
// 'tray.isToTray': false,
|
||||
'tray.themeId': 0,
|
||||
|
||||
'sync.enable': false,
|
||||
'sync.port': '23332',
|
||||
|
||||
'theme.id': 'blue_plus',
|
||||
// 'theme.id': 'green',
|
||||
'theme.lightId': 'green',
|
||||
'theme.darkId': 'black',
|
||||
|
||||
'odc.isAutoClearSearchInput': false,
|
||||
'odc.isAutoClearSearchList': false,
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 使用新年皮肤
|
||||
if (new Date().getMonth() < 2) {
|
||||
defaultSetting['theme.id'] = 'happy_new_year'
|
||||
defaultSetting['desktopLyric.style.lyricPlayedColor'] = 'rgba(255, 18, 34, 1)'
|
||||
}
|
||||
|
||||
|
||||
export default defaultSetting
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
const { log } = require('./utils')
|
||||
import { log } from './utils'
|
||||
|
||||
process.on('uncaughtException', function(err) {
|
||||
const ignoreErrorMessage = [
|
||||
'Possible side-effect in debug-evaluate',
|
||||
'Unexpected end of input',
|
||||
]
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
if (ignoreErrorMessage.includes(err.message)) return
|
||||
console.error('An uncaught error occurred!')
|
||||
console.error(err)
|
||||
log.error(err)
|
||||
@@ -1,84 +1,101 @@
|
||||
const names = require('@main/events/_name')
|
||||
import { APP_EVENT_NAMES } from './constants'
|
||||
|
||||
|
||||
const keyName = {
|
||||
common: APP_EVENT_NAMES.winMainName,
|
||||
player: APP_EVENT_NAMES.winMainName,
|
||||
desktop_lyric: APP_EVENT_NAMES.winLyricName,
|
||||
}
|
||||
|
||||
const hotKey = {
|
||||
common: {
|
||||
min: {
|
||||
name: 'min',
|
||||
action: 'min',
|
||||
type: '',
|
||||
},
|
||||
min_toggle: {
|
||||
name: 'toggle_min',
|
||||
action: 'toggle_min',
|
||||
type: '',
|
||||
},
|
||||
hide_toggle: {
|
||||
name: 'toggle_hide',
|
||||
action: 'toggle_hide',
|
||||
type: '',
|
||||
},
|
||||
close: {
|
||||
name: 'toggle_close',
|
||||
action: 'toggle_close',
|
||||
type: '',
|
||||
},
|
||||
focusSearchInput: {
|
||||
name: 'focus_search_input',
|
||||
action: 'focus_search_input',
|
||||
type: '',
|
||||
},
|
||||
},
|
||||
player: {
|
||||
toggle_play: {
|
||||
name: 'toggle_play',
|
||||
action: 'toggle_play',
|
||||
type: '',
|
||||
},
|
||||
next: {
|
||||
name: 'next',
|
||||
action: 'next',
|
||||
type: '',
|
||||
},
|
||||
prev: {
|
||||
name: 'prev',
|
||||
action: 'prev',
|
||||
type: '',
|
||||
},
|
||||
volume_up: {
|
||||
name: 'volume_up',
|
||||
action: 'volume_up',
|
||||
type: '',
|
||||
},
|
||||
volume_down: {
|
||||
name: 'volume_down',
|
||||
action: 'volume_down',
|
||||
type: '',
|
||||
},
|
||||
volume_mute: {
|
||||
name: 'volume_mute',
|
||||
action: 'volume_mute',
|
||||
type: '',
|
||||
},
|
||||
},
|
||||
desktop_lyric: {
|
||||
toggle_visible: {
|
||||
name: 'toggle_visible',
|
||||
action: 'toggle_visible',
|
||||
type: '',
|
||||
},
|
||||
toggle_lock: {
|
||||
name: 'toggle_lock',
|
||||
action: 'toggle_lock',
|
||||
type: '',
|
||||
},
|
||||
toggle_always_top: {
|
||||
name: 'toggle_always_top',
|
||||
action: 'toggle_always_top',
|
||||
type: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const keyName = {
|
||||
common: names.mainWindow.name,
|
||||
player: names.mainWindow.name,
|
||||
desktop_lyric: names.winLyric.name,
|
||||
}
|
||||
|
||||
for (const type of Object.keys(hotKey)) {
|
||||
let item = hotKey[type]
|
||||
for (const key of Object.keys(item)) {
|
||||
item[key].action = `${type}_${item[key].action}`
|
||||
item[key].name = `${type}_${item[key].name}`
|
||||
item[key].type = keyName[type]
|
||||
for (const type of Object.keys(hotKey) as Array<keyof typeof hotKey>) {
|
||||
let keys = hotKey[type]
|
||||
for (const key of Object.keys(keys) as Array<keyof typeof keys>) {
|
||||
const keyInfo: LX.HotKey = keys[key]
|
||||
keyInfo.action = `${type}_${keyInfo.action}`
|
||||
keyInfo.name = `${type}_${keyInfo.name}`
|
||||
keyInfo.type = keyName[type] as keyof typeof hotKey
|
||||
}
|
||||
}
|
||||
|
||||
exports.common = hotKey.common
|
||||
exports.player = hotKey.player
|
||||
exports.desktop_lyric = hotKey.desktop_lyric
|
||||
export const HOTKEY_COMMON = hotKey.common
|
||||
export const HOTKEY_PLAYER = hotKey.player
|
||||
export const HOTKEY_DESKTOP_LYRIC = hotKey.desktop_lyric
|
||||
@@ -1,52 +0,0 @@
|
||||
const { ipcMain, ipcRenderer } = require('electron')
|
||||
const names = require('./ipcNames')
|
||||
|
||||
|
||||
exports.mainOn = (name, callback) => {
|
||||
ipcMain.on(name, callback)
|
||||
}
|
||||
exports.mainOnce = (name, callback) => {
|
||||
ipcMain.once(name, callback)
|
||||
}
|
||||
exports.mainOff = (name, callback) => {
|
||||
ipcMain.removeListener(name, callback)
|
||||
}
|
||||
exports.mainOffAll = name => {
|
||||
ipcMain.removeAllListeners(name)
|
||||
}
|
||||
|
||||
exports.mainHandle = (name, callback) => {
|
||||
ipcMain.handle(name, callback)
|
||||
}
|
||||
exports.mainHandleOnce = (name, callback) => {
|
||||
ipcMain.handleOnce(name, callback)
|
||||
}
|
||||
exports.mainHandleRemove = name => {
|
||||
ipcMain.removeListener(name)
|
||||
}
|
||||
|
||||
exports.mainSend = (window, name, params) => {
|
||||
window.webContents.send(name, params)
|
||||
}
|
||||
|
||||
exports.rendererSend = (name, params) => {
|
||||
ipcRenderer.send(name, params)
|
||||
}
|
||||
exports.rendererSendSync = (name, params) => ipcRenderer.sendSync(name, params)
|
||||
|
||||
exports.rendererInvoke = (name, params) => ipcRenderer.invoke(name, params)
|
||||
|
||||
exports.rendererOn = (name, callback) => {
|
||||
ipcRenderer.on(name, callback)
|
||||
}
|
||||
exports.rendererOnce = (name, callback) => {
|
||||
ipcRenderer.once(name, callback)
|
||||
}
|
||||
exports.rendererOff = (name, callback) => {
|
||||
ipcRenderer.removeListener(name, callback)
|
||||
}
|
||||
exports.rendererOffAll = name => {
|
||||
ipcRenderer.removeAllListeners(name)
|
||||
}
|
||||
|
||||
exports.NAMES = names
|
||||
@@ -1,126 +0,0 @@
|
||||
const names = {
|
||||
mainWindow: {
|
||||
focus: 'focus',
|
||||
close: 'close',
|
||||
min: 'min',
|
||||
max: 'max',
|
||||
fullscreen: 'fullscreen',
|
||||
set_app_name: 'set_app_name',
|
||||
clear_cache: 'clear_cache',
|
||||
get_cache_size: 'get_cache_size',
|
||||
get_env_params: 'get_env_params',
|
||||
clear_env_params_deeplink: 'clear_env_params_deeplink',
|
||||
wait: 'wait',
|
||||
wait_cancel: 'wait_cancel',
|
||||
interval: 'interval',
|
||||
interval_callback: 'interval_callback',
|
||||
interval_cancel: 'interval_cancel',
|
||||
open_dev_tools: 'open_dev_tools',
|
||||
system_theme_change: 'system_theme_change',
|
||||
|
||||
set_music_meta: 'set_music_meta',
|
||||
progress: 'progress',
|
||||
change_tray: 'change_tray',
|
||||
quit_update: 'quit_update',
|
||||
update_available: 'update_available',
|
||||
update_error: 'update_error',
|
||||
update_progress: 'update_progress',
|
||||
update_downloaded: 'update_downloaded',
|
||||
update_not_available: 'update_not_available',
|
||||
set_ignore_mouse_events: 'set_ignore_mouse_events',
|
||||
set_app_setting: 'set_app_setting',
|
||||
set_window_size: 'set_window_size',
|
||||
show_save_dialog: 'show_save_dialog',
|
||||
get_system_fonts: 'get_system_fonts',
|
||||
|
||||
handle_request: 'handle_request',
|
||||
cancel_request: 'cancel_request',
|
||||
|
||||
handle_xm_verify_open: 'handle_xm_verify_open',
|
||||
handle_xm_verify_close: 'handle_xm_verify_close',
|
||||
select_dir: 'select_dir',
|
||||
|
||||
restart_window: 'restart_window',
|
||||
|
||||
lang_s2t: 'lang_s2t',
|
||||
|
||||
handle_kw_decode_lyric: 'handle_kw_decode_lyric',
|
||||
get_lyric_info: 'get_lyric_info',
|
||||
set_lyric_info: 'set_lyric_info',
|
||||
set_config: 'set_config',
|
||||
set_hot_key_config: 'set_hot_key_config',
|
||||
key_down: 'key_down',
|
||||
quit: 'quit',
|
||||
min_toggle: 'min_toggle',
|
||||
hide_toggle: 'hide_toggle',
|
||||
get_data_path: 'get_data_path',
|
||||
show_dialog: 'show_dialog',
|
||||
|
||||
get_setting: 'get_setting',
|
||||
get_playlist: 'get_playlist',
|
||||
save_playlist: 'save_playlist',
|
||||
get_data: 'get_data',
|
||||
save_data: 'save_data',
|
||||
get_hot_key: 'get_hot_key',
|
||||
|
||||
import_user_api: 'import_user_api',
|
||||
remove_user_api: 'remove_user_api',
|
||||
set_user_api: 'set_user_api',
|
||||
get_user_api_list: 'get_user_api_list',
|
||||
request_user_api: 'request_user_api',
|
||||
request_user_api_cancel: 'request_user_api_cancel',
|
||||
get_user_api_status: 'get_user_api_status',
|
||||
user_api_status: 'user_api_status',
|
||||
user_api_show_update_alert: 'user_api_show_update_alert',
|
||||
user_api_set_allow_update_alert: 'user_api_set_allow_update_alert',
|
||||
|
||||
get_lyric: 'get_lyric',
|
||||
save_lyric: 'save_lyric',
|
||||
clear_lyric: 'clear_lyric',
|
||||
get_lyric_raw: 'get_lyric_raw',
|
||||
save_lyric_raw: 'save_lyric_raw',
|
||||
clear_lyric_raw: 'clear_lyric_raw',
|
||||
get_lyric_edited: 'get_lyric_edited',
|
||||
save_lyric_edited: 'save_lyric_edited',
|
||||
remove_lyric_edited: 'remove_lyric_edited',
|
||||
get_music_url: 'get_music_url',
|
||||
save_music_url: 'save_music_url',
|
||||
clear_music_url: 'clear_music_url',
|
||||
|
||||
sync_enable: 'sync_enable',
|
||||
sync_status: 'sync_status',
|
||||
sync_get_status: 'sync_get_status',
|
||||
sync_generate_code: 'sync_generate_code',
|
||||
sync_action_list: 'sync_action_list',
|
||||
sync_list: 'sync_list',
|
||||
|
||||
taskbar_set_thumbar_buttons: 'taskbar_set_thumbar_buttons',
|
||||
taskbar_set_thumbnail_clip: 'taskbar_set_thumbnail_clip',
|
||||
taskbar_on_thumbar_button_click: 'taskbar_on_thumbar_button_click',
|
||||
},
|
||||
winLyric: {
|
||||
close: 'close',
|
||||
set_lyric_info: 'set_lyric_info',
|
||||
get_lyric_info: 'get_lyric_info',
|
||||
set_lyric_config: 'set_lyric_config',
|
||||
get_lyric_config: 'get_lyric_config',
|
||||
set_win_bounds: 'set_win_bounds',
|
||||
key_down: 'key_down',
|
||||
},
|
||||
hotKey: {
|
||||
enable: 'enable',
|
||||
status: 'status',
|
||||
set_config: 'set_config',
|
||||
},
|
||||
}
|
||||
|
||||
for (const item of Object.keys(names)) {
|
||||
let name = names[item]
|
||||
for (const key of Object.keys(name)) {
|
||||
name[key] = `${item}_${name[key]}`
|
||||
}
|
||||
}
|
||||
|
||||
exports.mainWindow = names.mainWindow
|
||||
exports.winLyric = names.winLyric
|
||||
exports.hotKey = names.hotKey
|
||||
@@ -0,0 +1,177 @@
|
||||
const modules = {
|
||||
common: {
|
||||
get_env_params: 'get_env_params',
|
||||
deeplink: 'deeplink',
|
||||
clear_env_params_deeplink: 'clear_env_params_deeplink',
|
||||
system_theme_change: 'system_theme_change',
|
||||
theme_change: 'theme_change',
|
||||
get_system_fonts: 'get_system_fonts',
|
||||
get_app_setting: 'get_app_setting',
|
||||
set_app_setting: 'set_app_setting',
|
||||
},
|
||||
player: {
|
||||
invoke_play_music: 'play_music',
|
||||
invoke_play_next: 'play_next',
|
||||
invoke_play_prev: 'play_prev',
|
||||
invoke_toggle_play: 'toggle_play',
|
||||
player_play: 'player_play',
|
||||
player_pause: 'player_pause',
|
||||
player_stop: 'player_stop',
|
||||
player_error: 'player_error',
|
||||
|
||||
list_data_overwire: 'list_data_overwire',
|
||||
list_get: 'list_get',
|
||||
list_add: 'list_add',
|
||||
list_remove: 'list_remove',
|
||||
list_update: 'list_update',
|
||||
list_update_position: 'list_update_position',
|
||||
list_music_get: 'list_music_get',
|
||||
list_music_add: 'list_music_add',
|
||||
list_music_move: 'list_music_move',
|
||||
list_music_remove: 'list_music_remove',
|
||||
list_music_update: 'list_music_update',
|
||||
list_music_update_position: 'list_music_update_position',
|
||||
list_music_overwrite: 'list_music_overwrite',
|
||||
list_music_clear: 'list_music_clear',
|
||||
list_music_check_exist: 'list_music_check_exist',
|
||||
list_music_get_list_ids: 'list_music_get_list_ids',
|
||||
},
|
||||
winMain: {
|
||||
focus: 'focus',
|
||||
close: 'close',
|
||||
min: 'min',
|
||||
max: 'max',
|
||||
fullscreen: 'fullscreen',
|
||||
set_app_name: 'set_app_name',
|
||||
clear_cache: 'clear_cache',
|
||||
get_cache_size: 'get_cache_size',
|
||||
inited: 'inited',
|
||||
show_save_dialog: 'show_save_dialog',
|
||||
show_select_dialog: 'show_select_dialog',
|
||||
show_dialog: 'show_dialog',
|
||||
open_dev_tools: 'open_dev_tools',
|
||||
|
||||
progress: 'progress',
|
||||
change_tray: 'change_tray',
|
||||
quit_update: 'quit_update',
|
||||
update_check: 'update_check',
|
||||
update_available: 'update_available',
|
||||
update_error: 'update_error',
|
||||
update_progress: 'update_progress',
|
||||
update_downloaded: 'update_downloaded',
|
||||
update_not_available: 'update_not_available',
|
||||
set_ignore_mouse_events: 'set_ignore_mouse_events',
|
||||
set_window_size: 'set_window_size',
|
||||
|
||||
handle_request: 'handle_request',
|
||||
cancel_request: 'cancel_request',
|
||||
|
||||
|
||||
restart_window: 'restart_window',
|
||||
|
||||
// lang_s2t: 'lang_s2t',
|
||||
|
||||
handle_kw_decode_lyric: 'handle_kw_decode_lyric',
|
||||
get_lyric_info: 'get_lyric_info',
|
||||
set_lyric_info: 'set_lyric_info',
|
||||
set_config: 'set_config',
|
||||
set_hot_key_config: 'set_hot_key_config',
|
||||
on_config_change: 'on_config_change',
|
||||
key_down: 'key_down',
|
||||
quit: 'quit',
|
||||
min_toggle: 'min_toggle',
|
||||
hide_toggle: 'hide_toggle',
|
||||
|
||||
get_other_source: 'get_other_source',
|
||||
save_other_source: 'save_other_source',
|
||||
clear_other_source: 'clear_other_source',
|
||||
get_other_source_count: 'get_other_source_count',
|
||||
get_data: 'get_data',
|
||||
save_data: 'save_data',
|
||||
get_hot_key: 'get_hot_key',
|
||||
|
||||
import_user_api: 'import_user_api',
|
||||
remove_user_api: 'remove_user_api',
|
||||
set_user_api: 'set_user_api',
|
||||
get_user_api_list: 'get_user_api_list',
|
||||
request_user_api: 'request_user_api',
|
||||
request_user_api_cancel: 'request_user_api_cancel',
|
||||
get_user_api_status: 'get_user_api_status',
|
||||
user_api_status: 'user_api_status',
|
||||
user_api_show_update_alert: 'user_api_show_update_alert',
|
||||
user_api_set_allow_update_alert: 'user_api_set_allow_update_alert',
|
||||
|
||||
get_palyer_lyric: 'get_lyric',
|
||||
// save_lyric: 'save_lyric',
|
||||
// clear_lyric: 'clear_lyric',
|
||||
get_lyric_raw: 'get_lyric_raw',
|
||||
save_lyric_raw: 'save_lyric_raw',
|
||||
clear_lyric_raw: 'clear_lyric_raw',
|
||||
get_lyric_raw_count: 'get_lyric_raw_count',
|
||||
get_lyric_edited: 'get_lyric_edited',
|
||||
save_lyric_edited: 'save_lyric_edited',
|
||||
remove_lyric_edited: 'remove_lyric_edited',
|
||||
clear_lyric_edited: 'clear_lyric_edited',
|
||||
get_lyric_edited_count: 'get_lyric_edited_count',
|
||||
get_music_url: 'get_music_url',
|
||||
save_music_url: 'save_music_url',
|
||||
clear_music_url: 'clear_music_url',
|
||||
get_music_url_count: 'get_music_url_count',
|
||||
|
||||
sync_action: 'sync_action',
|
||||
|
||||
process_new_desktop_lyric_client: 'process_new_desktop_lyric_client',
|
||||
|
||||
player_action_set_buttons: 'player_action_set_buttons',
|
||||
// player_action_set_thumbnail_clip: 'player_action_set_thumbnail_clip',
|
||||
player_action_on_button_click: 'player_action_on_button_click',
|
||||
|
||||
get_themes: 'get_themes',
|
||||
save_theme: 'save_theme',
|
||||
remove_theme: 'remove_theme',
|
||||
|
||||
download_list_get: 'download_list_get',
|
||||
download_list_add: 'download_list_add',
|
||||
download_list_update: 'download_list_update',
|
||||
download_list_remove: 'download_list_remove',
|
||||
download_list_clear: 'download_list_clear',
|
||||
},
|
||||
winLyric: {
|
||||
close: 'close',
|
||||
set_config: 'set_config',
|
||||
get_config: 'get_config',
|
||||
on_config_change: 'on_config_change',
|
||||
main_window_inited: 'main_window_inited',
|
||||
set_win_bounds: 'set_win_bounds',
|
||||
key_down: 'key_down',
|
||||
request_main_window_channel: 'request_main_window_channel',
|
||||
provide_main_window_channel: 'provide_main_window_channel',
|
||||
},
|
||||
hotKey: {
|
||||
enable: 'enable',
|
||||
status: 'status',
|
||||
set_config: 'set_config',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for (const moduleName of Object.keys(modules) as Array<keyof typeof modules>) {
|
||||
let eventNames = modules[moduleName]
|
||||
for (const eventName of Object.keys(eventNames) as Array<keyof typeof eventNames>) {
|
||||
eventNames[eventName] = `${moduleName}_${eventName as string}` as never
|
||||
}
|
||||
}
|
||||
|
||||
// for (const moduleName of Object.keys(modules) as Array<keyof typeof modules>) {
|
||||
// let eventNames = modules[moduleName]
|
||||
// for (const eventName of Object.keys(eventNames)) {
|
||||
// eventNames[eventName] = `${moduleName}_${eventName}`
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
export const CMMON_EVENT_NAME = modules.common
|
||||
export const PLAYER_EVENT_NAME = modules.player
|
||||
export const WIN_MAIN_RENDERER_EVENT_NAME = modules.winMain
|
||||
export const WIN_LYRIC_RENDERER_EVENT_NAME = modules.winLyric
|
||||
export const HOTKEY_RENDERER_EVENT_NAME = modules.hotKey
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ipcMain } from 'electron'
|
||||
|
||||
export function mainOn(name: string, listener: LX.IpcMainEventListener): void
|
||||
export function mainOn<T>(name: string, listener: LX.IpcMainEventListenerParams<T>): void
|
||||
export function mainOn<T>(name: string, listener: LX.IpcMainEventListenerParams<T>): void {
|
||||
ipcMain.on(name, (event, params) => {
|
||||
listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
export function mainOnce(name: string, listener: LX.IpcMainEventListener): void
|
||||
export function mainOnce<T>(name: string, listener: LX.IpcMainEventListenerParams<T>): void
|
||||
export function mainOnce<T>(name: string, listener: LX.IpcMainEventListenerParams<T>): void {
|
||||
ipcMain.once(name, (event, params) => {
|
||||
listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
export const mainOff = (name: string, listener: (...args: any[]) => void) => {
|
||||
ipcMain.removeListener(name, listener)
|
||||
}
|
||||
|
||||
export const mainOffAll = (name: string) => {
|
||||
ipcMain.removeAllListeners(name)
|
||||
}
|
||||
|
||||
export function mainHandle(name: string, listener: LX.IpcMainInvokeEventListener): void
|
||||
export function mainHandle<T>(name: string, listener: LX.IpcMainInvokeEventListenerParams<T>): void
|
||||
export function mainHandle<V>(name: string, listener: LX.IpcMainInvokeEventListenerValue<V>): void
|
||||
export function mainHandle<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void
|
||||
export function mainHandle<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void {
|
||||
ipcMain.handle(name, async(event, params) => {
|
||||
return await listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
export function mainHandleOnce(name: string, listener: LX.IpcMainInvokeEventListener): void
|
||||
export function mainHandleOnce<T>(name: string, listener: LX.IpcMainInvokeEventListenerParams<T>): void
|
||||
export function mainHandleOnce<V>(name: string, listener: LX.IpcMainInvokeEventListenerValue<V>): void
|
||||
export function mainHandleOnce<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void
|
||||
export function mainHandleOnce<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void {
|
||||
ipcMain.handleOnce(name, async(event, params) => {
|
||||
return await listener({ event, params })
|
||||
})
|
||||
}
|
||||
export const mainHandleRemove = (name: string) => {
|
||||
ipcMain.removeHandler(name)
|
||||
}
|
||||
|
||||
export function mainSend(window: Electron.BrowserWindow, name: string): void
|
||||
export function mainSend<T>(window: Electron.BrowserWindow, name: string, params: T): void
|
||||
export function mainSend<T>(window: Electron.BrowserWindow, name: string, params?: T): void {
|
||||
window.webContents.send(name, params)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
export function rendererSend(name: string): void
|
||||
export function rendererSend<T>(name: string, params: T): void
|
||||
export function rendererSend<T>(name: string, params?: T): void {
|
||||
ipcRenderer.send(name, params)
|
||||
}
|
||||
|
||||
export function rendererSendSync(name: string): void
|
||||
export function rendererSendSync<T>(name: string, params: T): void
|
||||
export function rendererSendSync<T>(name: string, params?: T): void {
|
||||
ipcRenderer.sendSync(name, params)
|
||||
}
|
||||
|
||||
export async function rendererInvoke(name: string): Promise<void>
|
||||
export async function rendererInvoke<V>(name: string): Promise<V>
|
||||
export async function rendererInvoke<T>(name: string, params: T): Promise<void>
|
||||
export async function rendererInvoke<T, V>(name: string, params: T): Promise<V>
|
||||
export async function rendererInvoke <T, V>(name: string, params?: T): Promise<V> {
|
||||
return await ipcRenderer.invoke(name, params)
|
||||
}
|
||||
|
||||
export function rendererOn(name: string, listener: LX.IpcRendererEventListener): void
|
||||
export function rendererOn<T>(name: string, listener: LX.IpcRendererEventListenerParams<T>): void
|
||||
export function rendererOn<T>(name: string, listener: LX.IpcRendererEventListenerParams<T>): void {
|
||||
ipcRenderer.on(name, (event, params) => {
|
||||
listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
export function rendererOnce(name: string, listener: LX.IpcRendererEventListener): void
|
||||
export function rendererOnce<T>(name: string, listener: LX.IpcRendererEventListenerParams<T>): void
|
||||
export function rendererOnce<T>(name: string, listener: LX.IpcRendererEventListenerParams<T>): void {
|
||||
ipcRenderer.once(name, (event, params) => {
|
||||
listener({ event, params })
|
||||
})
|
||||
}
|
||||
|
||||
export const rendererOff = (name: string, listener: (...args: any[]) => any) => {
|
||||
ipcRenderer.removeListener(name, listener)
|
||||
}
|
||||
|
||||
export const rendererOffAll = (name: string) => {
|
||||
ipcRenderer.removeAllListeners(name)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
const Store = require('electron-store')
|
||||
const { dialog, app, shell } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const log = require('electron-log')
|
||||
|
||||
const stores = {}
|
||||
|
||||
/**
|
||||
* 获取 Store 对象
|
||||
* @param {*} name store 名
|
||||
* @param {*} isIgnoredError 是否忽略错误
|
||||
* @param {*} isShowErrorAlert 是否显示错误弹窗
|
||||
* @returns Store
|
||||
*/
|
||||
module.exports = (name, isIgnoredError = true, isShowErrorAlert = true) => {
|
||||
if (stores[name]) return stores[name]
|
||||
let store
|
||||
try {
|
||||
store = stores[name] = new Store({ name, clearInvalidConfig: false })
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
|
||||
if (!isIgnoredError) throw error
|
||||
|
||||
|
||||
const backPath = path.join(app.getPath('userData'), name + '.json.bak')
|
||||
fs.copyFileSync(path.join(app.getPath('userData'), name + '.json'), backPath)
|
||||
if (isShowErrorAlert) {
|
||||
dialog.showMessageBoxSync({
|
||||
type: 'error',
|
||||
message: name + ' data load error',
|
||||
detail: `We have helped you back up the old ${name} file to: ${backPath}\nYou can try to repair and restore it manually\n\nError detail: ${error.message}`,
|
||||
})
|
||||
shell.showItemInFolder(backPath)
|
||||
}
|
||||
|
||||
|
||||
store = new Store({ name, clearInvalidConfig: true })
|
||||
}
|
||||
return store
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/* eslint-disable */
|
||||
// https://github.com/PimpTrizkit/PJs/wiki/12.-Shade,-Blend-and-Convert-a-Web-Color-(pSBC.js)#micro-functions-version-4
|
||||
|
||||
/**
|
||||
* Blend color (Lighten or Darken)
|
||||
* @param {number} p 混合百分比 范围 0.0 - 1.0
|
||||
* @param {string} c0 rgb(a) color1
|
||||
* @param {string} c1 rgb(a) color2
|
||||
* @returns color
|
||||
*/
|
||||
exports.RGB_Linear_Blend=(p,c0,c1)=>{
|
||||
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
|
||||
return"rgb"+(x?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+i(e[3]=="a"?e.slice(5):e.slice(4))*p)+","+r(i(b)*P+i(f)*p)+","+r(i(c)*P+i(g)*p)+j;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blend color (Lighten or Darken)
|
||||
* @param {number} p 混合百分比 范围 0.0 - 1.0
|
||||
* @param {string} c0 rgb(a) color1
|
||||
* @param {string} c1 rgb(a) color2
|
||||
* @returns color
|
||||
*/
|
||||
exports.RGB_Log_Blend=(p,c0,c1)=>{
|
||||
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
|
||||
return"rgb"+(x?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+p*i(e[3]=="a"?e.slice(5):e.slice(4))**2)**0.5)+","+r((P*i(b)**2+p*i(f)**2)**0.5)+","+r((P*i(c)**2+p*i(g)**2)**0.5)+j;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shade color (Lighten or Darken)
|
||||
* @param {number} p Shade 百分比范围为 -1.0 - 1.0 负为黑色,正为白色
|
||||
* @param {string} c0 rgb(a) color
|
||||
* @returns color
|
||||
*/
|
||||
exports.RGB_Linear_Shade=(p,c0)=>{
|
||||
var i=parseInt,r=Math.round,[a,b,c,d]=c0.split(","),n=p<0,t=n?0:255*p,P=n?1+p:1-p;
|
||||
return"rgb"+(d?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+t)+","+r(i(b)*P+t)+","+r(i(c)*P+t)+(d?","+d:")");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shade color (Lighten or Darken)
|
||||
* @param {number} p Shade 百分比范围为 -1.0 - 1.0 负为黑色,正为白色
|
||||
* @param {string} c0 rgb(a) color
|
||||
* @returns color
|
||||
*/
|
||||
exports.RGB_Log_Shade=(p,c0)=>{
|
||||
var i=parseInt,r=Math.round,[a,b,c,d]=c0.split(","),n=p<0,t=n?0:p*255**2,P=n?1+p:1-p;
|
||||
return"rgb"+(d?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+t)**0.5)+","+r((P*i(b)**2+t)**0.5)+","+r((P*i(c)**2+t)**0.5)+(d?","+d:")");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 修改透明度
|
||||
* @param {number} p 透明度 -1.0 - 1.0
|
||||
* @param {string} color
|
||||
* @returns color
|
||||
*/
|
||||
exports.RGB_Alpha_Shade = (p, color) => {
|
||||
var i = parseInt
|
||||
var n = p < 0
|
||||
var [r, g, b, a] = color.split(",")
|
||||
r = r[3] == 'a' ? r.slice(5) : r.slice(4)
|
||||
if (a) {
|
||||
a = parseFloat(a)
|
||||
a = a - (n ? (1 - a) * p : a * p)
|
||||
a = n ? Math.max(0, a) : Math.min(1, a)
|
||||
} else {
|
||||
a = 1 - p
|
||||
a = Math.min(1, a)
|
||||
}
|
||||
return `rgba(${i(r)}, ${i(g)}, ${i(b)}, ${a.toFixed(2)})`
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
//! 更新默认主题配置后,需要执行 npm run build:theme 重新构建index.json
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { createThemeColors } = require('./utils')
|
||||
|
||||
const defaultThemes = [
|
||||
{
|
||||
id: 'green',
|
||||
name: '绿意盎然',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(77, 175, 124)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#4baed5',
|
||||
'--color-badge-tertiary': '#e7aa36',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'blue',
|
||||
name: '蓝田生玉',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(52, 152, 219)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#5cbf9b',
|
||||
'--color-badge-tertiary': '#5cbf9b',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'blue_plus',
|
||||
name: '蛋雅深蓝',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(77, 131, 175)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-600)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': 'rgba(66.6, 150.7, 171, 1)',
|
||||
'--color-badge-tertiary': 'rgba(54, 196, 231, 1)',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'orange',
|
||||
name: '橙黄橘绿',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(245, 171, 53)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#9ed458',
|
||||
'--color-badge-tertiary': '#9ed458',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'red',
|
||||
name: '热情似火',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(214, 69, 65)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#dfbb6b',
|
||||
'--color-badge-tertiary': '#dfbb6b',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pink',
|
||||
name: '粉装玉琢',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(241, 130, 141)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#f5b684',
|
||||
'--color-badge-tertiary': '#f5b684',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'purple',
|
||||
name: '重斤球紫',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(155, 89, 182)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#e5a39f',
|
||||
'--color-badge-tertiary': '#e5a39f',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'grey',
|
||||
name: '灰常美丽',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(108, 122, 137)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#b19b9f',
|
||||
'--color-badge-tertiary': '#b19b9f',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ming',
|
||||
name: '青出于黑',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(51, 110, 123)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#6376a2',
|
||||
'--color-badge-tertiary': '#6376a2',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'blue2',
|
||||
name: '清热板蓝',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(79, 98, 208)',
|
||||
'--color-app-background': 'var(--color-primary-light-600-alpha-700)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 1)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'none',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#b080db',
|
||||
'--color-badge-tertiary': '#b080db',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'black',
|
||||
name: '黑灯瞎火',
|
||||
isDark: true,
|
||||
config: {
|
||||
primary: 'rgb(150, 150, 150)',
|
||||
'--color-app-background': 'rgba(0, 0, 0, 0)',
|
||||
'--color-main-background': 'rgba(19, 19, 19, 0.9)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'url(./theme_images/landingMoon.png)',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary-dark-200)',
|
||||
'--color-badge-secondary': 'var(--color-primary)',
|
||||
'--color-badge-tertiary': 'var(--color-primary-dark-300)',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'mid_autumn',
|
||||
name: '月里嫦娥',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(74, 55, 82)',
|
||||
'--color-app-background': 'rgba(255, 255, 255, 0)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 0.9)',
|
||||
'--color-nav-font': 'var(--color-primary-light-600)',
|
||||
'--background-image': 'url(./theme_images/jqbg.jpg)',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': '#af9479',
|
||||
'--color-badge-tertiary': '#af9479',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'naruto',
|
||||
name: '木叶之村',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(87, 144, 167)',
|
||||
'--color-app-background': 'rgba(255, 255, 255, 0.15)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 0.8)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'url(./theme_images/myzcbg.jpg)',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': 'var(--color-primary)',
|
||||
'--color-badge-secondary': 'var(--color-primary-light-100)',
|
||||
'--color-badge-tertiary': 'var(--color-primary-light-100)',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'happy_new_year',
|
||||
name: '新年快乐',
|
||||
isDark: false,
|
||||
config: {
|
||||
primary: 'rgb(192, 57, 43)',
|
||||
'--color-app-background': 'rgba(255, 255, 255, 0.15)',
|
||||
'--color-main-background': 'rgba(255, 255, 255, 0.8)',
|
||||
'--color-nav-font': 'var(--color-primary)',
|
||||
'--background-image': 'url(./theme_images/xnkl.png)',
|
||||
'--background-image-position': 'center',
|
||||
'--background-image-size': 'cover',
|
||||
|
||||
'--color-btn-hide': '#3bc2b2',
|
||||
'--color-btn-min': '#85c43b',
|
||||
'--color-btn-close': '#fab4a0',
|
||||
|
||||
'--color-badge-primary': '#7fb575',
|
||||
'--color-badge-secondary': '#dfbb6b',
|
||||
'--color-badge-tertiary': 'var(--color-primary-light-100)',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const themes = defaultThemes.map(({ config: { primary, ...extInfo }, ...themeInfo }) => {
|
||||
return {
|
||||
...themeInfo,
|
||||
isCustom: false,
|
||||
config: {
|
||||
themeColors: createThemeColors(primary, themeInfo.isDark),
|
||||
extInfo,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, 'index.json'), JSON.stringify(themes, null, 2))
|
||||
|
||||
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 997 KiB After Width: | Height: | Size: 997 KiB |
@@ -0,0 +1,42 @@
|
||||
const { RGB_Linear_Shade, RGB_Alpha_Shade } = require('./colorUtils')
|
||||
|
||||
exports.createThemeColors = (rgbaColor, isDark) => {
|
||||
const colors = {
|
||||
'--color-primary': rgbaColor,
|
||||
}
|
||||
|
||||
let preColor = rgbaColor
|
||||
for (let i = 1; i < 11; i += 1) {
|
||||
preColor = RGB_Linear_Shade(isDark ? 0.2 : -0.1, preColor)
|
||||
colors[`--color-primary-dark-${i * 100}`] = preColor
|
||||
for (let j = 1; j < 10; j += 1) {
|
||||
colors[`--color-primary-dark-${i * 100}-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, preColor)
|
||||
colors[`--color-primary-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, rgbaColor)
|
||||
}
|
||||
}
|
||||
preColor = rgbaColor
|
||||
for (let i = 1; i < 10; i += 1) {
|
||||
preColor = RGB_Linear_Shade(isDark ? -0.1 : 0.2, preColor)
|
||||
colors[`--color-primary-light-${i * 100}`] = preColor
|
||||
for (let j = 1; j < 10; j += 1) {
|
||||
colors[`--color-primary-light-${i * 100}-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, preColor)
|
||||
}
|
||||
}
|
||||
preColor = RGB_Linear_Shade(isDark ? -0.2 : 1, preColor)
|
||||
colors[`--color-primary-light-${1000}`] = preColor
|
||||
for (let j = 1; j < 10; j += 1) {
|
||||
colors[`--color-primary-light-${1000}-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, preColor)
|
||||
}
|
||||
|
||||
colors['--color-theme'] = isDark ? colors['--color-primary-light-900'] : rgbaColor
|
||||
|
||||
return colors
|
||||
}
|
||||
|
||||
// rgb(238, 238, 238)
|
||||
// let prec = 'rgb(255, 255, 255)'
|
||||
// let colors = [prec]
|
||||
// for (let j = 1; j < 11; j += 1) {
|
||||
// colors.push(prec = RGB_Linear_Shade(-0.15, prec))
|
||||
// }
|
||||
// console.log(colors)
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "nodenext",
|
||||
"typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"./types"
|
||||
],
|
||||
},
|
||||
// "include": [
|
||||
// "**/*.ts",
|
||||
// "**/*.js",
|
||||
// "**/*.vue",
|
||||
// "**/*.json",
|
||||
// ],
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
|
||||
|
||||
declare namespace LX {
|
||||
type AddMusicLocationType = 'top' | 'bottom'
|
||||
|
||||
interface AppSetting {
|
||||
version: string
|
||||
|
||||
/**
|
||||
* 窗口大小id
|
||||
*/
|
||||
'common.windowSizeId': number
|
||||
|
||||
/**
|
||||
* 窗口大小id
|
||||
*/
|
||||
'common.fontSize': number
|
||||
|
||||
/**
|
||||
* 是否以全屏启动
|
||||
*/
|
||||
'common.startInFullscreen': boolean
|
||||
|
||||
/**
|
||||
* 语言id
|
||||
*/
|
||||
'common.langId': string | null
|
||||
|
||||
/**
|
||||
* api id
|
||||
*/
|
||||
'common.apiSource': string
|
||||
|
||||
/**
|
||||
* 音源名称类型,原名、别名
|
||||
*/
|
||||
'common.sourceNameType': 'alias' | 'real'
|
||||
|
||||
/**
|
||||
* 显示的字体
|
||||
*/
|
||||
'common.font': string
|
||||
|
||||
/**
|
||||
* 是否启用动画
|
||||
*/
|
||||
'common.isShowAnimation': boolean
|
||||
|
||||
/**
|
||||
* 是否启用随机弹窗动画
|
||||
*/
|
||||
'common.randomAnimate': boolean
|
||||
|
||||
/**
|
||||
* 是否同意软件协议
|
||||
*/
|
||||
'common.isAgreePact': boolean
|
||||
|
||||
/**
|
||||
* 控制按钮位置,左边、右边
|
||||
*/
|
||||
'common.controlBtnPosition': 'left' | 'right'
|
||||
|
||||
/**
|
||||
* 启动时自动播放歌曲
|
||||
*/
|
||||
'player.startupAutoPlay': boolean
|
||||
|
||||
/**
|
||||
* 切歌模式
|
||||
*/
|
||||
'player.togglePlayMethod': 'listLoop' | 'random' | 'list' | 'singleLoop' | 'none'
|
||||
|
||||
/**
|
||||
* 是否优先播放320k音质
|
||||
*/
|
||||
'player.highQuality': boolean
|
||||
|
||||
/**
|
||||
* 是否显示任务栏进度条
|
||||
*/
|
||||
'player.isShowTaskProgess': boolean
|
||||
|
||||
/**
|
||||
* 音量大小
|
||||
*/
|
||||
'player.volume': number
|
||||
|
||||
/**
|
||||
* 是否静音
|
||||
*/
|
||||
'player.isMute': boolean
|
||||
|
||||
/**
|
||||
* 音频输出设备id
|
||||
*/
|
||||
'player.mediaDeviceId': string
|
||||
|
||||
/**
|
||||
* 是否在音频输出设备更改时暂停播放
|
||||
*/
|
||||
'player.isMediaDeviceRemovedStopPlay': boolean
|
||||
|
||||
/**
|
||||
* 是否显示歌词翻译
|
||||
*/
|
||||
'player.isShowLyricTranslation': boolean
|
||||
|
||||
/**
|
||||
* 是否显示歌词罗马音
|
||||
*/
|
||||
'player.isShowLyricRoma': boolean
|
||||
|
||||
/**
|
||||
* 是否将歌词从简体转换为繁体
|
||||
*/
|
||||
'player.isS2t': boolean
|
||||
|
||||
/**
|
||||
* 是否播放卡拉OK歌词
|
||||
*/
|
||||
'player.isPlayLxlrc': boolean
|
||||
|
||||
/**
|
||||
* 启动软件时是否恢复上次播放进度
|
||||
*/
|
||||
'player.isSavePlayTime': boolean
|
||||
|
||||
/**
|
||||
* 是否启用音频可视化
|
||||
*/
|
||||
'player.audioVisualization': boolean
|
||||
|
||||
/**
|
||||
* 定时暂停播放-是否等待歌曲播放完毕再暂停
|
||||
*/
|
||||
'player.waitPlayEndStop': boolean
|
||||
|
||||
/**
|
||||
* 定时暂停播放-倒计时时间
|
||||
*/
|
||||
'player.waitPlayEndStopTime': string
|
||||
|
||||
/**
|
||||
* 是否启用音频加载失败时自动切歌
|
||||
*/
|
||||
'player.autoSkipOnError': boolean
|
||||
|
||||
/**
|
||||
* 播放详情页-是否缩放当前播放的歌词行
|
||||
*/
|
||||
'playDetail.isZoomActiveLrc': boolean
|
||||
|
||||
/**
|
||||
* 播放详情页-是否允许通过歌词调整播放进度
|
||||
*/
|
||||
'playDetail.isShowLyricProgressSetting': boolean
|
||||
|
||||
/**
|
||||
* 播放详情页-歌词字体大小
|
||||
*/
|
||||
'playDetail.style.fontSize': number
|
||||
|
||||
/**
|
||||
* 播放详情页-歌词对齐方式
|
||||
*/
|
||||
'playDetail.style.align': 'center' | 'left' | 'right'
|
||||
|
||||
|
||||
/**
|
||||
* 是否启用桌面歌词
|
||||
*/
|
||||
'desktopLyric.enable': boolean
|
||||
|
||||
/**
|
||||
* 是否锁定桌面歌词
|
||||
*/
|
||||
'desktopLyric.isLock': boolean
|
||||
|
||||
/**
|
||||
* 是在置顶桌面
|
||||
*/
|
||||
'desktopLyric.isAlwaysOnTop': boolean
|
||||
|
||||
/**
|
||||
* 是否自动刷新歌词置顶
|
||||
*/
|
||||
'desktopLyric.isAlwaysOnTopLoop': boolean
|
||||
|
||||
/**
|
||||
* 是否启用音频可视化
|
||||
*/
|
||||
'desktopLyric.audioVisualization': boolean
|
||||
|
||||
/**
|
||||
* 桌面歌词窗口宽度
|
||||
*/
|
||||
'desktopLyric.width': number
|
||||
|
||||
/**
|
||||
* 桌面歌词窗口高度
|
||||
*/
|
||||
'desktopLyric.height': number
|
||||
|
||||
/**
|
||||
* 桌面歌词窗口x坐标
|
||||
*/
|
||||
'desktopLyric.x': number | null
|
||||
|
||||
/**
|
||||
* 桌面歌词窗口y坐标
|
||||
*/
|
||||
'desktopLyric.y': number | null
|
||||
|
||||
/**
|
||||
* 是否允许桌面歌词窗口拖出主屏幕之外
|
||||
*/
|
||||
'desktopLyric.isLockScreen': boolean
|
||||
|
||||
/**
|
||||
* 是否延迟桌面歌词滚动
|
||||
*/
|
||||
'desktopLyric.isDelayScroll': boolean
|
||||
|
||||
/**
|
||||
* 是否在鼠标划过桌面歌词窗口时降低歌词透明度
|
||||
*/
|
||||
'desktopLyric.isHoverHide': boolean
|
||||
|
||||
/**
|
||||
* 歌词方向
|
||||
*/
|
||||
'desktopLyric.direction': 'horizontal' | 'vertical'
|
||||
|
||||
/**
|
||||
* 是否在鼠标划过桌面歌词窗口时降低歌词透明度
|
||||
*/
|
||||
'desktopLyric.style.align': 'center' | 'left' | 'right'
|
||||
|
||||
/**
|
||||
* 桌面歌词字体
|
||||
*/
|
||||
'desktopLyric.style.font': string
|
||||
|
||||
/**
|
||||
* 桌面歌词字体大小
|
||||
*/
|
||||
'desktopLyric.style.fontSize': number
|
||||
|
||||
/**
|
||||
* 桌面歌词未播放字体颜色
|
||||
*/
|
||||
'desktopLyric.style.lyricUnplayColor': string
|
||||
|
||||
/**
|
||||
* 桌面歌词已播放字体颜色
|
||||
*/
|
||||
'desktopLyric.style.lyricPlayedColor': string
|
||||
|
||||
/**
|
||||
* 桌面歌词字体阴影颜色
|
||||
*/
|
||||
'desktopLyric.style.lyricShadowColor': string
|
||||
|
||||
/**
|
||||
* 桌面歌词加粗字体
|
||||
*/
|
||||
// 'desktopLyric.style.fontWeight': boolean
|
||||
|
||||
/**
|
||||
* 桌面歌词字体透明度
|
||||
*/
|
||||
'desktopLyric.style.opacity': number
|
||||
|
||||
/**
|
||||
* 桌面歌词是否允许换行
|
||||
*/
|
||||
'desktopLyric.style.ellipsis': boolean
|
||||
|
||||
/**
|
||||
* 是否缩放当前正在播放的桌面歌词
|
||||
*/
|
||||
'desktopLyric.style.isZoomActiveLrc': boolean
|
||||
|
||||
/**
|
||||
* 是否启用双击列表里的歌曲时自动切换到当前列表播放(仅对歌单、排行榜有效)
|
||||
*/
|
||||
'list.isClickPlayList': boolean
|
||||
|
||||
/**
|
||||
* 是否显示歌曲来源(仅对我的列表有效)
|
||||
*/
|
||||
'list.isShowSource': boolean
|
||||
|
||||
/**
|
||||
* 是否自动恢复列表滚动位置(仅对我的列表有效)
|
||||
*/
|
||||
'list.isSaveScrollLocation': boolean
|
||||
|
||||
/**
|
||||
* 添加歌曲到我的列表时的方式
|
||||
*/
|
||||
'list.addMusicLocationType': LX.AddMusicLocationType
|
||||
|
||||
/**
|
||||
* 是否显示列表操作按钮列
|
||||
*/
|
||||
'list.actionButtonsVisible': boolean
|
||||
|
||||
/**
|
||||
* 是否启用下载功能
|
||||
*/
|
||||
'download.enable': boolean
|
||||
|
||||
/**
|
||||
* 下载路径
|
||||
*/
|
||||
'download.savePath': string
|
||||
|
||||
/**
|
||||
* 文件命名方式
|
||||
*/
|
||||
'download.fileName': '歌名 - 歌手' | '歌手 - 歌名' | '歌名'
|
||||
|
||||
/**
|
||||
* 最大并发下载数
|
||||
*/
|
||||
'download.maxDownloadNum': number
|
||||
|
||||
/**
|
||||
* 是否下载lrc文件
|
||||
*/
|
||||
'download.isDownloadLrc': boolean
|
||||
|
||||
/**
|
||||
* 是否下载翻译歌词文件
|
||||
*/
|
||||
'download.isDownloadTLrc': boolean
|
||||
|
||||
/**
|
||||
* 是否下载罗马音歌词文件
|
||||
*/
|
||||
'download.isDownloadRLrc': boolean
|
||||
|
||||
/**
|
||||
* 保存lrc时的文本编码格式
|
||||
*/
|
||||
'download.lrcFormat': 'utf8' | 'gbk'
|
||||
|
||||
/**
|
||||
* 是否在音频文件中嵌入歌曲封面
|
||||
*/
|
||||
'download.isEmbedPic': boolean
|
||||
|
||||
/**
|
||||
* 是否在音频文件中嵌入歌词
|
||||
*/
|
||||
'download.isEmbedLyric': boolean
|
||||
|
||||
/**
|
||||
* 歌曲源不可用时,是否启用换源下载
|
||||
*/
|
||||
'download.isUseOtherSource': boolean
|
||||
|
||||
/**
|
||||
* 主题id
|
||||
*/
|
||||
'theme.id': string
|
||||
|
||||
/**
|
||||
* 亮色主题id
|
||||
*/
|
||||
'theme.lightId': string
|
||||
|
||||
/**
|
||||
* 暗色主题id
|
||||
*/
|
||||
'theme.darkId': string
|
||||
|
||||
/**
|
||||
* 是否显示热门搜索
|
||||
*/
|
||||
'search.isShowHotSearch': boolean
|
||||
|
||||
/**
|
||||
* 是否显示搜索历史
|
||||
*/
|
||||
'search.isShowHistorySearch': boolean
|
||||
|
||||
/**
|
||||
* 软件启动时是否自动聚焦搜索框
|
||||
*/
|
||||
'search.isFocusSearchBox': boolean
|
||||
|
||||
/**
|
||||
* 是否启用代理
|
||||
*/
|
||||
'network.proxy.enable': boolean
|
||||
|
||||
/**
|
||||
* 代理服务器地址
|
||||
*/
|
||||
'network.proxy.host': string
|
||||
|
||||
/**
|
||||
* 代理服务器端口号
|
||||
*/
|
||||
'network.proxy.port': string
|
||||
|
||||
/**
|
||||
* 代理服务器用户名
|
||||
*/
|
||||
'network.proxy.username': string
|
||||
|
||||
/**
|
||||
* 代理服务器密码
|
||||
*/
|
||||
'network.proxy.password': string
|
||||
|
||||
/**
|
||||
* 是否启用托盘
|
||||
*/
|
||||
'tray.enable': boolean
|
||||
|
||||
/**
|
||||
* 是否关闭时是否最小化到托盘
|
||||
*/
|
||||
// 'tray.isToTray': boolean
|
||||
|
||||
/**
|
||||
* 托盘主题id
|
||||
*/
|
||||
'tray.themeId': number
|
||||
|
||||
/**
|
||||
* 是否启用同步服务
|
||||
*/
|
||||
'sync.enable': boolean
|
||||
|
||||
/**
|
||||
* 同步服务端口号
|
||||
*/
|
||||
'sync.port': '23332' | string
|
||||
|
||||
/**
|
||||
* 是否在离开搜索界面时自动清空搜索框
|
||||
*/
|
||||
'odc.isAutoClearSearchInput': boolean
|
||||
|
||||
/**
|
||||
* 是否在离开搜索界面时自动清空搜索结果列表
|
||||
*/
|
||||
'odc.isAutoClearSearchList': boolean
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// import './app_setting'
|
||||
|
||||
declare namespace LX {
|
||||
interface CmdParams {
|
||||
/**
|
||||
* 搜索,启动软件时自动在搜索框搜索指定的内容,例如:-search="突然的自我 - 伍佰"
|
||||
*/
|
||||
search?: string
|
||||
|
||||
/**
|
||||
* 禁用硬件加速启动
|
||||
*/
|
||||
dha?: boolean
|
||||
|
||||
/**
|
||||
* 以非透明模式启动
|
||||
*/
|
||||
dt?: boolean
|
||||
|
||||
/**
|
||||
* 禁用硬件媒体密钥处理
|
||||
*/
|
||||
dhmkh?: boolean
|
||||
|
||||
/**
|
||||
* 设置代理服务器,代理应用的所有流量,例:-proxy-server="127.0.0.1:1081"(不支持设置账号密码,v1.17.0起新增)。注:应用内“设置-网络-代理设置”仅代理接口请求的流量,优先级更高
|
||||
*/
|
||||
'proxy-server'?: string
|
||||
|
||||
/**
|
||||
* 以分号分隔的主机列表绕过代理服务器,例:-proxy-bypass-list="<local>;*.google.com;*foo.com;1.2.3.4:5678"(与-proxy-server一起使用才有效,v1.17.0起新增)。注:此设置对应用内接口请求无效
|
||||
*/
|
||||
'proxy-bypass-list'?: string
|
||||
|
||||
/**
|
||||
* 启动时播放指定列表的音乐
|
||||
*/
|
||||
play?: string
|
||||
|
||||
[key: string]: boolean | number | string
|
||||
}
|
||||
|
||||
type OnlineSource = 'kw' | 'kg' | 'tx' | 'wy' | 'mg'
|
||||
type Source = OnlineSource | 'local'
|
||||
type Quality = '128k' | '320k' | 'flac' | 'flac24bit' | '192k' | 'ape' | 'wav'
|
||||
|
||||
type QualityList = Partial<Record<LX.Source, LX.Quality[]>>
|
||||
|
||||
interface EnvParams {
|
||||
deeplink?: string | null
|
||||
cmdParams: CmdParams
|
||||
workAreaSize?: Electron.Size
|
||||
}
|
||||
|
||||
interface HotKey {
|
||||
name: string
|
||||
action: string
|
||||
type: keyof typeof keyName
|
||||
}
|
||||
|
||||
interface HotKeyDownInfo {
|
||||
type: 'local' | 'global'
|
||||
key: string
|
||||
}
|
||||
|
||||
interface HotKeyConfig {
|
||||
enable: boolean
|
||||
keys: {
|
||||
[key: string]: HotKey
|
||||
}
|
||||
}
|
||||
interface HotKeyConfigAll {
|
||||
local: HotKeyConfig
|
||||
global: HotKeyConfig
|
||||
}
|
||||
interface RegisterKeyInfo {
|
||||
key: string
|
||||
info: HotKey
|
||||
}
|
||||
type HotKeyState = Map<string, {
|
||||
status: boolean
|
||||
info: HotKey
|
||||
}>
|
||||
interface HotKeyActionWrap<T, D> {
|
||||
action: T
|
||||
data: D
|
||||
source?: string
|
||||
}
|
||||
type HotKeyActions = HotKeyActionWrap<'config', HotKeyConfigAll>
|
||||
| HotKeyActionWrap<'enable', boolean>
|
||||
| HotKeyActionWrap<'register', RegisterKeyInfo>
|
||||
| HotKeyActionWrap<'unregister', string>
|
||||
|
||||
interface HotKeyEvent {
|
||||
type: string
|
||||
key: string
|
||||
}
|
||||
|
||||
interface TaskBarButtonFlags {
|
||||
empty: boolean
|
||||
collect: boolean
|
||||
play: boolean
|
||||
next: boolean
|
||||
prev: boolean
|
||||
}
|
||||
|
||||
interface Wait {
|
||||
time: number
|
||||
id: string
|
||||
}
|
||||
type WaitCancel = string
|
||||
interface Interval {
|
||||
time: number
|
||||
id: string
|
||||
}
|
||||
type IntervalCancel = string
|
||||
|
||||
interface VersionInfo {
|
||||
version: string
|
||||
desc: string
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
declare namespace LX {
|
||||
namespace ConfigFile {
|
||||
interface MyListInfoPart {
|
||||
type: 'playListPart_v2'
|
||||
data: LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull | LX.List.UserListInfoFull
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
declare namespace LX {
|
||||
namespace DesktopLyric {
|
||||
interface Config {
|
||||
'desktopLyric.enable': LX.AppSetting['desktopLyric.enable']
|
||||
'desktopLyric.isLock': LX.AppSetting['desktopLyric.isLock']
|
||||
'desktopLyric.isAlwaysOnTop': LX.AppSetting['desktopLyric.isAlwaysOnTop']
|
||||
'desktopLyric.isAlwaysOnTopLoop': LX.AppSetting['desktopLyric.isAlwaysOnTopLoop']
|
||||
'desktopLyric.audioVisualization': LX.AppSetting['desktopLyric.audioVisualization']
|
||||
'desktopLyric.width': LX.AppSetting['desktopLyric.width']
|
||||
'desktopLyric.height': LX.AppSetting['desktopLyric.height']
|
||||
'desktopLyric.x': LX.AppSetting['desktopLyric.x']
|
||||
'desktopLyric.y': LX.AppSetting['desktopLyric.y']
|
||||
'desktopLyric.isLockScreen': LX.AppSetting['desktopLyric.isLockScreen']
|
||||
'desktopLyric.isDelayScroll': LX.AppSetting['desktopLyric.isDelayScroll']
|
||||
'desktopLyric.isHoverHide': LX.AppSetting['desktopLyric.isHoverHide']
|
||||
'desktopLyric.direction': LX.AppSetting['desktopLyric.direction']
|
||||
'desktopLyric.style.align': LX.AppSetting['desktopLyric.style.align']
|
||||
'desktopLyric.style.font': LX.AppSetting['desktopLyric.style.font']
|
||||
'desktopLyric.style.fontSize': LX.AppSetting['desktopLyric.style.fontSize']
|
||||
'desktopLyric.style.lyricUnplayColor': LX.AppSetting['desktopLyric.style.lyricUnplayColor']
|
||||
'desktopLyric.style.lyricPlayedColor': LX.AppSetting['desktopLyric.style.lyricPlayedColor']
|
||||
'desktopLyric.style.lyricShadowColor': LX.AppSetting['desktopLyric.style.lyricShadowColor']
|
||||
// 'desktopLyric.style.fontWeight': LX.AppSetting['desktopLyric.style.fontWeight']
|
||||
'desktopLyric.style.opacity': LX.AppSetting['desktopLyric.style.opacity']
|
||||
'desktopLyric.style.ellipsis': LX.AppSetting['desktopLyric.style.ellipsis']
|
||||
'desktopLyric.style.isZoomActiveLrc': LX.AppSetting['desktopLyric.style.isZoomActiveLrc']
|
||||
'common.langId': LX.AppSetting['common.langId']
|
||||
'player.isShowLyricTranslation': LX.AppSetting['player.isShowLyricTranslation']
|
||||
'player.isShowLyricRoma': LX.AppSetting['player.isShowLyricRoma']
|
||||
'player.isPlayLxlrc': LX.AppSetting['player.isPlayLxlrc']
|
||||
}
|
||||
|
||||
type WinMainActions = 'get_info' | 'get_status' | 'get_analyser_data_array'
|
||||
|
||||
interface LyricActionBase <A> {
|
||||
action: A
|
||||
}
|
||||
interface LyricActionData<A, D> extends LyricActionBase<A> {
|
||||
data: D
|
||||
}
|
||||
type LyricAction<A, D = undefined> = D extends undefined ? LyricActionBase<A> : LyricActionData<A, D>
|
||||
|
||||
type LyricActions = LyricAction<'set_info', {
|
||||
id: string | null
|
||||
singer: string
|
||||
name: string
|
||||
album: string
|
||||
lrc: string | null
|
||||
tlrc: string | null
|
||||
rlrc: string | null
|
||||
lxlrc: string | null
|
||||
// pic: string | null
|
||||
isPlay: boolean
|
||||
line: number
|
||||
played_time: number
|
||||
}>
|
||||
| LyricAction<'set_status', {
|
||||
isPlay: boolean
|
||||
line: number
|
||||
played_time: number
|
||||
}>
|
||||
| LyricAction<'set_lyric', {
|
||||
lrc: string | null
|
||||
tlrc: string | null
|
||||
rlrc: string | null
|
||||
lxlrc: string | null
|
||||
}>
|
||||
| LyricAction<'set_offset', number>
|
||||
| LyricAction<'set_play', number>
|
||||
| LyricAction<'set_pause'>
|
||||
| LyricAction<'set_stop'>
|
||||
| LyricAction<'send_analyser_data_array', Uint8Array>
|
||||
|
||||
|
||||
interface NewBounds {
|
||||
x?: number | null
|
||||
y?: number
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
// interface DownloadList {
|
||||
|
||||
// }
|
||||
|
||||
|
||||
declare namespace LX {
|
||||
namespace Download {
|
||||
type DownloadTaskStatus = 'run'
|
||||
| 'waiting'
|
||||
| 'pause'
|
||||
| 'error'
|
||||
| 'completed'
|
||||
|
||||
type FileExt = 'mp3' | 'flac' | 'wav' | 'ape'
|
||||
|
||||
interface ProgressInfo {
|
||||
progress: number
|
||||
speed: string
|
||||
downloaded: number
|
||||
total: number
|
||||
}
|
||||
|
||||
interface DownloadTaskActionBase <A> {
|
||||
action: A
|
||||
}
|
||||
interface DownloadTaskActionData<A, D> extends DownloadTaskActionBase<A> {
|
||||
data: D
|
||||
}
|
||||
type DownloadTaskAction<A, D = undefined> = D extends undefined ? DownloadTaskActionBase<A> : DownloadTaskActionData<A, D>
|
||||
|
||||
type DownloadTaskActions = DownloadTaskAction<'start'>
|
||||
| DownloadTaskAction<'complete'>
|
||||
| DownloadTaskAction<'refreshUrl'>
|
||||
| DownloadTaskAction<'statusText', string>
|
||||
| DownloadTaskAction<'progress', ProgressInfo>
|
||||
| DownloadTaskAction<'error', {
|
||||
error?: string
|
||||
message?: string
|
||||
}>
|
||||
|
||||
interface ListItem {
|
||||
id: string
|
||||
isComplate: boolean
|
||||
status: DownloadTaskStatus
|
||||
statusText: string
|
||||
downloaded: number
|
||||
total: number
|
||||
progress: number
|
||||
speed: string
|
||||
metadata: {
|
||||
musicInfo: LX.Music.MusicInfoOnline
|
||||
url: string | null
|
||||
quality: LX.Quality
|
||||
ext: FileExt
|
||||
fileName: string
|
||||
filePath: string
|
||||
}
|
||||
}
|
||||
|
||||
interface saveDownloadMusicInfo {
|
||||
list: ListItem[]
|
||||
addMusicLocationType: LX.AddMusicLocationType
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
declare namespace LX {
|
||||
interface IpcMainEvent {
|
||||
event: Electron.IpcMainEvent
|
||||
}
|
||||
interface IpcMainEventParams<T> {
|
||||
event: Electron.IpcMainEvent
|
||||
params: T
|
||||
}
|
||||
type IpcMainEventListener = (params: LX.IpcMainEvent) => void
|
||||
type IpcMainEventListenerParams<T> = (params: LX.IpcMainEventParams<T>) => void
|
||||
|
||||
interface IpcMainInvokeEvent {
|
||||
event: Electron.IpcMainInvokeEvent
|
||||
}
|
||||
interface IpcMainInvokeEventParams<T> {
|
||||
event: Electron.IpcMainInvokeEvent
|
||||
params: T
|
||||
}
|
||||
|
||||
type IpcMainInvokeEventListener = (params: LX.IpcMainInvokeEvent) => Promise<void>
|
||||
type IpcMainInvokeEventListenerParams<T> = (params: LX.IpcMainInvokeEventParams<T>) => Promise<void>
|
||||
type IpcMainInvokeEventListenerValue<V> = (params: LX.IpcMainInvokeEvent) => Promise<V>
|
||||
type IpcMainInvokeEventListenerParamsValue<T, V> = (params: LX.IpcMainInvokeEventParams<T>) => Promise<V>
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
declare namespace LX {
|
||||
interface IpcRendererEvent {
|
||||
event: Electron.IpcRendererEvent
|
||||
}
|
||||
interface IpcRendererEventParams<T> {
|
||||
event: Electron.IpcRendererEvent
|
||||
params: T
|
||||
}
|
||||
type IpcRendererEventListener = (params: LX.IpcRendererEvent) => any
|
||||
type IpcRendererEventListenerParams<T> = (params: LX.IpcRendererEventParams<T>) => any
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
declare namespace LX {
|
||||
namespace List {
|
||||
interface UserListInfo {
|
||||
id: string
|
||||
name: string
|
||||
// list: LX.Music.MusicInfo[]
|
||||
source?: LX.Source
|
||||
sourceListId?: string
|
||||
// position?: number
|
||||
locationUpdateTime: number | null
|
||||
}
|
||||
|
||||
interface MyDefaultListInfo {
|
||||
id: 'default'
|
||||
name: '试听列表'
|
||||
// list: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
interface MyLoveListInfo {
|
||||
id: 'love'
|
||||
name: '我的收藏'
|
||||
// list: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
interface MyTempListInfo {
|
||||
id: 'temp'
|
||||
name: '临时列表'
|
||||
// list: LX.Music.MusicInfo[]
|
||||
// TODO: save default lists info
|
||||
meta: {
|
||||
id?: string
|
||||
}
|
||||
}
|
||||
|
||||
type MyListInfo = MyDefaultListInfo | MyLoveListInfo | UserListInfo
|
||||
|
||||
interface MyAllList {
|
||||
defaultList: MyDefaultListInfo
|
||||
loveList: MyLoveListInfo
|
||||
userList: UserListInfo[]
|
||||
tempList: MyTempListInfo
|
||||
}
|
||||
|
||||
|
||||
type SearchHistoryList = string[]
|
||||
type ListPositionInfo = Record<string, number>
|
||||
type ListUpdateInfo = Record<string, {
|
||||
updateTime: number
|
||||
isAutoUpdate: boolean
|
||||
}>
|
||||
|
||||
type ListSaveType = 'myList' | 'downloadList'
|
||||
type ListSaveInfo = {
|
||||
type: 'myList'
|
||||
data: Partial<MyAllList>
|
||||
} | {
|
||||
type: 'downloadList'
|
||||
data: LX.Download.ListItem[]
|
||||
}
|
||||
|
||||
|
||||
type ListActionDataOverwrite = MakeOptional<LX.List.ListDataFull, 'tempList'>
|
||||
interface ListActionAdd {
|
||||
position: number
|
||||
listInfos: UserListInfo[]
|
||||
}
|
||||
type ListActionRemove = string[]
|
||||
type ListActionUpdate = UserListInfo[]
|
||||
interface ListActionUpdatePosition {
|
||||
/**
|
||||
* 列表id
|
||||
*/
|
||||
ids: string[]
|
||||
/**
|
||||
* 位置
|
||||
*/
|
||||
position: number
|
||||
}
|
||||
|
||||
interface ListActionMusicAdd {
|
||||
id: string
|
||||
musicInfos: LX.Music.MusicInfo[]
|
||||
addMusicLocationType: LX.AddMusicLocationType
|
||||
}
|
||||
|
||||
interface ListActionMusicMove {
|
||||
fromId: string
|
||||
toId: string
|
||||
musicInfos: LX.Music.MusicInfo[]
|
||||
addMusicLocationType: LX.AddMusicLocationType
|
||||
}
|
||||
|
||||
interface ListActionCheckMusicExistList {
|
||||
listId: string
|
||||
musicInfoId: string
|
||||
}
|
||||
|
||||
interface ListActionMusicRemove {
|
||||
listId: string
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
type ListActionMusicUpdate = Array<{
|
||||
id: string
|
||||
musicInfo: LX.Music.MusicInfo
|
||||
}>
|
||||
|
||||
interface ListActionMusicUpdatePosition {
|
||||
listId: string
|
||||
position: number
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
interface ListActionMusicOverwrite {
|
||||
listId: string
|
||||
musicInfos: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
type ListActionMusicClear = string
|
||||
|
||||
interface MyDefaultListInfoFull extends MyDefaultListInfo {
|
||||
list: LX.Music.MusicInfo[]
|
||||
}
|
||||
interface MyLoveListInfoFull extends MyLoveListInfo {
|
||||
list: LX.Music.MusicInfo[]
|
||||
}
|
||||
interface UserListInfoFull extends UserListInfo {
|
||||
list: LX.Music.MusicInfo[]
|
||||
}
|
||||
interface MyTempListInfoFull extends MyTempListInfo {
|
||||
list: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
interface ListDataFull {
|
||||
defaultList: LX.Music.MusicInfo[]
|
||||
loveList: LX.Music.MusicInfo[]
|
||||
userList: UserListInfoFull[]
|
||||
tempList: LX.Music.MusicInfo[]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
declare namespace LX {
|
||||
namespace Music {
|
||||
interface MusicQualityType { // {"type": "128k", size: "3.56M"}
|
||||
type: LX.Quality
|
||||
size: string | null
|
||||
}
|
||||
interface MusicQualityTypeKg { // {"type": "128k", size: "3.56M"}
|
||||
type: LX.Quality
|
||||
size: string | null
|
||||
hash: string
|
||||
}
|
||||
type _MusicQualityType = Record<Quality, {
|
||||
size: string | null
|
||||
}>
|
||||
type _MusicQualityTypeKg = Record<Quality, {
|
||||
size: string | null
|
||||
hash: string
|
||||
}>
|
||||
|
||||
|
||||
interface MusicInfoMetaBase {
|
||||
songId: string | number // 歌曲ID,mg源为copyrightId,local为文件路径
|
||||
albumName: string // 歌曲专辑名称
|
||||
picUrl?: string | null // 歌曲图片链接
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_online extends MusicInfoMetaBase {
|
||||
qualitys: MusicQualityType[]
|
||||
_qualitys: _MusicQualityType
|
||||
albumId?: string | number // 歌曲专辑ID
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_local extends MusicInfoMetaBase {
|
||||
filePath: string
|
||||
ext: string
|
||||
}
|
||||
|
||||
|
||||
interface MusicInfoBase<S = LX.Source> {
|
||||
id: string
|
||||
name: string // 歌曲名
|
||||
singer: string // 艺术家名
|
||||
source: S // 源
|
||||
interval: string | null // 格式化后的歌曲时长,例:03:55
|
||||
meta: MusicInfoMetaBase
|
||||
}
|
||||
|
||||
interface MusicInfoLocal extends MusicInfoBase<'local'> {
|
||||
meta: MusicInfoMeta_local
|
||||
}
|
||||
|
||||
interface MusicInfo_online_common extends MusicInfoBase<'kw' | 'wy'> {
|
||||
meta: MusicInfoMeta_online
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_kg extends MusicInfoMeta_online {
|
||||
qualitys: MusicQualityTypeKg[]
|
||||
_qualitys: _MusicQualityTypeKg
|
||||
hash: string // 歌曲hash
|
||||
}
|
||||
interface MusicInfo_kg extends MusicInfoBase<'kg'> {
|
||||
meta: MusicInfoMeta_kg
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_tx extends MusicInfoMeta_online {
|
||||
strMediaMid: string // 歌曲strMediaMid
|
||||
albumMid?: string // 歌曲albumMid
|
||||
}
|
||||
interface MusicInfo_tx extends MusicInfoBase<'tx'> {
|
||||
meta: MusicInfoMeta_tx
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_mg extends MusicInfoMeta_online {
|
||||
copyrightId: string // 歌曲copyrightId
|
||||
lrcUrl?: string // 歌曲lrcUrl
|
||||
mrcUrl?: string // 歌曲mrcUrl
|
||||
trcUrl?: string // 歌曲trcUrl
|
||||
}
|
||||
interface MusicInfo_mg extends MusicInfoBase<'mg'> {
|
||||
meta: MusicInfoMeta_mg
|
||||
}
|
||||
|
||||
type MusicInfoOnline = MusicInfo_online_common | MusicInfo_kg | MusicInfo_tx | MusicInfo_mg
|
||||
type MusicInfo = MusicInfoOnline | MusicInfoLocal
|
||||
|
||||
interface LyricInfo {
|
||||
// 歌曲歌词
|
||||
lyric: string
|
||||
// 翻译歌词
|
||||
tlyric?: string | null
|
||||
// 罗马音歌词
|
||||
rlyric?: string | null
|
||||
// 逐字歌词
|
||||
lxlyric?: string | null
|
||||
}
|
||||
|
||||
interface LyricInfoSave {
|
||||
id: string
|
||||
lyrics: LyricInfo
|
||||
}
|
||||
|
||||
interface MusicFileMeta {
|
||||
title: string
|
||||
artist: string | null
|
||||
album: string | null
|
||||
APIC: string | null
|
||||
lyrics: string | null
|
||||
}
|
||||
|
||||
interface MusicUrlInfo {
|
||||
id: string
|
||||
url: string
|
||||
}
|
||||
|
||||
interface MusicInfoOtherSourceSave {
|
||||
id: string
|
||||
list: MusicInfoOnline[]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {
|
||||
IAudioMetadata as iAudioMetadata,
|
||||
} from 'music-metadata'
|
||||
|
||||
declare global {
|
||||
namespace LX {
|
||||
namespace MusicMetadataModule {
|
||||
type IAudioMetadata = iAudioMetadata
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
declare namespace LX {
|
||||
namespace Player {
|
||||
interface ProgressBarOptions {
|
||||
progress: number
|
||||
mode?: Electron.ProgressBarOptions['mode']
|
||||
}
|
||||
|
||||
type StatusButtonActions = 'unCollect'
|
||||
| 'collect'
|
||||
| 'prev'
|
||||
| 'pause'
|
||||
| 'play'
|
||||
| 'next'
|
||||
|
||||
interface LyricInfo extends LX.Music.LyricInfo {
|
||||
rawlrcInfo: LX.Music.LyricInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// declare module '*.vue' {
|
||||
// import { App } from 'vue'
|
||||
// export default App.Component
|
||||
// }
|
||||
|
||||
declare module '*.vue' {
|
||||
import { Component } from 'vue'
|
||||
const component: Component
|
||||
export default component
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
declare namespace LX {
|
||||
namespace Sync {
|
||||
|
||||
interface Enable {
|
||||
enable: boolean
|
||||
port: string
|
||||
}
|
||||
|
||||
interface SyncActionBase <A> {
|
||||
action: A
|
||||
}
|
||||
interface SyncActionData<A, D> extends SyncActionBase<A> {
|
||||
data: D
|
||||
}
|
||||
type SyncAction<A, D = undefined> = D extends undefined ? SyncActionBase<A> : SyncActionData<A, D>
|
||||
|
||||
type SyncMainWindowActions = SyncAction<'select_mode', KeyInfo>
|
||||
| SyncAction<'close_select_mode'>
|
||||
| SyncAction<'status', Status>
|
||||
|
||||
type SyncServiceActions = SyncAction<'select_mode', Mode>
|
||||
| SyncAction<'get_status'>
|
||||
| SyncAction<'generate_code'>
|
||||
| SyncAction<'enable', Enable>
|
||||
|
||||
type ActionList = SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite>
|
||||
| SyncAction<'list_create', LX.List.ListActionAdd>
|
||||
| SyncAction<'list_remove', LX.List.ListActionRemove>
|
||||
| SyncAction<'list_update', LX.List.ListActionUpdate>
|
||||
| SyncAction<'list_update_position', LX.List.ListActionUpdatePosition>
|
||||
| SyncAction<'list_music_add', LX.List.ListActionMusicAdd>
|
||||
| SyncAction<'list_music_move', LX.List.ListActionMusicMove>
|
||||
| SyncAction<'list_music_remove', LX.List.ListActionMusicRemove>
|
||||
| SyncAction<'list_music_update', LX.List.ListActionMusicUpdate>
|
||||
| SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition>
|
||||
| SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite>
|
||||
| SyncAction<'list_music_clear', LX.List.ListActionMusicClear>
|
||||
|
||||
|
||||
interface List {
|
||||
action: string
|
||||
data: any
|
||||
}
|
||||
|
||||
interface Status {
|
||||
status: boolean
|
||||
message: string
|
||||
address: string[]
|
||||
code: string
|
||||
devices: KeyInfo[]
|
||||
}
|
||||
|
||||
interface KeyInfo {
|
||||
clientId: string
|
||||
key: string
|
||||
iv: string
|
||||
deviceName: string
|
||||
connectionTime?: number
|
||||
}
|
||||
|
||||
type ListData = Omit<LX.List.ListDataFull, 'tempList'>
|
||||
|
||||
type Mode = 'merge_local_remote'
|
||||
| 'merge_remote_local'
|
||||
| 'overwrite_local_remote'
|
||||
| 'overwrite_remote_local'
|
||||
| 'overwrite_local_remote_full'
|
||||
| 'overwrite_remote_local_full'
|
||||
| 'none'
|
||||
| 'cancel'
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
declare namespace LX {
|
||||
|
||||
interface ThemeColors {
|
||||
// '--color-000': string
|
||||
// '--color-050': string
|
||||
// '--color-100': string
|
||||
// '--color-200': string
|
||||
// '--color-300': string
|
||||
// '--color-400': string
|
||||
// '--color-500': string
|
||||
// '--color-600': string
|
||||
// '--color-700': string
|
||||
// '--color-800': string
|
||||
// '--color-900': string
|
||||
'--color-theme': string
|
||||
|
||||
'--color-primary': string
|
||||
'--color-primary-alpha-100': string
|
||||
'--color-primary-alpha-200': string
|
||||
'--color-primary-alpha-300': string
|
||||
'--color-primary-alpha-400': string
|
||||
'--color-primary-alpha-500': string
|
||||
'--color-primary-alpha-600': string
|
||||
'--color-primary-alpha-700': string
|
||||
'--color-primary-alpha-800': string
|
||||
'--color-primary-alpha-900': string
|
||||
|
||||
'--color-primary-dark-100': string
|
||||
'--color-primary-dark-100-alpha-100': string
|
||||
'--color-primary-dark-100-alpha-200': string
|
||||
'--color-primary-dark-100-alpha-300': string
|
||||
'--color-primary-dark-100-alpha-400': string
|
||||
'--color-primary-dark-100-alpha-500': string
|
||||
'--color-primary-dark-100-alpha-600': string
|
||||
'--color-primary-dark-100-alpha-700': string
|
||||
'--color-primary-dark-100-alpha-800': string
|
||||
'--color-primary-dark-100-alpha-900': string
|
||||
|
||||
'--color-primary-dark-200': string
|
||||
'--color-primary-dark-200-alpha-100': string
|
||||
'--color-primary-dark-200-alpha-200': string
|
||||
'--color-primary-dark-200-alpha-300': string
|
||||
'--color-primary-dark-200-alpha-400': string
|
||||
'--color-primary-dark-200-alpha-500': string
|
||||
'--color-primary-dark-200-alpha-600': string
|
||||
'--color-primary-dark-200-alpha-700': string
|
||||
'--color-primary-dark-200-alpha-800': string
|
||||
'--color-primary-dark-200-alpha-900': string
|
||||
|
||||
'--color-primary-dark-300': string
|
||||
'--color-primary-dark-300-alpha-100': string
|
||||
'--color-primary-dark-300-alpha-200': string
|
||||
'--color-primary-dark-300-alpha-300': string
|
||||
'--color-primary-dark-300-alpha-400': string
|
||||
'--color-primary-dark-300-alpha-500': string
|
||||
'--color-primary-dark-300-alpha-600': string
|
||||
'--color-primary-dark-300-alpha-700': string
|
||||
'--color-primary-dark-300-alpha-800': string
|
||||
'--color-primary-dark-300-alpha-900': string
|
||||
|
||||
'--color-primary-dark-400': string
|
||||
'--color-primary-dark-400-alpha-100': string
|
||||
'--color-primary-dark-400-alpha-200': string
|
||||
'--color-primary-dark-400-alpha-300': string
|
||||
'--color-primary-dark-400-alpha-400': string
|
||||
'--color-primary-dark-400-alpha-500': string
|
||||
'--color-primary-dark-400-alpha-600': string
|
||||
'--color-primary-dark-400-alpha-700': string
|
||||
'--color-primary-dark-400-alpha-800': string
|
||||
'--color-primary-dark-400-alpha-900': string
|
||||
|
||||
'--color-primary-dark-500': string
|
||||
'--color-primary-dark-500-alpha-100': string
|
||||
'--color-primary-dark-500-alpha-200': string
|
||||
'--color-primary-dark-500-alpha-300': string
|
||||
'--color-primary-dark-500-alpha-400': string
|
||||
'--color-primary-dark-500-alpha-500': string
|
||||
'--color-primary-dark-500-alpha-600': string
|
||||
'--color-primary-dark-500-alpha-700': string
|
||||
'--color-primary-dark-500-alpha-800': string
|
||||
'--color-primary-dark-500-alpha-900': string
|
||||
|
||||
'--color-primary-dark-600': string
|
||||
'--color-primary-dark-600-alpha-100': string
|
||||
'--color-primary-dark-600-alpha-200': string
|
||||
'--color-primary-dark-600-alpha-300': string
|
||||
'--color-primary-dark-600-alpha-400': string
|
||||
'--color-primary-dark-600-alpha-500': string
|
||||
'--color-primary-dark-600-alpha-600': string
|
||||
'--color-primary-dark-600-alpha-700': string
|
||||
'--color-primary-dark-600-alpha-800': string
|
||||
'--color-primary-dark-600-alpha-900': string
|
||||
|
||||
'--color-primary-dark-700': string
|
||||
'--color-primary-dark-700-alpha-100': string
|
||||
'--color-primary-dark-700-alpha-200': string
|
||||
'--color-primary-dark-700-alpha-300': string
|
||||
'--color-primary-dark-700-alpha-400': string
|
||||
'--color-primary-dark-700-alpha-500': string
|
||||
'--color-primary-dark-700-alpha-600': string
|
||||
'--color-primary-dark-700-alpha-700': string
|
||||
'--color-primary-dark-700-alpha-800': string
|
||||
'--color-primary-dark-700-alpha-900': string
|
||||
|
||||
'--color-primary-dark-800': string
|
||||
'--color-primary-dark-800-alpha-100': string
|
||||
'--color-primary-dark-800-alpha-200': string
|
||||
'--color-primary-dark-800-alpha-300': string
|
||||
'--color-primary-dark-800-alpha-400': string
|
||||
'--color-primary-dark-800-alpha-500': string
|
||||
'--color-primary-dark-800-alpha-600': string
|
||||
'--color-primary-dark-800-alpha-700': string
|
||||
'--color-primary-dark-800-alpha-800': string
|
||||
'--color-primary-dark-800-alpha-900': string
|
||||
|
||||
'--color-primary-dark-900': string
|
||||
'--color-primary-dark-900-alpha-100': string
|
||||
'--color-primary-dark-900-alpha-200': string
|
||||
'--color-primary-dark-900-alpha-300': string
|
||||
'--color-primary-dark-900-alpha-400': string
|
||||
'--color-primary-dark-900-alpha-500': string
|
||||
'--color-primary-dark-900-alpha-600': string
|
||||
'--color-primary-dark-900-alpha-700': string
|
||||
'--color-primary-dark-900-alpha-800': string
|
||||
'--color-primary-dark-900-alpha-900': string
|
||||
|
||||
'--color-primary-dark-1000': string
|
||||
'--color-primary-dark-1000-alpha-100': string
|
||||
'--color-primary-dark-1000-alpha-200': string
|
||||
'--color-primary-dark-1000-alpha-300': string
|
||||
'--color-primary-dark-1000-alpha-400': string
|
||||
'--color-primary-dark-1000-alpha-500': string
|
||||
'--color-primary-dark-1000-alpha-600': string
|
||||
'--color-primary-dark-1000-alpha-700': string
|
||||
'--color-primary-dark-1000-alpha-800': string
|
||||
'--color-primary-dark-1000-alpha-900': string
|
||||
|
||||
'--color-primary-light-100': string
|
||||
'--color-primary-light-100-alpha-100': string
|
||||
'--color-primary-light-100-alpha-200': string
|
||||
'--color-primary-light-100-alpha-300': string
|
||||
'--color-primary-light-100-alpha-400': string
|
||||
'--color-primary-light-100-alpha-500': string
|
||||
'--color-primary-light-100-alpha-600': string
|
||||
'--color-primary-light-100-alpha-700': string
|
||||
'--color-primary-light-100-alpha-800': string
|
||||
'--color-primary-light-100-alpha-900': string
|
||||
|
||||
'--color-primary-light-200': string
|
||||
'--color-primary-light-200-alpha-100': string
|
||||
'--color-primary-light-200-alpha-200': string
|
||||
'--color-primary-light-200-alpha-300': string
|
||||
'--color-primary-light-200-alpha-400': string
|
||||
'--color-primary-light-200-alpha-500': string
|
||||
'--color-primary-light-200-alpha-600': string
|
||||
'--color-primary-light-200-alpha-700': string
|
||||
'--color-primary-light-200-alpha-800': string
|
||||
'--color-primary-light-200-alpha-900': string
|
||||
|
||||
'--color-primary-light-300': string
|
||||
'--color-primary-light-300-alpha-100': string
|
||||
'--color-primary-light-300-alpha-200': string
|
||||
'--color-primary-light-300-alpha-300': string
|
||||
'--color-primary-light-300-alpha-400': string
|
||||
'--color-primary-light-300-alpha-500': string
|
||||
'--color-primary-light-300-alpha-600': string
|
||||
'--color-primary-light-300-alpha-700': string
|
||||
'--color-primary-light-300-alpha-800': string
|
||||
'--color-primary-light-300-alpha-900': string
|
||||
|
||||
'--color-primary-light-400': string
|
||||
'--color-primary-light-400-alpha-100': string
|
||||
'--color-primary-light-400-alpha-200': string
|
||||
'--color-primary-light-400-alpha-300': string
|
||||
'--color-primary-light-400-alpha-400': string
|
||||
'--color-primary-light-400-alpha-500': string
|
||||
'--color-primary-light-400-alpha-600': string
|
||||
'--color-primary-light-400-alpha-700': string
|
||||
'--color-primary-light-400-alpha-800': string
|
||||
'--color-primary-light-400-alpha-900': string
|
||||
|
||||
'--color-primary-light-500': string
|
||||
'--color-primary-light-500-alpha-100': string
|
||||
'--color-primary-light-500-alpha-200': string
|
||||
'--color-primary-light-500-alpha-300': string
|
||||
'--color-primary-light-500-alpha-400': string
|
||||
'--color-primary-light-500-alpha-500': string
|
||||
'--color-primary-light-500-alpha-600': string
|
||||
'--color-primary-light-500-alpha-700': string
|
||||
'--color-primary-light-500-alpha-800': string
|
||||
'--color-primary-light-500-alpha-900': string
|
||||
|
||||
'--color-primary-light-600': string
|
||||
'--color-primary-light-600-alpha-100': string
|
||||
'--color-primary-light-600-alpha-200': string
|
||||
'--color-primary-light-600-alpha-300': string
|
||||
'--color-primary-light-600-alpha-400': string
|
||||
'--color-primary-light-600-alpha-500': string
|
||||
'--color-primary-light-600-alpha-600': string
|
||||
'--color-primary-light-600-alpha-700': string
|
||||
'--color-primary-light-600-alpha-800': string
|
||||
'--color-primary-light-600-alpha-900': string
|
||||
|
||||
'--color-primary-light-700': string
|
||||
'--color-primary-light-700-alpha-100': string
|
||||
'--color-primary-light-700-alpha-200': string
|
||||
'--color-primary-light-700-alpha-300': string
|
||||
'--color-primary-light-700-alpha-400': string
|
||||
'--color-primary-light-700-alpha-500': string
|
||||
'--color-primary-light-700-alpha-600': string
|
||||
'--color-primary-light-700-alpha-700': string
|
||||
'--color-primary-light-700-alpha-800': string
|
||||
'--color-primary-light-700-alpha-900': string
|
||||
|
||||
'--color-primary-light-800': string
|
||||
'--color-primary-light-800-alpha-100': string
|
||||
'--color-primary-light-800-alpha-200': string
|
||||
'--color-primary-light-800-alpha-300': string
|
||||
'--color-primary-light-800-alpha-400': string
|
||||
'--color-primary-light-800-alpha-500': string
|
||||
'--color-primary-light-800-alpha-600': string
|
||||
'--color-primary-light-800-alpha-700': string
|
||||
'--color-primary-light-800-alpha-800': string
|
||||
'--color-primary-light-800-alpha-900': string
|
||||
|
||||
'--color-primary-light-900': string
|
||||
'--color-primary-light-900-alpha-100': string
|
||||
'--color-primary-light-900-alpha-200': string
|
||||
'--color-primary-light-900-alpha-300': string
|
||||
'--color-primary-light-900-alpha-400': string
|
||||
'--color-primary-light-900-alpha-500': string
|
||||
'--color-primary-light-900-alpha-600': string
|
||||
'--color-primary-light-900-alpha-700': string
|
||||
'--color-primary-light-900-alpha-800': string
|
||||
'--color-primary-light-900-alpha-900': string
|
||||
|
||||
'--color-primary-light-1000': string
|
||||
'--color-primary-light-1000-alpha-100': string
|
||||
'--color-primary-light-1000-alpha-200': string
|
||||
'--color-primary-light-1000-alpha-300': string
|
||||
'--color-primary-light-1000-alpha-400': string
|
||||
'--color-primary-light-1000-alpha-500': string
|
||||
'--color-primary-light-1000-alpha-600': string
|
||||
'--color-primary-light-1000-alpha-700': string
|
||||
'--color-primary-light-1000-alpha-800': string
|
||||
'--color-primary-light-1000-alpha-900': string
|
||||
}
|
||||
|
||||
interface Theme {
|
||||
id: string
|
||||
name: string
|
||||
isDark: boolean
|
||||
isCustom: boolean
|
||||
config: {
|
||||
themeColors: ThemeColors
|
||||
extInfo: {
|
||||
'--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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ThemeInfo {
|
||||
themes: LX.Theme[]
|
||||
userThemes: LX.Theme[]
|
||||
dataPath: string
|
||||
}
|
||||
|
||||
interface ThemeSetting {
|
||||
shouldUseDarkColors: boolean
|
||||
theme: {
|
||||
id: string
|
||||
name: string
|
||||
isDark: boolean
|
||||
colors: Record<string, string>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
declare namespace LX {
|
||||
namespace UserApi {
|
||||
type UserApiSourceInfoType = 'music'
|
||||
type UserApiSourceInfoActions = 'musicUrl'
|
||||
|
||||
interface UserApiSourceInfo {
|
||||
name: string
|
||||
type: UserApiSourceInfoType
|
||||
actions: UserApiSourceInfoActions[]
|
||||
qualitys: LX.Quality[]
|
||||
}
|
||||
|
||||
type UserApiSources = Record<LX.Source, UserApiSourceInfo>
|
||||
|
||||
|
||||
interface UserApiInfo {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
script: string
|
||||
allowShowUpdateAlert: boolean
|
||||
sources?: UserApiSources
|
||||
}
|
||||
|
||||
interface UserApiStatus {
|
||||
status: boolean
|
||||
message?: string
|
||||
apiInfo?: UserApiInfo
|
||||
}
|
||||
|
||||
interface UserApiUpdateInfo {
|
||||
name: string
|
||||
description: string
|
||||
log: string
|
||||
updateUrl?: string
|
||||
}
|
||||
|
||||
interface UserApiRequestParams {
|
||||
requestKey: string
|
||||
data: any
|
||||
}
|
||||
type UserApiRequestCancelParams = string
|
||||
type UserApiSetApiParams = string
|
||||
|
||||
interface UserApiSetAllowUpdateAlertParams {
|
||||
id: string
|
||||
enable: boolean
|
||||
}
|
||||
|
||||
interface ImportUserApi {
|
||||
apiInfo: UserApiInfo
|
||||
apiList: UserApiInfo[]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
type MakeOptional<Type, Key extends keyof Type> = Omit<Type, Key> & Partial<Pick<Type, Key>>
|
||||
|
||||
type DeepPartial<T> = {
|
||||
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
||||
}
|
||||
|
||||
type Modify<T, R> = Omit<T, keyof R> & R
|
||||
@@ -1,222 +0,0 @@
|
||||
const log = require('electron-log')
|
||||
const { defaultSetting, overwriteSetting } = require('./defaultSetting')
|
||||
// const apiSource = require('../renderer/utils/music/api-source-info')
|
||||
const getStore = require('./store')
|
||||
const defaultHotKey = require('./defaultHotKey')
|
||||
|
||||
exports.isLinux = process.platform == 'linux'
|
||||
exports.isWin = process.platform == 'win32'
|
||||
exports.isMac = process.platform == 'darwin'
|
||||
|
||||
|
||||
/**
|
||||
* 生成节流函数
|
||||
* @param {*} fn
|
||||
* @param {*} delay
|
||||
*/
|
||||
exports.throttle = (fn, delay = 100) => {
|
||||
let timer = null
|
||||
let _args = null
|
||||
return function(...args) {
|
||||
_args = args
|
||||
if (timer) return
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
fn.apply(this, _args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成防抖函数
|
||||
* @param {*} fn
|
||||
* @param {*} delay
|
||||
*/
|
||||
exports.debounce = (fn, delay = 100) => {
|
||||
let timer = null
|
||||
let _args = null
|
||||
return function(...args) {
|
||||
_args = args
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
fn.apply(this, _args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
exports.log = log
|
||||
|
||||
// https://stackoverflow.com/a/53387532
|
||||
exports.compareVer = (currentVer, targetVer) => {
|
||||
// treat non-numerical characters as lower version
|
||||
// replacing them with a negative number based on charcode of each character
|
||||
const fix = s => `.${s.toLowerCase().charCodeAt(0) - 2147483647}.`
|
||||
|
||||
currentVer = ('' + currentVer).replace(/[^0-9.]/g, fix).split('.')
|
||||
targetVer = ('' + targetVer).replace(/[^0-9.]/g, fix).split('.')
|
||||
let c = Math.max(currentVer.length, targetVer.length)
|
||||
for (let i = 0; i < c; i++) {
|
||||
// convert to integer the most efficient way
|
||||
currentVer[i] = ~~currentVer[i]
|
||||
targetVer[i] = ~~targetVer[i]
|
||||
if (currentVer[i] > targetVer[i]) return 1
|
||||
else if (currentVer[i] < targetVer[i]) return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
exports.isObject = item => item && typeof item === 'object' && !Array.isArray(item)
|
||||
|
||||
/**
|
||||
* 对象深度合并
|
||||
* @param {} target 要合并源对象
|
||||
* @param {} source 要合并目标对象
|
||||
*/
|
||||
exports.objectDeepMerge = (target, source, mergedObj) => {
|
||||
if (!mergedObj) {
|
||||
mergedObj = new Set()
|
||||
mergedObj.add(target)
|
||||
}
|
||||
let base = {}
|
||||
Object.keys(source).forEach(item => {
|
||||
if (exports.isObject(source[item])) {
|
||||
if (mergedObj.has(source[item])) return
|
||||
if (!exports.isObject(target[item])) target[item] = {}
|
||||
mergedObj.add(source[item])
|
||||
exports.objectDeepMerge(target[item], source[item], mergedObj)
|
||||
return
|
||||
}
|
||||
base[item] = source[item]
|
||||
})
|
||||
Object.assign(target, base)
|
||||
}
|
||||
|
||||
exports.mergeSetting = (setting, version) => {
|
||||
let defaultSettingCopy = JSON.parse(JSON.stringify(defaultSetting))
|
||||
let overwriteSettingCopy = JSON.parse(JSON.stringify(overwriteSetting))
|
||||
const defaultVersion = defaultSettingCopy.version
|
||||
if (!version) {
|
||||
if (setting) {
|
||||
version = setting.version
|
||||
delete setting.version
|
||||
}
|
||||
}
|
||||
|
||||
if (!setting) {
|
||||
setting = defaultSettingCopy
|
||||
} else if (exports.compareVer(version, defaultVersion) < 0) {
|
||||
exports.objectDeepMerge(defaultSettingCopy, setting)
|
||||
exports.objectDeepMerge(defaultSettingCopy, overwriteSettingCopy)
|
||||
setting = defaultSettingCopy
|
||||
}
|
||||
|
||||
// if (!apiSource.some(api => api.id === setting.apiSource && !api.disabled)) {
|
||||
// let api = apiSource.find(api => !api.disabled)
|
||||
// if (api) setting.apiSource = api.id
|
||||
// }
|
||||
|
||||
return { setting, version: defaultVersion }
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化设置
|
||||
* @param {*} setting
|
||||
* @param {*} isShowErrorAlert
|
||||
*/
|
||||
exports.initSetting = isShowErrorAlert => {
|
||||
const electronStore_list = getStore('playList', true, isShowErrorAlert)
|
||||
const electronStore_config = getStore('config')
|
||||
const electronStore_downloadList = getStore('downloadList')
|
||||
|
||||
let setting = electronStore_config.get('setting')
|
||||
if (setting) {
|
||||
let version = electronStore_config.get('version')
|
||||
if (!version) { // 迁移配置
|
||||
version = electronStore_config.get('setting.version')
|
||||
electronStore_config.set('version', version)
|
||||
electronStore_config.delete('setting.version')
|
||||
const list = electronStore_config.get('list')
|
||||
if (list) {
|
||||
if (list.defaultList) electronStore_list.set('defaultList', list.defaultList)
|
||||
if (list.loveList) electronStore_list.set('loveList', list.loveList)
|
||||
electronStore_config.delete('list')
|
||||
}
|
||||
const downloadList = electronStore_config.get('download')
|
||||
if (downloadList) {
|
||||
if (downloadList.list) electronStore_downloadList.set('list', downloadList.list)
|
||||
electronStore_config.delete('download')
|
||||
}
|
||||
}
|
||||
|
||||
// 迁移列表滚动位置设置 ~0.18.3
|
||||
if (setting.list.scroll) {
|
||||
let scroll = setting.list.scroll
|
||||
electronStore_config.delete('setting.list.scroll')
|
||||
electronStore_config.set('setting.list.isSaveScrollLocation', scroll.enable)
|
||||
delete setting.list.scroll
|
||||
}
|
||||
}
|
||||
|
||||
// 从我的列表分离下载列表 v1.7.0 后
|
||||
let downloadList = electronStore_list.get('downloadList')
|
||||
if (downloadList) {
|
||||
electronStore_downloadList.set('list', downloadList)
|
||||
electronStore_list.delete('downloadList')
|
||||
}
|
||||
|
||||
const { version: settingVersion, setting: newSetting } = exports.mergeSetting(setting, electronStore_config.get('version'))
|
||||
|
||||
// 修正拼写问题 v1.8.2 及以前
|
||||
if (newSetting.player.isShowLyricTransition != null) {
|
||||
newSetting.player.isShowLyricTranslation = newSetting.player.isShowLyricTransition
|
||||
delete newSetting.player.isShowLyricTransition
|
||||
}
|
||||
|
||||
// 迁移v1.19.0之前的主题设置
|
||||
if (newSetting.themeId != null) {
|
||||
newSetting.theme.id = newSetting.themeId
|
||||
delete newSetting.themeId
|
||||
}
|
||||
|
||||
// 重置 ^0.18.2 排行榜ID
|
||||
if (!newSetting.leaderboard.tabId.includes('__')) newSetting.leaderboard.tabId = 'kw__16'
|
||||
|
||||
// newSetting.controlBtnPosition = 'right'
|
||||
electronStore_config.set({ version: settingVersion, setting: newSetting })
|
||||
return { version: settingVersion, setting: newSetting }
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化快捷键设置
|
||||
*/
|
||||
exports.initHotKey = () => {
|
||||
const electronStore_hotKey = getStore('hotKey')
|
||||
|
||||
let localConfig = electronStore_hotKey.get('local')
|
||||
if (!localConfig) {
|
||||
localConfig = defaultHotKey.local
|
||||
electronStore_hotKey.set('local', localConfig)
|
||||
}
|
||||
|
||||
let globalConfig = electronStore_hotKey.get('global')
|
||||
|
||||
// 移除v1.0.1及之前设置的全局声音媒体快捷键接管
|
||||
if (globalConfig && globalConfig.keys.VolumeUp) {
|
||||
delete globalConfig.keys.VolumeUp
|
||||
delete globalConfig.keys.VolumeDown
|
||||
delete globalConfig.keys.VolumeMute
|
||||
electronStore_hotKey.set('global', globalConfig)
|
||||
}
|
||||
|
||||
if (!globalConfig) {
|
||||
globalConfig = defaultHotKey.global
|
||||
electronStore_hotKey.set('global', globalConfig)
|
||||
}
|
||||
|
||||
return {
|
||||
global: globalConfig,
|
||||
local: localConfig,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// 非业务工具方法
|
||||
|
||||
/**
|
||||
* 获取两个数之间的随机整数,大于等于min,小于max
|
||||
* @param {*} min
|
||||
* @param {*} max
|
||||
*/
|
||||
export const getRandom = (min: number, max: number): number => Math.floor(Math.random() * (max - min)) + min
|
||||
|
||||
|
||||
export const sizeFormate = (size: number): string => {
|
||||
// https://gist.github.com/thomseddon/3511330
|
||||
if (!size) return '0 B'
|
||||
let units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
let number = Math.floor(Math.log(size) / Math.log(1024))
|
||||
return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}`
|
||||
}
|
||||
|
||||
const numFix = (n: number): string => n < 10 ? (`0${n}`) : n.toString()
|
||||
/**
|
||||
* 日期格式化
|
||||
* @param {*} date 时间
|
||||
* @param {String} format 时间格式,默认YYYY-MM-DD hh:mm:ss
|
||||
*/
|
||||
export const dateFormat = (date: string | number | Date, format = 'YYYY-MM-DD hh:mm:ss') => {
|
||||
if (typeof date != 'object') date = new Date(date)
|
||||
return format
|
||||
.replace('YYYY', date.getFullYear().toString())
|
||||
.replace('MM', numFix(date.getMonth() + 1).toString())
|
||||
.replace('DD', numFix(date.getDate()))
|
||||
.replace('hh', numFix(date.getHours()))
|
||||
.replace('mm', numFix(date.getMinutes()))
|
||||
.replace('ss', numFix(date.getSeconds()))
|
||||
}
|
||||
|
||||
|
||||
export const formatPlayTime = (time: number) => {
|
||||
let m = Math.trunc(time / 60)
|
||||
let s = Math.trunc(time % 60)
|
||||
return m == 0 && s == 0 ? '--/--' : numFix(m) + ':' + numFix(s)
|
||||
}
|
||||
|
||||
export const formatPlayTime2 = (time: number) => {
|
||||
let m = Math.trunc(time / 60)
|
||||
let s = Math.trunc(time % 60)
|
||||
return numFix(m) + ':' + numFix(s)
|
||||
}
|
||||
|
||||
|
||||
const encodeNames = {
|
||||
' ': ' ',
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
''': "'",
|
||||
''': "'",
|
||||
} as const
|
||||
export const decodeName = (str: string | null = '') => {
|
||||
return str?.replace(/(?:&|<|>|"|'|'| )/gm, (s: string) => encodeNames[s as keyof typeof encodeNames]) ?? ''
|
||||
}
|
||||
|
||||
// 解析URL参数为对象
|
||||
export const parseUrlParams = (str: string): Record<string, string> => {
|
||||
const params: Record<string, string> = {}
|
||||
if (typeof str !== 'string') return params
|
||||
const paramsArr = str.split('&')
|
||||
for (const param of paramsArr) {
|
||||
let [key, value] = param.split('=')
|
||||
params[key] = value
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成节流函数
|
||||
* @param fn 回调
|
||||
* @param delay 延迟
|
||||
* @returns
|
||||
*/
|
||||
export function throttle<Args extends any[]>(fn: (...args: Args) => void | Promise<void>, delay = 100) {
|
||||
let timer: NodeJS.Timeout | null = null
|
||||
let _args: Args
|
||||
return (...args: Args) => {
|
||||
_args = args
|
||||
if (timer) return
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
void fn(..._args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成防抖函数
|
||||
* @param fn 回调
|
||||
* @param delay 延迟
|
||||
* @returns
|
||||
*/
|
||||
export function debounce<Args extends any[]>(fn: (...args: Args) => void | Promise<void>, delay = 100) {
|
||||
let timer: NodeJS.Timeout | null = null
|
||||
let _args: Args
|
||||
return (...args: Args) => {
|
||||
_args = args
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
timer = null
|
||||
void fn(..._args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
const fileNameRxp = /[\\/:*?#"<>|]/g
|
||||
export const filterFileName = (name: string): string => name.replace(fileNameRxp, '')
|
||||
|
||||
|
||||
// https://blog.csdn.net/xcxy2015/article/details/77164126#comments
|
||||
/**
|
||||
*
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
export const similar = (a: string, b: string) => {
|
||||
if (!a || !b) return 0
|
||||
if (a.length > b.length) { // 保证 a <= b
|
||||
let t = b
|
||||
b = a
|
||||
a = t
|
||||
}
|
||||
let al = a.length
|
||||
let bl = b.length
|
||||
let mp = [] // 一个表
|
||||
let i, j, ai, lt, tmp // ai:字符串a的第i个字符。 lt:左上角的值。 tmp:暂存新的值。
|
||||
for (i = 0; i <= bl; i++) mp[i] = i
|
||||
for (i = 1; i <= al; i++) {
|
||||
ai = a.charAt(i - 1)
|
||||
lt = mp[0]
|
||||
mp[0] = mp[0] + 1
|
||||
for (j = 1; j <= bl; j++) {
|
||||
tmp = Math.min(mp[j] + 1, mp[j - 1] + 1, lt + (ai == b.charAt(j - 1) ? 0 : 1))
|
||||
lt = mp[j]
|
||||
mp[j] = tmp
|
||||
}
|
||||
}
|
||||
return 1 - (mp[bl] / bl)
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序字符串
|
||||
* @param arr
|
||||
* @param data
|
||||
*/
|
||||
export const sortInsert = (arr: Array<{ num: number, data: any }>, data: { num: number, data: any }) => {
|
||||
let key = data.num
|
||||
let left = 0
|
||||
let right = arr.length - 1
|
||||
|
||||
while (left <= right) {
|
||||
let middle = Math.trunc((left + right) / 2)
|
||||
if (key == arr[middle].num) {
|
||||
left = middle
|
||||
break
|
||||
} else if (key < arr[middle].num) {
|
||||
right = middle - 1
|
||||
} else {
|
||||
left = middle + 1
|
||||
}
|
||||
}
|
||||
while (left > 0) {
|
||||
if (arr[left - 1].num != key) break
|
||||
left--
|
||||
}
|
||||
|
||||
arr.splice(left, 0, data)
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// https://github.com/tholman/cursor-effects/blob/master/src/bubbleCursor.js
|
||||
|
||||
class Particle {
|
||||
constructor(x, y, fillStyle, strokeStyle) {
|
||||
const lifeSpan = Math.floor(Math.random() * 60 + 60)
|
||||
this.initialLifeSpan = lifeSpan //
|
||||
this.lifeSpan = lifeSpan // ms
|
||||
this.velocity = {
|
||||
x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 10),
|
||||
y: -0.4 + Math.random() * -1,
|
||||
}
|
||||
this.position = { x, y }
|
||||
this.fillStyle = fillStyle
|
||||
this.strokeStyle = strokeStyle
|
||||
|
||||
this.baseDimension = 4
|
||||
}
|
||||
|
||||
update(context, width) {
|
||||
this.position.x += this.velocity.x
|
||||
this.position.y += this.velocity.y
|
||||
|
||||
this.velocity.x += ((Math.random() < 0.5 ? -1 : 1) * 2) / 75
|
||||
this.velocity.y -= Math.random() / 600
|
||||
if (this.position.x >= width - 2 || this.position.x <= 2) this.lifeSpan = 0
|
||||
else if (this.position.y <= 5) this.lifeSpan = 0
|
||||
this.lifeSpan--
|
||||
|
||||
const scale =
|
||||
0.2 + (this.initialLifeSpan - this.lifeSpan) / this.initialLifeSpan
|
||||
|
||||
context.fillStyle = this.fillStyle
|
||||
context.strokeStyle = this.strokeStyle
|
||||
context.beginPath()
|
||||
context.arc(
|
||||
this.position.x - (this.baseDimension / 2) * scale,
|
||||
this.position.y - this.baseDimension / 2,
|
||||
this.baseDimension * scale,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
)
|
||||
|
||||
context.stroke()
|
||||
context.fill()
|
||||
|
||||
context.closePath()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default class BubbleCursor {
|
||||
constructor({ element, fillStyle = 'rgba(77, 175, 124, 0.1)', strokeStyle = 'rgba(77, 175, 124, 0.3)' } = {}) {
|
||||
this.hasWrapperEl = element
|
||||
this.element = this.hasWrapperEl || document.body
|
||||
|
||||
this.width = window.innerWidth
|
||||
this.height = window.innerHeight
|
||||
this.cursor = { x: this.width / 2, y: this.width / 2 }
|
||||
this.particles = []
|
||||
this.canvas = null
|
||||
this.context = null
|
||||
this.fillStyle = fillStyle
|
||||
this.strokeStyle = strokeStyle
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
this.canvas = document.createElement('canvas')
|
||||
this.context = this.canvas.getContext('2d')
|
||||
|
||||
this.canvas.style.top = '0px'
|
||||
this.canvas.style.left = '0px'
|
||||
this.canvas.style.pointerEvents = 'none'
|
||||
this.canvas.style.zIndex = 100
|
||||
|
||||
if (this.hasWrapperEl) {
|
||||
this.canvas.style.position = 'absolute'
|
||||
this.element.appendChild(this.canvas)
|
||||
this.canvas.width = this.element.clientWidth
|
||||
this.canvas.height = this.element.clientHeight
|
||||
} else {
|
||||
this.canvas.style.position = 'fixed'
|
||||
document.body.appendChild(this.canvas)
|
||||
this.canvas.width = this.width
|
||||
this.canvas.height = this.height
|
||||
}
|
||||
|
||||
this.bindEvents()
|
||||
this.loop()
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.element.addEventListener('mousemove', this.onMouseMove)
|
||||
this.element.addEventListener('touchmove', this.onTouchMove, { passive: true })
|
||||
this.element.addEventListener('touchstart', this.onTouchMove, { passive: true })
|
||||
window.addEventListener('resize', this.onWindowResize)
|
||||
}
|
||||
|
||||
|
||||
onWindowResize = (e) => {
|
||||
this.width = window.innerWidth
|
||||
this.height = window.innerHeight
|
||||
|
||||
if (this.hasWrapperEl) {
|
||||
this.canvas.width = this.element.clientWidth
|
||||
this.canvas.height = this.element.clientHeight
|
||||
} else {
|
||||
this.canvas.width = this.width
|
||||
this.canvas.height = this.height
|
||||
}
|
||||
}
|
||||
|
||||
onTouchMove = (e) => {
|
||||
if (e.touches.length > 0) {
|
||||
for (let i = 0; i < e.touches.length; i++) {
|
||||
this.addParticle(
|
||||
e.touches[i].clientX,
|
||||
e.touches[i].clientY,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove = (e) => {
|
||||
if (this.hasWrapperEl) {
|
||||
const boundingRect = this.element.getBoundingClientRect()
|
||||
this.cursor.x = e.clientX - boundingRect.left
|
||||
this.cursor.y = e.clientY - boundingRect.top
|
||||
} else {
|
||||
this.cursor.x = e.clientX
|
||||
this.cursor.y = e.clientY
|
||||
}
|
||||
|
||||
this.addParticle(this.cursor.x, this.cursor.y)
|
||||
}
|
||||
|
||||
addParticle(x, y) {
|
||||
this.particles.push(new Particle(x, y, this.fillStyle, this.strokeStyle))
|
||||
}
|
||||
|
||||
updateParticles() {
|
||||
this.context.clearRect(0, 0, this.width, this.height)
|
||||
|
||||
// Update
|
||||
for (let i = 0; i < this.particles.length; i++) {
|
||||
this.particles[i].update(this.context, this.width)
|
||||
}
|
||||
|
||||
// Remove dead particles
|
||||
for (let i = this.particles.length - 1; i >= 0; i--) {
|
||||
if (this.particles[i].lifeSpan < 0) {
|
||||
this.particles.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop = () => {
|
||||
this.updateParticles()
|
||||
window.requestAnimationFrame(this.loop)
|
||||
}
|
||||
|
||||
setColor(fillStyle, strokeStyle) {
|
||||
this.fillStyle = fillStyle
|
||||
this.strokeStyle = strokeStyle
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.element.removeEventListener('mousemove', this.onMouseMove)
|
||||
this.element.removeEventListener('touchmove', this.onTouchMove, { passive: true })
|
||||
this.element.removeEventListener('touchstart', this.onTouchMove, { passive: true })
|
||||
window.removeEventListener('resize', this.onWindowResize)
|
||||
|
||||
if (this.hasWrapperEl) {
|
||||
this.element.removeChild(this.canvas)
|
||||
} else {
|
||||
document.body.removeChild(this.canvas)
|
||||
}
|
||||
|
||||
this.canvas = null
|
||||
this.context = null
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,74 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import request from 'request'
|
||||
import { EventEmitter } from 'events'
|
||||
import { performance } from 'perf_hooks'
|
||||
import { STATUS } from './util'
|
||||
import http from 'http'
|
||||
import { request, Options as RequestOptions } from './request'
|
||||
|
||||
export interface Options {
|
||||
forceResume: boolean
|
||||
requestOptions: RequestOptions
|
||||
}
|
||||
|
||||
const defaultChunkInfo = {
|
||||
path: null,
|
||||
startByte: 0,
|
||||
path: '',
|
||||
startByte: '0',
|
||||
endByte: '',
|
||||
}
|
||||
|
||||
const defaultRequestOptions = {
|
||||
method: 'GET',
|
||||
const defaultRequestOptions: Options['requestOptions'] = {
|
||||
method: 'get',
|
||||
headers: {},
|
||||
}
|
||||
const defaultOptions = {
|
||||
|
||||
const defaultOptions: Options = {
|
||||
forceResume: true,
|
||||
requestOptions: { ...defaultRequestOptions },
|
||||
}
|
||||
|
||||
class Task extends EventEmitter {
|
||||
/**
|
||||
*
|
||||
* @param {String} url download url
|
||||
* @param {Object} chunkInfo
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(url, savePath, filename, options = {}) {
|
||||
resumeLastChunk: Buffer | null
|
||||
downloadUrl: string
|
||||
chunkInfo: { path: string, startByte: string, endByte: string }
|
||||
status: typeof STATUS[keyof typeof STATUS]
|
||||
options: Options
|
||||
requestOptions: Options['requestOptions']
|
||||
ws: fs.WriteStream | null = null
|
||||
progress = { total: 0, downloaded: 0, speed: 0, progress: 0 }
|
||||
statsEstimate = { time: 0, bytes: 0, prevBytes: 0 }
|
||||
requestInstance: http.ClientRequest | null = null
|
||||
|
||||
|
||||
constructor(url: string, savePath: string, filename: string, options: Partial<Options> = {}) {
|
||||
super()
|
||||
|
||||
this.resumeLastChunk = null
|
||||
this.downloadUrl = url
|
||||
this.chunkInfo = Object.assign({}, defaultChunkInfo, {
|
||||
path: path.join(savePath, filename),
|
||||
startByte: 0,
|
||||
startByte: '0',
|
||||
})
|
||||
if (!this.chunkInfo.endByte) this.chunkInfo.endByte = ''
|
||||
// if (!this.chunkInfo.endByte) this.chunkInfo.endByte = ''
|
||||
|
||||
this.options = Object.assign({}, defaultOptions, options)
|
||||
|
||||
this.requestOptions = Object.assign({}, defaultRequestOptions, this.options.requestOptions || {})
|
||||
if (!this.requestOptions.headers) this.requestOptions.headers = {}
|
||||
this.requestOptions.headers = this.requestOptions.headers ? { ...this.requestOptions.headers } : {}
|
||||
|
||||
this.progress = {
|
||||
total: 0,
|
||||
downloaded: 0,
|
||||
speed: 0,
|
||||
}
|
||||
this.statsEstimate = {
|
||||
time: 0,
|
||||
bytes: 0,
|
||||
prevBytes: 0,
|
||||
}
|
||||
this.status = STATUS.idle
|
||||
}
|
||||
|
||||
__init() {
|
||||
this.status = STATUS.init
|
||||
async __init() {
|
||||
const { path, startByte, endByte } = this.chunkInfo
|
||||
this.progress.downloaded = 0
|
||||
if (startByte != null) this.requestOptions.headers.range = `bytes=${startByte}-${endByte}`
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!path) return resolve()
|
||||
this.progress.progress = 0
|
||||
this.progress.speed = 0
|
||||
if (startByte) this.requestOptions.headers!.range = `bytes=${startByte}-${endByte}`
|
||||
|
||||
if (!path) return
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.stat(path, (errStat, stats) => {
|
||||
if (errStat) {
|
||||
// console.log(errStat.code)
|
||||
if (errStat.code !== 'ENOENT') {
|
||||
this.__handleError(errStat)
|
||||
reject(errStat)
|
||||
@@ -94,7 +98,7 @@ class Task extends EventEmitter {
|
||||
// console.log(buffer)
|
||||
this.resumeLastChunk = buffer
|
||||
this.progress.downloaded = stats.size
|
||||
this.requestOptions.headers.range = `bytes=${stats.size - 10}-${endByte || ''}`
|
||||
this.requestOptions.headers!.range = `bytes=${stats.size - 10}-${endByte || ''}`
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
@@ -106,15 +110,15 @@ class Task extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
__httpFetch(url, options) {
|
||||
__httpFetch(url: string, options: Options['requestOptions']) {
|
||||
// console.log(options)
|
||||
this.request = request(url, options)
|
||||
this.requestInstance = request(url, options)
|
||||
.on('response', response => {
|
||||
if (response.statusCode !== 200 && response.statusCode !== 206) {
|
||||
if (response.statusCode == 416) {
|
||||
fs.unlink(this.chunkInfo.path, async err => {
|
||||
await this.__handleError(new Error(response.statusMessage))
|
||||
this.chunkInfo.startByte = 0
|
||||
fs.unlink(this.chunkInfo.path, (err) => {
|
||||
this.__handleError(new Error(response.statusMessage))
|
||||
this.chunkInfo.startByte = '0'
|
||||
this.resumeLastChunk = null
|
||||
this.progress.downloaded = 0
|
||||
if (err) this.__handleError(err)
|
||||
@@ -124,13 +128,13 @@ class Task extends EventEmitter {
|
||||
this.status = STATUS.failed
|
||||
this.emit('fail', response)
|
||||
this.__closeRequest()
|
||||
this.__closeWriteStream()
|
||||
void this.__closeWriteStream()
|
||||
return
|
||||
}
|
||||
this.emit('response', response)
|
||||
try {
|
||||
this.__initDownload(response)
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
return this.__handleError(error)
|
||||
}
|
||||
this.status = STATUS.running
|
||||
@@ -142,34 +146,44 @@ class Task extends EventEmitter {
|
||||
this.__handleComplete()
|
||||
} else {
|
||||
// this.__handleError(new Error('The connection was terminated while the message was still being sent'))
|
||||
this.stop()
|
||||
void this.stop()
|
||||
}
|
||||
})
|
||||
})
|
||||
.on('error', err => this.__handleError(err))
|
||||
.on('close', () => this.__closeWriteStream())
|
||||
.on('close', () => {
|
||||
void this.__closeWriteStream()
|
||||
})
|
||||
.end()
|
||||
}
|
||||
|
||||
__initDownload(response) {
|
||||
this.progress.total = parseInt(response.headers['content-length'] || 0)
|
||||
let options = {}
|
||||
let isResumable = this.options.forceResume || response.headers['accept-ranges'] !== 'none' || (typeof response.headers['accept-ranges'] == 'string' && parseInt(response.headers['accept-ranges'].replace(/^bytes=(\d+)/, '$1')) > 0)
|
||||
__initDownload(response: http.IncomingMessage) {
|
||||
this.progress.total = response.headers['content-length'] ? parseInt(response.headers['content-length']) : 0
|
||||
if (!this.progress.total) return this.__handleError(new Error('Content length is 0'))
|
||||
let options: any = {}
|
||||
let isResumable = this.options.forceResume ||
|
||||
response.headers['accept-ranges'] !== 'none' ||
|
||||
(typeof response.headers['accept-ranges'] == 'string' &&
|
||||
parseInt(response.headers['accept-ranges'].replace(/^bytes=(\d+)/, '$1')) > 0)
|
||||
|
||||
if (isResumable) {
|
||||
options.flags = 'a'
|
||||
if (this.progress.downloaded) this.progress.total -= 10
|
||||
} else {
|
||||
if (this.chunkInfo.startByte > 0) return this.__handleError(new Error('The resource cannot be resumed download.'))
|
||||
if (this.chunkInfo.startByte != '0') return this.__handleError(new Error('The resource cannot be resumed download.'))
|
||||
}
|
||||
this.progress.total += this.progress.downloaded
|
||||
this.statsEstimate.prevBytes = this.progress.downloaded
|
||||
if (!this.chunkInfo.path) return this.__handleError(new Error('Chunk save Path is not set.'))
|
||||
this.ws = fs.createWriteStream(this.chunkInfo.path, options)
|
||||
|
||||
this.ws.on('finish', () => this.__closeWriteStream())
|
||||
this.ws.on('finish', () => {
|
||||
void this.__closeWriteStream()
|
||||
})
|
||||
this.ws.on('error', err => {
|
||||
fs.unlink(this.chunkInfo.path, async unlinkErr => {
|
||||
await this.__handleError(err)
|
||||
this.chunkInfo.startByte = 0
|
||||
fs.unlink(this.chunkInfo.path, (unlinkErr: any) => {
|
||||
this.__handleError(err)
|
||||
this.chunkInfo.startByte = '0'
|
||||
this.resumeLastChunk = null
|
||||
this.progress.downloaded = 0
|
||||
if (unlinkErr && unlinkErr.code !== 'ENOENT') this.__handleError(unlinkErr)
|
||||
@@ -179,7 +193,7 @@ class Task extends EventEmitter {
|
||||
|
||||
__handleComplete() {
|
||||
if (this.status == STATUS.error) return
|
||||
this.__closeWriteStream().then(() => {
|
||||
void this.__closeWriteStream().then(() => {
|
||||
if (this.progress.downloaded == this.progress.total) {
|
||||
this.status = STATUS.completed
|
||||
this.emit('completed')
|
||||
@@ -188,21 +202,22 @@ class Task extends EventEmitter {
|
||||
this.emit('stop')
|
||||
}
|
||||
})
|
||||
console.log('end')
|
||||
// console.log('end')
|
||||
}
|
||||
|
||||
__handleError(error) {
|
||||
__handleError(error: Error) {
|
||||
if (this.status == STATUS.error) return
|
||||
this.status = STATUS.error
|
||||
this.__closeRequest()
|
||||
this.__closeWriteStream()
|
||||
void this.__closeWriteStream()
|
||||
if (error.message == 'aborted') return
|
||||
this.emit('error', error)
|
||||
}
|
||||
|
||||
__closeWriteStream() {
|
||||
return new Promise((resolve, reject) => {
|
||||
async __closeWriteStream() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!this.ws) return resolve()
|
||||
console.log('close write stream')
|
||||
// console.log('close write stream')
|
||||
this.ws.close(err => {
|
||||
if (err) {
|
||||
this.status = STATUS.error
|
||||
@@ -217,18 +232,28 @@ class Task extends EventEmitter {
|
||||
}
|
||||
|
||||
__closeRequest() {
|
||||
if (!this.request) return
|
||||
console.log('close request')
|
||||
this.request.abort()
|
||||
this.request = null
|
||||
if (!this.requestInstance || this.requestInstance.destroyed) return
|
||||
// console.log('close request')
|
||||
this.requestInstance.destroy()
|
||||
this.requestInstance = null
|
||||
}
|
||||
|
||||
__handleWriteData(chunk) {
|
||||
__handleWriteData(chunk: Buffer) {
|
||||
if (this.resumeLastChunk) {
|
||||
chunk = this.__handleDiffChunk(chunk)
|
||||
if (!chunk) {
|
||||
const result = this.__handleDiffChunk(chunk)
|
||||
if (result) chunk = result
|
||||
else {
|
||||
this.__handleStop().finally(() => {
|
||||
this.__handleError(new Error('Resume failed, response chunk does not match.'))
|
||||
// this.__handleError(new Error('Resume failed, response chunk does not match.'))
|
||||
// Resume failed, response chunk does not match, remove file and restart download
|
||||
console.log('Resume failed, response chunk does not match.')
|
||||
fs.unlink(this.chunkInfo.path, (unlinkErr: any) => {
|
||||
// this.__handleError(err)
|
||||
this.chunkInfo.startByte = '0'
|
||||
this.resumeLastChunk = null
|
||||
if (unlinkErr && unlinkErr.code !== 'ENOENT') return this.__handleError(unlinkErr)
|
||||
void this.start()
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -240,35 +265,32 @@ class Task extends EventEmitter {
|
||||
if (!err) return
|
||||
console.log(err)
|
||||
this.__handleError(err)
|
||||
this.stop()
|
||||
void this.stop()
|
||||
})
|
||||
}
|
||||
|
||||
__handleDiffChunk(chunk) {
|
||||
__handleDiffChunk(chunk: Buffer): Buffer | null {
|
||||
// console.log('diff', chunk)
|
||||
let resumeLastChunkLen = this.resumeLastChunk.length
|
||||
let resumeLastChunkLen = this.resumeLastChunk!.length
|
||||
let chunkLen = chunk.length
|
||||
let isOk
|
||||
if (chunkLen >= resumeLastChunkLen) {
|
||||
isOk = chunk.slice(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk.toString('hex')
|
||||
isOk = chunk.slice(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk!.toString('hex')
|
||||
if (!isOk) return null
|
||||
|
||||
this.resumeLastChunk = null
|
||||
return chunk.slice(resumeLastChunkLen)
|
||||
} else {
|
||||
isOk = chunk.slice(0, chunkLen).toString('hex') === this.resumeLastChunk.slice(0, chunkLen).toString('hex')
|
||||
isOk = chunk.slice(0, chunkLen).toString('hex') === this.resumeLastChunk!.slice(0, chunkLen).toString('hex')
|
||||
if (!isOk) return null
|
||||
this.resumeLastChunk = this.resumeLastChunk.slice(chunkLen)
|
||||
this.resumeLastChunk = this.resumeLastChunk!.slice(chunkLen)
|
||||
return chunk.slice(chunkLen)
|
||||
}
|
||||
}
|
||||
|
||||
__handleStop() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.request) {
|
||||
this.request.abort()
|
||||
this.request = null
|
||||
}
|
||||
async __handleStop() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.__closeRequest()
|
||||
if (this.ws) {
|
||||
this.ws.close(err => {
|
||||
if (err) {
|
||||
@@ -285,7 +307,7 @@ class Task extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
__calculateProgress(receivedBytes) {
|
||||
__calculateProgress(receivedBytes: number) {
|
||||
const currentTime = performance.now()
|
||||
const elaspsedTime = currentTime - this.statsEstimate.time
|
||||
|
||||
@@ -309,24 +331,26 @@ class Task extends EventEmitter {
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.status = STATUS.running
|
||||
this.status = STATUS.init
|
||||
await this.__init()
|
||||
if (this.status !== STATUS.init) return
|
||||
this.status = STATUS.running
|
||||
this.__httpFetch(this.downloadUrl, this.requestOptions)
|
||||
this.emit('start')
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (this.status === STATUS.stopped) return
|
||||
if (this.status == STATUS.stopped || this.status == STATUS.completed) return
|
||||
this.status = STATUS.stopped
|
||||
await this.__handleStop()
|
||||
this.emit('stop')
|
||||
}
|
||||
|
||||
refreshUrl(url) {
|
||||
refreshUrl(url: string) {
|
||||
this.downloadUrl = url
|
||||
}
|
||||
|
||||
updateSaveInfo(filePath, fileName) {
|
||||
updateSaveInfo(filePath: string, fileName: string) {
|
||||
this.chunkInfo.path = path.join(filePath, fileName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import Downloader, { Options as DownloaderOptions } from './Downloader'
|
||||
import { getRequestAgent } from './util'
|
||||
import { sizeFormate } from '@common/utils'
|
||||
import http from 'http'
|
||||
|
||||
// these are the default options
|
||||
// const options = {
|
||||
// method: 'GET', // Request Method Verb
|
||||
// // Custom HTTP Header ex: Authorization, User-Agent
|
||||
// headers: {},
|
||||
// fileName: '', // Custom filename when saved
|
||||
// override: false, // if true it will override the file, otherwise will append '(number)' to the end of file
|
||||
// forceResume: false, // If the server does not return the "accept-ranges" header, can be force if it does support it
|
||||
// // httpRequestOptions: {}, // Override the http request options
|
||||
// // httpsRequestOptions: {}, // Override the https request options, ex: to add SSL Certs
|
||||
// }
|
||||
|
||||
export interface Options {
|
||||
url: string
|
||||
path: string
|
||||
fileName: string
|
||||
method?: DownloaderOptions['requestOptions']['method']
|
||||
headers?: DownloaderOptions['requestOptions']['headers']
|
||||
forceResume?: boolean
|
||||
proxy?: { host: string, port: number }
|
||||
onCompleted?: () => void
|
||||
onError?: (error: Error) => void
|
||||
onFail?: (response: http.IncomingMessage) => void
|
||||
onStart?: () => void
|
||||
onStop?: () => void
|
||||
onProgress?: (progress: LX.Download.ProgressInfo) => void
|
||||
}
|
||||
const noop = () => {}
|
||||
export const createDownload = ({
|
||||
url,
|
||||
path,
|
||||
fileName,
|
||||
method = 'get',
|
||||
forceResume,
|
||||
proxy,
|
||||
// resumeTime = 5000,
|
||||
onCompleted = noop,
|
||||
onError = noop,
|
||||
onFail = noop,
|
||||
onStart = noop,
|
||||
onStop = noop,
|
||||
onProgress = noop,
|
||||
}: Options) => {
|
||||
const dl = new Downloader(url, path, fileName, {
|
||||
requestOptions: {
|
||||
method,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
|
||||
},
|
||||
agent: getRequestAgent(url, proxy),
|
||||
timeout: 60 * 1000,
|
||||
},
|
||||
|
||||
forceResume,
|
||||
})
|
||||
|
||||
dl.on('completed', () => {
|
||||
onCompleted()
|
||||
}).on('error', (err: any) => {
|
||||
if (err.message === 'socket hang up') return
|
||||
onError(err)
|
||||
}).on('start', () => {
|
||||
onStart()
|
||||
// pauseResumeTimer(dl, resumeTime)
|
||||
}).on('progress', (stats) => {
|
||||
const speed = sizeFormate(stats.speed)
|
||||
onProgress({
|
||||
progress: parseInt(stats.progress.toFixed(2)),
|
||||
speed,
|
||||
downloaded: stats.downloaded,
|
||||
total: stats.total,
|
||||
})
|
||||
// if (debugDownload) {
|
||||
// const downloaded = sizeFormate(stats.downloaded)
|
||||
// const total = sizeFormate(stats.total)
|
||||
// console.log(`${speed}/s - ${progress}% [${downloaded}/${total}]`)
|
||||
// }
|
||||
}).on('stop', () => {
|
||||
onStop()
|
||||
// debugDownload && console.log('paused')
|
||||
}).on('fail', resp => {
|
||||
onFail(resp)
|
||||
// debugDownload && console.log('fail')
|
||||
})
|
||||
|
||||
// debugDownload && console.log('Downloading: ', url)
|
||||
|
||||
dl.start().catch(err => {
|
||||
onError(err)
|
||||
})
|
||||
|
||||
return dl
|
||||
}
|
||||
|
||||
export type DownloaderType = Downloader
|
||||
@@ -0,0 +1,73 @@
|
||||
import { URL } from 'url'
|
||||
import http from 'http'
|
||||
import https from 'https'
|
||||
|
||||
export interface Options {
|
||||
method: 'get' | 'head' | 'delete' | 'patch' | 'post' | 'put'
|
||||
params?: Record<string, string>
|
||||
// body?: Record<string, string>
|
||||
headers?: Record<string, string>
|
||||
timeout?: number
|
||||
agent?: http.Agent
|
||||
}
|
||||
|
||||
const defaultOptions: Options = {
|
||||
method: 'get',
|
||||
}
|
||||
|
||||
type HttpCallback = (res: http.IncomingMessage) => void
|
||||
|
||||
const sendRequest = (url: string, options: Options, callback?: HttpCallback) => {
|
||||
const urlParse = new URL(url)
|
||||
const httpOptions: http.RequestOptions | https.RequestOptions = {
|
||||
host: urlParse.hostname,
|
||||
port: urlParse.port,
|
||||
path: urlParse.pathname + urlParse.search,
|
||||
method: options.method,
|
||||
}
|
||||
|
||||
if (options.params) {
|
||||
(httpOptions.path as string) += `${urlParse.search ? '&' : '?'}${Object.entries(options.params)
|
||||
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
||||
.join('&')}`
|
||||
}
|
||||
|
||||
if (options.headers) httpOptions.headers = { ...options.headers }
|
||||
|
||||
if (options.agent) httpOptions.agent = options.agent
|
||||
|
||||
return urlParse.protocol == 'https:'
|
||||
? https.request(httpOptions, callback)
|
||||
: http.request(httpOptions, callback)
|
||||
}
|
||||
|
||||
const applyTimeout = (request: http.ClientRequest, time: number) => {
|
||||
let timeout: NodeJS.Timeout | null = setTimeout(() => {
|
||||
timeout = null
|
||||
if (request.destroyed) return
|
||||
request.destroy(new Error('Request timeout'))
|
||||
}, time)
|
||||
request.on('response', () => {
|
||||
if (!timeout) return
|
||||
clearTimeout(timeout)
|
||||
timeout = null
|
||||
})
|
||||
}
|
||||
|
||||
// const isRequireRedirect = (response: http.IncomingMessage) => {
|
||||
// return response.statusCode &&
|
||||
// response.statusCode > 300 &&
|
||||
// response.statusCode < 400 &&
|
||||
// Object.hasOwn(response.headers, 'location') &&
|
||||
// response.headers.location
|
||||
// }
|
||||
|
||||
// export function request(url: string, callback: HttpCallback)
|
||||
// export function request(url: string, options: Partial<Options>, callback: HttpCallback)
|
||||
export function request(url: string, _options: Partial<Options>, callback?: HttpCallback) {
|
||||
let options: Options = { ...defaultOptions, ..._options }
|
||||
const request = sendRequest(url, options, callback)
|
||||
if (options.timeout) applyTimeout(request, options.timeout)
|
||||
return request
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { httpOverHttp, httpsOverHttp } from 'tunnel'
|
||||
|
||||
export const STATUS = {
|
||||
idle: 'IDLE',
|
||||
init: 'INIT',
|
||||
running: 'RUNNING',
|
||||
paused: 'PAUSED',
|
||||
stopped: 'STOPPED',
|
||||
completed: 'COMPLETED',
|
||||
error: 'ERROR',
|
||||
failed: 'FAILED',
|
||||
} as const
|
||||
|
||||
const httpsRxp = /^https:/
|
||||
export const getRequestAgent = (url: string, proxy?: { host: string, port: number }) => {
|
||||
let options
|
||||
if (proxy) {
|
||||
options = {
|
||||
proxy: {
|
||||
host: proxy.host,
|
||||
port: proxy.port,
|
||||
},
|
||||
}
|
||||
}
|
||||
return options ? (httpsRxp.test(url) ? httpsOverHttp : httpOverHttp)(options) : undefined
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { shell, clipboard } from 'electron'
|
||||
|
||||
|
||||
/**
|
||||
* 在资源管理器中打开目录
|
||||
* @param {string} dir
|
||||
*/
|
||||
export const openDirInExplorer = (dir: string) => {
|
||||
shell.showItemInFolder(dir)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在浏览器打开URL
|
||||
* @param {*} url
|
||||
*/
|
||||
export const openUrl = async(url: string) => {
|
||||
if (!/^https?:\/\//.test(url)) return
|
||||
await shell.openExternal(url)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 复制文本到剪贴板
|
||||
* @param str
|
||||
*/
|
||||
export const clipboardWriteText = (str: string) => {
|
||||
clipboard.writeText(str)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从剪贴板读取文本
|
||||
* @returns
|
||||
*/
|
||||
export const clipboardReadText = (): string => {
|
||||
return clipboard.readText()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import log from 'electron-log'
|
||||
|
||||
log.transports.file.level = 'info'
|
||||
|
||||
export const isLinux = process.platform == 'linux'
|
||||
export const isWin = process.platform == 'win32'
|
||||
export const isMac = process.platform == 'darwin'
|
||||
export const isProd = process.env.NODE_ENV == 'production'
|
||||
|
||||
|
||||
// https://stackoverflow.com/a/53387532
|
||||
export function compareVer(currentVer: string, targetVer: string): -1 | 0 | 1 {
|
||||
// treat non-numerical characters as lower version
|
||||
// replacing them with a negative number based on charcode of each character
|
||||
const fix = (s: string) => `.${s.toLowerCase().charCodeAt(0) - 2147483647}.`
|
||||
|
||||
const currentVerArr: Array<string | number> = ('' + currentVer).replace(/[^0-9.]/g, fix).split('.')
|
||||
const targetVerArr: Array<string | number> = ('' + targetVer).replace(/[^0-9.]/g, fix).split('.')
|
||||
let c = Math.max(currentVerArr.length, targetVerArr.length)
|
||||
for (let i = 0; i < c; i++) {
|
||||
// convert to integer the most efficient way
|
||||
currentVerArr[i] = ~~currentVerArr[i]
|
||||
targetVerArr[i] = ~~targetVerArr[i]
|
||||
if (currentVerArr[i] > targetVerArr[i]) return 1
|
||||
else if (currentVerArr[i] < targetVerArr[i]) return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
export const encodePath = (path: string) => {
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/963
|
||||
return path.replaceAll('%', '%25')
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
log,
|
||||
}
|
||||
|
||||
export * from './common'
|
||||
@@ -2,14 +2,20 @@ const { getNow, TimeoutTools } = require('./utils')
|
||||
|
||||
// const fontFormateRxp = /(?=<\d+,\d+>).*?/g
|
||||
const fontSplitRxp = /(?=<\d+,\d+>).*?/g
|
||||
const timeRxpAll = /<(\d+),(\d+)>/g
|
||||
const timeRxp = /<(\d+),(\d+)>/
|
||||
|
||||
|
||||
// Create animation
|
||||
const createAnimation = (dom, duration) => new window.Animation(new window.KeyframeEffect(dom, [
|
||||
{ backgroundSize: '0 100%' },
|
||||
{ backgroundSize: '100% 100%' },
|
||||
], {
|
||||
const createAnimation = (dom, duration, isVertical) => new window.Animation(new window.KeyframeEffect(dom, isVertical
|
||||
? [
|
||||
{ backgroundSize: '100% 0' },
|
||||
{ backgroundSize: '100% 100%' },
|
||||
]
|
||||
: [
|
||||
{ backgroundSize: '0 100%' },
|
||||
{ backgroundSize: '100% 100%' },
|
||||
], {
|
||||
duration,
|
||||
easing: 'linear',
|
||||
},
|
||||
@@ -20,25 +26,45 @@ const createAnimation = (dom, duration) => new window.Animation(new window.Keyfr
|
||||
// https://jsfiddle.net/ceqpnbky/1/
|
||||
|
||||
module.exports = class FontPlayer {
|
||||
constructor({ time = 0, lyric = '', extendedLyrics = '', lineClassName = '', fontClassName = '', extendedLrcClassName = '', lineModeClassName = '', shadowContent = false, shadowClassName = '' }) {
|
||||
constructor({
|
||||
time = 0,
|
||||
lyric = '',
|
||||
lineContentClassName = 'line-content',
|
||||
lineClassName = 'line',
|
||||
shadowClassName = 'shadow',
|
||||
fontModeClassName = 'font-mode',
|
||||
lineModeClassName = 'line-mode',
|
||||
fontLrcClassName = 'font-lrc',
|
||||
extendedLrcClassName = 'extended',
|
||||
shadowContent = false,
|
||||
extendedLyrics = [],
|
||||
isVertical = false,
|
||||
}) {
|
||||
this.time = time
|
||||
this.lyric = lyric
|
||||
this.extendedLyrics = extendedLyrics
|
||||
|
||||
this.isVertical = isVertical
|
||||
|
||||
this.lineContentClassName = lineContentClassName
|
||||
this.lineClassName = lineClassName
|
||||
this.fontClassName = fontClassName
|
||||
this.extendedLrcClassName = extendedLrcClassName
|
||||
this.lineModeClassName = lineModeClassName
|
||||
|
||||
this.shadowContent = shadowContent
|
||||
this.shadowClassName = shadowClassName
|
||||
|
||||
this.extendedLyrics = extendedLyrics
|
||||
this.fontModeClassName = fontModeClassName
|
||||
this.fontLrcClassName = fontLrcClassName
|
||||
this.extendedLrcClassName = extendedLrcClassName
|
||||
this.lineModeClassName = lineModeClassName
|
||||
|
||||
|
||||
this.isPlay = false
|
||||
this.curFontNum = 0
|
||||
this.maxFontNum = 0
|
||||
this._performanceTime = 0
|
||||
this._startTime = 0
|
||||
|
||||
this.fontContent = null
|
||||
this.lineContent = null
|
||||
|
||||
this.timeoutTools = new TimeoutTools(80)
|
||||
this.waitPlayTimeout = new TimeoutTools(80)
|
||||
@@ -53,32 +79,43 @@ module.exports = class FontPlayer {
|
||||
|
||||
this.lineContent = document.createElement('div')
|
||||
this.lineContent.time = this.time
|
||||
if (this.lineClassName) this.lineContent.classList.add(this.lineClassName)
|
||||
this.fontContent = document.createElement('div')
|
||||
this.fontContent.style = 'position:relative;display:inline-block;'
|
||||
if (this.fontClassName) this.fontContent.classList.add(this.fontClassName)
|
||||
if (this.shadowContent) {
|
||||
this.fontShadowContent = document.createElement('div')
|
||||
this.fontShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
|
||||
this.fontShadowContent.className = this.shadowClassName
|
||||
this.fontContent.appendChild(this.fontShadowContent)
|
||||
}
|
||||
this.lineContent.appendChild(this.fontContent)
|
||||
this.lineContent.className = this.lineContentClassName
|
||||
|
||||
this.line = document.createElement('div')
|
||||
this.line.style = 'position:relative;display:inline-block;'
|
||||
this.line.className = this.lineClassName
|
||||
this.lineContent.appendChild(this.line)
|
||||
|
||||
this.lrcContent = document.createElement('div')
|
||||
this.lrcContent.className = this.fontLrcClassName
|
||||
// if (this.shadowContent) {
|
||||
// this.lrcShadowContent = document.createElement('div')
|
||||
// this.lrcShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
|
||||
// this.lrcShadowContent.className = this.shadowClassName
|
||||
// this.line.appendChild(this.lrcShadowContent)
|
||||
// }
|
||||
this.line.appendChild(this.lrcContent)
|
||||
|
||||
for (const lrc of this.extendedLyrics) {
|
||||
const extendedLrcContent = document.createElement('div')
|
||||
extendedLrcContent.style = 'position:relative;display:inline-block;'
|
||||
extendedLrcContent.className = this.extendedLrcClassName
|
||||
extendedLrcContent.textContent = lrc
|
||||
this.lineContent.appendChild(document.createElement('br'))
|
||||
this.lineContent.appendChild(extendedLrcContent)
|
||||
|
||||
if (this.shadowContent) {
|
||||
const extendedLrcShadowContent = document.createElement('div')
|
||||
extendedLrcShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
|
||||
extendedLrcShadowContent.className = this.shadowClassName
|
||||
extendedLrcShadowContent.textContent = lrc
|
||||
extendedLrcContent.appendChild(extendedLrcShadowContent)
|
||||
}
|
||||
|
||||
// if (this.shadowContent) {
|
||||
// const extendedLrcShadowContent = document.createElement('div')
|
||||
// extendedLrcShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
|
||||
// extendedLrcShadowContent.className = this.shadowClassName
|
||||
// extendedLrcShadowContent.textContent = lrc
|
||||
// extendedLrcContent.appendChild(extendedLrcShadowContent)
|
||||
// }
|
||||
|
||||
const lineContent = document.createElement('div')
|
||||
lineContent.className = this.fontLrcClassName
|
||||
lineContent.textContent = lrc.replace(timeRxpAll, '')
|
||||
extendedLrcContent.appendChild(lineContent)
|
||||
}
|
||||
this._parseLyric()
|
||||
}
|
||||
@@ -90,21 +127,24 @@ module.exports = class FontPlayer {
|
||||
this.maxFontNum = fonts.length - 1
|
||||
this.fonts = []
|
||||
let text
|
||||
// let lineText = ''
|
||||
let lrcShadowContent
|
||||
for (const font of fonts) {
|
||||
text = font.replace(timeRxp, '')
|
||||
if (RegExp.$2 == '') return this._handleLineParse()
|
||||
const time = parseInt(RegExp.$2)
|
||||
|
||||
const dom = document.createElement('span')
|
||||
let shadowDom
|
||||
dom.textContent = text
|
||||
const animation = createAnimation(dom, time)
|
||||
this.fontContent.appendChild(dom)
|
||||
const animation = createAnimation(dom, time, this.isVertical)
|
||||
this.lrcContent.appendChild(dom)
|
||||
// lineText += text
|
||||
|
||||
if (this.shadowContent) {
|
||||
shadowDom = document.createElement('span')
|
||||
if (!lrcShadowContent) lrcShadowContent = document.createElement('div')
|
||||
const shadowDom = document.createElement('span')
|
||||
shadowDom.textContent = text
|
||||
this.fontShadowContent.appendChild(shadowDom)
|
||||
lrcShadowContent.appendChild(shadowDom)
|
||||
}
|
||||
// dom.style = shadowDom.style = this.fontStyle
|
||||
// dom.className = shadowDom.className = this.fontClassName
|
||||
@@ -114,29 +154,34 @@ module.exports = class FontPlayer {
|
||||
startTime: parseInt(RegExp.$1),
|
||||
time,
|
||||
dom,
|
||||
shadowDom,
|
||||
animation,
|
||||
})
|
||||
}
|
||||
|
||||
if (this.shadowContent && lrcShadowContent) {
|
||||
lrcShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
|
||||
lrcShadowContent.className = this.shadowClassName
|
||||
this.line.appendChild(lrcShadowContent)
|
||||
}
|
||||
|
||||
this.line.appendChild(this.lrcContent)
|
||||
this.fonts.at(-1)?.animation.addEventListener('finish', () => {
|
||||
this.lineContent.classList.add('played')
|
||||
this.isPlay = false
|
||||
})
|
||||
this.lineContent.classList.add(this.fontModeClassName)
|
||||
// if (this.shadowContent) this.lrcShadowContent.textContent = lineText
|
||||
// console.log(this.fonts)
|
||||
}
|
||||
|
||||
_handleLineParse() {
|
||||
this.isLineMode = true
|
||||
const dom = document.createElement('span')
|
||||
let shadowDom
|
||||
dom.classList.add(this.lineModeClassName)
|
||||
dom.textContent = this.lyric
|
||||
if (this.shadowContent) {
|
||||
shadowDom = document.createElement('span')
|
||||
shadowDom.textContent = this.lyric
|
||||
this.fontShadowContent.appendChild(shadowDom)
|
||||
}
|
||||
this.fontContent.appendChild(dom)
|
||||
this.lineContent.classList.add(this.lineModeClassName)
|
||||
this.lrcContent.textContent = this.lyric
|
||||
|
||||
// if (this.shadowContent) this.lrcShadowContent.textContent = this.lyric
|
||||
this.fonts.push({
|
||||
text: this.lyric,
|
||||
dom,
|
||||
shadowDom,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -157,10 +202,11 @@ module.exports = class FontPlayer {
|
||||
const driftTime = currentTime - curFont.startTime
|
||||
if (currentTime > curFont.startTime + curFont.time) {
|
||||
this._handlePlayFont(curFont, driftTime, true)
|
||||
this.lineContent.classList.add('played')
|
||||
this.isPlay = false
|
||||
this.pause()
|
||||
} else {
|
||||
this._handlePlayFont(curFont, driftTime)
|
||||
this.isPlay = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +231,12 @@ module.exports = class FontPlayer {
|
||||
|
||||
_handlePlayLine(isPlayed) {
|
||||
this.isPlay = false
|
||||
this.fonts[0].dom.style.backgroundSize = isPlayed ? '100% 100%' : '100% 0'
|
||||
if (isPlayed) {
|
||||
this.lineContent.classList.add('played')
|
||||
} else {
|
||||
this.lineContent.classList.remove('played')
|
||||
}
|
||||
// this.fonts[0].dom.style.backgroundSize = isPlayed ? '100% 100%' : '100% 0'
|
||||
}
|
||||
|
||||
_handlePauseFont(font) {
|
||||
@@ -246,6 +297,7 @@ module.exports = class FontPlayer {
|
||||
this.pause()
|
||||
|
||||
if (this.isLineMode) return this._handlePlayLine(true)
|
||||
this.lineContent.classList.remove('played')
|
||||
this.isPlay = true
|
||||
this._performanceTime = getNow()
|
||||
this._startTime = curTime
|
||||
@@ -281,6 +333,7 @@ module.exports = class FontPlayer {
|
||||
finish() {
|
||||
this.pause()
|
||||
if (this.isLineMode) return this._handlePlayLine(true)
|
||||
this.lineContent.classList.add('played')
|
||||
|
||||
for (const font of this.fonts) {
|
||||
font.animation.cancel()
|
||||
@@ -292,6 +345,7 @@ module.exports = class FontPlayer {
|
||||
reset() {
|
||||
this.pause()
|
||||
if (this.isLineMode) return this._handlePlayLine(false)
|
||||
this.lineContent.classList.remove('played')
|
||||
for (const font of this.fonts) {
|
||||
font.animation.cancel()
|
||||
font.dom.style.backgroundSize = '0 100%'
|
||||
@@ -8,33 +8,46 @@ module.exports = class Lyric {
|
||||
lyric = '',
|
||||
extendedLyrics = [],
|
||||
offset = 0,
|
||||
lineClassName = '',
|
||||
fontClassName = 'font',
|
||||
lineContentClassName = 'line-content',
|
||||
lineClassName = 'line',
|
||||
shadowClassName = 'shadow',
|
||||
fontModeClassName = 'font-mode',
|
||||
lineModeClassName = 'line-mode',
|
||||
fontLrcClassName = 'font-lrc',
|
||||
extendedLrcClassName = 'extended',
|
||||
activeLineClassName = 'active',
|
||||
lineModeClassName = 'line',
|
||||
shadowClassName = '',
|
||||
shadowContent = false,
|
||||
onPlay = function() { },
|
||||
onSetLyric = function() { },
|
||||
isVertical = false,
|
||||
onPlay = function(line, text) { },
|
||||
onSetLyric = function(lines, offset) { },
|
||||
onUpdateLyric = function(lines) { },
|
||||
}) {
|
||||
this.lyric = lyric
|
||||
this.extendedLyrics = extendedLyrics
|
||||
this.offset = offset
|
||||
this.onPlay = onPlay
|
||||
this.onSetLyric = onSetLyric
|
||||
this.onUpdateLyric = onUpdateLyric
|
||||
|
||||
this.lineContentClassName = lineContentClassName
|
||||
this.lineClassName = lineClassName
|
||||
this.fontClassName = fontClassName
|
||||
this.shadowClassName = shadowClassName
|
||||
this.fontModeClassName = fontModeClassName
|
||||
this.lineModeClassName = lineModeClassName
|
||||
this.fontLrcClassName = fontLrcClassName
|
||||
this.extendedLrcClassName = extendedLrcClassName
|
||||
this.activeLineClassName = activeLineClassName
|
||||
this.lineModeClassName = lineModeClassName
|
||||
this.shadowClassName = shadowClassName
|
||||
this.shadowContent = shadowContent
|
||||
|
||||
this.isVertical = isVertical
|
||||
this.playingLineNum = -1
|
||||
this.isLineMode = false
|
||||
|
||||
this.initInfo = {
|
||||
lines: [],
|
||||
offset: 0,
|
||||
}
|
||||
|
||||
this.linePlayer = new LinePlayer({
|
||||
offset: this.offset,
|
||||
onPlay: this._handleLinePlayerOnPlay,
|
||||
@@ -93,7 +106,7 @@ module.exports = class Lyric {
|
||||
this.onPlay(num, this._lines[num].text)
|
||||
}
|
||||
|
||||
_handleLinePlayerOnSetLyric = (lyricLines, offset) => {
|
||||
_initLines = (lyricLines, offset, isUpdate) => {
|
||||
// console.log(lyricLines)
|
||||
// this._lines = lyricsLines
|
||||
this.isLineMode = lyricLines.length && !/^<\d+,\d+>/.test(lyricLines[0].text)
|
||||
@@ -105,12 +118,15 @@ module.exports = class Lyric {
|
||||
time: line.time,
|
||||
lyric: line.text,
|
||||
extendedLyrics: line.extendedLyrics,
|
||||
lineContentClassName: this.lineContentClassName,
|
||||
lineClassName: this.lineClassName,
|
||||
fontClassName: this.fontClassName,
|
||||
extendedLrcClassName: this.extendedLrcClassName,
|
||||
lineModeClassName: this.lineModeClassName,
|
||||
shadowClassName: this.shadowClassName,
|
||||
fontModeClassName: this.fontModeClassName,
|
||||
lineModeClassName: this.lineModeClassName,
|
||||
fontLrcClassName: this.fontLrcClassName,
|
||||
extendedLrcClassName: this.extendedLrcClassName,
|
||||
shadowContent: this.shadowContent,
|
||||
isVertical: this.isVertical,
|
||||
})
|
||||
|
||||
this._lineFonts.push(fontPlayer)
|
||||
@@ -127,11 +143,15 @@ module.exports = class Lyric {
|
||||
time: line.time,
|
||||
lyric: line.text,
|
||||
extendedLyrics: line.extendedLyrics,
|
||||
lineContentClassName: this.lineContentClassName,
|
||||
lineClassName: this.lineClassName,
|
||||
fontClassName: this.fontClassName,
|
||||
extendedLrcClassName: this.extendedLrcClassName,
|
||||
shadowClassName: this.shadowClassName,
|
||||
fontModeClassName: this.fontModeClassName,
|
||||
lineModeClassName: this.lineModeClassName,
|
||||
fontLrcClassName: this.fontLrcClassName,
|
||||
extendedLrcClassName: this.extendedLrcClassName,
|
||||
shadowContent: this.shadowContent,
|
||||
isVertical: this.isVertical,
|
||||
})
|
||||
|
||||
this._lineFonts.push(fontPlayer)
|
||||
@@ -148,7 +168,15 @@ module.exports = class Lyric {
|
||||
let newOffset = this.isLineMode ? this.offset + 60 : this.offset
|
||||
offset = offset - this.linePlayer.offset + newOffset
|
||||
this.linePlayer.offset = newOffset
|
||||
this.onSetLyric(this._lines, offset)
|
||||
if (isUpdate) this.onUpdateLyric(this._lines)
|
||||
else this.onSetLyric(this._lines, offset)
|
||||
}
|
||||
|
||||
_handleLinePlayerOnSetLyric = (lyricLines, offset) => {
|
||||
this._initLines(lyricLines, offset, false)
|
||||
this.playingLineNum = 0
|
||||
this.initInfo.lines = lyricLines
|
||||
this.initInfo.offset = offset
|
||||
}
|
||||
|
||||
play(curTime) {
|
||||
@@ -159,7 +187,11 @@ module.exports = class Lyric {
|
||||
pause() {
|
||||
if (!this.linePlayer) return
|
||||
this.linePlayer.pause()
|
||||
if (this.playingLineNum > -1) this._lineFonts[this.playingLineNum].pause()
|
||||
if (this.playingLineNum > -1) this._lineFonts[this.playingLineNum]?.pause()
|
||||
}
|
||||
|
||||
setOffset(offset) {
|
||||
this.linePlayer.offset = offset
|
||||
}
|
||||
|
||||
setLyric(lyric, extendedLyrics) {
|
||||
@@ -167,4 +199,14 @@ module.exports = class Lyric {
|
||||
this.extendedLyrics = extendedLyrics
|
||||
this._init()
|
||||
}
|
||||
|
||||
setVertical(isVertical) {
|
||||
this.isVertical = isVertical
|
||||
this._initLines(this.initInfo.lines, this.initInfo.offset, true)
|
||||
if (this.linePlayer.isPlay) {
|
||||
const num = this.playingLineNum
|
||||
this.playingLineNum = 0
|
||||
this._handleLinePlayerOnPlay(num, '', this.linePlayer._currentTime())
|
||||
} else this.playingLineNum = 0
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
const { getNow, TimeoutTools } = require('./utils')
|
||||
|
||||
const timeExp = /^\[([\d:.]*)\]{1}/g
|
||||
const timeFieldExp = /^(?:\[[\d:.]+\])+/g
|
||||
const timeExp = /[\d:.]+/g
|
||||
const tagRegMap = {
|
||||
title: 'ti',
|
||||
artist: 'ar',
|
||||
@@ -15,13 +16,18 @@ const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
|
||||
const extendedLines = extendedLyric.split(/\r\n|\n|\r/)
|
||||
for (let i = 0; i < extendedLines.length; i++) {
|
||||
const line = extendedLines[i].trim()
|
||||
let result = timeExp.exec(line)
|
||||
let result = timeFieldExp.exec(line)
|
||||
if (result) {
|
||||
const text = line.replace(timeExp, '').trim()
|
||||
const timeField = result[0]
|
||||
const text = line.replace(timeFieldExp, '').trim()
|
||||
if (text) {
|
||||
const timeStr = RegExp.$1.replace(/(\.\d\d)0$/, '$1')
|
||||
const targetLine = lrcLinesMap[timeStr]
|
||||
if (targetLine) targetLine.extendedLyrics.push(text)
|
||||
const times = timeField.match(timeExp)
|
||||
if (times == null) continue
|
||||
for (const time of times) {
|
||||
const timeStr = time.replace(/(\.\d\d)0$/, '$1')
|
||||
const targetLine = lrcLinesMap[timeStr]
|
||||
if (targetLine) targetLine.extendedLyrics.push(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,21 +75,30 @@ module.exports = class LinePlayer {
|
||||
const linesMap = {}
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim()
|
||||
let result = timeExp.exec(line)
|
||||
let result = timeFieldExp.exec(line)
|
||||
if (result) {
|
||||
const text = line.replace(timeExp, '').trim()
|
||||
const timeField = result[0]
|
||||
const text = line.replace(timeFieldExp, '').trim()
|
||||
if (text) {
|
||||
const timeStr = RegExp.$1.replace(/(\.\d\d)0$/, '$1')
|
||||
const timeArr = timeStr.split(':')
|
||||
if (timeArr.length < 3) timeArr.unshift(0)
|
||||
if (timeArr[2].indexOf('.') > -1) {
|
||||
timeArr.push(...timeArr[2].split('.'))
|
||||
timeArr.splice(2, 1)
|
||||
}
|
||||
linesMap[timeStr] = {
|
||||
time: parseInt(timeArr[0]) * 60 * 60 * 1000 + parseInt(timeArr[1]) * 60 * 1000 + parseInt(timeArr[2]) * 1000 + parseInt(timeArr[3] || 0),
|
||||
text,
|
||||
extendedLyrics: [],
|
||||
const times = timeField.match(timeExp)
|
||||
if (times == null) continue
|
||||
for (const time of times) {
|
||||
const timeStr = time.replace(/(\.\d\d)0$/, '$1')
|
||||
if (linesMap[timeStr]) {
|
||||
linesMap[timeStr].extendedLyrics.push(text)
|
||||
continue
|
||||
}
|
||||
const timeArr = timeStr.split(':')
|
||||
if (timeArr.length < 3) timeArr.unshift(0)
|
||||
if (timeArr[2].indexOf('.') > -1) {
|
||||
timeArr.push(...timeArr[2].split('.'))
|
||||
timeArr.splice(2, 1)
|
||||
}
|
||||
linesMap[timeStr] = {
|
||||
time: parseInt(timeArr[0]) * 60 * 60 * 1000 + parseInt(timeArr[1]) * 60 * 1000 + parseInt(timeArr[2]) * 1000 + parseInt(timeArr[3] || 0),
|
||||
text,
|
||||
extendedLyrics: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ exports.TimeoutTools = class TimeoutTools {
|
||||
// console.log('diff', diff)
|
||||
if (diff > 0) {
|
||||
if (diff < this.thresholdTime) return this.run()
|
||||
return this.timeoutId = setTimeout(() => {
|
||||
return this.timeoutId = window.setTimeout(() => {
|
||||
this.timeoutId = null
|
||||
this.run()
|
||||
}, diff - this.thresholdTime)
|
||||
@@ -0,0 +1,139 @@
|
||||
import { compareVer } from './index'
|
||||
|
||||
const oldThemeMap = {
|
||||
0: 'green',
|
||||
1: 'blue',
|
||||
2: 'yellow',
|
||||
3: 'orange',
|
||||
4: 'red',
|
||||
10: 'pink',
|
||||
5: 'purple',
|
||||
6: 'grey',
|
||||
11: 'ming',
|
||||
12: 'blue2',
|
||||
13: 'black',
|
||||
7: 'mid_autumn',
|
||||
8: 'naruto',
|
||||
9: 'happy_new_year',
|
||||
} as const
|
||||
|
||||
export default (setting: any): Partial<LX.AppSetting> => {
|
||||
setting = { ...setting }
|
||||
|
||||
// 迁移 v2 之前的配置
|
||||
if (compareVer(setting.version, '2.0.0') < 0) {
|
||||
// 迁移列表滚动位置设置 ~0.18.3
|
||||
if (setting.list.scroll) {
|
||||
let scroll = setting.list.scroll
|
||||
if (setting.list?.isSaveScrollLocation) setting.list.isSaveScrollLocation = scroll.enable
|
||||
delete setting.list.scroll
|
||||
}
|
||||
|
||||
// 修正拼写问题 v1.8.2 及以前
|
||||
if (setting.player.isShowLyricTransition != null) {
|
||||
setting.player.isShowLyricTranslation = setting.player.isShowLyricTransition
|
||||
delete setting.player.isShowLyricTransition
|
||||
}
|
||||
|
||||
// 迁移v1.19.0之前的主题设置
|
||||
if (setting.themeId != null) {
|
||||
setting.theme = {
|
||||
id: setting.themeId,
|
||||
}
|
||||
delete setting.themeId
|
||||
}
|
||||
|
||||
setting.tray.enable = setting.tray.isShow
|
||||
|
||||
setting['common.windowSizeId'] = setting.windowSizeId
|
||||
setting['common.startInFullscreen'] = setting.startInFullscreen
|
||||
setting['common.langId'] = setting.langId
|
||||
setting['common.apiSource'] = setting.apiSource
|
||||
setting['common.sourceNameType'] = setting.sourceNameType
|
||||
setting['common.font'] = setting.font
|
||||
setting['common.isShowAnimation'] = setting.isShowAnimation
|
||||
setting['common.randomAnimate'] = setting.randomAnimate
|
||||
setting['common.isAgreePact'] = setting.isAgreePact
|
||||
setting['common.controlBtnPosition'] = setting.controlBtnPosition
|
||||
|
||||
setting['player.togglePlayMethod'] = setting.player.togglePlayMethod
|
||||
setting['player.highQuality'] = setting.player.highQuality
|
||||
setting['player.isShowTaskProgess'] = setting.player.isShowTaskProgess
|
||||
setting['player.volume'] = setting.player.volume
|
||||
setting['player.isMute'] = setting.player.isMute
|
||||
setting['player.mediaDeviceId'] = setting.player.mediaDeviceId
|
||||
setting['player.isMediaDeviceRemovedStopPlay'] = setting.player.isMediaDeviceRemovedStopPlay
|
||||
setting['player.isShowLyricTranslation'] = setting.player.isShowLyricTranslation
|
||||
setting['player.isShowLyricRoma'] = setting.player.isShowLyricRoma
|
||||
setting['player.isS2t'] = setting.player.isS2t
|
||||
setting['player.isPlayLxlrc'] = setting.player.isPlayLxlrc
|
||||
setting['player.isSavePlayTime'] = setting.player.isSavePlayTime
|
||||
setting['player.audioVisualization'] = setting.player.audioVisualization
|
||||
setting['player.waitPlayEndStop'] = setting.player.waitPlayEndStop
|
||||
setting['player.waitPlayEndStopTime'] = setting.player.waitPlayEndStopTime
|
||||
setting['player.autoSkipOnError'] = setting.player.autoSkipOnError
|
||||
|
||||
setting['playDetail.isZoomActiveLrc'] = setting.playDetail.isZoomActiveLrc
|
||||
setting['playDetail.isShowLyricProgressSetting'] = setting.playDetail.isShowLyricProgressSetting
|
||||
setting['playDetail.style.fontSize'] = setting.playDetail.style.fontSize
|
||||
setting['playDetail.style.align'] = setting.playDetail.style.align
|
||||
|
||||
setting['desktopLyric.enable'] = setting.desktopLyric.enable
|
||||
setting['desktopLyric.isLock'] = setting.desktopLyric.isLock
|
||||
setting['desktopLyric.isAlwaysOnTop'] = setting.desktopLyric.isAlwaysOnTop
|
||||
setting['desktopLyric.isAlwaysOnTopLoop'] = setting.desktopLyric.isAlwaysOnTopLoop
|
||||
setting['desktopLyric.width'] = setting.desktopLyric.width
|
||||
setting['desktopLyric.height'] = setting.desktopLyric.height
|
||||
setting['desktopLyric.x'] = setting.desktopLyric.x
|
||||
setting['desktopLyric.y'] = setting.desktopLyric.y
|
||||
setting['desktopLyric.isLockScreen'] = setting.desktopLyric.isLockScreen
|
||||
setting['desktopLyric.isDelayScroll'] = setting.desktopLyric.isDelayScroll
|
||||
setting['desktopLyric.isHoverHide'] = setting.desktopLyric.isHoverHide
|
||||
setting['desktopLyric.style.font'] = setting.desktopLyric.style.font
|
||||
setting['desktopLyric.style.fontSize'] = setting.desktopLyric.style.fontSize / 100 * 16
|
||||
setting['desktopLyric.style.opacity'] = setting.desktopLyric.style.opacity
|
||||
setting['desktopLyric.style.isZoomActiveLrc'] = setting.desktopLyric.style.isZoomActiveLrc
|
||||
|
||||
setting['list.isClickPlayList'] = setting.list.isClickPlayList
|
||||
setting['list.isShowAlbumName'] = setting.list.isShowAlbumName
|
||||
setting['list.isShowSource'] = setting.list.isShowSource
|
||||
setting['list.isSaveScrollLocation'] = setting.list.isSaveScrollLocation
|
||||
setting['list.addMusicLocationType'] = setting.list.addMusicLocationType
|
||||
|
||||
setting['download.enable'] = setting.download.enable
|
||||
setting['download.savePath'] = setting.download.savePath
|
||||
setting['download.fileName'] = setting.download.fileName
|
||||
setting['download.maxDownloadNum'] = setting.download.maxDownloadNum
|
||||
setting['download.isDownloadLrc'] = setting.download.isDownloadLrc
|
||||
setting['download.lrcFormat'] = setting.download.lrcFormat
|
||||
setting['download.isEmbedPic'] = setting.download.isEmbedPic
|
||||
setting['download.isEmbedLyric'] = setting.download.isEmbedLyric
|
||||
setting['download.isUseOtherSource'] = setting.download.isUseOtherSource
|
||||
|
||||
setting['search.isShowHotSearch'] = setting.search.isShowHotSearch
|
||||
setting['search.isShowHistorySearch'] = setting.search.isShowHistorySearch
|
||||
setting['search.isFocusSearchBox'] = setting.search.isFocusSearchBox
|
||||
|
||||
setting['network.proxy.enable'] = setting.network.proxy.enable
|
||||
setting['network.proxy.host'] = setting.network.proxy.host
|
||||
setting['network.proxy.port'] = setting.network.proxy.port
|
||||
setting['network.proxy.username'] = setting.network.proxy.username
|
||||
setting['network.proxy.password'] = setting.network.proxy.password
|
||||
|
||||
setting['tray.enable'] = setting.tray.enable
|
||||
setting['tray.themeId'] = setting.tray.themeId
|
||||
|
||||
|
||||
setting['sync.enable'] = setting.sync.enable
|
||||
setting['sync.port'] = setting.sync.port
|
||||
|
||||
setting['theme.id'] = oldThemeMap[setting.theme.id as keyof typeof oldThemeMap]
|
||||
setting['theme.lightId'] = oldThemeMap[setting.theme.lightId as keyof typeof oldThemeMap]
|
||||
setting['theme.darkId'] = oldThemeMap[setting.theme.darkId as keyof typeof oldThemeMap]
|
||||
|
||||
setting['odc.isAutoClearSearchInput'] = setting.odc.isAutoClearSearchInput
|
||||
setting['odc.isAutoClearSearchList'] = setting.odc.isAutoClearSearchList
|
||||
}
|
||||
|
||||
return setting
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
const http = require('http')
|
||||
const https = require('https')
|
||||
const fs = require('fs')
|
||||
|
||||
const sendRequest = (url) => {
|
||||
const urlParse = new URL(url)
|
||||
const httpOptions = {
|
||||
method: 'get',
|
||||
host: urlParse.hostname,
|
||||
port: urlParse.port,
|
||||
path: urlParse.pathname + urlParse.search,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
|
||||
},
|
||||
}
|
||||
|
||||
// console.log(httpOptions)
|
||||
return url.protocol === 'https:'
|
||||
? https.request(httpOptions)
|
||||
: http.request(httpOptions)
|
||||
}
|
||||
|
||||
module.exports = (url, filePath) => {
|
||||
return new Promise((resolve) => {
|
||||
sendRequest(url)
|
||||
.on('response', response => {
|
||||
// console.log(response.statusCode)
|
||||
if (response.statusCode !== 200 && response.statusCode != 206) {
|
||||
response.destroy(new Error('failed'))
|
||||
return
|
||||
}
|
||||
response
|
||||
.pipe(fs.createWriteStream(filePath))
|
||||
.on('finish', () => {
|
||||
// console.log('finish')
|
||||
if (response.complete) {
|
||||
// console.log('complete')
|
||||
// meta.APIC = picPath
|
||||
// handleWriteMeta(meta, filePath)
|
||||
resolve(true)
|
||||
} else {
|
||||
resolve(false)
|
||||
fs.unlink(filePath, err => {
|
||||
if (err) console.log(err.message)
|
||||
})
|
||||
}
|
||||
}).on('error', err => {
|
||||
// console.log('response error')
|
||||
if (err) console.log(err.message)
|
||||
fs.unlink(filePath, err => {
|
||||
if (err) console.log(err.message)
|
||||
})
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
.on('error', err => {
|
||||
if (err) console.log(err.message)
|
||||
// delete meta.APIC
|
||||
// handleWriteMeta(meta, filePath)
|
||||
resolve(false)
|
||||
})
|
||||
.end()
|
||||
})
|
||||
}
|
||||
|
||||
// const url = 'https://y.gtimg.cn/music/photo_new/T002R500x500M000000nfgwP0D6qxd.jpg'
|
||||
// // const url = 'http://p4.music.126.net/-U2K8GKlASCSXK0cRre1gA==/109951163188718762.jpg'
|
||||
// const picPath = require('path').join(__dirname, 'test.jpg')
|
||||
// module.exports(url, picPath).then((sucee) => {
|
||||
// console.log(sucee)
|
||||
// })
|
||||
@@ -2,9 +2,9 @@ const fs = require('fs')
|
||||
const fsPromises = fs.promises
|
||||
const path = require('path')
|
||||
const getImgSize = require('image-size')
|
||||
const request = require('request')
|
||||
const download = require('./downloader')
|
||||
|
||||
const FlacProcessor = require('./flac-metadata')
|
||||
const FlacProcessor = require('./flac-metadata/index')
|
||||
|
||||
const extReg = /^(\.(?:jpe?g|png)).*$/
|
||||
const vendor = 'reference libFLAC 1.2.1 20070917'
|
||||
@@ -67,29 +67,14 @@ module.exports = (filePath, meta) => {
|
||||
let ext = path.extname(picUrl)
|
||||
let picPath = filePath.replace(/\.flac$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg')
|
||||
|
||||
request(picUrl)
|
||||
.on('response', respones => {
|
||||
if (respones.statusCode !== 200 && respones.statusCode != 206) return writeMeta(filePath, meta)
|
||||
respones
|
||||
.pipe(fs.createWriteStream(picPath))
|
||||
.on('finish', async() => {
|
||||
if (respones.complete) {
|
||||
await writeMeta(filePath, meta, picPath)
|
||||
} else {
|
||||
writeMeta(filePath, meta)
|
||||
}
|
||||
fs.unlink(picPath, err => {
|
||||
if (err) console.log(err.message)
|
||||
})
|
||||
})
|
||||
.on('error', err => {
|
||||
download(picUrl, picPath).then(success => {
|
||||
if (success) {
|
||||
writeMeta(filePath, meta, picPath).finally(() => {
|
||||
fs.unlink(picPath, err => {
|
||||
if (err) console.log(err.message)
|
||||
writeMeta(filePath, meta)
|
||||
})
|
||||
})
|
||||
.on('error', err => {
|
||||
if (err) console.log(err.message)
|
||||
writeMeta(filePath, meta)
|
||||
})
|
||||
})
|
||||
} else writeMeta(filePath, meta)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface MusicMeta {
|
||||
title: string
|
||||
artist: string | null
|
||||
album: string | null
|
||||
APIC: string | null
|
||||
lyrics: string | null
|
||||
}
|
||||
export function setMeta(filePath: string, meta: MusicMeta): void
|
||||
@@ -0,0 +1,38 @@
|
||||
const NodeID3 = require('node-id3')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const download = require('./downloader')
|
||||
const extReg = /^(\.(?:jpe?g|png)).*$/
|
||||
|
||||
const handleWriteMeta = (meta, filePath) => {
|
||||
if (meta.lyrics) {
|
||||
meta.unsynchronisedLyrics = {
|
||||
language: 'zho',
|
||||
text: meta.lyrics,
|
||||
}
|
||||
delete meta.lyrics
|
||||
}
|
||||
NodeID3.write(meta, filePath)
|
||||
}
|
||||
|
||||
module.exports = (filePath, meta) => {
|
||||
if (!meta.APIC) return handleWriteMeta(meta, filePath)
|
||||
if (!/^http/.test(meta.APIC)) {
|
||||
delete meta.APIC
|
||||
return handleWriteMeta(meta, filePath)
|
||||
}
|
||||
let ext = path.extname(meta.APIC)
|
||||
let picPath = filePath.replace(/\.mp3$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg')
|
||||
download(meta.APIC, picPath).then(success => {
|
||||
if (success) {
|
||||
meta.APIC = picPath
|
||||
handleWriteMeta(meta, filePath)
|
||||
fs.unlink(picPath, err => {
|
||||
if (err) console.log(err.message)
|
||||
})
|
||||
} else {
|
||||
delete meta.APIC
|
||||
handleWriteMeta(meta, filePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import fs from 'fs'
|
||||
import crypto from 'crypto'
|
||||
import { gzip, gunzip } from 'zlib'
|
||||
import { log } from '@common/utils'
|
||||
import path from 'path'
|
||||
|
||||
export const joinPath = (...paths: string[]): string => path.join(...paths)
|
||||
|
||||
export const extname = (p: string): string => path.extname(p)
|
||||
export const basename = (p: string, ext?: string): string => path.basename(p, ext)
|
||||
export const dirname = (p: string): string => path.dirname(p)
|
||||
|
||||
/**
|
||||
* 检查路径是否存在
|
||||
* @param {*} path 路径
|
||||
*/
|
||||
export const checkPath = async(path: string): Promise<boolean> => {
|
||||
return await new Promise(resolve => {
|
||||
fs.access(path, fs.constants.F_OK, err => {
|
||||
if (err) return resolve(false)
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const getFileStats = async(path: string): Promise<fs.Stats | null> => {
|
||||
return await new Promise(resolve => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
if (err) return resolve(null)
|
||||
resolve(stats)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路径并创建目录
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
export const createDir = async(path: string): Promise<void> => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
fs.access(path, fs.constants.F_OK | fs.constants.W_OK, err => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
fs.mkdir(path, { recursive: true }, err => {
|
||||
if (err) return reject(err)
|
||||
resolve()
|
||||
})
|
||||
return
|
||||
}
|
||||
return reject(err)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const removeFile = async(path: string) => new Promise<void>((resolve, reject) => {
|
||||
fs.access(path, fs.constants.F_OK, err => {
|
||||
if (err) return err.code == 'ENOENT' ? resolve() : reject(err)
|
||||
fs.unlink(path, err => {
|
||||
if (err) return reject(err)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
export const readFile = async(path: string) => fs.promises.readFile(path)
|
||||
|
||||
|
||||
/**
|
||||
* 创建 MD5 hash
|
||||
* @param {*} str
|
||||
*/
|
||||
export const toMD5 = (str: string) => crypto.createHash('md5').update(str).digest('hex')
|
||||
|
||||
export const gzipData = async(str: string): Promise<Buffer> => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
gzip(str, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const gunzipData = async(buf: Buffer): Promise<string> => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
gunzip(buf, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
resolve(result.toString())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存lx配置文件
|
||||
* @param path 保存路径
|
||||
* @param data 数据
|
||||
*/
|
||||
export const saveLxConfigFile = async(path: string, data: any) => {
|
||||
if (!path.endsWith('.lxmc')) path += '.lxmc'
|
||||
fs.writeFile(path, await gzipData(JSON.stringify(data)), 'binary', err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取lx配置文件
|
||||
* @param path 文件路径
|
||||
* @returns 数据
|
||||
*/
|
||||
export const readLxConfigFile = async(path: string): Promise<any> => {
|
||||
let isJSON = path.endsWith('.json')
|
||||
let data: string | Buffer = await fs.promises.readFile(path, isJSON ? 'utf8' : 'binary')
|
||||
if (!data || isJSON) return data
|
||||
data = await gunzipData(Buffer.from(data, 'binary'))
|
||||
data = JSON.parse(data)
|
||||
|
||||
// 修复v1.14.0出现的导出数据被序列化两次的问题
|
||||
if (typeof data != 'object') {
|
||||
try {
|
||||
data = JSON.parse(data)
|
||||
} catch (err) {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export const saveStrToFile = async(path: string, str: string | Buffer): Promise<void> => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(path, str, err => {
|
||||
if (err) {
|
||||
log.error(err)
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const b64DecodeUnicode = (str: string): string => {
|
||||
// Going backwards: from bytestream, to percent-encoding, to original string.
|
||||
// return decodeURIComponent(window.atob(str).split('').map(function(c) {
|
||||
// return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
||||
// }).join(''))
|
||||
|
||||
return Buffer.from(str, 'base64').toString()
|
||||
}
|
||||
|
||||
export const copyFile = async(sourcePath: string, distPath: string) => {
|
||||
return fs.promises.copyFile(sourcePath, distPath)
|
||||
}
|
||||
|
||||
export const moveFile = async(sourcePath: string, distPath: string) => {
|
||||
return fs.promises.rename(sourcePath, distPath)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
const fs = require('fs').promises
|
||||
const path = require('path')
|
||||
|
||||
const sourceFilePath = path.join(__dirname, './kMandarin_8105.txt')
|
||||
const distFilePath = path.join(__dirname, './pinyin.txt')
|
||||
|
||||
const yuanyin = [
|
||||
['ā', 'a'],
|
||||
['á', 'a'],
|
||||
['ǎ', 'a'],
|
||||
['à', 'a'],
|
||||
['ē', 'e'],
|
||||
['é', 'e'],
|
||||
['ě', 'e'],
|
||||
['è', 'e'],
|
||||
['ī', 'i'],
|
||||
['í', 'i'],
|
||||
['ǐ', 'i'],
|
||||
['ì', 'i'],
|
||||
['ō', 'o'],
|
||||
['ó', 'o'],
|
||||
['ǒ', 'o'],
|
||||
['ò', 'o'],
|
||||
['ū', 'u'],
|
||||
['ú', 'u'],
|
||||
['ǔ', 'u'],
|
||||
['ù', 'u'],
|
||||
['ǖ', 'v'],
|
||||
['ǘ', 'v'],
|
||||
['ǚ', 'v'],
|
||||
['ǜ', 'v'],
|
||||
]
|
||||
|
||||
const parse = async() => {
|
||||
let datas = (await fs.readFile(sourceFilePath)).toString()
|
||||
datas = datas.replace(/ +=> +(\w|\+)+ */gm, ' ')
|
||||
for (const [y1, y2] of yuanyin) datas = datas.replaceAll(y1, y2)
|
||||
// console.log(datas)
|
||||
const lines = datas.split('\n')
|
||||
const dict = {}
|
||||
for (let line of lines) {
|
||||
if (!line || line.startsWith('#')) continue
|
||||
line = line.trim().replace(/^[\w+]+: */, '')
|
||||
let [p1, comment] = line.split('#')
|
||||
let [z, ps] = comment.split(/(?: *\? *-> *| *-> *)/)
|
||||
const ys = new Set([p1.trim()])
|
||||
if (ps != null) ps.split(/(?: +| *, *)/).forEach(y => ys.add(y.trim()))
|
||||
dict[z.trim()] = Array.from(ys)
|
||||
}
|
||||
|
||||
fs.writeFile(distFilePath, JSON.stringify(dict))
|
||||
}
|
||||
|
||||
|
||||
parse()
|
||||
|
||||
// let dict = {}
|
||||
// let line = 'U+2CBBF: qi # 𬮿 ?-> gai,ai'
|
||||
|
||||
// line = line.trim().replace(/^[\w+]+: */, '')
|
||||
// let [p1, comment] = line.split('#')
|
||||
// let [z, ps] = comment.split(/ *\? *-> */)
|
||||
// const ys = dict[z.trim()] = [p1.trim()]
|
||||
// console.log(ps)
|
||||
// if (ps != null) ys.push(...ps.split(/(?: +| *, *)/).map(y => y.trim()))
|
||||
// console.log(dict)
|
||||
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
|
||||
const easeInOutQuad = (t: number, b: number, c: number, d: number): number => {
|
||||
t /= d / 2
|
||||
if (t < 1) return (c / 2) * t * t + b
|
||||
t--
|
||||
return (-c / 2) * (t * (t - 2) - 1) + b
|
||||
}
|
||||
|
||||
type Noop = () => void
|
||||
const noop: Noop = () => {}
|
||||
|
||||
const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = noop): Noop => {
|
||||
if (!element) {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
// @ts-expect-error
|
||||
const start = element.scrollTop ?? element.scrollY ?? 0
|
||||
let cancel = false
|
||||
if (to > start) {
|
||||
let maxScrollTop = element.scrollHeight - element.clientHeight
|
||||
if (to > maxScrollTop) to = maxScrollTop
|
||||
} else if (to < start) {
|
||||
if (to < 0) to = 0
|
||||
} else {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
const change = to - start
|
||||
const increment = 10
|
||||
if (!change) {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
|
||||
let currentTime = 0
|
||||
let val
|
||||
|
||||
const animateScroll = () => {
|
||||
currentTime += increment
|
||||
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
|
||||
if (element.scrollTo) {
|
||||
element.scrollTo(0, val)
|
||||
} else {
|
||||
element.scrollTop = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
if (cancel) return fn()
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
animateScroll()
|
||||
return () => {
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置滚动条位置
|
||||
* @param {*} element 要设置滚动的容器 dom
|
||||
* @param {*} to 滚动的目标位置
|
||||
* @param {*} duration 滚动完成时间 ms
|
||||
* @param {*} fn 滚动完成后的回调
|
||||
* @param {*} delay 延迟执行时间
|
||||
*/
|
||||
export const scrollTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
|
||||
let cancelFn: () => void
|
||||
let timeout: number | null
|
||||
if (delay) {
|
||||
let scrollCancelFn: Noop
|
||||
cancelFn = () => {
|
||||
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
|
||||
}
|
||||
timeout = window.setTimeout(() => {
|
||||
timeout = null
|
||||
scrollCancelFn = handleScrollY(element, to, duration, fn)
|
||||
}, delay)
|
||||
} else {
|
||||
cancelFn = handleScrollY(element, to, duration, fn) ?? noop
|
||||
}
|
||||
return cancelFn
|
||||
}
|
||||
const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = () => {}): () => void => {
|
||||
if (!element) {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
// @ts-expect-error
|
||||
const start = element.scrollLeft || element.scrollX || 0
|
||||
let cancel = false
|
||||
if (to > start) {
|
||||
let maxScrollLeft = element.scrollWidth - element.clientWidth
|
||||
if (to > maxScrollLeft) to = maxScrollLeft
|
||||
} else if (to < start) {
|
||||
if (to < 0) to = 0
|
||||
} else {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
const change = to - start
|
||||
const increment = 10
|
||||
if (!change) {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
|
||||
let currentTime = 0
|
||||
let val
|
||||
|
||||
const animateScroll = () => {
|
||||
currentTime += increment
|
||||
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
|
||||
if (element.scrollTo) {
|
||||
element.scrollTo(val, 0)
|
||||
} else {
|
||||
element.scrollLeft = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
if (cancel) return fn()
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
animateScroll()
|
||||
return () => {
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置滚动条位置
|
||||
* @param {*} element 要设置滚动的容器 dom
|
||||
* @param {*} to 滚动的目标位置
|
||||
* @param {*} duration 滚动完成时间 ms
|
||||
* @param {*} fn 滚动完成后的回调
|
||||
* @param {*} delay 延迟执行时间
|
||||
*/
|
||||
export const scrollXTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
|
||||
let cancelFn: Noop
|
||||
let timeout: number | null
|
||||
if (delay) {
|
||||
let scrollCancelFn: Noop
|
||||
cancelFn = () => {
|
||||
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
|
||||
}
|
||||
timeout = window.setTimeout(() => {
|
||||
timeout = null
|
||||
scrollCancelFn = handleScrollX(element, to, duration, fn)
|
||||
}, delay)
|
||||
} else {
|
||||
cancelFn = handleScrollX(element, to, duration, fn)
|
||||
}
|
||||
return cancelFn
|
||||
}
|
||||
|
||||
const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = () => {}): () => void => {
|
||||
if (!element) {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
// @ts-expect-error
|
||||
const start = element.scrollLeft || element.scrollX as number || 0
|
||||
let cancel = false
|
||||
if (to < start) {
|
||||
let maxScrollLeft = -element.scrollWidth + element.clientWidth
|
||||
if (to < maxScrollLeft) to = maxScrollLeft
|
||||
} else if (to > start) {
|
||||
if (to > 0) to = 0
|
||||
} else {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
|
||||
const change = to - start
|
||||
const increment = 10
|
||||
if (!change) {
|
||||
fn()
|
||||
return noop
|
||||
}
|
||||
|
||||
let currentTime = 0
|
||||
let val
|
||||
|
||||
const animateScroll = () => {
|
||||
currentTime += increment
|
||||
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
|
||||
|
||||
if (element.scrollTo) {
|
||||
element.scrollTo(val, 0)
|
||||
} else {
|
||||
element.scrollLeft = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
if (cancel) return fn()
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
animateScroll()
|
||||
return () => {
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置滚动条位置 (writing-mode: vertical-rl 专用)
|
||||
* @param element 要设置滚动的容器 dom
|
||||
* @param to 滚动的目标位置
|
||||
* @param duration 滚动完成时间 ms
|
||||
* @param fn 滚动完成后的回调
|
||||
* @param delay 延迟执行时间
|
||||
*/
|
||||
export const scrollXRTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
|
||||
let cancelFn: Noop
|
||||
let timeout: number | null
|
||||
if (delay) {
|
||||
let scrollCancelFn: Noop
|
||||
cancelFn = () => {
|
||||
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
|
||||
}
|
||||
timeout = window.setTimeout(() => {
|
||||
timeout = null
|
||||
scrollCancelFn = handleScrollXR(element, to, duration, fn)
|
||||
}, delay)
|
||||
} else {
|
||||
cancelFn = handleScrollXR(element, to, duration, fn)
|
||||
}
|
||||
return cancelFn
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置标题
|
||||
*/
|
||||
let dom_title = document.getElementsByTagName('title')[0]
|
||||
export const setTitle = (title: string | null) => {
|
||||
if (!title) title = '洛雪音乐助手'
|
||||
dom_title.innerText = title
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// 业务工具方法
|
||||
|
||||
export const toNewMusicInfo = (oldMusicInfo: any): LX.Music.MusicInfoOnline => {
|
||||
const meta: Record<string, any> = {
|
||||
songId: oldMusicInfo.songmid, // 歌曲ID,mg源为copyrightId,local为文件路径
|
||||
albumName: oldMusicInfo.albumName, // 歌曲专辑名称
|
||||
albumId: oldMusicInfo.albumId,
|
||||
picUrl: oldMusicInfo.img, // 歌曲图片链接
|
||||
qualitys: oldMusicInfo.types,
|
||||
_qualitys: oldMusicInfo._types,
|
||||
}
|
||||
if (meta._qualitys.flac32bit && !meta._qualitys.flac24bit) {
|
||||
meta._qualitys.flac24bit = meta._qualitys.flac32bit
|
||||
delete meta._qualitys.flac32bit
|
||||
|
||||
meta.qualitys = (meta.qualitys as any[]).map(quality => {
|
||||
if (quality.type == 'flac32bit') quality.type = 'flac24bit'
|
||||
return quality
|
||||
})
|
||||
}
|
||||
|
||||
switch (oldMusicInfo.source) {
|
||||
case 'kg':
|
||||
meta.hash = oldMusicInfo.hash
|
||||
break
|
||||
case 'tx':
|
||||
meta.strMediaMid = oldMusicInfo.strMediaMid
|
||||
meta.albumMid = oldMusicInfo.albumMid
|
||||
break
|
||||
case 'mg':
|
||||
meta.copyrightId = oldMusicInfo.copyrightId
|
||||
meta.lrcUrl = oldMusicInfo.lrcUrl
|
||||
meta.mrcUrl = oldMusicInfo.mrcUrl
|
||||
meta.trcUrl = oldMusicInfo.trcUrl
|
||||
break
|
||||
}
|
||||
return {
|
||||
id: `${oldMusicInfo.source as string}_${oldMusicInfo.songmid as string}`,
|
||||
name: oldMusicInfo.name,
|
||||
singer: oldMusicInfo.singer,
|
||||
source: oldMusicInfo.source,
|
||||
interval: oldMusicInfo.interval,
|
||||
meta: meta as LX.Music.MusicInfoOnline['meta'],
|
||||
}
|
||||
}
|
||||
|
||||
export const toOldMusicInfo = (minfo: LX.Music.MusicInfoOnline) => {
|
||||
const oInfo: Record<string, any> = {
|
||||
name: minfo.name,
|
||||
singer: minfo.singer,
|
||||
source: minfo.source,
|
||||
songmid: minfo.meta.songId,
|
||||
albumId: minfo.meta.albumId,
|
||||
interval: minfo.interval,
|
||||
albumName: minfo.meta.albumName,
|
||||
img: minfo.meta.picUrl,
|
||||
types: minfo.meta.qualitys,
|
||||
_types: minfo.meta._qualitys,
|
||||
typeUrl: {},
|
||||
}
|
||||
switch (minfo.source) {
|
||||
case 'kg':
|
||||
oInfo.hash = minfo.meta.hash
|
||||
break
|
||||
case 'tx':
|
||||
oInfo.strMediaMid = minfo.meta.strMediaMid
|
||||
oInfo.albumMid = minfo.meta.albumMid
|
||||
break
|
||||
case 'mg':
|
||||
oInfo.copyrightId = minfo.meta.copyrightId
|
||||
oInfo.lrcUrl = minfo.meta.lrcUrl
|
||||
oInfo.mrcUrl = minfo.meta.mrcUrl
|
||||
oInfo.trcUrl = minfo.meta.trcUrl
|
||||
break
|
||||
}
|
||||
|
||||
return oInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复2.0.0-dev.8之前的新列表数据音质
|
||||
* @param musicInfo
|
||||
*/
|
||||
export const fixNewMusicInfoQuality = (musicInfo: LX.Music.MusicInfo) => {
|
||||
if (musicInfo.source == 'local') return musicInfo
|
||||
|
||||
// @ts-expect-error
|
||||
if (musicInfo.meta._qualitys.flac32bit && !musicInfo.meta._qualitys.flac24bit) {
|
||||
// @ts-expect-error
|
||||
musicInfo.meta._qualitys.flac24bit = musicInfo.meta._qualitys.flac32bit
|
||||
// @ts-expect-error
|
||||
delete musicInfo.meta._qualitys.flac32bit
|
||||
|
||||
musicInfo.meta.qualitys = musicInfo.meta.qualitys.map(quality => {
|
||||
// @ts-expect-error
|
||||
if (quality.type == 'flac32bit') quality.type = 'flac24bit'
|
||||
return quality
|
||||
})
|
||||
}
|
||||
|
||||
return musicInfo
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { useRouter, useRoute, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
computed,
|
||||
watch,
|
||||
watchEffect,
|
||||
nextTick,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
toRaw,
|
||||
useCssModule,
|
||||
toRef,
|
||||
toRefs,
|
||||
shallowRef,
|
||||
unref,
|
||||
markRaw,
|
||||
ComputedRef,
|
||||
Ref,
|
||||
ShallowRef,
|
||||
defineProps,
|
||||
defineEmits,
|
||||
defineComponent,
|
||||
shallowReactive,
|
||||
defineExpose,
|
||||
} from 'vue'
|
||||
// import { useStore } from 'vuex'
|
||||
|
||||
// export const useState = name => {
|
||||
// const store = useStore()
|
||||
// return store.state[name]
|
||||
// }
|
||||
// export const useGetter = (...names) => {
|
||||
// const store = useStore()
|
||||
// return store.getters[names.join('/')]
|
||||
// }
|
||||
// export const useRefGetter = (...names) => {
|
||||
// const store = useStore()
|
||||
// return computed(() => store.getters[names.join('/')])
|
||||
// }
|
||||
|
||||
// export const useAction = (...names) => {
|
||||
// const store = useStore()
|
||||
// return params => {
|
||||
// return store.dispatch(names.join('/'), params)
|
||||
// }
|
||||
// }
|
||||
// export const useCommit = (...names) => {
|
||||
// const store = useStore()
|
||||
// return params => {
|
||||
// return store.commit(names.join('/'), params)
|
||||
// }
|
||||
// }
|
||||
|
||||
export const markRawList = <T extends any[]>(list: T) => {
|
||||
for (const item of list) {
|
||||
markRaw(item)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
export {
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
ref,
|
||||
toRaw,
|
||||
reactive,
|
||||
watch,
|
||||
watchEffect,
|
||||
computed,
|
||||
useCssModule,
|
||||
toRef,
|
||||
toRefs,
|
||||
shallowRef,
|
||||
unref,
|
||||
onMounted,
|
||||
markRaw,
|
||||
ComputedRef,
|
||||
Ref,
|
||||
ShallowRef,
|
||||
defineProps,
|
||||
defineEmits,
|
||||
defineComponent,
|
||||
shallowReactive,
|
||||
defineExpose,
|
||||
}
|
||||
@@ -4,6 +4,9 @@
|
||||
"alert_button_text": "All right",
|
||||
"audio_visualization": "Audio visualization (experimental)",
|
||||
"back": "Back",
|
||||
"btn_cancel": "Cancel",
|
||||
"btn_confirm": "Confirm",
|
||||
"btn_save": "Save",
|
||||
"cancel_button_text": "Cancel",
|
||||
"close": "Close",
|
||||
"comment__hot_load_error": "Hot comments failed to load, click to try to reload",
|
||||
@@ -16,6 +19,7 @@
|
||||
"comment__refresh": "Refresh comments",
|
||||
"comment__show": "Song comments",
|
||||
"comment__title": "{name} comment",
|
||||
"comment__unavailable": "This song does not support getting comments",
|
||||
"confirm_button_text": "Yes",
|
||||
"copy_tip": " (Click to copy)",
|
||||
"date_format_hour": "{num} hours ago",
|
||||
@@ -26,8 +30,8 @@
|
||||
"default_list": "Recently Played",
|
||||
"desktop_lyric__back": "Back",
|
||||
"desktop_lyric__close": "Close",
|
||||
"desktop_lyric__font_decrease": "Reduce font size (Right click to fine-tune)",
|
||||
"desktop_lyric__font_increase": "Increase font size (Right click to fine-tune)",
|
||||
"desktop_lyric__font_decrease": "Reduce font size",
|
||||
"desktop_lyric__font_increase": "Increase font size",
|
||||
"desktop_lyric__lock": "Lock Lyrics",
|
||||
"desktop_lyric__lrc_active_zoom_off": "Unzoom the currently playing lyrics",
|
||||
"desktop_lyric__lrc_active_zoom_on": "Zoom the currently playing lyrics",
|
||||
@@ -38,6 +42,11 @@
|
||||
"desktop_lyric__win_top_off": "Cancel the top lyrics interface",
|
||||
"desktop_lyric__win_top_on": "Top lyrics interface",
|
||||
"download": "Downloads",
|
||||
"download___status_complated": "Download completed",
|
||||
"download___status_error": "task error",
|
||||
"download___status_paused": "Pause downloads",
|
||||
"download___status_runing": "downloading",
|
||||
"download___status_wating": "waiting to download",
|
||||
"download__all": "All Tasks",
|
||||
"download__error": "Error",
|
||||
"download__finished": "Download complete",
|
||||
@@ -52,8 +61,20 @@
|
||||
"download__quality": "Quality",
|
||||
"download__runing": "Downloading",
|
||||
"download__status": "Status",
|
||||
"download_status_error_check_path": "There is an error in checking the download path, please check whether the set download directory is normal",
|
||||
"download_status_error_refresh_url": "Link is dead, link is being refreshed",
|
||||
"download_status_error_url_failed": "Failed to get music link",
|
||||
"download_status_error_write": "The song save location is occupied or does not have write permission, please try to change the song save directory or restart the software or restart the computer, the error details:",
|
||||
"download_status_start": "start download",
|
||||
"download_status_url_geting": "Getting music link...",
|
||||
"duplicate_list_tip": "You have favorited this list [{name}] before, do you need to update the songs in it?",
|
||||
"export": "Export",
|
||||
"fullscreen_exit": "Exit Full Screen",
|
||||
"theme_edit_modal__copy": "Copy theme",
|
||||
"theme_edit_modal__remove": "delete",
|
||||
"theme_edit_modal__remove_tip": "Do you really want to remove this thread?",
|
||||
"theme_edit_modal__save_new": "Save new",
|
||||
"theme_edit_modal__select_bg_file": "Choose a background image",
|
||||
"history_clear": "Clear History",
|
||||
"history_remove": "Right click to remove this entry",
|
||||
"history_search": "History Searches",
|
||||
@@ -114,6 +135,7 @@
|
||||
"list_update_modal__tips": "💡 The list with \"Automatic Updates\" checked will be automatically updated each time the software is launched",
|
||||
"list_update_modal__title": "List update management",
|
||||
"list_update_modal__update": "Sync",
|
||||
"lists__add_local_file_desc": "select song file",
|
||||
"lists__duplicate": "Duplicate song",
|
||||
"lists__export": "Export",
|
||||
"lists__export_part_desc": "Choose where to save the list file",
|
||||
@@ -129,6 +151,7 @@
|
||||
"lists__remove_tip": "Do you really want to remove {name}?",
|
||||
"lists__remove_tip_button": "Yes, that's right",
|
||||
"lists__rename": "Rename",
|
||||
"lists__select_local_file": "Add local songs",
|
||||
"lists__sort_list": "Sort songs",
|
||||
"lists__source_detail": "Playlist Page",
|
||||
"lists__sync": "Update",
|
||||
@@ -157,8 +180,6 @@
|
||||
"music_duplicate": "Duplicate song",
|
||||
"music_name": "Name",
|
||||
"music_singer": "Artist",
|
||||
"music_sort__btn_cancel": "Cancel",
|
||||
"music_sort__btn_confirm": "Confirm",
|
||||
"music_sort__input_tip": "Please input which position you want to adjust to",
|
||||
"music_sort__title": "Adjust the position of {name} to:",
|
||||
"music_sort__title_multiple": "Adjust the position of the selected {num} songs to:",
|
||||
@@ -208,6 +229,8 @@
|
||||
"player__volume": "Volume: ",
|
||||
"search": "Search",
|
||||
"search__hot_search": "Top Searches",
|
||||
"search__type_music": "Song",
|
||||
"search__type_songlist": "Song List",
|
||||
"search__welcome": "Search what I want to 😉",
|
||||
"setting": "Settings",
|
||||
"setting__about": "About lx-music-desktop",
|
||||
@@ -228,6 +251,7 @@
|
||||
"setting__backup_part_export_setting": "Export settings",
|
||||
"setting__backup_part_export_setting_desc": "Save the list to...",
|
||||
"setting__backup_part_import_list": "Import lists",
|
||||
"setting__backup_part_import_list_confirm": "If the list in the backup file has the same ID as the existing list, the songs in the existing list will be overwritten. Do you want to continue?",
|
||||
"setting__backup_part_import_list_desc": "Select a list backup",
|
||||
"setting__backup_part_import_setting": "Import settings",
|
||||
"setting__backup_part_import_setting_desc": "Select the Settings file",
|
||||
@@ -237,6 +261,13 @@
|
||||
"setting__basic_control_btn_position_left": "Left",
|
||||
"setting__basic_control_btn_position_right": "Right",
|
||||
"setting__basic_font": "Font",
|
||||
"setting__basic_font_size": "font size",
|
||||
"setting__basic_font_size_14px": "Smaller",
|
||||
"setting__basic_font_size_15px": "Small",
|
||||
"setting__basic_font_size_16px": "standard",
|
||||
"setting__basic_font_size_17px": "Big",
|
||||
"setting__basic_font_size_18px": "Larger",
|
||||
"setting__basic_font_size_19px": "Very big",
|
||||
"setting__basic_lang": "Language",
|
||||
"setting__basic_lang_title": "The language displayed in the software",
|
||||
"setting__basic_show_animation": "Show switching animation",
|
||||
@@ -246,7 +277,6 @@
|
||||
"setting__basic_source_status_success": "Initialization successful",
|
||||
"setting__basic_source_temp": "Temporary API (some features not available; workaround if Test API unavailable)",
|
||||
"setting__basic_source_test": "Test API (Available for most software features)",
|
||||
"setting__basic_source_title": "Choose a music source",
|
||||
"setting__basic_source_user_api_btn": "Custom Source Management",
|
||||
"setting__basic_sourcename": "Source name",
|
||||
"setting__basic_sourcename_alias": "Aliases",
|
||||
@@ -268,15 +298,28 @@
|
||||
"setting__click_copy": "Click to copy",
|
||||
"setting__click_open": "Click to open",
|
||||
"setting__desktop_lyric": "Desktop Lyric Settings",
|
||||
"setting__desktop_lyric_align": "Lyric Alignment",
|
||||
"setting__desktop_lyric_align_center": "Centered",
|
||||
"setting__desktop_lyric_align_left": "left",
|
||||
"setting__desktop_lyric_align_right": "right",
|
||||
"setting__desktop_lyric_always_on_top": "Make the lyrics always above other windows",
|
||||
"setting__desktop_lyric_always_on_top_loop": "Automatically refresh the top of the lyrics (try to enable this setting when the lyrics are still blocked by some programs)",
|
||||
"setting__desktop_lyric_audio_visualization": "Audio Visualization (Experimental)",
|
||||
"setting__desktop_lyric_color_reset": "reset color",
|
||||
"setting__desktop_lyric_delay_scroll": "Delayed lyrics scroll",
|
||||
"setting__desktop_lyric_direction_horizontal": "horizontal direction",
|
||||
"setting__desktop_lyric_direction_vertical": "vertical direction",
|
||||
"setting__desktop_lyric_ellipsis": "Lyrics are not allowed to wrap",
|
||||
"setting__desktop_lyric_enable": "Display lyrics",
|
||||
"setting__desktop_lyric_font": "Lyric font",
|
||||
"setting__desktop_lyric_font_default": "Default",
|
||||
"setting__desktop_lyric_font_weight": "Bold font",
|
||||
"setting__desktop_lyric_hover_hide": "Reduce the transparency of lyrics when the mouse moves into the lyrics area (this feature has platform compatibility issues)",
|
||||
"setting__desktop_lyric_lock": "Lock lyrics",
|
||||
"setting__desktop_lyric_lock_screen": "It is not allowed to drag the lyrics window out of the main screen",
|
||||
"setting__desktop_lyric_played_color": "color played",
|
||||
"setting__desktop_lyric_shadow_color": "shadow color",
|
||||
"setting__desktop_lyric_unplay_color": "Color not playing",
|
||||
"setting__download": "Download",
|
||||
"setting__download_data_embed": "Whether to embed the following content in the audio file",
|
||||
"setting__download_embed_lyric": "Embedding lyric",
|
||||
@@ -297,7 +340,9 @@
|
||||
"setting__download_path_label": "Current: ",
|
||||
"setting__download_path_open_label": "Click to open this path",
|
||||
"setting__download_path_title": "Define the path to downloading",
|
||||
"setting__download_rlyric": "Also write Roman lyrics to the lyrics file (if any)",
|
||||
"setting__download_select_save_path": "Select the save path",
|
||||
"setting__download_tlyric": "Also write the translated lyrics to the lyrics file (if any)",
|
||||
"setting__download_use_other_source": "Auto source download",
|
||||
"setting__download_use_other_source_tip": "When the song cannot be downloaded from the original source, try to switch to another source to download. Note: This function does not 100% guarantee that the version of the song after changing the source is consistent with the original version",
|
||||
"setting__hot_key": "Shortcut Key Settings",
|
||||
@@ -322,6 +367,7 @@
|
||||
"setting__is_enable": "Enabled",
|
||||
"setting__is_show": "Showed",
|
||||
"setting__list": "List",
|
||||
"setting__list_action_btn": "Show list action buttons",
|
||||
"setting__list_add_music_location_type": "Position when adding a song to the list",
|
||||
"setting__list_add_music_location_type_bottom": "Bottom",
|
||||
"setting__list_add_music_location_type_top": "Top",
|
||||
@@ -338,10 +384,17 @@
|
||||
"setting__odc_clear_search_input": "Clear the search box when you are not searching",
|
||||
"setting__odc_clear_search_list": "Clear the search list when you are not searching",
|
||||
"setting__other": "Extras",
|
||||
"setting__other_play_list_cache": "List cache management",
|
||||
"setting__other_play_list_cache_clear_btn": "Clear list cache information",
|
||||
"setting__other_play_list_cache_tip": "The cached song links and playback sources in my list need to be re-acquired when playing or downloading songs after cleaning. Do not clean up if there are no issues related to song playback",
|
||||
"setting__other_play_list_cache_tip_confirm": "In my list, the cached song links and play alternative sources need to be re-obtained when playing and downloading songs after cleaning. Do not clean up if there are no issues related to song playback. Are you sure to clean up?",
|
||||
"setting__other_lyric_edited_cache": "Lyric management with adjusted offset time",
|
||||
"setting__other_lyric_edited_clear_btn": "Clean up time-adjusted lyrics",
|
||||
"setting__other_lyric_edited_clear_tip_confirm": "This will clear all the lyrics that you have adjusted the offset time before, confirm to clear? \n(Hand shaking to confirm 🤪)",
|
||||
"setting__other_lyric_edited_label": "Number of lyrics:",
|
||||
"setting__other_lyric_raw_clear_btn": "Clear Lyrics Cache",
|
||||
"setting__other_lyric_raw_label": "Number of lyrics:",
|
||||
"setting__other_music_url_clear_btn": "Clean up song URL cache",
|
||||
"setting__other_music_url_label": "Number of song URLs:",
|
||||
"setting__other_other_cache": "Other cache management",
|
||||
"setting__other_other_source_clear_btn": "Clear the cache of swapped songs",
|
||||
"setting__other_other_source_label": "Change the number of source song information:",
|
||||
"setting__other_resource_cache": "Resource cache management",
|
||||
"setting__other_resource_cache_clear_btn": "Clear resource cache",
|
||||
"setting__other_resource_cache_confirm": "I want to clear",
|
||||
@@ -364,7 +417,7 @@
|
||||
"setting__play_detail_font_size_reset": "Reset",
|
||||
"setting__play_detail_font_zoom": "Zoom the currently playing lyrics",
|
||||
"setting__play_detail_lyric_progress": "Allows to adjust playback progress by lyrics",
|
||||
"setting__play_lyric_lxlrc": "Play with karaoke-style lyrics (if available)",
|
||||
"setting__play_lyric_lxlrc": "Use karaoke-style lyrics to play (if available, this function consumes more performance, and it is not recommended to enable it on computers with low configuration)",
|
||||
"setting__play_lyric_roma": "Show lyrics roman",
|
||||
"setting__play_lyric_s2t": "Convert the playing and downloading lyrics to Traditional Chinese",
|
||||
"setting__play_lyric_transition": "Show lyrics translation",
|
||||
@@ -375,6 +428,7 @@
|
||||
"setting__play_media_device_tip": "This feature conflicts with Audio Visualization, both cannot be enabled at the same time, would you like to turn Audio Visualization off and apply the selected audio output settings?",
|
||||
"setting__play_quality": "Priority playback of 320K quality songs (if available)",
|
||||
"setting__play_save_play_time": "Remember playback progress",
|
||||
"setting__play_startup_auto_play": "Play music automatically after launching the software",
|
||||
"setting__play_task_bar": "Show playing progress on the taskbar",
|
||||
"setting__play_timeout": "Timed pause",
|
||||
"setting__player_audio_visualization_tip": "The custom audio output device will conflict with the audio visualization function. After the audio visualization is enabled, the audio output device will be reset to the default output device. At present, this problem cannot be solved. Do you still want to enable it?",
|
||||
@@ -402,11 +456,16 @@
|
||||
"setting__update_progress": "Download progress: ",
|
||||
"setting__update_unknown": "Unknown",
|
||||
"song_list": "Playlists",
|
||||
"songlist__import_input_btn_confirm": "Open",
|
||||
"songlist__import_input_show_btn": "Open Playlist",
|
||||
"songlist__import_input_tip": "Enter songlist link or songlist ID",
|
||||
"songlist__import_input_tip_1": "Cross-source playlists are not supported, please confirm whether the playlist to be opened corresponds to the current playlist source",
|
||||
"songlist__import_input_tip_2": "If you encounter a link to a playlist that cannot be opened, welcome feedback",
|
||||
"songlist__import_input_tip_3": "Kugou source does not support opening with playlist ID, but supports Kugou code opening",
|
||||
"songlist__import_input_tip_4": "Netease source's \"I like\" playlist requires Token to open, see for details",
|
||||
"songlist__import_input_title": "Open shared playlist",
|
||||
"songlist__open_list": "open {name} playlist",
|
||||
"songlist__tag_info_hot_tag": "Hot tags",
|
||||
"source_alias_all": "Aggregated",
|
||||
"source_alias_bd": "BD Music",
|
||||
"source_alias_kg": "KG Music",
|
||||
@@ -443,14 +502,40 @@
|
||||
"tag__high_quality": "HQ",
|
||||
"tag__lossless": "SQ",
|
||||
"tag__lossless_24bit": "24bit",
|
||||
"theme_add": "Add a theme",
|
||||
"theme_auto": "Auto",
|
||||
"theme_auto_tip": "Right-click to open the light and dark theme settings window",
|
||||
"theme_black": "Black",
|
||||
"theme_blue": "Blue",
|
||||
"theme_blue2": "Purple Blue",
|
||||
"theme_blue_plus": "Blue Plus",
|
||||
"theme_edit_modal__app_bg": "Apply background color",
|
||||
"theme_edit_modal__aside_color": "Sidebar button color",
|
||||
"theme_edit_modal__badge": "label color",
|
||||
"theme_edit_modal__badge_primary": "Main color",
|
||||
"theme_edit_modal__badge_secondary": "Secondary Color",
|
||||
"theme_edit_modal__badge_tertiary": "Third color",
|
||||
"theme_edit_modal__bg_image": "Background picture",
|
||||
"theme_edit_modal__bg_image_add": "Add background image",
|
||||
"theme_edit_modal__bg_image_change": "Change background image",
|
||||
"theme_edit_modal__bg_image_remove": "remove background image",
|
||||
"theme_edit_modal__close_btn": "Closure",
|
||||
"theme_edit_modal__control_btn": "Left control button color",
|
||||
"theme_edit_modal__dark": "Dark theme",
|
||||
"theme_edit_modal__hide_btn": "Hide play details page",
|
||||
"theme_edit_modal__main_bg": "Content area background color",
|
||||
"theme_edit_modal__min_btn": "Minimize",
|
||||
"theme_edit_modal__pick_cancel": "Reset",
|
||||
"theme_edit_modal__pick_color": "Choose color",
|
||||
"theme_edit_modal__pick_last_color": "Use previous color",
|
||||
"theme_edit_modal__pick_save": "Confirm",
|
||||
"theme_edit_modal__preview": "Preview theme",
|
||||
"theme_edit_modal__primary": "Theme color",
|
||||
"theme_edit_modal__title": "Edit topic",
|
||||
"theme_green": "Green",
|
||||
"theme_grey": "Grey",
|
||||
"theme_happy_new_year": "New Year",
|
||||
"theme_max_tip": "You can only add up to 10 themes, delete some and add more 😜",
|
||||
"theme_mid_autumn": "Mid-Autumn",
|
||||
"theme_ming": "Ming",
|
||||
"theme_naruto": "Naruto",
|
||||
@@ -460,9 +545,11 @@
|
||||
"theme_red": "Red",
|
||||
"theme_selector_modal__dark_title": "dark theme",
|
||||
"theme_selector_modal__light_title": "Bright theme",
|
||||
"theme_selector_modal__theme_name": "topic name",
|
||||
"theme_selector_modal__title": "Follow system theme settings",
|
||||
"theme_selector_modal__title_tip": "Note: You can set a light theme and a dark theme in advance, and then it will automatically switch to the corresponding theme you set in advance according to the light and dark theme colors of the system.",
|
||||
"theme_yellow": "Yellow",
|
||||
"toggle_source_failed": "Failed to automatically change the source, please try to manually search for the song in other sources to play",
|
||||
"toggle_source_try": "Try switching to another source...",
|
||||
"user_api__allow_show_update_alert": "Allow update popup to show",
|
||||
"user_api__btn_export": "Export",
|
||||
"user_api__btn_import": "Import",
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { App, ref } from 'vue'
|
||||
import { messages, Messages, Message } from './index'
|
||||
|
||||
|
||||
type TranslateValues = Record<string, string | number | boolean>
|
||||
|
||||
export declare interface I18n {
|
||||
locale: keyof Messages
|
||||
fallbackLocale: keyof Messages
|
||||
availableLocales: Array<keyof Messages>
|
||||
messages: Messages
|
||||
message: Message
|
||||
setLanguage: (locale: string) => void
|
||||
fillMessage: (message: string, val: TranslateValues) => string
|
||||
getMessage: (key: keyof Message, val?: TranslateValues) => string
|
||||
t: (key: keyof Message, val?: TranslateValues) => string
|
||||
}
|
||||
|
||||
const locale = ref('zh-cn')
|
||||
|
||||
let i18n: I18n
|
||||
|
||||
|
||||
const trackReactivityValues = (): any => {
|
||||
return locale.value
|
||||
}
|
||||
|
||||
const i18nPlugin = {
|
||||
install: (app: App) => {
|
||||
// inject a globally available $translate() method
|
||||
app.config.globalProperties.$t = (key: keyof Message, val?: TranslateValues): string => {
|
||||
// retrieve a nested property in `options`
|
||||
// using `key` as the path
|
||||
// return key.split('.').reduce((o, i) => {
|
||||
// if (o) return o[i]
|
||||
// }, options)
|
||||
trackReactivityValues()
|
||||
return i18n.getMessage(key, val)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const useI18n = () => {
|
||||
return (key: keyof Message, val?: TranslateValues): string => {
|
||||
trackReactivityValues()
|
||||
return i18n.getMessage(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
const setLanguage = (lang: string) => {
|
||||
i18n.setLanguage(lang)
|
||||
}
|
||||
|
||||
const createI18n = (): I18n => {
|
||||
return i18n = {
|
||||
locale: locale.value,
|
||||
fallbackLocale: 'zh-cn',
|
||||
availableLocales: Object.keys(messages),
|
||||
messages,
|
||||
message: messages[locale.value],
|
||||
setLanguage(_locale: string) {
|
||||
this.locale = _locale
|
||||
this.message = messages[_locale]
|
||||
locale.value = _locale
|
||||
},
|
||||
fillMessage(message: string, vals: TranslateValues): string {
|
||||
for (const [key, val] of Object.entries(vals)) {
|
||||
message = message.replaceAll(`{${key}}`, String(val))
|
||||
}
|
||||
return message
|
||||
},
|
||||
getMessage(key: keyof Message, val?: TranslateValues): string {
|
||||
let targetMessage = this.message[key] ?? this.messages[this.fallbackLocale][key] ?? ''
|
||||
return val ? this.fillMessage(targetMessage, val) : targetMessage
|
||||
},
|
||||
t(key: keyof Message, val?: TranslateValues): string {
|
||||
trackReactivityValues()
|
||||
return this.getMessage(key, val)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
i18nPlugin,
|
||||
setLanguage,
|
||||
useI18n,
|
||||
createI18n,
|
||||
}
|
||||
@@ -2,7 +2,20 @@ import zh_cn from './zh-cn.json'
|
||||
import zh_tw from './zh-tw.json'
|
||||
import en_us from './en-us.json'
|
||||
|
||||
const langs = [
|
||||
type Message = Record<keyof typeof zh_cn, string>
|
||||
| Record<keyof typeof zh_tw, string>
|
||||
| Record<keyof typeof en_us, string>
|
||||
|
||||
interface Lang {
|
||||
name: string
|
||||
locale: string
|
||||
alternate?: string
|
||||
country: string
|
||||
fallback?: boolean
|
||||
message: Message
|
||||
}
|
||||
|
||||
const langs: Lang[] = [
|
||||
{
|
||||
name: '简体中文',
|
||||
locale: 'zh-cn',
|
||||
@@ -26,8 +39,13 @@ const langs = [
|
||||
},
|
||||
]
|
||||
|
||||
const langList = []
|
||||
const messages = {}
|
||||
const langList: Array<{
|
||||
name: string
|
||||
locale: string
|
||||
alternate?: string
|
||||
}> = []
|
||||
type Messages = Record<string, Message>
|
||||
const messages: Messages = {}
|
||||
langs.forEach(item => {
|
||||
langList.push({
|
||||
name: item.name,
|
||||
@@ -40,4 +58,8 @@ langs.forEach(item => {
|
||||
export {
|
||||
langList,
|
||||
messages,
|
||||
Messages,
|
||||
Message,
|
||||
}
|
||||
|
||||
export * from './i18n'
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext", /* Specify what module code is generated. */
|
||||
"moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
},
|
||||
// "include": [
|
||||
// "**/*.ts",
|
||||
// "**/*.js",
|
||||
// "**/*.vue",
|
||||
// "**/*.json",
|
||||
// ],
|
||||
// "exclude": ["dbService"]
|
||||
}
|
||||
@@ -4,6 +4,9 @@
|
||||
"alert_button_text": "好吧",
|
||||
"audio_visualization": "音频可视化(实验性)",
|
||||
"back": "返回",
|
||||
"btn_cancel": "取消",
|
||||
"btn_confirm": "确定",
|
||||
"btn_save": "保存",
|
||||
"cancel_button_text": "我不",
|
||||
"close": "关闭",
|
||||
"comment__hot_load_error": "热门评论加载失败,点击尝试重新加载",
|
||||
@@ -16,6 +19,7 @@
|
||||
"comment__refresh": "刷新评论",
|
||||
"comment__show": "歌曲评论",
|
||||
"comment__title": "{name} 的评论",
|
||||
"comment__unavailable": "此歌曲不支持获取评论",
|
||||
"confirm_button_text": "是的",
|
||||
"copy_tip": "(点击复制)",
|
||||
"date_format_hour": "{num}小时前",
|
||||
@@ -26,8 +30,8 @@
|
||||
"default_list": "试听列表",
|
||||
"desktop_lyric__back": "返回",
|
||||
"desktop_lyric__close": "关闭",
|
||||
"desktop_lyric__font_decrease": "减小字体大小(右击可微调)",
|
||||
"desktop_lyric__font_increase": "增加字体大小(右击可微调)",
|
||||
"desktop_lyric__font_decrease": "减小字体大小",
|
||||
"desktop_lyric__font_increase": "增加字体大小",
|
||||
"desktop_lyric__lock": "锁定歌词",
|
||||
"desktop_lyric__lrc_active_zoom_off": "取消缩放当前播放的歌词",
|
||||
"desktop_lyric__lrc_active_zoom_on": "缩放当前播放的歌词",
|
||||
@@ -38,6 +42,11 @@
|
||||
"desktop_lyric__win_top_off": "取消置顶歌词界面",
|
||||
"desktop_lyric__win_top_on": "置顶歌词界面",
|
||||
"download": "下载",
|
||||
"download___status_complated": "下载完成",
|
||||
"download___status_error": "任务出错",
|
||||
"download___status_paused": "暂停下载",
|
||||
"download___status_runing": "正在下载",
|
||||
"download___status_wating": "等待下载",
|
||||
"download__all": "全部任务",
|
||||
"download__error": "出错",
|
||||
"download__finished": "下载完成",
|
||||
@@ -52,8 +61,20 @@
|
||||
"download__quality": "品质",
|
||||
"download__runing": "正在下载",
|
||||
"download__status": "状态",
|
||||
"download_status_error_check_path": "检查下载路径出错,请检查设置的下载目录是否正常",
|
||||
"download_status_error_refresh_url": "链接失效,正在刷新链接",
|
||||
"download_status_error_url_failed": "获取音乐链接失败",
|
||||
"download_status_error_write": "歌曲保存位置被占用或没有写入权限,请尝试更改歌曲保存目录或重启软件或重启电脑,错误详情:",
|
||||
"download_status_start": "开始下载",
|
||||
"download_status_url_geting": "音乐链接获取中...",
|
||||
"duplicate_list_tip": "你之前已收藏过该列表 [{name}],是否需要更新里面的歌曲?",
|
||||
"export": "导出",
|
||||
"fullscreen_exit": "退出全屏",
|
||||
"theme_edit_modal__copy": "复制主题",
|
||||
"theme_edit_modal__remove": "删除",
|
||||
"theme_edit_modal__remove_tip": "是否真的要移除这个主题?",
|
||||
"theme_edit_modal__save_new": "另存",
|
||||
"theme_edit_modal__select_bg_file": "选择背景图片",
|
||||
"history_clear": "清空搜索历史",
|
||||
"history_remove": "右击移除该历史",
|
||||
"history_search": "历史搜索",
|
||||
@@ -114,6 +135,7 @@
|
||||
"list_update_modal__tips": "💡 每次启动软件时将会自动更新已勾选“自动更新”的列表",
|
||||
"list_update_modal__title": "列表更新管理",
|
||||
"list_update_modal__update": "立即更新",
|
||||
"lists__add_local_file_desc": "选择歌曲文件",
|
||||
"lists__duplicate": "重复歌曲",
|
||||
"lists__export": "导出",
|
||||
"lists__export_part_desc": "选择列表文件保存位置",
|
||||
@@ -129,6 +151,7 @@
|
||||
"lists__remove_tip": "你真的想要移除 {name} 吗?",
|
||||
"lists__remove_tip_button": "是的 没错",
|
||||
"lists__rename": "重命名",
|
||||
"lists__select_local_file": "添加本地歌曲",
|
||||
"lists__sort_list": "排序歌曲",
|
||||
"lists__source_detail": "歌单详情页",
|
||||
"lists__sync": "更新",
|
||||
@@ -157,8 +180,6 @@
|
||||
"music_duplicate": "重复歌曲",
|
||||
"music_name": "歌曲名",
|
||||
"music_singer": "艺术家",
|
||||
"music_sort__btn_cancel": "取消",
|
||||
"music_sort__btn_confirm": "确定",
|
||||
"music_sort__input_tip": "请输入要调整到第几个位置",
|
||||
"music_sort__title": "将 {name} 的位置调整到:",
|
||||
"music_sort__title_multiple": "将已选的 {num} 首歌曲的位置调整到:",
|
||||
@@ -208,6 +229,8 @@
|
||||
"player__volume": "当前音量:",
|
||||
"search": "搜索",
|
||||
"search__hot_search": "热门搜索",
|
||||
"search__type_music": "歌曲",
|
||||
"search__type_songlist": "歌单",
|
||||
"search__welcome": "搜我所想~~😉",
|
||||
"setting": "设置",
|
||||
"setting__about": "关于洛雪音乐",
|
||||
@@ -228,6 +251,7 @@
|
||||
"setting__backup_part_export_setting": "导出设置",
|
||||
"setting__backup_part_export_setting_desc": "选择设置保存位置",
|
||||
"setting__backup_part_import_list": "导入列表",
|
||||
"setting__backup_part_import_list_confirm": "备份文件中列表与现有列表ID相同时,现有列表内歌曲将会被覆盖,是否要继续?",
|
||||
"setting__backup_part_import_list_desc": "选择列表文件",
|
||||
"setting__backup_part_import_setting": "导入设置",
|
||||
"setting__backup_part_import_setting_desc": "选择配置文件",
|
||||
@@ -237,6 +261,13 @@
|
||||
"setting__basic_control_btn_position_left": "左边",
|
||||
"setting__basic_control_btn_position_right": "右边",
|
||||
"setting__basic_font": "字体",
|
||||
"setting__basic_font_size": "字体大小",
|
||||
"setting__basic_font_size_14px": "较小",
|
||||
"setting__basic_font_size_15px": "小",
|
||||
"setting__basic_font_size_16px": "标准",
|
||||
"setting__basic_font_size_17px": "大",
|
||||
"setting__basic_font_size_18px": "较大",
|
||||
"setting__basic_font_size_19px": "非常大",
|
||||
"setting__basic_lang": "语言",
|
||||
"setting__basic_lang_title": "软件显示的语言",
|
||||
"setting__basic_show_animation": "显示切换动画",
|
||||
@@ -246,7 +277,6 @@
|
||||
"setting__basic_source_status_success": "初始化成功",
|
||||
"setting__basic_source_temp": "临时接口(软件的某些功能不可用,建议测试接口不可用再使用本接口)",
|
||||
"setting__basic_source_test": "测试接口(几乎软件的所有功能都可用)",
|
||||
"setting__basic_source_title": "选择音乐来源",
|
||||
"setting__basic_source_user_api_btn": "自定义源管理",
|
||||
"setting__basic_sourcename": "音源名字",
|
||||
"setting__basic_sourcename_alias": "别名",
|
||||
@@ -268,15 +298,30 @@
|
||||
"setting__click_copy": "点击复制",
|
||||
"setting__click_open": "点击打开",
|
||||
"setting__desktop_lyric": "桌面歌词设置",
|
||||
"setting__desktop_lyric_align": "歌词对齐方式",
|
||||
"setting__desktop_lyric_align_center": "居中",
|
||||
"setting__desktop_lyric_align_left": "居左",
|
||||
"setting__desktop_lyric_align_right": "居右",
|
||||
"setting__desktop_lyric_always_on_top": "使歌词总是在其他窗口之上",
|
||||
"setting__desktop_lyric_always_on_top_loop": "自动刷新歌词置顶(当歌词置顶后仍被某些程序遮挡时可尝试启用此设置)",
|
||||
"setting__desktop_lyric_audio_visualization": "音频可视化(实验性)",
|
||||
"setting__desktop_lyric_color": "歌词字体颜色",
|
||||
"setting__desktop_lyric_color_reset": "重置颜色",
|
||||
"setting__desktop_lyric_delay_scroll": "延迟歌词滚动",
|
||||
"setting__desktop_lyric_direction": "歌词显示方向",
|
||||
"setting__desktop_lyric_direction_horizontal": "水平方向",
|
||||
"setting__desktop_lyric_direction_vertical": "垂直方向",
|
||||
"setting__desktop_lyric_ellipsis": "不允许歌词换行",
|
||||
"setting__desktop_lyric_enable": "显示歌词",
|
||||
"setting__desktop_lyric_font": "歌词字体",
|
||||
"setting__desktop_lyric_font_default": "默认",
|
||||
"setting__desktop_lyric_font_weight": "加粗字体",
|
||||
"setting__desktop_lyric_hover_hide": "鼠标移入歌词区域时降低歌词透明度(此功能存在平台兼容性问题)",
|
||||
"setting__desktop_lyric_lock": "锁定歌词",
|
||||
"setting__desktop_lyric_lock_screen": "不允许歌词窗口拖出主屏幕之外",
|
||||
"setting__desktop_lyric_played_color": "已播放颜色",
|
||||
"setting__desktop_lyric_shadow_color": "阴影颜色",
|
||||
"setting__desktop_lyric_unplay_color": "未播放颜色",
|
||||
"setting__download": "下载设置",
|
||||
"setting__download_data_embed": "是否将以下内容嵌入到音频文件中",
|
||||
"setting__download_embed_lyric": "歌词嵌入",
|
||||
@@ -297,7 +342,9 @@
|
||||
"setting__download_path_label": "当前下载路径:",
|
||||
"setting__download_path_open_label": "点击打开当前路径",
|
||||
"setting__download_path_title": "下载歌曲保存的路径",
|
||||
"setting__download_rlyric": "同时将罗马音歌词写入歌词文件中(如果有)",
|
||||
"setting__download_select_save_path": "选择歌曲保存路径",
|
||||
"setting__download_tlyric": "同时将翻译歌词写入歌词文件中(如果有)",
|
||||
"setting__download_use_other_source": "自动换源下载",
|
||||
"setting__download_use_other_source_tip": "当无法从歌曲的原始源下载时,尝试切换到其他源下载,注:此功能不100%保证换源后的歌曲版本与原版一致",
|
||||
"setting__hot_key": "快捷键设置",
|
||||
@@ -322,6 +369,7 @@
|
||||
"setting__is_enable": "是否启用",
|
||||
"setting__is_show": "是否显示",
|
||||
"setting__list": "列表设置",
|
||||
"setting__list_action_btn": "显示列表操作按钮",
|
||||
"setting__list_add_music_location_type": "添加歌曲到列表时的位置",
|
||||
"setting__list_add_music_location_type_bottom": "底部",
|
||||
"setting__list_add_music_location_type_top": "顶部",
|
||||
@@ -338,10 +386,17 @@
|
||||
"setting__odc_clear_search_input": "离开搜索界面时清空搜索框",
|
||||
"setting__odc_clear_search_list": "离开搜索界面时清空搜索列表",
|
||||
"setting__other": "其他",
|
||||
"setting__other_play_list_cache": "列表缓存管理",
|
||||
"setting__other_play_list_cache_clear_btn": "清理列表缓存信息",
|
||||
"setting__other_play_list_cache_tip": "我的列表中已缓存的歌曲链接、播放代替源,清理后播放、下载歌曲时需要重新获取,没有歌曲播放相关的问题不要清理",
|
||||
"setting__other_play_list_cache_tip_confirm": "我的列表中已缓存的歌曲链接、播放代替源,清理后播放、下载歌曲时需要重新获取,没有歌曲播放相关的问题不要清理,是否确认清理?",
|
||||
"setting__other_lyric_edited_cache": "已调整过偏移时间的歌词管理",
|
||||
"setting__other_lyric_edited_clear_btn": "清理已调整过时间的歌词",
|
||||
"setting__other_lyric_edited_clear_tip_confirm": "这将清理所有你之前已调整过偏移时间的歌词,是否确认清理?(手抖确认🤪)",
|
||||
"setting__other_lyric_edited_label": "歌词数量:",
|
||||
"setting__other_lyric_raw_clear_btn": "清理歌词缓存",
|
||||
"setting__other_lyric_raw_label": "歌词数量:",
|
||||
"setting__other_music_url_clear_btn": "清理歌曲URL缓存",
|
||||
"setting__other_music_url_label": "歌曲URL数量:",
|
||||
"setting__other_other_cache": "其他缓存管理",
|
||||
"setting__other_other_source_clear_btn": "清理换源歌曲缓存",
|
||||
"setting__other_other_source_label": "换源歌曲信息数量:",
|
||||
"setting__other_resource_cache": "资源缓存管理",
|
||||
"setting__other_resource_cache_clear_btn": "清理资源缓存",
|
||||
"setting__other_resource_cache_confirm": "我要清掉",
|
||||
@@ -364,7 +419,7 @@
|
||||
"setting__play_detail_font_size_reset": "重置",
|
||||
"setting__play_detail_font_zoom": "缩放当前正在播放的歌词",
|
||||
"setting__play_detail_lyric_progress": "允许通过歌词调整播放进度",
|
||||
"setting__play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果可用)",
|
||||
"setting__play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果可用,此功能比较耗性能,低配置电脑不建议开启)",
|
||||
"setting__play_lyric_roma": "显示歌词罗马音(如果可用)",
|
||||
"setting__play_lyric_s2t": "将播放与下载的歌词转换为繁体中文",
|
||||
"setting__play_lyric_transition": "显示歌词翻译(如果可用)",
|
||||
@@ -375,6 +430,7 @@
|
||||
"setting__play_media_device_tip": "此功能与音频可视化功能冲突,两者无法同时启用,是否将音频可视化关闭 并 应用所选音频输出设置?",
|
||||
"setting__play_quality": "优先播放320K品质的歌曲(如果可用)",
|
||||
"setting__play_save_play_time": "记住播放进度",
|
||||
"setting__play_startup_auto_play": "启动软件后自动播放音乐",
|
||||
"setting__play_task_bar": "在任务栏上显示当前歌曲播放进度",
|
||||
"setting__play_timeout": "定时暂停",
|
||||
"setting__player_audio_visualization_tip": "自定义音频输出设备与音频可视化功能会冲突,启用了音频可视化后音频输出设备将会被重置为默认的输出设备,目前此问题暂无法解决,是否仍要开启?",
|
||||
@@ -402,11 +458,16 @@
|
||||
"setting__update_progress": "下载进度:",
|
||||
"setting__update_unknown": "未知",
|
||||
"song_list": "歌单",
|
||||
"songlist__import_input_btn_confirm": "打开",
|
||||
"songlist__import_input_show_btn": "打开歌单",
|
||||
"songlist__import_input_tip": "输入歌单链接或歌单ID",
|
||||
"songlist__import_input_tip_1": "不支持跨源打开歌单,请确认要打开的歌单与当前歌单源是否对应",
|
||||
"songlist__import_input_tip_2": "若遇到无法打开的歌单链接,欢迎反馈",
|
||||
"songlist__import_input_tip_3": "酷狗源不支持用歌单ID打开,但支持酷狗码打开",
|
||||
"songlist__import_input_tip_4": "网易源的“我喜欢”歌单需要 Token 才能打开,详情看 ",
|
||||
"songlist__import_input_title": "打开分享的歌单",
|
||||
"songlist__open_list": "打开{name}歌单",
|
||||
"songlist__tag_info_hot_tag": "热门标签",
|
||||
"source_alias_all": "聚合大会",
|
||||
"source_alias_bd": "小杜音乐",
|
||||
"source_alias_kg": "小枸音乐",
|
||||
@@ -443,14 +504,40 @@
|
||||
"tag__high_quality": "HQ",
|
||||
"tag__lossless": "SQ",
|
||||
"tag__lossless_24bit": "24bit",
|
||||
"theme_add": "添加主题",
|
||||
"theme_auto": "道法自然",
|
||||
"theme_auto_tip": "鼠标 右击 可打开亮、暗主题设置窗口",
|
||||
"theme_black": "黑灯瞎火",
|
||||
"theme_blue": "蓝田生玉",
|
||||
"theme_blue2": "清热版蓝",
|
||||
"theme_blue_plus": "蛋雅深蓝",
|
||||
"theme_edit_modal__app_bg": "应用背景颜色",
|
||||
"theme_edit_modal__aside_color": "侧栏按钮颜色",
|
||||
"theme_edit_modal__badge": "标签颜色",
|
||||
"theme_edit_modal__badge_primary": "主颜色",
|
||||
"theme_edit_modal__badge_secondary": "次要颜色",
|
||||
"theme_edit_modal__badge_tertiary": "第三颜色",
|
||||
"theme_edit_modal__bg_image": "背景图片",
|
||||
"theme_edit_modal__bg_image_add": "背景图片:",
|
||||
"theme_edit_modal__bg_image_change": "更改背景图片",
|
||||
"theme_edit_modal__bg_image_remove": "移除背景图片",
|
||||
"theme_edit_modal__close_btn": "关闭",
|
||||
"theme_edit_modal__control_btn": "左侧控制按钮颜色",
|
||||
"theme_edit_modal__dark": "暗色主题",
|
||||
"theme_edit_modal__hide_btn": "隐藏播放详情页",
|
||||
"theme_edit_modal__main_bg": "内容区域背景颜色",
|
||||
"theme_edit_modal__min_btn": "最小化",
|
||||
"theme_edit_modal__pick_cancel": "重置",
|
||||
"theme_edit_modal__pick_color": "选择颜色",
|
||||
"theme_edit_modal__pick_last_color": "使用之前的颜色",
|
||||
"theme_edit_modal__pick_save": "确认",
|
||||
"theme_edit_modal__preview": "预览主题",
|
||||
"theme_edit_modal__primary": "主题色",
|
||||
"theme_edit_modal__title": "编辑主题",
|
||||
"theme_green": "绿意盎然",
|
||||
"theme_grey": "灰常美丽",
|
||||
"theme_happy_new_year": "新年快乐",
|
||||
"theme_max_tip": "最多只能添加10个主题哦,删掉一些再添加吧 😜 ",
|
||||
"theme_mid_autumn": "月里嫦娥",
|
||||
"theme_ming": "青出于黑",
|
||||
"theme_naruto": "木叶之村",
|
||||
@@ -460,9 +547,11 @@
|
||||
"theme_red": "热情似火",
|
||||
"theme_selector_modal__dark_title": "暗色主题",
|
||||
"theme_selector_modal__light_title": "亮色主题",
|
||||
"theme_selector_modal__theme_name": "主题名称",
|
||||
"theme_selector_modal__title": "跟随系统主题设置",
|
||||
"theme_selector_modal__title_tip": "注:你可以预先设置一个亮色主题及暗色主题,此后将根据系统的亮、暗主题色自动切换为你预先设置的相应主题。",
|
||||
"theme_yellow": "信口雌黄",
|
||||
"toggle_source_failed": "自动换源失败,请尝试手动在其他源搜索该歌曲播放",
|
||||
"toggle_source_try": "尝试切换到其他源...",
|
||||
"user_api__allow_show_update_alert": "允许显示更新弹窗",
|
||||
"user_api__btn_export": "导出",
|
||||
"user_api__btn_import": "导入",
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"alert_button_text": "好吧",
|
||||
"audio_visualization": "音頻可視化(實驗性)",
|
||||
"back": "返回",
|
||||
"btn_cancel": "取消",
|
||||
"btn_confirm": "確定",
|
||||
"btn_save": "保存",
|
||||
"cancel_button_text": "取消",
|
||||
"close": "關閉",
|
||||
"comment__hot_load_error": "熱門評論加載失敗,點擊嘗試重新加載",
|
||||
@@ -16,6 +19,7 @@
|
||||
"comment__refresh": "刷新評論",
|
||||
"comment__show": "歌曲評論",
|
||||
"comment__title": "{name} 的評論",
|
||||
"comment__unavailable": "此歌曲不支持獲取評論",
|
||||
"confirm_button_text": "是的",
|
||||
"copy_tip": "(點擊複製)",
|
||||
"date_format_hour": "{num}小時前",
|
||||
@@ -26,8 +30,8 @@
|
||||
"default_list": "試聽列表",
|
||||
"desktop_lyric__back": "返回",
|
||||
"desktop_lyric__close": "關閉",
|
||||
"desktop_lyric__font_decrease": "減小字體大小(右擊可微調)",
|
||||
"desktop_lyric__font_increase": "增加字體大小(右擊可微調)",
|
||||
"desktop_lyric__font_decrease": "減小字體大小",
|
||||
"desktop_lyric__font_increase": "增加字體大小",
|
||||
"desktop_lyric__lock": "鎖定歌詞",
|
||||
"desktop_lyric__lrc_active_zoom_off": "取消縮放當前播放的歌詞",
|
||||
"desktop_lyric__lrc_active_zoom_on": "縮放當前播放的歌詞",
|
||||
@@ -37,7 +41,12 @@
|
||||
"desktop_lyric__unlock": "解鎖歌詞",
|
||||
"desktop_lyric__win_top_off": "取消置頂歌詞界面",
|
||||
"desktop_lyric__win_top_on": "置頂歌詞界面",
|
||||
"download": "下載",
|
||||
"download": "下載管理",
|
||||
"download___status_complated": "下載完成",
|
||||
"download___status_error": "任務出錯",
|
||||
"download___status_paused": "暫停下載",
|
||||
"download___status_runing": "正在下載",
|
||||
"download___status_wating": "等待下載",
|
||||
"download__all": "全部任務",
|
||||
"download__error": "出錯",
|
||||
"download__finished": "下載完成",
|
||||
@@ -52,8 +61,20 @@
|
||||
"download__quality": "品質",
|
||||
"download__runing": "正在下載",
|
||||
"download__status": "狀態",
|
||||
"download_status_error_check_path": "檢查下載路徑出錯,請檢查設置的下載目錄是否正常",
|
||||
"download_status_error_refresh_url": "鏈接失效,正在刷新鏈接",
|
||||
"download_status_error_url_failed": "獲取音樂鏈接失敗",
|
||||
"download_status_error_write": "歌曲保存位置被佔用或沒有寫入權限,請嘗試更改歌曲保存目錄或重啟軟件或重啟電腦,錯誤詳情:",
|
||||
"download_status_start": "開始下載",
|
||||
"download_status_url_geting": "音樂鏈接獲取中...",
|
||||
"duplicate_list_tip": "你之前已收藏過該列表 [{name}],是否需要更新里面的歌曲?",
|
||||
"export": "導出",
|
||||
"fullscreen_exit": "退出全屏",
|
||||
"theme_edit_modal__copy": "複製主題",
|
||||
"theme_edit_modal__remove": "刪除",
|
||||
"theme_edit_modal__remove_tip": "是否真的要移除這個主題?",
|
||||
"theme_edit_modal__save_new": "另存",
|
||||
"theme_edit_modal__select_bg_file": "選擇背景圖片",
|
||||
"history_clear": "清空搜索歷史",
|
||||
"history_remove": "右擊移除該歷史",
|
||||
"history_search": "歷史搜索",
|
||||
@@ -91,7 +112,7 @@
|
||||
"list_add__btn_title": "把該歌曲添加到 {name}",
|
||||
"list_add__multiple_btn_title": "把這些歌曲添加到 {name}",
|
||||
"list_add__multiple_title_add": "添加已選的 {num} 首歌曲到...",
|
||||
"list_add__multiple_title_move": "移動已選的 {num} 首歌曲到...",
|
||||
"list_add__multiple_title_move": "添加移動已選的 {num} 首歌曲到...",
|
||||
"list_add__title_first_add": "添加",
|
||||
"list_add__title_first_move": "移動",
|
||||
"list_add__title_last": "到...",
|
||||
@@ -114,6 +135,7 @@
|
||||
"list_update_modal__tips": "💡 每次啟動軟件時將會自動更新已勾選“自動更新”的列表",
|
||||
"list_update_modal__title": "列表更新管理",
|
||||
"list_update_modal__update": "立即更新",
|
||||
"lists__add_local_file_desc": "選擇歌曲文件",
|
||||
"lists__duplicate": "重複歌曲",
|
||||
"lists__export": "導出",
|
||||
"lists__export_part_desc": "選擇列表文件保存位置",
|
||||
@@ -129,14 +151,15 @@
|
||||
"lists__remove_tip": "你真的想要移除 {name} 嗎?",
|
||||
"lists__remove_tip_button": "是的 沒錯",
|
||||
"lists__rename": "重命名",
|
||||
"lists__select_local_file": "添加本地歌曲",
|
||||
"lists__sort_list": "排序歌曲",
|
||||
"lists__source_detail": "歌單詳情頁",
|
||||
"lists__sync": "更新",
|
||||
"lists__sync_confirm_tip": "這將會把 {name} 內的歌曲替換成在線列表的歌曲,你確認要更新嗎?",
|
||||
"load_list_file_error_detail": "我們已經幫你把舊的列表文件備份到{path}\n它以 JSON 格式存儲,你可以嘗試手動修復並恢復它\n\n錯誤詳情:{detail}",
|
||||
"load_list_file_error_title": "播放列表數據加載錯誤(建議到GitHub或加群反饋)",
|
||||
"load_list_file_error_title": "播放列表數據加載錯誤",
|
||||
"loding": "加載中...",
|
||||
"love_list": "收藏",
|
||||
"love_list": "收藏列表",
|
||||
"lyric__load_error": "歌詞獲取失敗",
|
||||
"lyric__select": "歌詞文本選擇",
|
||||
"lyric_menu__align": "歌詞對齊方式",
|
||||
@@ -144,11 +167,11 @@
|
||||
"lyric_menu__align_left": "居左",
|
||||
"lyric_menu__lrc_size": "字體大小 [ {size} ]",
|
||||
"lyric_menu__offset": "歌詞偏移 [ {offset}ms ]",
|
||||
"lyric_menu__offset_add_10": "加快10毫秒",
|
||||
"lyric_menu__offset_add_10": "加快10毫秒(右擊加快5毫秒)",
|
||||
"lyric_menu__offset_add_100": "加快100毫秒",
|
||||
"lyric_menu__offset_dec_10": "減慢10毫秒",
|
||||
"lyric_menu__offset_dec_100": "減慢100毫秒",
|
||||
"lyric_menu__offset_reset": "重置",
|
||||
"lyric_menu__offset_reset": "重置偏移",
|
||||
"lyric_menu__size_add": "加大字體(右擊可微調)",
|
||||
"lyric_menu__size_dec": "減小字體(右擊可微調)",
|
||||
"lyric_menu__size_reset": "重置",
|
||||
@@ -157,8 +180,6 @@
|
||||
"music_duplicate": "重複歌曲",
|
||||
"music_name": "歌曲名",
|
||||
"music_singer": "藝術家",
|
||||
"music_sort__btn_cancel": "取消",
|
||||
"music_sort__btn_confirm": "確定",
|
||||
"music_sort__input_tip": "請輸入要調整到第幾個位置",
|
||||
"music_sort__title": "將 {name} 的位置調整到:",
|
||||
"music_sort__title_multiple": "將已選的 {num} 首歌曲的位置調整到:",
|
||||
@@ -179,6 +200,7 @@
|
||||
"play_timeout_unit": "分鐘",
|
||||
"play_timeout_update": "更新定時",
|
||||
"player__add_music_to": "添加當前歌曲到...",
|
||||
"player__album": "專輯名:",
|
||||
"player__buffering": "緩衝中...",
|
||||
"player__desktop_lyric_lock": "右擊鎖定歌詞",
|
||||
"player__desktop_lyric_off": "關閉桌面歌詞",
|
||||
@@ -189,7 +211,6 @@
|
||||
"player__geting_url": "歌曲鏈接獲取中...",
|
||||
"player__hide_detail_tip": "隱藏詳情頁(界面內右鍵雙擊可快速隱藏詳情頁)",
|
||||
"player__loading": "音樂加載中...",
|
||||
"player__music_album": "專輯名:",
|
||||
"player__music_name": "歌曲名:",
|
||||
"player__music_singer": "藝術家:",
|
||||
"player__next": "下一首",
|
||||
@@ -208,6 +229,8 @@
|
||||
"player__volume": "當前音量:",
|
||||
"search": "搜索",
|
||||
"search__hot_search": "熱門搜索",
|
||||
"search__type_music": "歌曲",
|
||||
"search__type_songlist": "歌單",
|
||||
"search__welcome": "搜我所想~~😉",
|
||||
"setting": "設置",
|
||||
"setting__about": "關於洛雪音樂",
|
||||
@@ -228,6 +251,7 @@
|
||||
"setting__backup_part_export_setting": "導出設置",
|
||||
"setting__backup_part_export_setting_desc": "選擇設置保存位置",
|
||||
"setting__backup_part_import_list": "導入列表",
|
||||
"setting__backup_part_import_list_confirm": "備份文件中列表與現有列表ID相同時,現有列表內歌曲將會被覆蓋,是否要繼續?",
|
||||
"setting__backup_part_import_list_desc": "選擇列表文件",
|
||||
"setting__backup_part_import_setting": "導入設置",
|
||||
"setting__backup_part_import_setting_desc": "選擇配置文件",
|
||||
@@ -237,6 +261,13 @@
|
||||
"setting__basic_control_btn_position_left": "左邊",
|
||||
"setting__basic_control_btn_position_right": "右邊",
|
||||
"setting__basic_font": "字體",
|
||||
"setting__basic_font_size": "字體大小",
|
||||
"setting__basic_font_size_14px": "較小",
|
||||
"setting__basic_font_size_15px": "較小",
|
||||
"setting__basic_font_size_16px": "標準",
|
||||
"setting__basic_font_size_17px": "大",
|
||||
"setting__basic_font_size_18px": "較大",
|
||||
"setting__basic_font_size_19px": "非常大",
|
||||
"setting__basic_lang": "語言",
|
||||
"setting__basic_lang_title": "軟件顯示的語言",
|
||||
"setting__basic_show_animation": "顯示切換動畫",
|
||||
@@ -246,7 +277,6 @@
|
||||
"setting__basic_source_status_success": "初始化成功",
|
||||
"setting__basic_source_temp": "臨時接口(軟件的某些功能不可用,建議測試接口不可用再使用本接口)",
|
||||
"setting__basic_source_test": "測試接口(幾乎軟件的所有功能都可用)",
|
||||
"setting__basic_source_title": "選擇音樂來源",
|
||||
"setting__basic_source_user_api_btn": "自定義源管理",
|
||||
"setting__basic_sourcename": "音源名字",
|
||||
"setting__basic_sourcename_alias": "別名",
|
||||
@@ -268,15 +298,29 @@
|
||||
"setting__click_copy": "點擊複製",
|
||||
"setting__click_open": "點擊打開",
|
||||
"setting__desktop_lyric": "桌面歌詞設置",
|
||||
"setting__desktop_lyric_align": "歌詞對齊方式",
|
||||
"setting__desktop_lyric_align_center": "居中",
|
||||
"setting__desktop_lyric_align_left": "居左",
|
||||
"setting__desktop_lyric_align_right": "居右",
|
||||
"setting__desktop_lyric_always_on_top": "使歌詞總是在其他窗口之上",
|
||||
"setting__desktop_lyric_always_on_top_loop": "自動刷新歌詞置頂(當歌詞置頂後仍被某些程序遮擋時可嘗試啟用此設置)",
|
||||
"setting__desktop_lyric_audio_visualization": "音頻可視化(實驗性)",
|
||||
"setting__desktop_lyric_color_reset": "重置顏色",
|
||||
"setting__desktop_lyric_delay_scroll": "延遲歌詞滾動",
|
||||
"setting__desktop_lyric_direction": "歌詞顯示方向",
|
||||
"setting__desktop_lyric_direction_horizontal": "水平方向",
|
||||
"setting__desktop_lyric_direction_vertical": "垂直方向",
|
||||
"setting__desktop_lyric_ellipsis": "不允許桌面歌詞換行",
|
||||
"setting__desktop_lyric_enable": "顯示歌詞",
|
||||
"setting__desktop_lyric_font": "歌詞字體",
|
||||
"setting__desktop_lyric_font_default": "默認",
|
||||
"setting__desktop_lyric_font_weight": "加粗字體",
|
||||
"setting__desktop_lyric_hover_hide": "鼠標移入歌詞區域時降低歌詞透明度(此功能存在平台兼容性問題)",
|
||||
"setting__desktop_lyric_lock": "鎖定歌詞",
|
||||
"setting__desktop_lyric_lock_screen": "不允許歌詞窗口拖出主屏幕之外",
|
||||
"setting__desktop_lyric_played_color": "已播放顏色",
|
||||
"setting__desktop_lyric_shadow_color": "陰影顏色",
|
||||
"setting__desktop_lyric_unplay_color": "未播放顏色",
|
||||
"setting__download": "下載設置",
|
||||
"setting__download_data_embed": "是否將以下內容嵌入到音頻文件中",
|
||||
"setting__download_embed_lyric": "歌詞嵌入",
|
||||
@@ -297,7 +341,9 @@
|
||||
"setting__download_path_label": "當前下載路徑:",
|
||||
"setting__download_path_open_label": "點擊打開當前路徑",
|
||||
"setting__download_path_title": "下載歌曲保存的路徑",
|
||||
"setting__download_rlyric": "同時將羅馬音歌詞寫入歌詞文件中(如果有)",
|
||||
"setting__download_select_save_path": "選擇歌曲保存路徑",
|
||||
"setting__download_tlyric": "同時將翻譯歌詞寫入歌詞文件中(如果有)",
|
||||
"setting__download_use_other_source": "自動換源下載",
|
||||
"setting__download_use_other_source_tip": "當無法從歌曲的原始源下載時,嘗試切換到其他源下載,注:此功能不100%保證換源後的歌曲版本與原版一致",
|
||||
"setting__hot_key": "快捷鍵設置",
|
||||
@@ -313,7 +359,7 @@
|
||||
"setting__hot_key_local_title": "軟件內快捷鍵",
|
||||
"setting__hot_key_player_next": "下一首歌曲",
|
||||
"setting__hot_key_player_prev": "上一首歌曲",
|
||||
"setting__hot_key_player_toggle_play": "播放/暫停控制",
|
||||
"setting__hot_key_player_toggle_play": "播放/暫停控制",
|
||||
"setting__hot_key_player_volume_down": "減少音量",
|
||||
"setting__hot_key_player_volume_mute": "靜音切換",
|
||||
"setting__hot_key_player_volume_up": "增加音量",
|
||||
@@ -322,6 +368,7 @@
|
||||
"setting__is_enable": "是否啟用",
|
||||
"setting__is_show": "是否顯示",
|
||||
"setting__list": "列表設置",
|
||||
"setting__list_action_btn": "顯示列表操作按鈕",
|
||||
"setting__list_add_music_location_type": "添加歌曲到列表時的位置",
|
||||
"setting__list_add_music_location_type_bottom": "底部",
|
||||
"setting__list_add_music_location_type_top": "頂部",
|
||||
@@ -338,10 +385,17 @@
|
||||
"setting__odc_clear_search_input": "離開搜索界面時清空搜索框",
|
||||
"setting__odc_clear_search_list": "離開搜索界面時清空搜索列表",
|
||||
"setting__other": "其他",
|
||||
"setting__other_play_list_cache": "列表緩存管理",
|
||||
"setting__other_play_list_cache_clear_btn": "清理列表緩存信息",
|
||||
"setting__other_play_list_cache_tip": "我的列表中已緩存的歌曲鏈接、播放代替源,清理後播放、下載歌曲時需要重新獲取,沒有歌曲播放相關的問題不要清理",
|
||||
"setting__other_play_list_cache_tip_confirm": "我的列表中已緩存的歌曲鏈接、播放代替源,清理後播放、下載歌曲時需要重新獲取,沒有歌曲播放相關的問題不要清理,是否確認清理?",
|
||||
"setting__other_lyric_edited_cache": "已調整過偏移時間的歌詞管理",
|
||||
"setting__other_lyric_edited_clear_btn": "清理已調整過時間的歌詞",
|
||||
"setting__other_lyric_edited_clear_tip_confirm": "這將清理所有你之前已調整過偏移時間的歌詞,是否確認清理? \n(手抖確認🤪)",
|
||||
"setting__other_lyric_edited_label": "歌詞數量:",
|
||||
"setting__other_lyric_raw_clear_btn": "清理歌詞緩存",
|
||||
"setting__other_lyric_raw_label": "歌詞數量:",
|
||||
"setting__other_music_url_clear_btn": "清理歌曲URL緩存",
|
||||
"setting__other_music_url_label": "歌曲URL數量:",
|
||||
"setting__other_other_cache": "其他緩存管理",
|
||||
"setting__other_other_source_clear_btn": "清理換源歌曲緩存",
|
||||
"setting__other_other_source_label": "換源歌曲信息數量:",
|
||||
"setting__other_resource_cache": "資源緩存管理",
|
||||
"setting__other_resource_cache_clear_btn": "清理資源緩存",
|
||||
"setting__other_resource_cache_confirm": "我要清掉",
|
||||
@@ -364,7 +418,7 @@
|
||||
"setting__play_detail_font_size_reset": "重置",
|
||||
"setting__play_detail_font_zoom": "縮放當前正在播放的歌詞",
|
||||
"setting__play_detail_lyric_progress": "允許通過歌詞調整播放進度",
|
||||
"setting__play_lyric_lxlrc": "使用卡拉OK式歌詞播放(如果可用)",
|
||||
"setting__play_lyric_lxlrc": "使用卡拉OK式歌詞播放(如果可用,此功能比較耗性能,低配置電腦不建議開啟)",
|
||||
"setting__play_lyric_roma": "顯示歌詞羅馬音(如果可用)",
|
||||
"setting__play_lyric_s2t": "將播放與下載的歌詞轉換為繁體中文",
|
||||
"setting__play_lyric_transition": "顯示歌詞翻譯(如果可用)",
|
||||
@@ -375,6 +429,7 @@
|
||||
"setting__play_media_device_tip": "此功能與音頻可視化功能衝突,兩者無法同時啟用,是否將音頻可視化關閉 並 應用所選音頻輸出設置?",
|
||||
"setting__play_quality": "優先播放320K品質的歌曲(如果可用)",
|
||||
"setting__play_save_play_time": "記住播放進度",
|
||||
"setting__play_startup_auto_play": "啟動軟件後自動播放音樂",
|
||||
"setting__play_task_bar": "在任務欄上顯示當前歌曲播放進度",
|
||||
"setting__play_timeout": "定時暫停",
|
||||
"setting__player_audio_visualization_tip": "自定義音頻輸出設備與音頻可視化功能會衝突,啟用了音頻可視化後音頻輸出設備將會被重置為默認的輸出設備,目前此問題暫無法解決,是否仍要開啟?",
|
||||
@@ -402,11 +457,15 @@
|
||||
"setting__update_progress": "下載進度:",
|
||||
"setting__update_unknown": "未知",
|
||||
"song_list": "歌單",
|
||||
"songlist__import_input_show_btn": "打開歌單",
|
||||
"songlist__import_input_tip": "輸入歌單鏈接或歌單ID",
|
||||
"songlist__import_input_tip_1": "不支持跨源打開歌單,請確認要打開的歌單與當前歌單源是否對應",
|
||||
"songlist__import_input_tip_2": "若遇到無法打開的歌單鏈接,歡迎反饋",
|
||||
"songlist__import_input_tip_3": "酷狗源不支持用歌單ID打開,但支持酷狗碼打開",
|
||||
"songlist__import_input_tip_4": "網易源的“我喜歡”歌單需要 Token 才能打開,詳情看",
|
||||
"songlist__import_input_title": "打開分享的歌單",
|
||||
"songlist__open_list": "打開{name}歌單",
|
||||
"songlist__tag_info_hot_tag": "熱門標籤",
|
||||
"source_alias_all": "聚合大會",
|
||||
"source_alias_bd": "小杜音樂",
|
||||
"source_alias_kg": "小枸音樂",
|
||||
@@ -443,14 +502,40 @@
|
||||
"tag__high_quality": "HQ",
|
||||
"tag__lossless": "SQ",
|
||||
"tag__lossless_24bit": "24bit",
|
||||
"theme_add": "添加主題",
|
||||
"theme_auto": "道法自然",
|
||||
"theme_auto_tip": "鼠標 右擊 可打開亮、暗主題設置窗口",
|
||||
"theme_black": "黑燈瞎火",
|
||||
"theme_blue": "藍田生玉",
|
||||
"theme_blue2": "清熱版藍",
|
||||
"theme_blue_plus": "蛋雅深藍",
|
||||
"theme_edit_modal__app_bg": "應用背景顏色",
|
||||
"theme_edit_modal__aside_color": "側欄按鈕顏色",
|
||||
"theme_edit_modal__badge": "標籤顏色",
|
||||
"theme_edit_modal__badge_primary": "主顏色",
|
||||
"theme_edit_modal__badge_secondary": "次要顏色",
|
||||
"theme_edit_modal__badge_tertiary": "第三顏色",
|
||||
"theme_edit_modal__bg_image": "背景圖片",
|
||||
"theme_edit_modal__bg_image_add": "添加背景圖片",
|
||||
"theme_edit_modal__bg_image_change": "更改背景圖片",
|
||||
"theme_edit_modal__bg_image_remove": "移除背景圖片",
|
||||
"theme_edit_modal__close_btn": "關閉",
|
||||
"theme_edit_modal__control_btn": "左側控制按鈕顏色",
|
||||
"theme_edit_modal__dark": "暗色主題",
|
||||
"theme_edit_modal__hide_btn": "隱藏播放詳情頁",
|
||||
"theme_edit_modal__main_bg": "內容區域背景顏色",
|
||||
"theme_edit_modal__min_btn": "最小化",
|
||||
"theme_edit_modal__pick_cancel": "取消",
|
||||
"theme_edit_modal__pick_color": "選擇顏色",
|
||||
"theme_edit_modal__pick_last_color": "使用之前的顏色",
|
||||
"theme_edit_modal__pick_save": "確認",
|
||||
"theme_edit_modal__preview": "預覽主題",
|
||||
"theme_edit_modal__primary": "主題色",
|
||||
"theme_edit_modal__title": "編輯主題",
|
||||
"theme_green": "綠意盎然",
|
||||
"theme_grey": "灰常美麗",
|
||||
"theme_happy_new_year": "新年快樂",
|
||||
"theme_max_tip": "最多只能添加10個主題哦,刪掉一些再添加吧 😜",
|
||||
"theme_mid_autumn": "月裡嫦娥",
|
||||
"theme_ming": "青出於黑",
|
||||
"theme_naruto": "木葉之村",
|
||||
@@ -460,9 +545,11 @@
|
||||
"theme_red": "熱情似火",
|
||||
"theme_selector_modal__dark_title": "暗色主題",
|
||||
"theme_selector_modal__light_title": "亮色主題",
|
||||
"theme_selector_modal__theme_name": "主題名稱",
|
||||
"theme_selector_modal__title": "跟隨系統主題設置",
|
||||
"theme_selector_modal__title_tip": "注:你可以預先設置一個亮色主題及暗色主題,此後將根據系統的亮、暗主題色自動切換為你預先設置的相應主題。",
|
||||
"theme_yellow": "信口雌黃",
|
||||
"toggle_source_failed": "自動換源失敗,請嘗試手動在其他源搜索該歌曲播放",
|
||||
"toggle_source_try": "嘗試切換到其他源...",
|
||||
"user_api__allow_show_update_alert": "允許顯示更新彈窗",
|
||||
"user_api__btn_export": "導出",
|
||||
"user_api__btn_import": "導入",
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
import { join, dirname } from 'path'
|
||||
import { existsSync, mkdirSync } from 'fs'
|
||||
import { app, shell, screen, nativeTheme } from 'electron'
|
||||
import { URL_SCHEME_RXP } from '@common/constants'
|
||||
import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
|
||||
import { navigationUrlWhiteList } from '@common/config'
|
||||
import defaultSetting from '@common/defaultSetting'
|
||||
import { closeWindow, isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
|
||||
import { createAppEvent, createListEvent } from '@main/event'
|
||||
import { encodePath, isMac, log } from '@common/utils'
|
||||
import createWorkers from './worker'
|
||||
import { migrateDBData } from './utils/migrate'
|
||||
|
||||
export const initGlobalData = () => {
|
||||
global.isDev = process.env.NODE_ENV !== 'production'
|
||||
const envParams = parseEnvParams()
|
||||
global.envParams = {
|
||||
cmdParams: envParams.cmdParams,
|
||||
deeplink: envParams.deeplink,
|
||||
}
|
||||
|
||||
if (global.isDev) {
|
||||
// eslint-disable-next-line no-undef
|
||||
global.staticPath = webpackStaticPath
|
||||
} else {
|
||||
global.staticPath = join(__dirname, '/static')
|
||||
}
|
||||
}
|
||||
|
||||
export const initSingleInstanceHandle = () => {
|
||||
// 单例应用程序
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
app.on('second-instance', (event, argv, cwd) => {
|
||||
for (const param of argv) {
|
||||
if (URL_SCHEME_RXP.test(param)) {
|
||||
global.envParams.deeplink = param
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (isExistMainWindow()) {
|
||||
if (global.envParams.deeplink) global.lx.event_app.deeplink(global.envParams.deeplink)
|
||||
else showMainWindow()
|
||||
} else {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const applyElectronEnvParams = () => {
|
||||
// Is disable hardware acceleration
|
||||
if (global.envParams.cmdParams.dha) app.disableHardwareAcceleration()
|
||||
if (global.envParams.cmdParams.dhmkh) app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling')
|
||||
|
||||
// fix linux transparent fail. https://github.com/electron/electron/issues/25153#issuecomment-843688494
|
||||
if (process.platform == 'linux') app.commandLine.appendSwitch('use-gl', 'desktop')
|
||||
|
||||
// https://github.com/electron/electron/issues/22691
|
||||
app.commandLine.appendSwitch('wm-window-animations-disabled')
|
||||
|
||||
// proxy
|
||||
if (global.envParams.cmdParams['proxy-server']) {
|
||||
app.commandLine.appendSwitch('proxy-server', global.envParams.cmdParams['proxy-server'])
|
||||
app.commandLine.appendSwitch('proxy-bypass-list', global.envParams.cmdParams['proxy-bypass-list'] ?? '<local>')
|
||||
}
|
||||
}
|
||||
|
||||
export const setUserDataPath = () => {
|
||||
// windows平台下如果应用目录下存在 portable 文件夹则将数据存在此文件下
|
||||
if (process.platform == 'win32') {
|
||||
const portablePath = join(dirname(app.getPath('exe')), '/portable')
|
||||
if (existsSync(portablePath)) {
|
||||
app.setPath('appData', portablePath)
|
||||
const appDataPath = join(portablePath, '/userData')
|
||||
if (!existsSync(appDataPath)) mkdirSync(appDataPath)
|
||||
app.setPath('userData', appDataPath)
|
||||
}
|
||||
}
|
||||
|
||||
const userDataPath = app.getPath('userData')
|
||||
global.lxDataPath = join(encodePath(userDataPath.replaceAll('\\', '/')), 'LxDatas')
|
||||
global.lxOldDataPath = encodePath(userDataPath.replaceAll('\\', '/'))
|
||||
if (!existsSync(global.lxDataPath)) mkdirSync(global.lxDataPath)
|
||||
}
|
||||
|
||||
export const registerDeeplink = (startApp: () => void) => {
|
||||
if (global.isDev && process.platform === 'win32') {
|
||||
// Set the path of electron.exe and your app.
|
||||
// These two additional parameters are only available on windows.
|
||||
// console.log(process.execPath, process.argv)
|
||||
app.setAsDefaultProtocolClient('lxmusic', process.execPath, process.argv.slice(1))
|
||||
} else {
|
||||
app.setAsDefaultProtocolClient('lxmusic')
|
||||
}
|
||||
|
||||
// deep link
|
||||
app.on('open-url', (event, url) => {
|
||||
if (!URL_SCHEME_RXP.test(url)) return
|
||||
event.preventDefault()
|
||||
global.envParams.deeplink = url
|
||||
if (isExistMainWindow()) {
|
||||
if (global.envParams.deeplink) global.lx.event_app.deeplink(global.envParams.deeplink)
|
||||
else showMainWindow()
|
||||
} else {
|
||||
startApp()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const listenerAppEvent = (startApp: () => void) => {
|
||||
app.on('web-contents-created', (event, contents) => {
|
||||
contents.on('will-navigate', (event, navigationUrl) => {
|
||||
if (global.isDev) return console.log('navigation to url:', navigationUrl)
|
||||
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) return event.preventDefault()
|
||||
console.log('navigation to url:', navigationUrl)
|
||||
})
|
||||
contents.setWindowOpenHandler(({ url }) => {
|
||||
if (!/^devtools/.test(url) && /^https?:\/\//.test(url)) {
|
||||
void shell.openExternal(url)
|
||||
}
|
||||
console.log(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
||||
// Strip away preload scripts if unused or verify their location is legitimate
|
||||
delete webPreferences.preload
|
||||
// delete webPreferences.preloadURL
|
||||
|
||||
// Disable Node.js integration
|
||||
webPreferences.nodeIntegration = false
|
||||
|
||||
// Verify URL being loaded
|
||||
if (!navigationUrlWhiteList.some(url => url.test(params.src))) {
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
// disable create dictionary
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/773
|
||||
contents.session.setSpellCheckerDictionaryDownloadURL('http://0.0.0.0')
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (isExistMainWindow()) {
|
||||
showMainWindow()
|
||||
} else {
|
||||
startApp()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (isMac) return
|
||||
|
||||
app.quit()
|
||||
})
|
||||
|
||||
const initScreenParams = () => {
|
||||
global.envParams.workAreaSize = screen.getPrimaryDisplay().workAreaSize
|
||||
}
|
||||
app.on('ready', () => {
|
||||
screen.on('display-metrics-changed', initScreenParams)
|
||||
initScreenParams()
|
||||
})
|
||||
|
||||
nativeTheme.addListener('updated', (event: any) => {
|
||||
const themeInfo: Electron.NativeTheme = event.sender
|
||||
global.lx.event_app.system_theme_change(themeInfo.shouldUseDarkColors)
|
||||
})
|
||||
}
|
||||
|
||||
const initTheme = () => {
|
||||
global.lx.theme = getTheme()
|
||||
const themeConfigKeys = ['theme.id', 'theme.lightId', 'theme.darkId']
|
||||
global.lx.event_app.on('updated_config', (keys) => {
|
||||
let requireUpdate = false
|
||||
for (const key of keys) {
|
||||
if (themeConfigKeys.includes(key)) {
|
||||
requireUpdate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (requireUpdate) {
|
||||
global.lx.theme = getTheme()
|
||||
global.lx.event_app.theme_change()
|
||||
}
|
||||
})
|
||||
global.lx.event_app.on('system_theme_change', () => {
|
||||
if (global.lx.appSetting['theme.id'] == 'auto') {
|
||||
global.lx.theme = getTheme()
|
||||
global.lx.event_app.theme_change()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let isInitialized = false
|
||||
export const initAppSetting = async() => {
|
||||
if (!global.lx) {
|
||||
const config = await initHotKey()
|
||||
global.lx = {
|
||||
isTrafficLightClose: false,
|
||||
isQuitting: false,
|
||||
event_app: createAppEvent(),
|
||||
event_list: createListEvent(),
|
||||
appSetting: defaultSetting,
|
||||
worker: createWorkers(),
|
||||
hotKey: {
|
||||
enable: true,
|
||||
config: {
|
||||
local: config.local,
|
||||
global: config.global,
|
||||
},
|
||||
state: new Map(),
|
||||
},
|
||||
theme: {
|
||||
shouldUseDarkColors: false,
|
||||
theme: {
|
||||
id: '',
|
||||
name: '',
|
||||
isDark: false,
|
||||
colors: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (!isInitialized) {
|
||||
const dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
|
||||
global.lx.appSetting = (await initSetting()).setting
|
||||
if (!dbFileExists) await migrateDBData().catch(err => log.error(err))
|
||||
initTheme()
|
||||
}
|
||||
// global.lx.theme = getTheme()
|
||||
|
||||
isInitialized = true
|
||||
}
|
||||
|
||||
export const quitApp = () => {
|
||||
global.lx.isQuitting = true
|
||||
closeWindow()
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
const urlSchemeRxp = /^lxmusic:\/\//
|
||||
|
||||
const parseEnv = () => {
|
||||
const params = {}
|
||||
const rx = /^-\w+/
|
||||
for (let param of process.argv) {
|
||||
if (urlSchemeRxp.test(param)) {
|
||||
global.envParams.deeplink = param
|
||||
}
|
||||
|
||||
if (!rx.test(param)) continue
|
||||
param = param.substring(1)
|
||||
let index = param.indexOf('=')
|
||||
if (index < 0) {
|
||||
params[param] = true
|
||||
} else {
|
||||
params[param.substring(0, index)] = param.substring(index + 1)
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
global.envParams.cmdParams = parseEnv()
|
||||
@@ -1,4 +0,0 @@
|
||||
global.envParams = {}
|
||||
|
||||
require('./cmdParams')
|
||||
require('./screen')
|
||||
@@ -1,11 +0,0 @@
|
||||
const { app, screen } = require('electron')
|
||||
|
||||
const initScreenParams = () => {
|
||||
global.envParams.workAreaSize = screen.getPrimaryDisplay().workAreaSize
|
||||
}
|
||||
|
||||
|
||||
app.on('ready', () => {
|
||||
screen.on('display-metrics-changed', initScreenParams)
|
||||
initScreenParams()
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
const { common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = require('./events/_name')
|
||||
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('./../common/ipc')
|
||||
const { getAppHotKeyConfig } = require('./utils')
|
||||
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, name => {
|
||||
if (MAIN_WINDOW_EVENT_NAME.name === name) return
|
||||
if (global.modules.mainWindow) mainSend(global.modules.mainWindow, ipcMainWindowNames.set_config, global.appSetting)
|
||||
})
|
||||
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.initHotKey, () => {
|
||||
global.appHotKey.config = getAppHotKeyConfig()
|
||||
})
|
||||
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.quit, () => {
|
||||
global.isTrafficLightClose = false
|
||||
global.isQuitting = true
|
||||
global.modules.mainWindow.close()
|
||||
})
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.toggle_minimize, () => {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
if (!global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
global.modules.mainWindow.restore()
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.minimize()
|
||||
}
|
||||
})
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.toggle_hide, () => {
|
||||
global.modules.mainWindow.isVisible()
|
||||
? global.modules.mainWindow.hide()
|
||||
: global.modules.mainWindow.show()
|
||||
})
|
||||
@@ -0,0 +1,101 @@
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
import { saveAppHotKeyConfig, updateSetting } from '@main/utils'
|
||||
|
||||
export class Event extends EventEmitter {
|
||||
// closeAll() {
|
||||
// this.emit(COMMON_EVENT_NAME.closeAll)
|
||||
// }
|
||||
// initSetting() {
|
||||
// this.emit(COMMON_EVENT_NAME.initConfig)
|
||||
// // this.configStatus(null)
|
||||
// }
|
||||
|
||||
/**
|
||||
* 初始化APP
|
||||
*/
|
||||
app_inited() {
|
||||
this.emit('app_inited')
|
||||
}
|
||||
|
||||
/**
|
||||
* 已更新的配置
|
||||
* @param keys 已更新配置的key
|
||||
* @param setting 已更新配置
|
||||
*/
|
||||
updated_config(keys: Array<keyof LX.AppSetting>, setting: Partial<LX.AppSetting>) {
|
||||
this.emit('updated_config', keys, setting)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
* @param setting 新设置
|
||||
*/
|
||||
update_config(setting: Partial<LX.AppSetting>) {
|
||||
const { setting: newSetting, updatedSettingKeys, updatedSetting } = updateSetting(setting)
|
||||
global.lx.appSetting = newSetting
|
||||
if (!updatedSettingKeys.length) return
|
||||
this.emit('update_config', newSetting)
|
||||
// console.log(updatedSetting)
|
||||
this.updated_config(updatedSettingKeys, updatedSetting)
|
||||
}
|
||||
|
||||
system_theme_change(isDark: boolean) {
|
||||
this.emit('system_theme_change', isDark)
|
||||
}
|
||||
|
||||
theme_change() {
|
||||
this.emit('theme_change')
|
||||
}
|
||||
|
||||
deeplink(link: string) {
|
||||
this.emit('deeplink', link)
|
||||
}
|
||||
|
||||
hot_key_down(keyInfo: LX.HotKeyDownInfo) {
|
||||
this.emit('hot_key_down', keyInfo)
|
||||
}
|
||||
|
||||
hot_key_config_update(config: LX.HotKeyConfigAll) {
|
||||
saveAppHotKeyConfig(config)
|
||||
this.emit('hot_key_config_update', config)
|
||||
}
|
||||
|
||||
main_window_ready_to_show() {
|
||||
this.emit('main_window_ready_to_show')
|
||||
}
|
||||
|
||||
main_window_inited() {
|
||||
this.emit('main_window_inited')
|
||||
}
|
||||
|
||||
main_window_show() {
|
||||
this.emit('main_window_show')
|
||||
}
|
||||
|
||||
main_window_hide() {
|
||||
this.emit('main_window_hide')
|
||||
}
|
||||
|
||||
main_window_focus() {
|
||||
this.emit('main_window_focus')
|
||||
}
|
||||
|
||||
main_window_blur() {
|
||||
this.emit('main_window_blur')
|
||||
}
|
||||
|
||||
main_window_close() {
|
||||
this.emit('main_window_close')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type EventMethods = Omit<EventType, keyof EventEmitter>
|
||||
declare class EventType extends Event {
|
||||
on<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
once<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
off<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
}
|
||||
|
||||
export declare type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>
|
||||
@@ -0,0 +1,174 @@
|
||||
import { EventEmitter } from 'events'
|
||||
// import {
|
||||
// // getAllUserList as getAllUserListByDB,
|
||||
// createUserLists,
|
||||
// removeUserLists,
|
||||
// updateUserLists,
|
||||
// updateUserListsPosition,
|
||||
// musicsAdd,
|
||||
// musicsMove,
|
||||
// musicsRemove,
|
||||
// musicsUpdate,
|
||||
// musicsClear,
|
||||
// musicsPositionUpdate,
|
||||
// musicOverwrite,
|
||||
// } from '@main/workers/dbService/modules/list'
|
||||
|
||||
export class Event extends EventEmitter {
|
||||
list_changed() {
|
||||
this.emit('list_changed')
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖整个列表数据
|
||||
* @param listData 列表数据
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_data_overwrite(listData: MakeOptional<LX.List.ListDataFull, 'tempList'>, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.listDataOverwrite(listData)
|
||||
this.emit('list_data_overwrite', listData, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建列表
|
||||
* @param position 列表位置
|
||||
* @param lists 列表信息
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_create(position: number, lists: LX.List.UserListInfo[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.createUserLists(position, lists)
|
||||
this.emit('list_create', position, lists, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除列表及列表内歌曲
|
||||
* @param ids 列表ids
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_remove(ids: string[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.removeUserLists(ids)
|
||||
this.emit('list_remove', ids, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新列表信息
|
||||
* @param lists 列表信息
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_update(lists: LX.List.UserListInfo[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.updateUserLists(lists)
|
||||
this.emit('list_update', lists, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新列表位置
|
||||
* @param position 列表位置
|
||||
* @param ids 列表ids
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_update_position(position: number, ids: string[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.updateUserListsPosition(position, ids)
|
||||
this.emit('list_update_position', position, ids, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖列表内歌曲
|
||||
* @param listId 列表id
|
||||
* @param musicInfos 音乐信息
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_overwrite(listId: string, musicInfos: LX.Music.MusicInfo[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicOverwrite(listId, musicInfos)
|
||||
this.emit('list_music_overwrite', listId, musicInfos, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加歌曲到列表
|
||||
* @param listId 列表id
|
||||
* @param musicInfos 添加的歌曲信息
|
||||
* @param addMusicLocationType 添加在到列表的位置
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_add(listId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsAdd(listId, musicInfos, addMusicLocationType)
|
||||
this.emit('list_music_add', listId, musicInfos, addMusicLocationType, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量移动歌曲
|
||||
* @param fromId 源列表id
|
||||
* @param toId 目标列表id
|
||||
* @param musicInfos 移动的歌曲信息
|
||||
* @param addMusicLocationType 添加在到列表的位置
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_move(fromId: string, toId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsMove(fromId, toId, musicInfos, addMusicLocationType)
|
||||
this.emit('list_music_move', fromId, toId, musicInfos, addMusicLocationType, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量移除歌曲
|
||||
* @param listId
|
||||
* @param listId 列表Id
|
||||
* @param ids 要删除歌曲的id
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_remove(listId: string, ids: string[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsRemove(listId, ids)
|
||||
this.emit('list_music_remove', listId, ids, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新歌曲信息
|
||||
* @param musicInfos 歌曲&列表信息
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_update(musicInfos: LX.List.ListActionMusicUpdate, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsUpdate(musicInfos)
|
||||
this.emit('list_music_update', musicInfos, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空列表内的歌曲
|
||||
* @param listId 列表Id
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_clear(listId: string, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsClear(listId)
|
||||
this.emit('list_music_clear', listId, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新歌曲位置
|
||||
* @param listId 列表ID
|
||||
* @param position 新位置
|
||||
* @param ids 歌曲id
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_update_position(listId: string, position: number, ids: string[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsPositionUpdate(listId, position, ids)
|
||||
this.emit('list_music_update_position', listId, position, ids, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type EventMethods = Omit<EventType, keyof EventEmitter>
|
||||
declare class EventType extends Event {
|
||||
on<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
once<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
off<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
}
|
||||
export declare type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Event as App, Type as AppType } from './AppEvent'
|
||||
import { Event as List, Type as ListType } from './ListEvent'
|
||||
|
||||
export {
|
||||
AppType,
|
||||
ListType,
|
||||
}
|
||||
|
||||
export const createAppEvent = (): AppType => {
|
||||
return new App()
|
||||
}
|
||||
|
||||
export const createListEvent = (): ListType => {
|
||||
return new List()
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { common: COMMON_EVENT_NAME } = require('./_name')
|
||||
|
||||
class Common extends EventEmitter {
|
||||
initSetting() {
|
||||
this.emit(COMMON_EVENT_NAME.initConfig)
|
||||
this.configStatus(null)
|
||||
}
|
||||
|
||||
configStatus(name) {
|
||||
this.emit(COMMON_EVENT_NAME.configStatus, name)
|
||||
}
|
||||
|
||||
saveMyList(data) {
|
||||
this.emit(COMMON_EVENT_NAME.saveMyList, data)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Common
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { hotKey: HOT_KEY_EVENT_NAME } = require('./_name')
|
||||
const { saveAppHotKeyConfig } = require('../utils')
|
||||
|
||||
class HotKey extends EventEmitter {
|
||||
init() {
|
||||
this.emit(HOT_KEY_EVENT_NAME.init)
|
||||
}
|
||||
|
||||
saveConfig(config, source) {
|
||||
if (config) saveAppHotKeyConfig(config)
|
||||
this.emit(HOT_KEY_EVENT_NAME.config, config, source)
|
||||
}
|
||||
|
||||
keyDown(info) {
|
||||
this.emit(HOT_KEY_EVENT_NAME.keyDown, info)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HotKey
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('./_name')
|
||||
|
||||
class MainWindow extends EventEmitter {
|
||||
setLyricInfo(info) {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info)
|
||||
}
|
||||
|
||||
quit() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.quit)
|
||||
}
|
||||
|
||||
toggleMinimize() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.toggle_minimize)
|
||||
}
|
||||
|
||||
toggleHide() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.toggle_hide)
|
||||
}
|
||||
|
||||
readyToShow() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.ready_to_show)
|
||||
}
|
||||
|
||||
show() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.show)
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.focus)
|
||||
}
|
||||
|
||||
blur() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.blur)
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.hide)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MainWindow
|
||||
@@ -1,14 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { tray: TRAY_EVENT_NAME } = require('./_name')
|
||||
|
||||
class Tray extends EventEmitter {
|
||||
create() {
|
||||
this.emit(TRAY_EVENT_NAME.create)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.emit(TRAY_EVENT_NAME.destroy)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Tray
|
||||
@@ -1,15 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('./_name')
|
||||
// const { updateSetting } = require('../utils')
|
||||
|
||||
class WinLyric extends EventEmitter {
|
||||
create() {
|
||||
this.emit(WIN_LYRIC_EVENT_NAME.create)
|
||||
}
|
||||
|
||||
close() {
|
||||
this.emit(WIN_LYRIC_EVENT_NAME.close)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WinLyric
|
||||
@@ -1,38 +0,0 @@
|
||||
exports.common = {
|
||||
initConfig: 'initConfig',
|
||||
configStatus: 'config',
|
||||
saveMyList: 'saveMyList',
|
||||
}
|
||||
|
||||
exports.mainWindow = {
|
||||
name: 'mainWindow',
|
||||
setLyricInfo: 'setLyricInfo',
|
||||
destroy: 'destroy',
|
||||
quit: 'quit',
|
||||
toggle_minimize: 'toggle_minimize',
|
||||
toggle_hide: 'toggle_hide',
|
||||
ready_to_show: 'ready_to_show',
|
||||
show: 'show',
|
||||
hide: 'hide',
|
||||
focus: 'focus',
|
||||
blur: 'blur',
|
||||
}
|
||||
|
||||
exports.tray = {
|
||||
name: 'tray',
|
||||
create: 'create',
|
||||
destroy: 'destroy',
|
||||
}
|
||||
|
||||
exports.winLyric = {
|
||||
name: 'winLyric',
|
||||
create: 'create',
|
||||
close: 'close',
|
||||
}
|
||||
|
||||
exports.hotKey = {
|
||||
name: 'hotKey',
|
||||
init: 'init',
|
||||
config: 'config',
|
||||
keyDown: 'keyDown',
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
if (!global.lx_event) global.lx_event = {}
|
||||
|
||||
const Common = require('./Common')
|
||||
const MainWindow = require('./MainWindow')
|
||||
const Tray = require('./Tray')
|
||||
const WinLyric = require('./WinLyric')
|
||||
const HotKey = require('./HotKey')
|
||||
|
||||
const { Event: TaskBar } = require('../modules/taskbar')
|
||||
const { Event: UserApi } = require('../modules/userApi')
|
||||
const { Event: Sync } = require('../modules/sync')
|
||||
|
||||
if (!global.lx_event.common) global.lx_event.common = new Common()
|
||||
if (!global.lx_event.mainWindow) global.lx_event.mainWindow = new MainWindow()
|
||||
if (!global.lx_event.tray) global.lx_event.tray = new Tray()
|
||||
if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
|
||||
if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey()
|
||||
|
||||
if (!global.lx_event.taskbar) global.lx_event.taskbar = new TaskBar()
|
||||
if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi()
|
||||
if (!global.lx_event.sync) global.lx_event.sync = new Sync()
|
||||
@@ -4,9 +4,10 @@
|
||||
* modify this file, but it can be used to extend your development
|
||||
* environment.
|
||||
*/
|
||||
const electron = require('electron')
|
||||
const electronDebug = require('electron-debug')
|
||||
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer')
|
||||
|
||||
import { app } from 'electron'
|
||||
import * as electronDebug from 'electron-debug'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
// Install `electron-debug` with `devtron`
|
||||
electronDebug({
|
||||
showDevTools: true,
|
||||
@@ -14,10 +15,10 @@ electronDebug({
|
||||
})
|
||||
|
||||
// Install `vue-devtools`
|
||||
electron.app.on('ready', () => {
|
||||
app.on('ready', () => {
|
||||
installExtension(VUEJS_DEVTOOLS)
|
||||
.then(name => console.log(`Added Extension: ${name}`))
|
||||
.catch(err => console.log('An error occurred: ', err))
|
||||
.then((name: string) => console.log(`Added Extension: ${name}`))
|
||||
.catch((err: Error) => console.log('An error occurred: ', err))
|
||||
})
|
||||
|
||||
// Require `main` process to boot app
|
||||
@@ -1,250 +0,0 @@
|
||||
const { app, BrowserWindow, shell, nativeTheme } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
const urlSchemeRxp = /^lxmusic:\/\//
|
||||
|
||||
// 单例应用程序
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
process.exit(0)
|
||||
}
|
||||
if (!global.modules) global.modules = {}
|
||||
app.on('second-instance', (event, argv, cwd) => {
|
||||
for (const param of argv) {
|
||||
if (urlSchemeRxp.test(param)) {
|
||||
global.envParams.deeplink = param
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (global.modules.mainWindow) {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
global.modules.mainWindow.restore()
|
||||
}
|
||||
if (global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
} else {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
// windows平台下如果应用目录下存在 portable 文件夹则将数据存在此文件下
|
||||
if (process.platform === 'win32') {
|
||||
const fs = require('fs')
|
||||
const portablePath = path.join(path.dirname(app.getPath('exe')), '/portable')
|
||||
if (fs.existsSync(portablePath)) {
|
||||
app.setPath('appData', portablePath)
|
||||
const appDataPath = path.join(portablePath, '/userData')
|
||||
if (!fs.existsSync(appDataPath)) fs.mkdirSync(appDataPath)
|
||||
app.setPath('userData', appDataPath)
|
||||
}
|
||||
}
|
||||
|
||||
const isDev = global.isDev = process.env.NODE_ENV !== 'production'
|
||||
require('./env')
|
||||
// console.log(global.envParams.cmdParams)
|
||||
|
||||
// Is disable hardware acceleration
|
||||
if (global.envParams.cmdParams.dha) app.disableHardwareAcceleration()
|
||||
|
||||
if (global.envParams.cmdParams.dt == null && global.envParams.cmdParams.nt != null) global.envParams.cmdParams.dt = global.envParams.cmdParams.nt
|
||||
if (global.envParams.cmdParams.dhmkh) app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling')
|
||||
// fix linux transparent fail. https://github.com/electron/electron/issues/25153#issuecomment-843688494
|
||||
if (process.platform == 'linux') app.commandLine.appendSwitch('use-gl', 'desktop')
|
||||
|
||||
// https://github.com/electron/electron/issues/22691
|
||||
app.commandLine.appendSwitch('wm-window-animations-disabled')
|
||||
|
||||
// proxy
|
||||
if (global.envParams.cmdParams['proxy-server']) {
|
||||
app.commandLine.appendSwitch('proxy-server', global.envParams.cmdParams['proxy-server'])
|
||||
app.commandLine.appendSwitch('proxy-bypass-list', global.envParams.cmdParams['proxy-bypass-list'] ?? '<local>')
|
||||
}
|
||||
// if (global.envParams.cmdParams['proxy-pac-url']) app.commandLine.appendSwitch('proxy-pac-url', global.envParams.cmdParams['proxy-pac-url'])
|
||||
|
||||
// deep link
|
||||
app.on('open-url', (event, url) => {
|
||||
if (!urlSchemeRxp.test(url)) return
|
||||
event.preventDefault()
|
||||
global.envParams.deeplink = url
|
||||
if (global.modules.mainWindow) {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
global.modules.mainWindow.restore()
|
||||
}
|
||||
if (global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
} else if (global.modules.mainWindow === null) {
|
||||
init()
|
||||
}
|
||||
})
|
||||
// if (isDev && process.platform === 'win32') {
|
||||
// // Set the path of electron.exe and your app.
|
||||
// // These two additional parameters are only available on windows.
|
||||
// // console.log(process.execPath, process.argv)
|
||||
// app.setAsDefaultProtocolClient('lxmusic', process.execPath, process.argv.slice(1))
|
||||
// }
|
||||
if (!isDev) app.setAsDefaultProtocolClient('lxmusic')
|
||||
|
||||
const { navigationUrlWhiteList, themes } = require('../common/config')
|
||||
const { getWindowSizeInfo, initSetting, updateSetting } = require('./utils')
|
||||
const { isMac, isLinux, initHotKey } = require('../common/utils')
|
||||
|
||||
|
||||
// https://github.com/electron/electron/issues/18397
|
||||
// 开发模式下为true时 多次引入native模块会导致渲染进程卡死
|
||||
// https://github.com/electron/electron/issues/22791
|
||||
// app.allowRendererProcessReuse = !isDev
|
||||
|
||||
|
||||
app.on('web-contents-created', (event, contents) => {
|
||||
contents.on('will-navigate', (event, navigationUrl) => {
|
||||
if (isDev) return console.log('navigation to url:', navigationUrl)
|
||||
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) return event.preventDefault()
|
||||
console.log('navigation to url:', navigationUrl)
|
||||
})
|
||||
contents.setWindowOpenHandler(({ url }) => {
|
||||
if (!/^devtools/.test(url) && /^https?:\/\//.test(url)) {
|
||||
shell.openExternal(url)
|
||||
}
|
||||
console.log(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
||||
// Strip away preload scripts if unused or verify their location is legitimate
|
||||
delete webPreferences.preload
|
||||
delete webPreferences.preloadURL
|
||||
|
||||
// Disable Node.js integration
|
||||
webPreferences.nodeIntegration = false
|
||||
|
||||
// Verify URL being loaded
|
||||
if (!navigationUrlWhiteList.some(url => url.test(params.src))) {
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
// disable create dictionary
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/773
|
||||
contents.session.setSpellCheckerDictionaryDownloadURL('http://0.0.0.0')
|
||||
})
|
||||
|
||||
|
||||
require('../common/error')
|
||||
require('./events')
|
||||
require('./event')
|
||||
require('./rendererEvents')
|
||||
const winEvent = require('./rendererEvents/winEvent')
|
||||
const autoUpdate = require('./utils/autoUpdate')
|
||||
|
||||
|
||||
let winURL
|
||||
|
||||
if (isDev) {
|
||||
// eslint-disable-next-line no-undef
|
||||
global.__static = __static
|
||||
winURL = 'http://localhost:9080'
|
||||
} else {
|
||||
global.__static = path.join(__dirname, '/static')
|
||||
winURL = `file://${path.join(__dirname, 'index.html')}`
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
const options = {
|
||||
height: windowSizeInfo.height,
|
||||
useContentSize: true,
|
||||
width: windowSizeInfo.width,
|
||||
frame: false,
|
||||
transparent: !global.envParams.cmdParams.dt,
|
||||
enableRemoteModule: false,
|
||||
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: true,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
webSecurity: !isDev,
|
||||
nodeIntegration: true,
|
||||
spellcheck: false, // 禁用拼写检查器
|
||||
},
|
||||
}
|
||||
if (global.appSetting.startInFullscreen) {
|
||||
options.fullscreen = true
|
||||
if (isLinux) options.resizable = true
|
||||
}
|
||||
global.modules.mainWindow = new BrowserWindow(options)
|
||||
|
||||
const shouldUseDarkColors = nativeTheme.shouldUseDarkColors
|
||||
const themeId = global.appSetting.theme.id == 'auto'
|
||||
? shouldUseDarkColors
|
||||
? global.appSetting.theme.darkId
|
||||
: global.appSetting.theme.lightId
|
||||
: global.appSetting.theme.id
|
||||
const themeClass = themes.find(t => t.id == themeId)?.className ?? themes[0].className
|
||||
global.modules.mainWindow.loadURL(winURL + `?dt=${!!global.envParams.cmdParams.dt}&dark=${shouldUseDarkColors}&theme=${themeClass}`)
|
||||
|
||||
winEvent(global.modules.mainWindow)
|
||||
if (global.envParams.cmdParams.odt) require('@main/utils').openDevTools(global.modules.mainWindow.webContents)
|
||||
// global.modules.mainWindow.webContents.openDevTools()
|
||||
|
||||
if (!isDev) autoUpdate()
|
||||
}
|
||||
|
||||
global.appHotKey = {
|
||||
enable: true,
|
||||
config: {},
|
||||
state: null,
|
||||
}
|
||||
|
||||
global.lx_core = {
|
||||
setAppConfig(setting, name) {
|
||||
updateSetting(setting)
|
||||
global.lx_event.common.configStatus(name)
|
||||
},
|
||||
}
|
||||
|
||||
function init() {
|
||||
console.log('init')
|
||||
initSetting()
|
||||
global.appHotKey.config = initHotKey()
|
||||
global.lx_event.common.initSetting()
|
||||
global.lx_event.hotKey.init()
|
||||
createWindow()
|
||||
}
|
||||
|
||||
// https://github.com/electron/electron/issues/16809
|
||||
app.on('ready', isLinux ? () => setTimeout(init, 300) : init)
|
||||
|
||||
app.on('activate', () => {
|
||||
if (global.modules.mainWindow) {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
global.modules.mainWindow.restore()
|
||||
} else if (global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
} else if (global.modules.mainWindow === null) {
|
||||
init()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (isMac) {
|
||||
global.lx_event.tray.destroy()
|
||||
} else {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
require('./modules')
|
||||
@@ -0,0 +1,33 @@
|
||||
import { app } from 'electron'
|
||||
import '@common/error'
|
||||
import {
|
||||
initGlobalData,
|
||||
initSingleInstanceHandle,
|
||||
applyElectronEnvParams,
|
||||
setUserDataPath,
|
||||
registerDeeplink,
|
||||
listenerAppEvent,
|
||||
} from './app'
|
||||
import { isLinux } from '@common/utils'
|
||||
import { initAppSetting } from '@main/app'
|
||||
import registerModules from '@main/modules'
|
||||
|
||||
// 初始化应用
|
||||
const init = () => {
|
||||
console.log('init')
|
||||
void initAppSetting().then(() => {
|
||||
registerModules()
|
||||
global.lx.event_app.app_inited()
|
||||
})
|
||||
}
|
||||
|
||||
initGlobalData()
|
||||
initSingleInstanceHandle()
|
||||
applyElectronEnvParams()
|
||||
setUserDataPath()
|
||||
registerDeeplink(init)
|
||||
listenerAppEvent(init)
|
||||
|
||||
|
||||
// https://github.com/electron/electron/issues/16809
|
||||
void app.whenReady().then(() => isLinux ? setTimeout(init, 300) : init())
|
||||
@@ -1,48 +0,0 @@
|
||||
const { app, Menu } = require('electron')
|
||||
const { isMac } = require('../../common/utils')
|
||||
|
||||
if (isMac) {
|
||||
const template = [
|
||||
{
|
||||
label: app.getName(),
|
||||
submenu: [
|
||||
{ label: '关于洛雪音乐', role: 'about' },
|
||||
{ type: 'separator' },
|
||||
{ label: '隐藏', role: 'hide' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '退出',
|
||||
accelerator: 'Command+Q',
|
||||
click() {
|
||||
global.isQuitting = true
|
||||
app.quit()
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '窗口',
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{ label: '最小化', role: 'minimize', accelerator: 'Command+W' },
|
||||
{ label: '关闭', role: 'close' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
submenu: [
|
||||
{ label: '撤销', accelerator: 'Command+Z', role: 'undo' },
|
||||
{ label: '恢复', accelerator: 'Shift+Command+Z', role: 'redo' },
|
||||
{ type: 'separator' },
|
||||
{ label: '剪切', accelerator: 'Command+X', role: 'cut' },
|
||||
{ label: '复制', accelerator: 'Command+C', role: 'copy' },
|
||||
{ label: '粘贴', accelerator: 'Command+V', role: 'paste' },
|
||||
{ label: '选择全部', accelerator: 'Command+A', role: 'selectAll' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||
} else {
|
||||
Menu.setApplicationMenu(null)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { app, Menu } from 'electron'
|
||||
import { isMac } from '@common/utils'
|
||||
import { quitApp } from '@main/app'
|
||||
|
||||
|
||||
export default () => {
|
||||
if (isMac) {
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: app.getName(),
|
||||
submenu: [
|
||||
{ label: '关于洛雪音乐', role: 'about' },
|
||||
{ type: 'separator' },
|
||||
{ label: '隐藏', role: 'hide' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '退出',
|
||||
accelerator: 'Command+Q',
|
||||
click() {
|
||||
quitApp()
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '窗口',
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{ label: '最小化', role: 'minimize', accelerator: 'Command+W' },
|
||||
{ label: '关闭', role: 'close' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
submenu: [
|
||||
{ label: '撤销', accelerator: 'Command+Z', role: 'undo' },
|
||||
{ label: '恢复', accelerator: 'Shift+Command+Z', role: 'redo' },
|
||||
{ type: 'separator' },
|
||||
{ label: '剪切', accelerator: 'Command+X', role: 'cut' },
|
||||
{ label: '复制', accelerator: 'Command+C', role: 'copy' },
|
||||
{ label: '粘贴', accelerator: 'Command+V', role: 'paste' },
|
||||
{ label: '选择全部', accelerator: 'Command+A', role: 'selectAll' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||
} else {
|
||||
Menu.setApplicationMenu(null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { registerRendererEvents } from './winRendererEvent'
|
||||
|
||||
export { default } from './rendererEvent'
|
||||
@@ -0,0 +1,26 @@
|
||||
import { mainHandle, mainOn } from '@common/mainIpc'
|
||||
import { CMMON_EVENT_NAME } from '@common/ipcNames'
|
||||
import { getFonts } from '@main/utils/fontManage'
|
||||
|
||||
// 公共操作事件(公共,只注册一次)
|
||||
export default () => {
|
||||
mainHandle<LX.AppSetting>(CMMON_EVENT_NAME.get_app_setting, async() => {
|
||||
return global.lx.appSetting
|
||||
})
|
||||
mainHandle<Partial<LX.AppSetting>>(CMMON_EVENT_NAME.set_app_setting, async({ params: config }) => {
|
||||
global.lx.event_app.update_config(config)
|
||||
})
|
||||
|
||||
mainHandle<LX.EnvParams>(CMMON_EVENT_NAME.get_env_params, async() => {
|
||||
return global.envParams
|
||||
})
|
||||
|
||||
mainOn(CMMON_EVENT_NAME.clear_env_params_deeplink, () => {
|
||||
global.envParams.deeplink = null
|
||||
})
|
||||
|
||||
mainHandle<string[]>(CMMON_EVENT_NAME.get_system_fonts, async() => {
|
||||
return await getFonts()
|
||||
})
|
||||
}
|
||||
|
||||