2024-09-26 11:57:23 +08:00
|
|
|
|
import type { SongType, UpdateLogType } from "@/types/main";
|
2024-09-27 11:34:53 +08:00
|
|
|
|
import { NTooltip, SelectOption } from "naive-ui";
|
2024-09-26 11:57:23 +08:00
|
|
|
|
import { h, VNode } from "vue";
|
|
|
|
|
|
import { useClipboard } from "@vueuse/core";
|
|
|
|
|
|
import { getCacheData } from "./cache";
|
|
|
|
|
|
import { updateLog } from "@/api/other";
|
2024-09-27 09:13:02 +08:00
|
|
|
|
import { isEmpty } from "lodash-es";
|
|
|
|
|
|
import { convertToLocalTime } from "./time";
|
2024-09-27 11:34:53 +08:00
|
|
|
|
import { useSettingStore } from "@/stores";
|
2024-09-26 11:57:23 +08:00
|
|
|
|
import { marked } from "marked";
|
|
|
|
|
|
import SvgIcon from "@/components/Global/SvgIcon.vue";
|
|
|
|
|
|
|
|
|
|
|
|
type AnyObject = { [key: string]: any };
|
|
|
|
|
|
|
|
|
|
|
|
// 必要数据
|
|
|
|
|
|
let imageBlobURL: string = "";
|
|
|
|
|
|
|
|
|
|
|
|
// 环境判断
|
|
|
|
|
|
export const isDev = import.meta.env.MODE === "development" || import.meta.env.DEV;
|
|
|
|
|
|
|
|
|
|
|
|
// 系统判断
|
|
|
|
|
|
const userAgent = window.navigator.userAgent;
|
|
|
|
|
|
export const isWin = userAgent.includes("Windows");
|
|
|
|
|
|
export const isMac = userAgent.includes("Macintosh");
|
|
|
|
|
|
export const isLinux = userAgent.includes("Linux");
|
|
|
|
|
|
export const isElectron = userAgent.includes("Electron");
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 打开链接
|
|
|
|
|
|
* @param url 链接地址
|
|
|
|
|
|
* @param target 打开方式(_self 或 _blank)
|
|
|
|
|
|
*/
|
2024-09-26 11:57:23 +08:00
|
|
|
|
export const openLink = (url: string, target: "_self" | "_blank" = "_blank") => {
|
|
|
|
|
|
window.open(url, target);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 渲染图标
|
|
|
|
|
|
* @param iconName 图标名称
|
|
|
|
|
|
* @param option 图标选项(大小和样式)
|
|
|
|
|
|
* @returns 图标组件
|
|
|
|
|
|
*/
|
2024-09-26 11:57:23 +08:00
|
|
|
|
export const renderIcon = (
|
|
|
|
|
|
iconName: string,
|
|
|
|
|
|
option: {
|
|
|
|
|
|
size?: number;
|
|
|
|
|
|
style?: AnyObject;
|
|
|
|
|
|
} = {},
|
|
|
|
|
|
) => {
|
|
|
|
|
|
const { size, style } = option;
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
return h(SvgIcon, { name: iconName, size, style });
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 延时函数
|
|
|
|
|
|
* @param ms 延时时间(毫秒)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const sleep = (ms: number): Promise<void> => {
|
|
|
|
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 渲染选项
|
|
|
|
|
|
* @param param0 包含节点和选项的对象
|
|
|
|
|
|
* @returns 包含工具提示的节点
|
|
|
|
|
|
*/
|
2024-09-26 11:57:23 +08:00
|
|
|
|
export const renderOption = ({ node, option }: { node: VNode; option: SelectOption }) =>
|
|
|
|
|
|
h(
|
|
|
|
|
|
NTooltip,
|
|
|
|
|
|
{ placement: "left" },
|
|
|
|
|
|
{
|
|
|
|
|
|
trigger: () => node,
|
|
|
|
|
|
default: () => option.label,
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 模糊搜索
|
|
|
|
|
|
* @param keyword 搜索关键词
|
|
|
|
|
|
* @param data 要搜索的数据数组
|
|
|
|
|
|
* @returns 包含匹配项的数组
|
|
|
|
|
|
*/
|
2024-09-26 11:57:23 +08:00
|
|
|
|
export const fuzzySearch = (keyword: string, data: SongType[]): SongType[] => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result: SongType[] = [];
|
|
|
|
|
|
const regex = new RegExp(keyword, "i");
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 递归函数:遍历对象及其嵌套属性,过滤包含关键词的对象
|
|
|
|
|
|
* @param {Object} obj - 要检查的对象
|
|
|
|
|
|
* @returns {boolean} - 如果找到匹配的属性值,返回 true;否则返回 false
|
|
|
|
|
|
*/
|
|
|
|
|
|
const searchInObject = (obj: AnyObject): boolean => {
|
|
|
|
|
|
for (const key in obj) {
|
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
|
|
|
|
const value = obj[key];
|
|
|
|
|
|
// 如果属性值是对象,则递归调用
|
|
|
|
|
|
if (typeof value === "object" && value !== null) {
|
|
|
|
|
|
if (searchInObject(value)) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 检查属性值是否是字符串并包含关键词
|
|
|
|
|
|
if (value && typeof value === "string" && regex.test(value)) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (!data) return [];
|
|
|
|
|
|
|
|
|
|
|
|
// 如果传入的是数组,遍历数组
|
|
|
|
|
|
if (Array.isArray(data)) {
|
|
|
|
|
|
for (const item of data) {
|
|
|
|
|
|
if (searchInObject(item)) {
|
|
|
|
|
|
result.push(item);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果传入的是对象,直接调用递归函数
|
|
|
|
|
|
if (searchInObject(data)) {
|
|
|
|
|
|
result.push(data);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("模糊搜索出现错误:", error);
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将 32 位 ARGB 颜色值转换为 24 位 RGB 颜色值
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {number} x - 32位ARGB颜色值
|
|
|
|
|
|
* @returns {number[]} - 包含红色、绿色和蓝色分量的24位RGB颜色值数组(0-255)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const argbToRgb = (x: number): number[] => {
|
|
|
|
|
|
// 提取红色、绿色和蓝色分量
|
|
|
|
|
|
const r = (x >> 16) & 0xff;
|
|
|
|
|
|
const g = (x >> 8) & 0xff;
|
|
|
|
|
|
const b = x & 0xff;
|
|
|
|
|
|
// 返回24位RGB颜色值数组
|
|
|
|
|
|
return [r, g, b];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 封面加载完成时,设置透明度为 1
|
|
|
|
|
|
* @param e 事件对象
|
|
|
|
|
|
*/
|
2024-09-26 11:57:23 +08:00
|
|
|
|
export const coverLoaded = (e: Event) => {
|
|
|
|
|
|
const target = e.target as HTMLElement | null;
|
|
|
|
|
|
if (target && target.nodeType === Node.ELEMENT_NODE) {
|
|
|
|
|
|
target.style.opacity = "1";
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 格式化数字
|
|
|
|
|
|
* @param num 要格式化的数字
|
|
|
|
|
|
* @returns 格式化后的数字字符串
|
|
|
|
|
|
*/
|
2024-09-26 11:57:23 +08:00
|
|
|
|
export const formatNumber = (num: number): string => {
|
|
|
|
|
|
if (num < 10000) {
|
|
|
|
|
|
return num.toString();
|
|
|
|
|
|
} else if (num < 100000000) {
|
|
|
|
|
|
return `${(num / 10000).toFixed(1)}万`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return `${(num / 100000000).toFixed(1)}亿`;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 格式化文件大小
|
|
|
|
|
|
* @param bytes 文件大小(字节)
|
|
|
|
|
|
* @returns 格式化后的文件大小字符串
|
|
|
|
|
|
*/
|
2024-09-26 11:57:23 +08:00
|
|
|
|
export const formatFileSize = (bytes: number): string => {
|
|
|
|
|
|
if (bytes < 1024) {
|
|
|
|
|
|
return `${bytes} B`;
|
|
|
|
|
|
} else if (bytes < 1024 * 1024) {
|
|
|
|
|
|
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
|
|
|
|
} else if (bytes < 1024 * 1024 * 1024) {
|
|
|
|
|
|
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 将图片链接转为 BlobUrl
|
|
|
|
|
|
* @param imageUrl 图片链接
|
|
|
|
|
|
* @returns BlobUrl
|
|
|
|
|
|
*/
|
2024-09-26 11:57:23 +08:00
|
|
|
|
export const convertImageUrlToBlobUrl = async (imageUrl: string) => {
|
|
|
|
|
|
const response = await fetch(imageUrl);
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
throw new Error("Network response was not ok");
|
|
|
|
|
|
}
|
|
|
|
|
|
// 将响应数据转换为 Blob 对象
|
|
|
|
|
|
const blob = await response.blob();
|
|
|
|
|
|
// 撤销之前生成的对象 URL
|
|
|
|
|
|
if (imageBlobURL) URL.revokeObjectURL(imageBlobURL);
|
|
|
|
|
|
// 生成对象 URL
|
|
|
|
|
|
imageBlobURL = URL.createObjectURL(blob);
|
|
|
|
|
|
return imageBlobURL;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 复制数据到剪贴板
|
|
|
|
|
|
* @param text 要复制的数据
|
|
|
|
|
|
* @param message 复制成功提示消息(可选)
|
|
|
|
|
|
* @returns 无
|
|
|
|
|
|
*/
|
2024-09-26 11:57:23 +08:00
|
|
|
|
export const copyData = async (text: any, message?: string) => {
|
|
|
|
|
|
const { copy, copied, isSupported } = useClipboard({ legacy: true });
|
|
|
|
|
|
if (!isSupported.value) {
|
|
|
|
|
|
window.$message.error("暂时无法使用复制功能");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 开始复制
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!text) return;
|
|
|
|
|
|
text = typeof text === "string" ? text.trim() : JSON.stringify(text, null, 2);
|
|
|
|
|
|
await copy(text);
|
|
|
|
|
|
if (copied.value) {
|
|
|
|
|
|
window.$message.success(message ?? "已复制到剪贴板");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.$message.error("复制出错,请重试");
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
window.$message.error("复制出错,请重试");
|
|
|
|
|
|
console.error("复制出错:", error);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/*
|
|
|
|
|
|
* 获取剪贴板内容
|
|
|
|
|
|
* @returns 剪贴板内容字符串或 null
|
|
|
|
|
|
*/
|
2024-09-26 11:57:23 +08:00
|
|
|
|
export const getClipboardData = async (): Promise<string | null> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const text = await navigator.clipboard.readText();
|
|
|
|
|
|
return text;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("Failed to read clipboard content:", error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 格式化为 Electron 快捷键
|
|
|
|
|
|
* @param shortcut 快捷键
|
|
|
|
|
|
* @returns Accelerator
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const formatForGlobalShortcut = (shortcut: string): string => {
|
|
|
|
|
|
return shortcut
|
|
|
|
|
|
.split("+")
|
|
|
|
|
|
.map((part) => {
|
|
|
|
|
|
// 字母
|
|
|
|
|
|
if (part.startsWith("Key")) {
|
|
|
|
|
|
return part.replace("Key", "");
|
|
|
|
|
|
}
|
|
|
|
|
|
// 数字
|
|
|
|
|
|
if (part.startsWith("Digit")) {
|
|
|
|
|
|
return part.replace("Digit", "num");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (part.startsWith("Numpad")) {
|
|
|
|
|
|
return part.replace("Numpad", "num");
|
|
|
|
|
|
}
|
|
|
|
|
|
// 方向键
|
|
|
|
|
|
if (part.startsWith("Arrow")) {
|
|
|
|
|
|
return part.replace("Arrow", "");
|
|
|
|
|
|
}
|
|
|
|
|
|
return part;
|
|
|
|
|
|
})
|
|
|
|
|
|
.join("+");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-20 18:22:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取更新日志
|
|
|
|
|
|
* @returns 更新日志数组
|
|
|
|
|
|
*/
|
2024-09-27 09:13:02 +08:00
|
|
|
|
export const getUpdateLog = async (): Promise<UpdateLogType[]> => {
|
|
|
|
|
|
const result = await getCacheData(updateLog, { key: "updateLog", time: 10 });
|
|
|
|
|
|
if (!result || isEmpty(result)) return [];
|
|
|
|
|
|
const updateLogs = await Promise.all(
|
|
|
|
|
|
result.map(async (v: any) => ({
|
|
|
|
|
|
version: v.tag_name,
|
|
|
|
|
|
changelog: await marked(v.body),
|
|
|
|
|
|
time: convertToLocalTime(v.published_at),
|
|
|
|
|
|
url: v.html_url,
|
2024-09-27 16:14:38 +08:00
|
|
|
|
prerelease: v.prerelease,
|
2024-09-27 09:13:02 +08:00
|
|
|
|
})),
|
|
|
|
|
|
);
|
|
|
|
|
|
return updateLogs;
|
2024-09-26 11:57:23 +08:00
|
|
|
|
};
|
2024-09-27 11:34:53 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
2025-10-22 13:33:33 +08:00
|
|
|
|
* 获取 更改本地目录 函数
|
|
|
|
|
|
* @param settingsKey 设置项 key
|
|
|
|
|
|
* @param includeSubFolders 是否包含子文件夹
|
|
|
|
|
|
* @param errorConsole 控制台输出的错误信息
|
|
|
|
|
|
* @param errorMessage 错误信息
|
2025-10-22 17:57:48 +08:00
|
|
|
|
* @param needDefaultMusicPath 是否需要获取默认音乐路径
|
2024-09-27 11:34:53 +08:00
|
|
|
|
*/
|
2025-10-22 17:57:48 +08:00
|
|
|
|
const changeLocalPath =
|
|
|
|
|
|
(
|
|
|
|
|
|
settingsKey: string,
|
|
|
|
|
|
includeSubFolders: boolean,
|
|
|
|
|
|
errorConsole: string,
|
|
|
|
|
|
errorMessage: string,
|
|
|
|
|
|
needDefaultMusicPath: boolean = false,
|
|
|
|
|
|
) =>
|
|
|
|
|
|
async (delIndex?: number) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!isElectron) return;
|
|
|
|
|
|
const settingStore = useSettingStore();
|
|
|
|
|
|
if (typeof delIndex === "number" && delIndex >= 0) {
|
|
|
|
|
|
settingStore[settingsKey].splice(delIndex, 1);
|
2024-09-27 11:34:53 +08:00
|
|
|
|
} else {
|
2025-10-22 17:57:48 +08:00
|
|
|
|
const selectedDir = await window.electron.ipcRenderer.invoke("choose-path");
|
|
|
|
|
|
if (!selectedDir) return;
|
|
|
|
|
|
// 动态获取默认路径
|
|
|
|
|
|
let allPath = [...settingStore[settingsKey]];
|
|
|
|
|
|
if (needDefaultMusicPath) {
|
|
|
|
|
|
const defaultDir = await window.electron.ipcRenderer.invoke("get-default-dir", "music");
|
|
|
|
|
|
if (defaultDir) allPath = [defaultDir, ...allPath];
|
|
|
|
|
|
}
|
|
|
|
|
|
// 检查是否为子文件夹
|
|
|
|
|
|
if (includeSubFolders) {
|
|
|
|
|
|
const isSubfolder = await window.electron.ipcRenderer.invoke(
|
|
|
|
|
|
"check-if-subfolder",
|
|
|
|
|
|
allPath,
|
|
|
|
|
|
selectedDir,
|
|
|
|
|
|
);
|
|
|
|
|
|
if (!isSubfolder) {
|
|
|
|
|
|
settingStore[settingsKey].push(selectedDir);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.$message.error("添加的目录与现有目录有重叠,请重新选择");
|
|
|
|
|
|
}
|
2025-10-22 13:33:33 +08:00
|
|
|
|
} else {
|
2025-10-22 17:57:48 +08:00
|
|
|
|
if (allPath.includes(selectedDir)) {
|
|
|
|
|
|
window.$message.error("添加的目录已存在");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
settingStore[settingsKey].push(selectedDir);
|
|
|
|
|
|
}
|
2025-10-22 13:33:33 +08:00
|
|
|
|
}
|
2024-09-27 11:34:53 +08:00
|
|
|
|
}
|
2025-10-22 17:57:48 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`${errorConsole}: `, error);
|
|
|
|
|
|
window.$message.error(errorMessage);
|
2024-09-27 11:34:53 +08:00
|
|
|
|
}
|
2025-10-22 17:57:48 +08:00
|
|
|
|
};
|
2025-10-22 13:33:33 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更改本地音乐目录
|
|
|
|
|
|
* @param delIndex 删除文件夹路径的索引
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const changeLocalMusicPath = changeLocalPath(
|
2025-10-22 17:57:48 +08:00
|
|
|
|
"localFilesPath",
|
|
|
|
|
|
true,
|
2025-10-22 13:33:33 +08:00
|
|
|
|
"Error changing local path",
|
|
|
|
|
|
"更改本地歌曲文件夹出错,请重试",
|
2025-10-22 17:57:48 +08:00
|
|
|
|
true,
|
2025-10-22 13:33:33 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更改本地歌词目录
|
|
|
|
|
|
* @param delIndex 删除文件夹路径的索引
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const changeLocalLyricPath = changeLocalPath(
|
2025-10-22 17:57:48 +08:00
|
|
|
|
"localLyricPath",
|
|
|
|
|
|
false,
|
2025-10-22 13:33:33 +08:00
|
|
|
|
"Error changing local lyric path",
|
|
|
|
|
|
"更改本地歌词文件夹出错,请重试",
|
2025-10-22 17:57:48 +08:00
|
|
|
|
false,
|
2025-10-22 13:59:56 +08:00
|
|
|
|
);
|
2025-10-20 18:22:40 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 洗牌数组(Fisher-Yates)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const shuffleArray = <T>(arr: T[]): T[] => {
|
|
|
|
|
|
const copy = arr.slice();
|
|
|
|
|
|
for (let i = copy.length - 1; i > 0; i--) {
|
|
|
|
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
|
|
|
|
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
|
|
|
|
}
|
|
|
|
|
|
return copy;
|
|
|
|
|
|
};
|
2025-10-29 18:29:58 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 在浏览器空闲时执行任务
|
|
|
|
|
|
* @param task 要执行的任务
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const runIdle = (task: () => void) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const ric = window?.requestIdleCallback as ((cb: () => void) => number) | undefined;
|
|
|
|
|
|
if (typeof ric === "function") {
|
|
|
|
|
|
ric(() => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
task();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
/* empty */
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
task();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
/* empty */
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
task();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
/* empty */
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|