Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71739a3ffb | |||
| db3becb071 | |||
| a458f69990 | |||
| 82cf627488 | |||
| e00bd5dab0 |
@@ -10,7 +10,5 @@
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/renderer/main.tsx"></script>
|
||||
<script src="/lib/Valine.min.js"></script>
|
||||
<script src="/lib/av-min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+11
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "media-downloader",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "在线视频下载",
|
||||
"author": "caorushizi",
|
||||
"main": "dist/main/index.js",
|
||||
@@ -11,7 +11,9 @@
|
||||
"watch": "cross-env NODE_ENV=development node script/watch.js",
|
||||
"electron": "cross-env NODE_ENV=production electron .",
|
||||
"pack": "electron-builder --dir",
|
||||
"build": "electron-builder"
|
||||
"build": "electron-builder",
|
||||
"publish": "npm run dist && npm run build",
|
||||
"build-debug": "npm run dist && npm run pack && ./build/win-unpacked/在线视频下载.exe"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
@@ -20,9 +22,13 @@
|
||||
"@chakra-ui/react": "^1.7.2",
|
||||
"@emotion/react": "^11",
|
||||
"@emotion/styled": "^11",
|
||||
"@sentry/electron": "^2.5.4",
|
||||
"@sentry/react": "^6.16.1",
|
||||
"@sentry/tracing": "^6.16.1",
|
||||
"antd": "^4.17.2",
|
||||
"classnames": "^2.3.1",
|
||||
"connected-react-router": "^6.9.1",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-log": "^4.3.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"electron-store": "^7.0.2",
|
||||
@@ -70,6 +76,7 @@
|
||||
"dotenv": "^8.2.0",
|
||||
"electron": "16.0.4",
|
||||
"electron-builder": "^22.14.2",
|
||||
"electron-updater": "^4.6.1",
|
||||
"esbuild": "^0.9.5",
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
@@ -91,6 +98,7 @@
|
||||
"productName": "在线视频下载",
|
||||
"appId": "mediago.ziying.site",
|
||||
"copyright": "caorushizi",
|
||||
"artifactName": "media-downloader-setup-${version}.${ext}",
|
||||
"directories": {
|
||||
"output": "build"
|
||||
},
|
||||
@@ -103,6 +111,7 @@
|
||||
],
|
||||
"win": {
|
||||
"icon": "assets/icon.ico",
|
||||
"requestedExecutionLevel": "requireAdministrator",
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis"
|
||||
|
||||
Vendored
-26
File diff suppressed because one or more lines are too long
Vendored
-21
File diff suppressed because one or more lines are too long
+10
-2
@@ -4,10 +4,15 @@ const rimraf = require("rimraf");
|
||||
|
||||
rimraf.sync(resolve(__dirname, "../dist"));
|
||||
|
||||
require("dotenv").config({
|
||||
path: resolve(__dirname, `.env.${process.env.NODE_ENV}`),
|
||||
const { parsed } = require("dotenv").config({
|
||||
path: resolve(__dirname, `../.env.${process.env.NODE_ENV}.local`),
|
||||
});
|
||||
|
||||
const mainDefined = Object.keys(parsed).reduce((prev, cur) => {
|
||||
prev[`process.env.${[cur]}`] = JSON.stringify(parsed[cur]);
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
function buildMain() {
|
||||
return require("esbuild").build({
|
||||
entryPoints: [
|
||||
@@ -21,6 +26,9 @@ function buildMain() {
|
||||
external: ["electron"],
|
||||
outdir: resolve(__dirname, "../dist"),
|
||||
loader: { ".png": "file" },
|
||||
define: {
|
||||
...mainDefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+4
-13
@@ -5,13 +5,12 @@ const electron = require("electron");
|
||||
const reactRefresh = require("@vitejs/plugin-react-refresh");
|
||||
const path = require("path");
|
||||
const { spawn } = require("child_process");
|
||||
// const eslint = require("@rollup/plugin-eslint");
|
||||
|
||||
let electronProcess = null;
|
||||
let manualRestart = false;
|
||||
|
||||
require("dotenv").config({
|
||||
path: resolve(__dirname, `.env.${process.env.NODE_ENV}`),
|
||||
path: resolve(__dirname, `../.env.${process.env.NODE_ENV}.local`),
|
||||
});
|
||||
|
||||
function startMain() {
|
||||
@@ -71,16 +70,7 @@ function startRenderer() {
|
||||
{ find: /^~/, replacement: "" },
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// {
|
||||
// ...eslint({
|
||||
// include: "**/*.+(js|jsx|ts|tsx)",
|
||||
// }),
|
||||
// enforce: "pre",
|
||||
// apply: "serve",
|
||||
// },
|
||||
reactRefresh(),
|
||||
],
|
||||
plugins: [reactRefresh()],
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
@@ -120,8 +110,9 @@ function electronLog(data, color) {
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const [server] = await Promise.all([startRenderer(), startMain()]);
|
||||
const server = await startRenderer();
|
||||
await server.listen();
|
||||
await startMain();
|
||||
startElectron();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import installExtension, {
|
||||
REACT_DEVELOPER_TOOLS,
|
||||
REDUX_DEVTOOLS,
|
||||
} from "electron-devtools-installer";
|
||||
import { log } from "main/utils";
|
||||
|
||||
export default async function handleExtension(): Promise<void> {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
try {
|
||||
await installExtension(REACT_DEVELOPER_TOOLS);
|
||||
await installExtension(REDUX_DEVTOOLS);
|
||||
} catch (e) {
|
||||
log.info(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { app, dialog, ipcMain, Menu, shell } from "electron";
|
||||
import { failFn, successFn } from "./utils";
|
||||
import windowManager from "./window/windowManager";
|
||||
import executor from "main/executor";
|
||||
import request from "main/request";
|
||||
import { binDir, Windows } from "main/variables";
|
||||
import { failFn, successFn } from "../utils";
|
||||
import windowManager from "../window/windowManager";
|
||||
import executor from "main/utils/executor";
|
||||
import request from "main/utils/request";
|
||||
import { binDir, Windows } from "main/utils/variables";
|
||||
|
||||
const handleIpc = (): void => {
|
||||
ipcMain.on("close-main-window", async () => {
|
||||
@@ -89,17 +89,29 @@ const handleIpc = (): void => {
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle("exec", async (event, exeFile: string, args: M3u8DLArgs) => {
|
||||
let resp;
|
||||
try {
|
||||
const result = await executor(exeFile, args);
|
||||
resp = successFn(result);
|
||||
} catch (e: any) {
|
||||
resp = failFn(-1, e.message);
|
||||
ipcMain.on("window-minimize", (e, name) => {
|
||||
let window;
|
||||
if (name === "main") {
|
||||
window = windowManager.get(Windows.MAIN_WINDOW);
|
||||
} else {
|
||||
window = windowManager.get(Windows.BROWSER_WINDOW);
|
||||
}
|
||||
return resp;
|
||||
|
||||
window.minimize();
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
"exec-command",
|
||||
async (event, exeFile: string, args: M3u8DLArgs) => {
|
||||
try {
|
||||
const result = await executor(exeFile, args);
|
||||
return successFn(result);
|
||||
} catch (e: any) {
|
||||
return failFn(-1, e.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle("get-bin-dir", async () => binDir);
|
||||
|
||||
ipcMain.handle("set-store", (e, key, value) => global.store.set(key, value));
|
||||
@@ -0,0 +1,25 @@
|
||||
import Store from "electron-store";
|
||||
import { workspace } from "main/utils/variables";
|
||||
|
||||
export default function handleStore(): void {
|
||||
let exeFile = "";
|
||||
if (process.platform === "win32") {
|
||||
exeFile = "N_m3u8DL-CLI";
|
||||
} else {
|
||||
exeFile = "mediago";
|
||||
}
|
||||
|
||||
global.store = new Store<AppStore>({
|
||||
name: "config",
|
||||
cwd: workspace,
|
||||
fileExtension: "json",
|
||||
watch: true,
|
||||
defaults: {
|
||||
workspace: "",
|
||||
exeFile,
|
||||
tip: true,
|
||||
proxy: "",
|
||||
useProxy: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import windowManager from "main/window/windowManager";
|
||||
import { Windows } from "main/utils/variables";
|
||||
import createBrowserView from "main/utils/browserView";
|
||||
|
||||
export default async function handleWindows(
|
||||
webviewSession: Electron.Session
|
||||
): Promise<void> {
|
||||
await Promise.all([
|
||||
windowManager.create(Windows.MAIN_WINDOW),
|
||||
await windowManager.create(Windows.BROWSER_WINDOW),
|
||||
]);
|
||||
await createBrowserView(webviewSession);
|
||||
}
|
||||
+53
-80
@@ -1,20 +1,26 @@
|
||||
import { app, BrowserWindow, protocol, session } from "electron";
|
||||
import { resolve } from "path";
|
||||
import { log } from "main/utils";
|
||||
import windowManager from "main/window/windowManager";
|
||||
import handleIpc from "main/handleIpc";
|
||||
import createBrowserView from "main/browserView";
|
||||
import Store from "electron-store";
|
||||
import {
|
||||
defaultScheme,
|
||||
webviewPartition,
|
||||
Windows,
|
||||
workspace,
|
||||
} from "main/variables";
|
||||
import createSession from "main/session";
|
||||
import { URL } from "url";
|
||||
import { join, extname } from "path";
|
||||
import { readFile, readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { URL } from "url";
|
||||
import { app, BrowserWindow, protocol, crashReporter } from "electron";
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import { log } from "main/utils";
|
||||
import handleIpc from "main/helper/handleIpc";
|
||||
import { defaultScheme, webviewPartition } from "main/utils/variables";
|
||||
import createSession from "main/utils/session";
|
||||
import handleStore from "main/helper/handleStore";
|
||||
import handleExtension from "main/helper/handleExtension";
|
||||
import handleWindows from "main/helper/handleWindows";
|
||||
import * as Sentry from "@sentry/electron/dist/main";
|
||||
import { name, author } from "../../package.json";
|
||||
|
||||
Sentry.init({ dsn: process.env.VITE_APP_SENTRY_DSN });
|
||||
|
||||
crashReporter.start({
|
||||
companyName: author,
|
||||
productName: name,
|
||||
ignoreSystemCrashHandler: true,
|
||||
submitURL: process.env.VITE_APP_SENTRY_DSN,
|
||||
});
|
||||
|
||||
if (require("electron-squirrel-startup")) {
|
||||
app.quit();
|
||||
@@ -26,12 +32,6 @@ app.on("window-all-closed", () => {
|
||||
}
|
||||
});
|
||||
|
||||
const init = async (webviewSession: Electron.Session) => {
|
||||
windowManager.create(Windows.MAIN_WINDOW);
|
||||
await windowManager.create(Windows.BROWSER_WINDOW);
|
||||
await createBrowserView(webviewSession);
|
||||
};
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
scheme: defaultScheme,
|
||||
@@ -42,24 +42,25 @@ protocol.registerSchemesAsPrivileged([
|
||||
},
|
||||
]);
|
||||
|
||||
app.whenReady().then(() => {
|
||||
app.whenReady().then(async () => {
|
||||
autoUpdater.logger = log;
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
|
||||
protocol.registerBufferProtocol(defaultScheme, (request, callback) => {
|
||||
let pathName = new URL(request.url).pathname;
|
||||
pathName = decodeURI(pathName);
|
||||
|
||||
readFile(path.join(__dirname, "../renderer", pathName), (error, data) => {
|
||||
readFile(join(__dirname, "../renderer", pathName), (error, data) => {
|
||||
if (error) {
|
||||
console.error(
|
||||
`Failed to register ${webviewPartition} protocol\n`,
|
||||
error,
|
||||
"\n"
|
||||
);
|
||||
const data = readFileSync(
|
||||
path.join(__dirname, "../renderer/index.html")
|
||||
);
|
||||
const data = readFileSync(join(__dirname, "../renderer/index.html"));
|
||||
callback({ mimeType: "text/html", data });
|
||||
} else {
|
||||
const extension = path.extname(pathName).toLowerCase();
|
||||
const extension = extname(pathName).toLowerCase();
|
||||
let mimeType = "";
|
||||
|
||||
if (extension === ".js") {
|
||||
@@ -82,65 +83,37 @@ app.whenReady().then(() => {
|
||||
const webviewSession = createSession(webviewPartition);
|
||||
app.on("activate", async () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
await init(webviewSession);
|
||||
await handleWindows(webviewSession);
|
||||
}
|
||||
});
|
||||
|
||||
init(webviewSession);
|
||||
handleWindows(webviewSession);
|
||||
handleExtension();
|
||||
handleStore();
|
||||
handleIpc();
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
// 设置软件代理
|
||||
const setProxy = (isInit?: boolean) => {
|
||||
try {
|
||||
const reactTool = resolve(__dirname, "../../devtools/react");
|
||||
session.defaultSession.loadExtension(reactTool);
|
||||
const reduxTool = resolve(__dirname, "../../devtools/redux");
|
||||
session.defaultSession.loadExtension(reduxTool);
|
||||
} catch (e) {
|
||||
log.info(e);
|
||||
}
|
||||
}
|
||||
|
||||
let exeFile = "";
|
||||
if (process.platform === "win32") {
|
||||
exeFile = "N_m3u8DL-CLI";
|
||||
} else {
|
||||
exeFile = "mediago";
|
||||
}
|
||||
|
||||
const store = new Store<AppStore>({
|
||||
name: "config",
|
||||
cwd: workspace,
|
||||
fileExtension: "json",
|
||||
watch: true,
|
||||
defaults: {
|
||||
workspace: "",
|
||||
exeFile,
|
||||
tip: true,
|
||||
proxy: "",
|
||||
useProxy: false,
|
||||
},
|
||||
});
|
||||
|
||||
const setProxy = () => {
|
||||
const proxy = store.get("proxy");
|
||||
if (proxy) {
|
||||
log.info("proxy 设置成功");
|
||||
webviewSession.setProxy({ proxyRules: proxy });
|
||||
} else {
|
||||
webviewSession.setProxy({});
|
||||
const proxy = global.store.get("proxy");
|
||||
const useProxy = global.store.get("useProxy");
|
||||
if (proxy && useProxy) {
|
||||
log.info(
|
||||
`[proxy] ${isInit ? "初始化" : "开启"}成功,代理地址为${proxy}`
|
||||
);
|
||||
webviewSession.setProxy({ proxyRules: proxy });
|
||||
} else {
|
||||
if (!isInit) log.info(`[proxy] 关闭成功`);
|
||||
webviewSession.setProxy({});
|
||||
}
|
||||
} catch (e: any) {
|
||||
log.error(
|
||||
`[proxy] ${isInit ? "初始化" : ""}设置代理失败:\n${e.message}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
setProxy();
|
||||
setProxy(true);
|
||||
|
||||
store.onDidChange("useProxy", (newValue) => {
|
||||
try {
|
||||
if (newValue) setProxy();
|
||||
} catch (e: any) {
|
||||
log.error("设置代理失败:", e.message);
|
||||
}
|
||||
});
|
||||
|
||||
global.store = store;
|
||||
global.store.onDidChange("useProxy", setProxy);
|
||||
});
|
||||
|
||||
handleIpc();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import windowManager from "main/window/windowManager";
|
||||
import { BrowserView } from "electron";
|
||||
import { log } from "main/utils";
|
||||
import { webviewPartition, Windows } from "main/variables";
|
||||
import { webviewPartition, Windows } from "main/utils/variables";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
const createBrowserView = (session: Electron.Session): void => {
|
||||
@@ -52,7 +52,7 @@ const createBrowserView = (session: Electron.Session): void => {
|
||||
headers: details.requestHeaders,
|
||||
duration: 0,
|
||||
};
|
||||
mainWindow.send("m3u8", value);
|
||||
mainWindow.send("m3u8-notifier", value);
|
||||
cancel = true;
|
||||
}
|
||||
callback({
|
||||
@@ -1,7 +1,10 @@
|
||||
import semver from "semver";
|
||||
import { globWrapper, log, spawnWrapper } from "main/utils";
|
||||
import { binDir } from "main/variables";
|
||||
import { binDir } from "main/utils/variables";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
// 找到可执行文件并进行参数拼接
|
||||
const execM3u8DL = async (args: M3u8DLArgs): Promise<string[]> => {
|
||||
let binNameList = await globWrapper("N_m3u8DL-CLI*.exe", {
|
||||
cwd: binDir,
|
||||
@@ -13,6 +16,13 @@ const execM3u8DL = async (args: M3u8DLArgs): Promise<string[]> => {
|
||||
if (binNameList.length === 0) throw new Error("没有找到 N_m3u8DL-CLI");
|
||||
const binName = `N_m3u8DL-CLI_v${binNameList[0]}`;
|
||||
|
||||
let binFileName = binName;
|
||||
if (process.platform === "win32") {
|
||||
binFileName = `${binName}.exe`;
|
||||
}
|
||||
const binExist = fs.existsSync(path.resolve(binDir, binFileName));
|
||||
if (!binExist) throw new Error("没有找到 N_m3u8DL-CLI");
|
||||
|
||||
let argsStr = Object.entries(args)
|
||||
.reduce((prev: string[], [key, value]) => {
|
||||
if (key === "url") return prev;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { spawn, SpawnOptions } from "child_process";
|
||||
import glob from "glob";
|
||||
import { workspace } from "main/variables";
|
||||
import { workspace } from "main/utils/variables";
|
||||
import spawnArgs from "main/utils/spawn-args";
|
||||
import log from "electron-log";
|
||||
import path from "path";
|
||||
@@ -59,7 +59,7 @@ interface IpcResponse {
|
||||
data: any;
|
||||
}
|
||||
|
||||
const successFn = (data: any): IpcResponse => ({ code: 0, msg: "", data });
|
||||
const successFn = (data: unknown): IpcResponse => ({ code: 0, msg: "", data });
|
||||
const failFn = (code: number, msg: string): IpcResponse => ({
|
||||
code,
|
||||
msg,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IWindowListItem } from "types/main";
|
||||
import { resolve } from "path";
|
||||
import { Windows } from "main/variables";
|
||||
import { Windows } from "main/utils/variables";
|
||||
|
||||
const windowList = new Map<Windows, IWindowListItem>();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
import { IWindowListItem, IWindowManager } from "types/main";
|
||||
import windowList from "./windowList";
|
||||
import { Windows } from "../variables";
|
||||
import { Windows } from "../utils/variables";
|
||||
|
||||
class WindowManager implements IWindowManager {
|
||||
private windowMap: Map<Windows | string, BrowserWindow> = new Map();
|
||||
|
||||
@@ -13,7 +13,7 @@ const api: ElectronApi = {
|
||||
},
|
||||
isWindows: process.platform === "win32",
|
||||
isMacos: process.platform === "darwin",
|
||||
ipcExec: (exeFile, args) => ipcRenderer.invoke("exec", exeFile, args),
|
||||
ipcExec: (exeFile, args) => ipcRenderer.invoke("exec-command", exeFile, args),
|
||||
openBinDir: async () => {
|
||||
const binDir = await ipcRenderer.invoke("get-bin-dir");
|
||||
await shell.openPath(binDir);
|
||||
@@ -47,6 +47,7 @@ const api: ElectronApi = {
|
||||
request: (options) => ipcRenderer.invoke("request", options),
|
||||
itemContextMenu: (item) =>
|
||||
ipcRenderer.send("open-download-item-context-menu", item),
|
||||
minimize: (name) => ipcRenderer.send("window-minimize", name),
|
||||
};
|
||||
|
||||
contextBridge.exposeInMainWorld(apiKey, api);
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
class Comment extends React.Component {
|
||||
componentDidMount(): void {
|
||||
// @ts-ignore
|
||||
new Valine({
|
||||
el: "#vcomments",
|
||||
appId: import.meta.env.VITE_APP_VC_AK,
|
||||
appKey: import.meta.env.VITE_APP_VC_SK,
|
||||
placeholder: "快来评论下吧~",
|
||||
avatar: "hide",
|
||||
meta: ["nick", "mail"],
|
||||
requiredFields: ["nick"],
|
||||
path: import.meta.env.VITE_APP_VC_PATH,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const links = document.querySelectorAll("a[href]");
|
||||
links.forEach((link) => {
|
||||
link.addEventListener("click", (e) => {
|
||||
const url = link.getAttribute("href")!;
|
||||
e.preventDefault();
|
||||
window.electron.openExternal(url);
|
||||
});
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
|
||||
render(): ReactNode {
|
||||
return <div id="vcomments" />;
|
||||
}
|
||||
}
|
||||
|
||||
export default Comment;
|
||||
@@ -0,0 +1,63 @@
|
||||
import "./index.scss";
|
||||
import React, { ChangeEvent, FC, useCallback } from "react";
|
||||
import { Form, Input } from "antd";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
export interface HeaderEditProps {
|
||||
label: string;
|
||||
name: string;
|
||||
valuePropName?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
interface PriceInputProps {
|
||||
value?: Record<string, string>;
|
||||
onChange?: (value: Record<string, string>) => void;
|
||||
}
|
||||
|
||||
// 编辑 header
|
||||
const HeaderFieldInput: FC<PriceInputProps> = ({ value, onChange }) => {
|
||||
const processHeader = useCallback((value?: Record<string, string>) => {
|
||||
if (!value) return "";
|
||||
return Object.keys(value).map((key) => `${key}: ${value[key]}`);
|
||||
}, []);
|
||||
|
||||
const parsedValue = processHeader(value);
|
||||
|
||||
const onInputChange = (e: ChangeEvent<HTMLTextAreaElement>): void => {
|
||||
const value = e.target.value
|
||||
.split("\n")
|
||||
.reduce((cur: Record<string, string>, prev) => {
|
||||
const [key, value] = prev.split(":");
|
||||
cur[key] = value;
|
||||
return cur;
|
||||
}, {});
|
||||
onChange?.(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextArea
|
||||
value={parsedValue}
|
||||
onChange={onInputChange}
|
||||
autoSize={{ minRows: 3, maxRows: 5 }}
|
||||
allowClear
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// Http Header 编辑组件
|
||||
const HeaderEdit: FC<HeaderEditProps> = ({
|
||||
label,
|
||||
valuePropName,
|
||||
name,
|
||||
placeholder,
|
||||
}) => {
|
||||
return (
|
||||
<Form.Item name={name} label={label} valuePropName={valuePropName}>
|
||||
<HeaderFieldInput />
|
||||
</Form.Item>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderEdit;
|
||||
@@ -1,47 +1,43 @@
|
||||
import React, { Component, ReactNode } from "react";
|
||||
import React, { FC, PropsWithChildren } from "react";
|
||||
import "./index.scss";
|
||||
import { CloseOutlined, MinusOutlined } from "@ant-design/icons";
|
||||
import useElectron from "renderer/hooks/electron";
|
||||
|
||||
interface Props {
|
||||
color?: string;
|
||||
onClose: () => void;
|
||||
onClose?: () => void;
|
||||
onMinimize?: () => void;
|
||||
}
|
||||
|
||||
class WindowToolBar extends Component<Props, Record<string, never>> {
|
||||
// 最小化窗口
|
||||
minimizeWindow = (): void => {
|
||||
// TODO: 最小化窗口
|
||||
// remote.getCurrentWindow().minimize();
|
||||
};
|
||||
const WindowToolBar: FC<PropsWithChildren<Props>> = ({
|
||||
onClose,
|
||||
onMinimize,
|
||||
color = "#fff",
|
||||
children,
|
||||
}) => {
|
||||
const { isMacos, isWindows } = useElectron();
|
||||
|
||||
render(): ReactNode {
|
||||
const { children, onClose, color = "#fff" } = this.props;
|
||||
return (
|
||||
<div className="window-tool-bar" style={{ background: color }}>
|
||||
<div className="window-tool-bar-left">
|
||||
{window.electron.isMacos && (
|
||||
<div className="mac-btn close" onClick={onClose} />
|
||||
)}
|
||||
{window.electron.isMacos && (
|
||||
<div className="mac-btn min" onClick={this.minimizeWindow} />
|
||||
)}
|
||||
</div>
|
||||
<div className="window-tool-bar-title">{children}</div>
|
||||
<div className="window-tool-bar-right">
|
||||
{window.electron.isWindows && (
|
||||
<div className="btn" onClick={this.minimizeWindow}>
|
||||
<MinusOutlined />
|
||||
</div>
|
||||
)}
|
||||
{window.electron.isWindows && (
|
||||
<div className="btn close" onClick={onClose}>
|
||||
<CloseOutlined />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<div className="window-tool-bar" style={{ background: color }}>
|
||||
<div className="window-tool-bar-left">
|
||||
{isMacos && <div className="mac-btn close" onClick={onClose} />}
|
||||
{isMacos && <div className="mac-btn min" onClick={onMinimize} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
<div className="window-tool-bar-title">{children}</div>
|
||||
<div className="window-tool-bar-right">
|
||||
{isWindows && (
|
||||
<div className="btn" onClick={onMinimize}>
|
||||
<MinusOutlined />
|
||||
</div>
|
||||
)}
|
||||
{isWindows && (
|
||||
<div className="btn close" onClick={onClose}>
|
||||
<CloseOutlined />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WindowToolBar;
|
||||
|
||||
@@ -9,6 +9,14 @@ import BrowserPage from "./nodes/browser";
|
||||
import MainPage from "./nodes/main";
|
||||
import "./main.scss";
|
||||
import store from "./store";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { Integrations } from "@sentry/tracing";
|
||||
|
||||
Sentry.init({
|
||||
dsn: String(import.meta.env.VITE_APP_SENTRY_DSN || ""),
|
||||
integrations: [new Integrations.BrowserTracing()],
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
|
||||
tdApp.init();
|
||||
|
||||
|
||||
@@ -38,15 +38,20 @@ const BrowserWindow: FC = () => {
|
||||
browserViewGoBack,
|
||||
browserViewReload,
|
||||
browserViewLoadURL,
|
||||
addEventListener,
|
||||
setBrowserViewRect,
|
||||
removeEventListener,
|
||||
closeBrowserWindow,
|
||||
minimize,
|
||||
} = useElectron();
|
||||
|
||||
useEffect(() => {
|
||||
initWebView();
|
||||
window.electron.addEventListener("dom-ready", handleViewDOMReady);
|
||||
addEventListener("dom-ready", handleViewDOMReady);
|
||||
|
||||
return () => {
|
||||
window.electron.setBrowserViewRect({ x: 0, y: 0, height: 0, width: 0 });
|
||||
window.electron.removeEventListener("dom-ready", handleViewDOMReady);
|
||||
setBrowserViewRect({ x: 0, y: 0, height: 0, width: 0 });
|
||||
removeEventListener("dom-ready", handleViewDOMReady);
|
||||
resizeObserver.current?.disconnect();
|
||||
};
|
||||
}, []);
|
||||
@@ -65,7 +70,7 @@ const BrowserWindow: FC = () => {
|
||||
const initWebView = () => {
|
||||
if (webviewRef.current) {
|
||||
const rect = computeRect(webviewRef.current.getBoundingClientRect());
|
||||
window.electron.setBrowserViewRect(rect);
|
||||
setBrowserViewRect(rect);
|
||||
|
||||
// 监控 webview 元素的大小
|
||||
resizeObserver.current = new ResizeObserver((entries) => {
|
||||
@@ -73,7 +78,7 @@ const BrowserWindow: FC = () => {
|
||||
const viewRect = computeRect(entry.contentRect);
|
||||
viewRect.x += rect.x;
|
||||
viewRect.y += rect.y;
|
||||
window.electron.setBrowserViewRect(viewRect);
|
||||
setBrowserViewRect(viewRect);
|
||||
});
|
||||
|
||||
resizeObserver.current.observe(webviewRef.current);
|
||||
@@ -116,7 +121,10 @@ const BrowserWindow: FC = () => {
|
||||
<div className="browser-window">
|
||||
<WindowToolBar
|
||||
onClose={() => {
|
||||
window.electron.closeBrowserWindow();
|
||||
closeBrowserWindow();
|
||||
}}
|
||||
onMinimize={() => {
|
||||
minimize("");
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
Form,
|
||||
Input,
|
||||
Menu,
|
||||
message,
|
||||
Modal,
|
||||
Space,
|
||||
Switch,
|
||||
@@ -29,10 +30,9 @@ import onEvent from "renderer/utils/td-utils";
|
||||
import {
|
||||
AppstoreAddOutlined,
|
||||
BlockOutlined,
|
||||
CloseOutlined,
|
||||
PlusOutlined,
|
||||
QuestionCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { helpUrl } from "renderer/utils/variables";
|
||||
import { processHeaders } from "renderer/utils/utils";
|
||||
import {
|
||||
getFavs,
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
removeVideos,
|
||||
updateVideoStatus,
|
||||
} from "renderer/utils/localforge";
|
||||
import { ModalForm, ProFormText, ProFormTextArea } from "@ant-design/pro-form";
|
||||
import { ModalForm, ProFormText } from "@ant-design/pro-form";
|
||||
import { isUrl } from "renderer/utils";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { AppState } from "renderer/store/reducers";
|
||||
@@ -52,10 +52,8 @@ import ProForm from "@ant-design/pro-form";
|
||||
import useElectron from "renderer/hooks/electron";
|
||||
import { nanoid } from "nanoid";
|
||||
import { Settings } from "renderer/store/actions/settings.actions";
|
||||
import {
|
||||
MainState,
|
||||
updateNotifyCount,
|
||||
} from "renderer/store/actions/main.actions";
|
||||
import { updateNotifyCount } from "renderer/store/actions/main.actions";
|
||||
import HeaderEdit from "renderer/components/HeaderEdit";
|
||||
|
||||
type ActionButton = {
|
||||
key: string;
|
||||
@@ -116,7 +114,10 @@ const DownloadList: React.FC<Props> = ({
|
||||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
|
||||
const [favsList, setFavsList] = useState<Fav[]>([]);
|
||||
const [maxWidth, setMaxWidth] = useState<number>(winWidth);
|
||||
const [currentSourceItem, setCurrentSourceItem] = useState<SourceItem>();
|
||||
const [
|
||||
currentSourceItem,
|
||||
setCurrentSourceItem,
|
||||
] = useState<SourceItem | null>();
|
||||
const settings = useSelector<AppState, Settings>((state) => state.settings);
|
||||
const dispatch = useDispatch();
|
||||
const tableDataRef = useRef<SourceItem[]>([]);
|
||||
@@ -125,6 +126,7 @@ const DownloadList: React.FC<Props> = ({
|
||||
itemContextMenu,
|
||||
addEventListener,
|
||||
removeEventListener,
|
||||
ipcExec,
|
||||
} = useElectron();
|
||||
const { exeFile } = settings;
|
||||
const [formRef] = Form.useForm();
|
||||
@@ -146,7 +148,7 @@ const DownloadList: React.FC<Props> = ({
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", calcMaxWidth);
|
||||
removeEventListener("context-menu-command", contextMenuDetail);
|
||||
removeEventListener("download-context-menu-detail", contextMenuDetail);
|
||||
removeEventListener(
|
||||
"download-context-menu-download",
|
||||
contextMenuDownload
|
||||
@@ -164,7 +166,7 @@ const DownloadList: React.FC<Props> = ({
|
||||
item: SourceItem
|
||||
) => {
|
||||
setCurrentSourceItem(item);
|
||||
detailForm.setFieldsValue(preProcessFormData(item));
|
||||
detailForm.setFieldsValue(item);
|
||||
calcMaxWidth();
|
||||
};
|
||||
const contextMenuDownload = (
|
||||
@@ -177,12 +179,12 @@ const DownloadList: React.FC<Props> = ({
|
||||
event: Electron.IpcRendererEvent,
|
||||
item: SourceItem
|
||||
) => {
|
||||
await removeVideo(item.url);
|
||||
await removeVideo(item.id);
|
||||
await updateTableData();
|
||||
};
|
||||
const contextMenuClearAll = async () => {
|
||||
const keys = tableDataRef.current.map((item) => item.url);
|
||||
await removeVideos(keys);
|
||||
const ids = tableDataRef.current.map((item) => item.id);
|
||||
await removeVideos(ids);
|
||||
await updateTableData();
|
||||
};
|
||||
|
||||
@@ -191,26 +193,6 @@ const DownloadList: React.FC<Props> = ({
|
||||
setFavsList(favs);
|
||||
};
|
||||
|
||||
// 表单数据预处理
|
||||
const preProcessFormData = (data: SourceItem) => {
|
||||
const formatter = (key: string, value: any) => {
|
||||
switch (key) {
|
||||
// case "headers":
|
||||
// return Object.entries(value)
|
||||
// .map(([key, value]) => `${key}:${value}`)
|
||||
// .join("\n");
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
const result: Record<string, any> = {};
|
||||
Object.keys(data).forEach((key) => {
|
||||
result[key] = formatter(key, data[key as keyof SourceItem]);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// 渲染视频下载的状态
|
||||
const renderStatus = (item: SourceItem) => {
|
||||
const status = item.status;
|
||||
@@ -238,12 +220,6 @@ const DownloadList: React.FC<Props> = ({
|
||||
window.electron.openBrowserWindow();
|
||||
};
|
||||
|
||||
// 打开使用帮助
|
||||
const openHelp = () => {
|
||||
onEvent.mainPageHelp();
|
||||
window.electron.openExternal(helpUrl);
|
||||
};
|
||||
|
||||
// 向列表中插入一条数据并且请求详情
|
||||
const insertUpdateTableData = async (
|
||||
item: SourceItemForm
|
||||
@@ -352,11 +328,12 @@ const DownloadList: React.FC<Props> = ({
|
||||
};
|
||||
}
|
||||
|
||||
const { code, msg } = await window.electron.ipcExec(exeFile, args);
|
||||
const { code, msg } = await ipcExec(exeFile, args);
|
||||
if (code === 0) {
|
||||
await changeSourceStatus(item, SourceStatus.Success);
|
||||
onEvent.mainPageDownloadSuccess({ msg, url, exeFile });
|
||||
} else {
|
||||
message.error(msg);
|
||||
await changeSourceStatus(item, SourceStatus.Failed);
|
||||
onEvent.mainPageDownloadFail({ msg, url, exeFile });
|
||||
}
|
||||
@@ -442,23 +419,24 @@ const DownloadList: React.FC<Props> = ({
|
||||
return (
|
||||
<Box p={10} borderBottom={"1px solid #EBEEF5"}>
|
||||
<Space>
|
||||
<Button key={"1"} onClick={newDownload}>
|
||||
<AppstoreAddOutlined />
|
||||
<Button
|
||||
key={"1"}
|
||||
onClick={newDownload}
|
||||
icon={<AppstoreAddOutlined />}
|
||||
size={"middle"}
|
||||
>
|
||||
新建下载
|
||||
</Button>
|
||||
<Dropdown.Button
|
||||
size={"middle"}
|
||||
key={"2"}
|
||||
trigger={["click"]}
|
||||
onClick={openBrowser}
|
||||
overlay={browserMenu}
|
||||
icon={<BlockOutlined />}
|
||||
>
|
||||
<BlockOutlined />
|
||||
打开浏览器
|
||||
</Dropdown.Button>
|
||||
<Button key={"4"} onClick={openHelp}>
|
||||
<QuestionCircleOutlined />
|
||||
使用帮助
|
||||
</Button>
|
||||
</Space>
|
||||
</Box>
|
||||
);
|
||||
@@ -547,7 +525,7 @@ const DownloadList: React.FC<Props> = ({
|
||||
<Box display={"flex"}>
|
||||
{buttons.map((button) =>
|
||||
button.showTooltip ? (
|
||||
<Tooltip title={button.tooltip}>
|
||||
<Tooltip title={button.tooltip} placement={"left"}>
|
||||
<Box
|
||||
pl={10}
|
||||
key={button.key}
|
||||
@@ -577,7 +555,6 @@ const DownloadList: React.FC<Props> = ({
|
||||
files: FileList | null,
|
||||
event: ReactDragEvent<HTMLDivElement>
|
||||
) => {
|
||||
console.log(files, event);
|
||||
if (files?.length === 1) {
|
||||
// 只有一个文件被拽入
|
||||
await setIsModalVisible(true);
|
||||
@@ -603,6 +580,9 @@ const DownloadList: React.FC<Props> = ({
|
||||
minHeight={"100%"}
|
||||
minWidth={currentSourceItem ? "350px" : "100%"}
|
||||
maxWidth={currentSourceItem ? maxWidth : "100%"}
|
||||
style={{
|
||||
borderRight: "1px solid rgb(235, 238, 245)",
|
||||
}}
|
||||
>
|
||||
<AutoSizer className={"new-download-list"}>
|
||||
{({ height, width }) => (
|
||||
@@ -640,9 +620,7 @@ const DownloadList: React.FC<Props> = ({
|
||||
className={"list-item-inner"}
|
||||
onClick={() => {
|
||||
setCurrentSourceItem(item);
|
||||
detailForm.setFieldsValue(
|
||||
preProcessFormData(item)
|
||||
);
|
||||
detailForm.setFieldsValue(item);
|
||||
calcMaxWidth();
|
||||
dispatch(updateNotifyCount(0));
|
||||
}}
|
||||
@@ -661,14 +639,29 @@ const DownloadList: React.FC<Props> = ({
|
||||
{currentSourceItem && (
|
||||
<Box
|
||||
p={15}
|
||||
pt={0}
|
||||
height={"100%"}
|
||||
flex={1}
|
||||
overflowY={"auto"}
|
||||
minW={"300px"}
|
||||
>
|
||||
<Box
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
justifyContent={"flex-end"}
|
||||
h={40}
|
||||
>
|
||||
<Button
|
||||
size={"small"}
|
||||
icon={<CloseOutlined />}
|
||||
type={"link"}
|
||||
onClick={() => {
|
||||
setCurrentSourceItem(null);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<ProForm
|
||||
form={detailForm}
|
||||
size={"small"}
|
||||
layout={"horizontal"}
|
||||
submitter={{
|
||||
searchConfig: {
|
||||
@@ -687,31 +680,27 @@ const DownloadList: React.FC<Props> = ({
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name={"title"}
|
||||
label="视频名称"
|
||||
placeholder="请输入视频名称"
|
||||
/>
|
||||
<ProFormText
|
||||
name={"url"}
|
||||
label="请求地址"
|
||||
placeholder="请输入请求地址"
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name={"workspace"}
|
||||
label="本地路径"
|
||||
placeholder="请输入本地路径"
|
||||
/>
|
||||
<ProFormText
|
||||
name={"exeFile"}
|
||||
label="执行程序"
|
||||
placeholder="请选择可执行程序"
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProFormTextArea label={"请求标头"} name={"headers"} />
|
||||
<ProFormText
|
||||
name={"title"}
|
||||
label="视频名称"
|
||||
placeholder="请输入视频名称"
|
||||
/>
|
||||
<ProFormText
|
||||
name={"url"}
|
||||
label="请求地址"
|
||||
placeholder="请输入请求地址"
|
||||
/>
|
||||
<ProFormText
|
||||
name={"workspace"}
|
||||
label="本地路径"
|
||||
placeholder="请输入本地路径"
|
||||
/>
|
||||
<ProFormText
|
||||
name={"exeFile"}
|
||||
label="执行程序"
|
||||
placeholder="请选择可执行程序"
|
||||
/>
|
||||
<HeaderEdit label={"请求标头"} name={"headers"} />
|
||||
</ProForm>
|
||||
</Box>
|
||||
)}
|
||||
@@ -741,11 +730,6 @@ const DownloadList: React.FC<Props> = ({
|
||||
<Button type={"link"} onClick={openBrowser}>
|
||||
打开浏览器
|
||||
</Button>
|
||||
<br />
|
||||
或者
|
||||
<Button type={"link"} onClick={openHelp}>
|
||||
查看帮助
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -43,7 +43,6 @@ const Setting: FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
const formRef = useRef<FormInstance<FormData>>();
|
||||
const [proxyChecked, setProxyChecked] = useState<boolean>(false);
|
||||
console.log("settings: ", settings);
|
||||
|
||||
useEffect(() => {
|
||||
const { useProxy } = settings;
|
||||
|
||||
@@ -3,7 +3,6 @@ import "./index.scss";
|
||||
import { Badge, Button, Drawer, message, Tabs } from "antd";
|
||||
import WindowToolBar from "renderer/components/WindowToolBar";
|
||||
import Setting from "renderer/nodes/main/elements/Setting";
|
||||
import Comment from "renderer/components/Comment";
|
||||
import { SourceStatus, SourceType } from "renderer/types";
|
||||
import {
|
||||
getVideos,
|
||||
@@ -24,6 +23,8 @@ import {
|
||||
MainState,
|
||||
updateNotifyCount,
|
||||
} from "renderer/store/actions/main.actions";
|
||||
import { QuestionCircleOutlined } from "@ant-design/icons";
|
||||
import { helpUrl } from "renderer/utils/variables";
|
||||
|
||||
const audio = new Audio(audioSrc);
|
||||
|
||||
@@ -35,7 +36,6 @@ enum TabKey {
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const MainPage: FC = () => {
|
||||
const [isDrawerVisible, setIsDrawerVisible] = useState<boolean>(false);
|
||||
const [tableData, setTableData] = useState<SourceItem[]>([]);
|
||||
const [activeKey, setActiveKey] = useState<TabKey>(TabKey.HomeTab);
|
||||
const dispatch = useDispatch();
|
||||
@@ -50,21 +50,23 @@ const MainPage: FC = () => {
|
||||
addEventListener,
|
||||
removeEventListener,
|
||||
closeMainWindow,
|
||||
store,
|
||||
minimize,
|
||||
} = useElectron();
|
||||
|
||||
useEffect(() => {
|
||||
initData();
|
||||
|
||||
addEventListener("m3u8", handleWebViewMessage);
|
||||
addEventListener("m3u8-notifier", handleWebViewMessage);
|
||||
return () => {
|
||||
removeEventListener("m3u8", handleWebViewMessage);
|
||||
removeEventListener("m3u8-notifier", handleWebViewMessage);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const initData = async () => {
|
||||
// 开始初始化表格数据
|
||||
const tableData = await getVideos();
|
||||
const initialSettings = await window.electron.store.get();
|
||||
const initialSettings = await store.get();
|
||||
dispatch(updateSettings(initialSettings));
|
||||
setTableData(tableData);
|
||||
setActiveKey(
|
||||
@@ -93,10 +95,6 @@ const MainPage: FC = () => {
|
||||
dispatch(updateNotifyCount(countRef.current + 1));
|
||||
};
|
||||
|
||||
const handleDrawerClose = (): void => {
|
||||
setIsDrawerVisible(false);
|
||||
};
|
||||
|
||||
// 首页面板切换事件
|
||||
const onTabChange = (activeKey: TabKey): void => {
|
||||
if (activeKey === TabKey.HomeTab) {
|
||||
@@ -104,6 +102,12 @@ const MainPage: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 打开使用帮助
|
||||
const openHelp = () => {
|
||||
onEvent.mainPageHelp();
|
||||
window.electron.openExternal(helpUrl);
|
||||
};
|
||||
|
||||
// 首页面板点击事件
|
||||
const onTabClick = async (activeKey: TabKey): Promise<void> => {
|
||||
if (!settings.workspace) {
|
||||
@@ -146,6 +150,9 @@ const MainPage: FC = () => {
|
||||
onClose={() => {
|
||||
closeMainWindow();
|
||||
}}
|
||||
onMinimize={() => {
|
||||
minimize("main");
|
||||
}}
|
||||
/>
|
||||
<div className="main-window">
|
||||
<Tabs
|
||||
@@ -176,27 +183,13 @@ const MainPage: FC = () => {
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<Drawer
|
||||
title="评论反馈"
|
||||
placement="right"
|
||||
closable={false}
|
||||
width={500}
|
||||
onClose={handleDrawerClose}
|
||||
visible={isDrawerVisible}
|
||||
className="comment-drawer"
|
||||
>
|
||||
<Comment />
|
||||
</Drawer>
|
||||
|
||||
<div className="toolbar">
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => {
|
||||
onEvent.mainPageOpenComment();
|
||||
setIsDrawerVisible(true);
|
||||
}}
|
||||
type={"link"}
|
||||
onClick={openHelp}
|
||||
icon={<QuestionCircleOutlined />}
|
||||
>
|
||||
评论反馈
|
||||
使用帮助
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@ const updateVideoStatus = async (
|
||||
// fixme: 当数据量比较大的时候
|
||||
let videos = await localforage.getItem<SourceItem[]>(keys.videos);
|
||||
if (!Array.isArray(videos)) videos = [];
|
||||
const findIndex = videos.findIndex((video) => source.url === video.url);
|
||||
const findIndex = videos.findIndex((video) => source.id === video.id);
|
||||
if (findIndex >= 0) {
|
||||
videos.splice(findIndex, 1, { ...source, status });
|
||||
await localforage.setItem(keys.videos, videos);
|
||||
@@ -39,7 +39,7 @@ const updateVideoStatus = async (
|
||||
const updateVideoTitle = async (source: SourceItem, title: string) => {
|
||||
let videos = await localforage.getItem<SourceItem[]>(keys.videos);
|
||||
if (!Array.isArray(videos)) videos = [];
|
||||
const findIndex = videos.findIndex((video) => source.url === video.url);
|
||||
const findIndex = videos.findIndex((video) => source.id === video.id);
|
||||
if (findIndex >= 0) {
|
||||
videos.splice(findIndex, 1, { ...source, title });
|
||||
await localforage.setItem(keys.videos, videos);
|
||||
@@ -49,25 +49,25 @@ const updateVideoTitle = async (source: SourceItem, title: string) => {
|
||||
const updateVideoUrl = async (source: SourceItem, url: string) => {
|
||||
let videos = await localforage.getItem<SourceItem[]>(keys.videos);
|
||||
if (!Array.isArray(videos)) videos = [];
|
||||
const findIndex = videos.findIndex((video) => source.url === video.url);
|
||||
const findIndex = videos.findIndex((video) => source.id === video.id);
|
||||
if (findIndex >= 0) {
|
||||
videos.splice(findIndex, 1, { ...source, url });
|
||||
await localforage.setItem(keys.videos, videos);
|
||||
}
|
||||
};
|
||||
|
||||
export const removeVideo = async (url: string) => {
|
||||
export const removeVideo = async (id: string) => {
|
||||
let videos = await localforage.getItem<SourceItem[]>(keys.videos);
|
||||
if (!Array.isArray(videos)) videos = [];
|
||||
const favIndex = videos.findIndex((item) => item.url === url);
|
||||
const favIndex = videos.findIndex((item) => item.id === id);
|
||||
if (favIndex >= 0) videos.splice(favIndex, 1);
|
||||
await localforage.setItem(keys.videos, videos);
|
||||
return videos;
|
||||
};
|
||||
|
||||
const removeVideos = async (urls: (string | number)[]): Promise<void> => {
|
||||
for (const url of urls) {
|
||||
await removeVideo(String(url));
|
||||
const removeVideos = async (ids: string[]): Promise<void> => {
|
||||
for (const id of ids) {
|
||||
await removeVideo(String(id));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
import tdApp from "renderer/utils/td";
|
||||
|
||||
const onEvent = {
|
||||
browserPageGoBack: () => tdApp.onEvent("浏览器页面-点击返回按钮"),
|
||||
browserPageReload: () => tdApp.onEvent("浏览器页面-点击刷新按钮"),
|
||||
mainPageDownloadFail: (kv: any) => tdApp.onEvent("下载页面-下载视频失败", kv),
|
||||
mainPageDownloadSuccess: (kv: any) =>
|
||||
browserPageGoBack: (): void => tdApp.onEvent("浏览器页面-点击返回按钮"),
|
||||
browserPageReload: (): void => tdApp.onEvent("浏览器页面-点击刷新按钮"),
|
||||
mainPageDownloadFail: (kv: unknown): void =>
|
||||
tdApp.onEvent("下载页面-下载视频失败", kv),
|
||||
mainPageDownloadSuccess: (kv: unknown): void =>
|
||||
tdApp.onEvent("下载页面-下载视频成功", kv),
|
||||
mainPageOpenBrowserPage: () => tdApp.onEvent("下载页面-打开浏览器页面"),
|
||||
// ===================================
|
||||
mainPageNewSource: () => tdApp.onEvent("下载页面-新建下载"),
|
||||
mainPageOpenLocalPath: () => tdApp.onEvent("下载页面-打开本地路径"),
|
||||
mainPageHelp: () => tdApp.onEvent("下载页面-打开使用帮助"),
|
||||
tableStartDownload: () => tdApp.onEvent("资源表格-下载按钮"),
|
||||
tableOpenDetail: () => tdApp.onEvent("资源表格-详情按钮"),
|
||||
tableReNewStatus: () => tdApp.onEvent("资源表格-重置状态"),
|
||||
mainPageOpenComment: () => tdApp.onEvent("下载页面-点击评论反馈"),
|
||||
mainPageSourceCode: () => tdApp.onEvent("下载页面-源码地址"),
|
||||
mainUpdateLog: () => tdApp.onEvent("下载页面-更新日志"),
|
||||
toFavPage: () => tdApp.onEvent("下载页面-点击切换收藏页面"),
|
||||
toSettingPage: () => tdApp.onEvent("下载页面-点击切换设置页面"),
|
||||
toMainPage: () => tdApp.onEvent("下载页面-点击切换主页面"),
|
||||
favPageAddFav: () => tdApp.onEvent("收藏页面-添加收藏"),
|
||||
favPageOpenLink: () => tdApp.onEvent("收藏页面-打开链接"),
|
||||
favPageDeleteLink: () => tdApp.onEvent("收藏页面-删除链接"),
|
||||
addSourceDownload: () => tdApp.onEvent("新建下载-立即下载"),
|
||||
addSourceAddSource: () => tdApp.onEvent("新建下载-添加资源"),
|
||||
mainPageOpenBrowserPage: (): void => tdApp.onEvent("下载页面-打开浏览器页面"),
|
||||
mainPageNewSource: (): void => tdApp.onEvent("下载页面-新建下载"),
|
||||
mainPageHelp: (): void => tdApp.onEvent("下载页面-打开使用帮助"),
|
||||
tableStartDownload: (): void => tdApp.onEvent("资源表格-下载按钮"),
|
||||
tableReNewStatus: (): void => tdApp.onEvent("资源表格-重置状态"),
|
||||
toSettingPage: (): void => tdApp.onEvent("下载页面-点击切换设置页面"),
|
||||
toMainPage: (): void => tdApp.onEvent("下载页面-点击切换主页面"),
|
||||
favPageAddFav: (): void => tdApp.onEvent("收藏页面-添加收藏"),
|
||||
favPageOpenLink: (): void => tdApp.onEvent("收藏页面-打开链接"),
|
||||
favPageDeleteLink: (): void => tdApp.onEvent("收藏页面-删除链接"),
|
||||
addSourceDownload: (): void => tdApp.onEvent("新建下载-立即下载"),
|
||||
addSourceAddSource: (): void => tdApp.onEvent("新建下载-添加资源"),
|
||||
};
|
||||
|
||||
export default onEvent;
|
||||
|
||||
+25
-19
@@ -1,22 +1,28 @@
|
||||
const onEvent = (eventId: string, mapKv: any = {}): void => {
|
||||
try {
|
||||
window.TDAPP.onEvent(eventId, "", mapKv);
|
||||
console.log(`添加埋点成功:${eventId}`, mapKv);
|
||||
} catch (err) {
|
||||
console.warn(`添加埋点失败:${eventId}`, err);
|
||||
import { version } from "../../../package.json";
|
||||
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
|
||||
class TDEvent {
|
||||
appId?: string | boolean;
|
||||
vn: string;
|
||||
vc: string;
|
||||
|
||||
constructor() {
|
||||
this.appId = import.meta.env.VITE_APP_TDID;
|
||||
this.vn = isProd ? `${version}生产版` : `${version}开发版`;
|
||||
this.vc = `${version}`;
|
||||
}
|
||||
};
|
||||
|
||||
const init = (): void => {
|
||||
const appId = import.meta.env.VITE_APP_TDID;
|
||||
const vn = import.meta.env.VITE_APP_TDVN;
|
||||
const vc = import.meta.env.VITE_APP_TDVC;
|
||||
const script = document.createElement("script");
|
||||
const src = `https://jic.talkingdata.com/app/h5/v1?appid=${appId}&vn=${vn}&vc=${vc}`;
|
||||
script.src = src;
|
||||
console.info("埋点地址是:", src);
|
||||
const headElement = document.getElementsByTagName("head")[0];
|
||||
headElement.appendChild(script);
|
||||
};
|
||||
init() {
|
||||
const script = document.createElement("script");
|
||||
script.src = `https://jic.talkingdata.com/app/h5/v1?appid=${this.appId}&vn=${this.vn}&vc=${this.vc}`;
|
||||
const headElement = document.getElementsByTagName("head")[0];
|
||||
headElement.appendChild(script);
|
||||
}
|
||||
|
||||
export default { onEvent, init };
|
||||
onEvent(eventId: string, mapKv: any = {}) {
|
||||
window.TDAPP.onEvent(eventId, "", mapKv);
|
||||
}
|
||||
}
|
||||
|
||||
export default new TDEvent();
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
import Store from "electron-store";
|
||||
import { Windows } from "main/variables";
|
||||
import { Windows } from "main/utils/variables";
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
|
||||
Vendored
+1
@@ -103,6 +103,7 @@ interface ElectronApi {
|
||||
browserViewLoadURL: (url?: string) => void;
|
||||
request: <T>(options: RequestOptions) => Promise<RequestResponse<T>>;
|
||||
itemContextMenu: (item: SourceItem) => void;
|
||||
minimize: (name: string) => void;
|
||||
}
|
||||
|
||||
declare interface Window {
|
||||
|
||||
Reference in New Issue
Block a user