451 lines
11 KiB
JavaScript
451 lines
11 KiB
JavaScript
import fs from 'fs'
|
|
import { shell, clipboard } from 'electron'
|
|
import path from 'path'
|
|
import os from 'os'
|
|
import crypto from 'crypto'
|
|
import { rendererSend, rendererInvoke } from '../../common/ipc'
|
|
|
|
/**
|
|
* 获取两个数之间的随机整数,大于等于min,小于max
|
|
* @param {*} min
|
|
* @param {*} max
|
|
*/
|
|
export const getRandom = (min, max) => Math.floor(Math.random() * (max - min)) + min
|
|
|
|
|
|
export const sizeFormate = size => {
|
|
// 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]}`
|
|
}
|
|
|
|
export const formatPlayTime = time => {
|
|
let m = parseInt(time / 60)
|
|
let s = parseInt(time % 60)
|
|
return m === 0 && s === 0 ? '--/--' : (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
|
|
}
|
|
|
|
export const formatPlayTime2 = time => {
|
|
let m = parseInt(time / 60)
|
|
let s = parseInt(time % 60)
|
|
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
|
|
}
|
|
|
|
export const b64DecodeUnicode = str => {
|
|
// 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(''))
|
|
}
|
|
|
|
const encodeNames = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
''': "'",
|
|
}
|
|
export const decodeName = str => str.replace(/(?:&|<|>|"|')/g, s => encodeNames[s])
|
|
|
|
const easeInOutQuad = (t, b, c, d) => {
|
|
t /= d / 2
|
|
if (t < 1) return (c / 2) * t * t + b
|
|
t--
|
|
return (-c / 2) * (t * (t - 2) - 1) + b
|
|
}
|
|
const handleScroll = (element, to, duration = 300, fn = () => {}) => {
|
|
if (!element) return fn()
|
|
const start = element.scrollTop || element.scrollY || 0
|
|
let cancel = false
|
|
if (to > start) {
|
|
let maxScrollTop = element.scrollHeight - element.clientHeight
|
|
if (to > maxScrollTop) to = maxScrollTop
|
|
} else if (to < start) {
|
|
if (to < 0) to = 0
|
|
} else return fn()
|
|
const change = to - start
|
|
const increment = 10
|
|
if (!change) return fn()
|
|
|
|
let currentTime = 0
|
|
let val
|
|
|
|
const animateScroll = () => {
|
|
currentTime += increment
|
|
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
|
|
if (element.scrollTo) {
|
|
element.scrollTo(0, val)
|
|
} else {
|
|
element.scrollTop = val
|
|
}
|
|
if (currentTime < duration) {
|
|
if (cancel) return fn()
|
|
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, to, duration = 300, fn = () => {}, delay) => {
|
|
let cancelFn
|
|
let timeout
|
|
if (delay) {
|
|
let scrollCancelFn
|
|
cancelFn = () => {
|
|
timeout == null ? scrollCancelFn && scrollCancelFn() : clearTimeout(timeout)
|
|
}
|
|
timeout = setTimeout(() => {
|
|
timeout = null
|
|
scrollCancelFn = handleScroll(element, to, duration, fn, delay)
|
|
}, delay)
|
|
} else {
|
|
cancelFn = handleScroll(element, to, duration, fn, delay)
|
|
}
|
|
return cancelFn
|
|
}
|
|
|
|
/**
|
|
* 检查路径是否存在
|
|
* @param {*} path 路径
|
|
*/
|
|
export const checkPath = (path) => new Promise(resolve => {
|
|
fs.access(path, fs.constants.F_OK, err => {
|
|
if (err) return resolve(false)
|
|
resolve(true)
|
|
})
|
|
})
|
|
|
|
/**
|
|
* 选择路径
|
|
* @param {*} 选项
|
|
*/
|
|
export const selectDir = options => rendererInvoke('selectDir', options)
|
|
|
|
/**
|
|
* 打开保存对话框
|
|
* @param {*} 选项
|
|
*/
|
|
export const openSaveDir = options => rendererInvoke('showSaveDialog', options)
|
|
|
|
/**
|
|
* 在资源管理器中打开目录
|
|
* @param {*} dir
|
|
*/
|
|
export const openDirInExplorer = dir => {
|
|
shell.showItemInFolder(dir)
|
|
}
|
|
|
|
export const checkVersion = (currentVer, targetVer) => {
|
|
// console.log(currentVer)
|
|
// console.log(targetVer)
|
|
currentVer = currentVer.split('.')
|
|
targetVer = targetVer.split('.')
|
|
let maxLen = Math.max(currentVer.length, targetVer.length)
|
|
if (currentVer.length < maxLen) {
|
|
for (let index = 0, len = maxLen - currentVer.length; index < len; index++) {
|
|
currentVer.push(0)
|
|
}
|
|
}
|
|
if (targetVer.length < maxLen) {
|
|
for (let index = 0, len = maxLen - targetVer.length; index < len; index++) {
|
|
targetVer.push(0)
|
|
}
|
|
}
|
|
for (let index = 0; index < currentVer.length; index++) {
|
|
if (parseInt(currentVer[index]) < parseInt(targetVer[index])) return true
|
|
if (parseInt(currentVer[index]) > parseInt(targetVer[index])) return false
|
|
}
|
|
return false
|
|
}
|
|
|
|
export const isObject = item => item && typeof item === 'object' && !Array.isArray(item)
|
|
|
|
/**
|
|
* 对象深度合并
|
|
* 注意:循环引用的对象会出现死循环
|
|
* @param {} target 要合并源对象
|
|
* @param {} source 要合并目标对象
|
|
*/
|
|
export const objectDeepMerge = (target, source) => {
|
|
let base = {}
|
|
Object.keys(source).forEach(item => {
|
|
if (Array.isArray(source[item])) {
|
|
let arr = Array.isArray(target[item]) ? target[item] : []
|
|
target[item] = arr.concat(source[item])
|
|
return
|
|
} else if (isObject(source[item])) {
|
|
if (!isObject(target[item])) target[item] = {}
|
|
objectDeepMerge(target[item], source[item])
|
|
return
|
|
}
|
|
base[item] = source[item]
|
|
})
|
|
Object.assign(target, base)
|
|
}
|
|
|
|
/**
|
|
* 升级设置
|
|
* @param {*} setting
|
|
*/
|
|
export const updateSetting = (setting, version) => {
|
|
const defaultVersion = '1.0.26'
|
|
if (!version) {
|
|
if (setting) {
|
|
version = setting.version
|
|
delete setting.version
|
|
}
|
|
}
|
|
const defaultSetting = {
|
|
player: {
|
|
togglePlayMethod: 'listLoop',
|
|
highQuality: false,
|
|
isShowTaskProgess: true,
|
|
volume: 1,
|
|
mediaDeviceId: 'default',
|
|
isMediaDeviceRemovedStopPlay: false,
|
|
},
|
|
list: {
|
|
isShowAlbumName: true,
|
|
isShowSource: true,
|
|
prevSelectListId: 'default',
|
|
isSaveScrollLocation: true,
|
|
},
|
|
download: {
|
|
savePath: path.join(os.homedir(), 'Desktop'),
|
|
fileName: '歌名 - 歌手',
|
|
maxDownloadNum: 3,
|
|
isDownloadLrc: false,
|
|
isEmbedPic: true,
|
|
},
|
|
leaderboard: {
|
|
source: 'kw',
|
|
tabId: 'kwbiaosb',
|
|
},
|
|
songList: {
|
|
source: 'kg',
|
|
sortId: '5',
|
|
tagInfo: {
|
|
name: '默认',
|
|
id: null,
|
|
},
|
|
},
|
|
odc: {
|
|
isAutoClearSearchInput: false,
|
|
isAutoClearSearchList: false,
|
|
},
|
|
search: {
|
|
searchSource: 'kw',
|
|
tempSearchSource: 'kw',
|
|
isShowHotSearch: false,
|
|
isShowHistorySearch: false,
|
|
isFocusSearchBox: false,
|
|
},
|
|
network: {
|
|
proxy: {
|
|
enable: false,
|
|
host: '',
|
|
port: '',
|
|
username: '',
|
|
password: '',
|
|
},
|
|
},
|
|
tray: {
|
|
isShow: false,
|
|
isToTray: false,
|
|
},
|
|
windowSizeId: 2,
|
|
themeId: 0,
|
|
langId: 'cns',
|
|
sourceId: 'kw',
|
|
apiSource: 'temp',
|
|
sourceNameType: 'alias',
|
|
randomAnimate: true,
|
|
ignoreVersion: null,
|
|
isAgreePact: false,
|
|
}
|
|
|
|
// 使用新年皮肤
|
|
if (new Date().getMonth() < 2) defaultSetting.themeId = 9
|
|
|
|
const overwriteSetting = {
|
|
|
|
}
|
|
|
|
|
|
if (!setting) {
|
|
setting = defaultSetting
|
|
} else if (checkVersion(version, defaultVersion)) {
|
|
objectDeepMerge(defaultSetting, setting)
|
|
objectDeepMerge(defaultSetting, overwriteSetting)
|
|
setting = defaultSetting
|
|
}
|
|
if (setting.apiSource != 'temp') setting.apiSource = 'test' // 强制设置回 test 接口源
|
|
|
|
return { setting, version: defaultVersion }
|
|
}
|
|
|
|
/**
|
|
* 在浏览器打开URL
|
|
* @param {*} url
|
|
*/
|
|
export const openUrl = url => {
|
|
shell.openExternal(url)
|
|
}
|
|
|
|
/**
|
|
* 设置标题
|
|
*/
|
|
let dom_title = document.getElementsByTagName('title')[0]
|
|
export const setTitle = (title = '洛雪音乐助手') => {
|
|
dom_title.innerText = title
|
|
}
|
|
|
|
|
|
/**
|
|
* 创建 MD5 hash
|
|
* @param {*} str
|
|
*/
|
|
export const toMD5 = str => crypto.createHash('md5').update(str).digest('hex')
|
|
|
|
/**
|
|
* 复制文本到剪贴板
|
|
* @param {*} str
|
|
*/
|
|
export const clipboardWriteText = str => clipboard.writeText(str)
|
|
|
|
/**
|
|
* 从剪贴板读取文本
|
|
* @param {*} str
|
|
*/
|
|
export const clipboardReadText = str => clipboard.readText()
|
|
|
|
/**
|
|
* 设置音频 meta 信息
|
|
* @param {*} filePath
|
|
* @param {*} meta
|
|
*/
|
|
export const setMeta = (filePath, meta) => {
|
|
rendererSend('setMusicMeta', { filePath, meta })
|
|
}
|
|
|
|
/**
|
|
* 保存歌词文件
|
|
* @param {*} filePath
|
|
* @param {*} lrc
|
|
*/
|
|
export const saveLrc = (filePath, lrc) => {
|
|
fs.writeFile(filePath, lrc, 'utf8', err => {
|
|
if (err) console.log(err)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 生成节流函数
|
|
* @param {*} fn
|
|
* @param {*} delay
|
|
*/
|
|
export const 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
|
|
*/
|
|
export const 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)
|
|
}
|
|
}
|
|
|
|
const async_removeItem = (arr, num, callback) => window.requestAnimationFrame(() => {
|
|
let len = arr.length
|
|
if (len > num) {
|
|
arr.splice(0, num)
|
|
return async_removeItem(arr, num, callback)
|
|
} else {
|
|
arr.splice(0, len)
|
|
return callback()
|
|
}
|
|
})
|
|
const async_addItem = (arr, newArr, num, callback) => window.requestAnimationFrame(() => {
|
|
let len = newArr.length
|
|
if (len > num) {
|
|
arr.push(...newArr.splice(0, num))
|
|
return async_addItem(arr, newArr, num, callback)
|
|
} else {
|
|
arr.push(...newArr.splice(0, len))
|
|
return callback()
|
|
}
|
|
})
|
|
/**
|
|
* 异步设置数组
|
|
* @param {*} from 原数组
|
|
* @param {*} to 设置后的数组内容
|
|
* @param {*} num 每次设置的个数
|
|
*/
|
|
export const asyncSetArray = (from, to, num = 100) => new Promise(resolve => {
|
|
async_removeItem(from, num, () => {
|
|
async_addItem(from, Array.from(to), num, () => {
|
|
resolve()
|
|
})
|
|
})
|
|
})
|
|
|
|
|
|
/**
|
|
* 获取缓存大小
|
|
*/
|
|
export const getCacheSize = () => rendererInvoke('getCacheSize')
|
|
|
|
/**
|
|
* 清除缓存
|
|
*/
|
|
export const clearCache = () => rendererInvoke('clearCache')
|
|
|
|
/**
|
|
* 设置窗口大小
|
|
* @param {*} width
|
|
* @param {*} height
|
|
*/
|
|
export const setWindowSize = (width, height) => rendererSend('setWindowSize', { width, height })
|
|
|
|
|
|
export const getProxyInfo = () => window.globalObj.proxy.enable
|
|
? `http://${window.globalObj.proxy.username}:${window.globalObj.proxy.password}@${window.globalObj.proxy.host}:${window.globalObj.proxy.port};`
|
|
: undefined
|
|
|
|
|
|
export const assertApiSupport = source => window.globalObj.qualityList[source] != undefined
|