diff --git a/enjoy/package.json b/enjoy/package.json index 148c2569..18c4feef 100644 --- a/enjoy/package.json +++ b/enjoy/package.json @@ -183,7 +183,6 @@ "tslib": "^2.8.1", "turndown": "^7.2.0", "typescript": "^5.7.2", - "update-electron-app": "^3.0.0", "vite": "^6.0.3", "vite-plugin-static-copy": "^2.2.0", "wavesurfer.js": "^7.8.10", diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index cb7fe01d..3290efb4 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -1,4 +1,19 @@ { + "menu": { + "about": "About", + "toggleFullScreen": "Toggle Full Screen", + "hide": "Hide", + "unhide": "Unhide", + "quit": "Quit", + "undo": "Undo", + "redo": "Redo", + "cut": "Cut", + "copy": "Copy", + "paste": "Paste", + "selectAll": "Select All", + "checkForUpdates": "Check for Updates", + "reportIssue": "Report Issue" + }, "models": { "user": { "id": "ID", @@ -360,7 +375,6 @@ "currentVersion": "Current version", "checkUpdate": "Check update", "checkingLatestVersion": "Checking latest version", - "updateAvailable": "Update available", "updateDownloaded": "A new version has been downloaded. Restart the application to apply the updates.", "restart": "Restart", "later": "Later", @@ -938,5 +952,8 @@ "horizontal": "Horizontal", "vertical": "Vertical", "copied": "Copied", - "copyFullText": "Copy full text" + "copyFullText": "Copy full text", + "checkingForUpdate": "Checking for update", + "updateAvailable": "Update available", + "quitAndInstall": "Quit and install" } diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index 85d28b1e..019affa1 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -1,4 +1,19 @@ { + "menu": { + "about": "关于", + "toggleFullScreen": "全屏", + "hide": "隐藏", + "unhide": "显示", + "quit": "退出", + "undo": "撤销", + "redo": "重做", + "cut": "剪切", + "copy": "复制", + "paste": "粘贴", + "selectAll": "全选", + "checkForUpdates": "检查更新", + "reportIssue": "报告问题" + }, "models": { "user": { "id": "ID", @@ -360,7 +375,6 @@ "currentVersion": "当前版本", "checkUpdate": "检查更新", "checkingLatestVersion": "正在检查最新版本", - "updateAvailable": "有新版本可用", "updateDownloaded": "新版本已下载,点击重新启动以更新", "restart": "Restart", "later": "Later", @@ -938,5 +952,8 @@ "horizontal": "水平", "vertical": "垂直", "copied": "已复制", - "copyFullText": "复制全文" + "copyFullText": "复制全文", + "checkingForUpdate": "正在检查更新", + "updateAvailable": "有新版本", + "quitAndInstall": "退出并安装新版本" } diff --git a/enjoy/src/main.ts b/enjoy/src/main.ts index 2a5f249c..7bd9cfdf 100644 --- a/enjoy/src/main.ts +++ b/enjoy/src/main.ts @@ -7,7 +7,6 @@ import mainWindow from "@main/window"; import ElectronSquirrelStartup from "electron-squirrel-startup"; import contextMenu from "electron-context-menu"; import { t } from "i18next"; -import { updateElectronApp, UpdateSourceType } from "update-electron-app"; import unhandled from "electron-unhandled"; import newGithubIssueUrl from "new-github-issue-url"; @@ -15,19 +14,6 @@ const logger = log.scope("main"); app.commandLine.appendSwitch("enable-features", "SharedArrayBuffer"); -// config auto updater -if (!process.env.CI) { - updateElectronApp({ - updateSource: { - type: UpdateSourceType.StaticStorage, - baseUrl: `https://dl.enjoy.bot/app/${process.platform}/${process.arch}`, - }, - updateInterval: "1 hour", - logger: logger, - notifyUser: true, - }); -} - if (!app.isPackaged) { app.disableHardwareAcceleration(); app.commandLine.appendSwitch("disable-software-rasterizer"); diff --git a/enjoy/src/main/window.ts b/enjoy/src/main/window.ts index 5821451b..4b8ea56e 100644 --- a/enjoy/src/main/window.ts +++ b/enjoy/src/main/window.ts @@ -8,6 +8,7 @@ import { dialog, systemPreferences, MenuItemConstructorOptions, + autoUpdater, } from "electron"; import path from "path"; import db from "@main/db"; @@ -25,7 +26,9 @@ import dict from "./dict"; import mdict from "./mdict"; import decompresser from "./decompresser"; import { UserSetting } from "@main/db/models"; -import { platform } from "os"; +import { t } from "i18next"; +import { format } from "util"; +import pkg from "../../package.json" assert { type: "json" }; const __dirname = import.meta.dirname; @@ -37,6 +40,25 @@ const youtubeProvider = new YoutubeProvider(); const ffmpeg = new Ffmpeg(); const waveform = new Waveform(); +const FEED_BASE_URL = `https://dl.enjoy.bot/app/${process.platform}/${process.arch}`; +autoUpdater.setFeedURL({ + url: + process.platform === "darwin" + ? `${FEED_BASE_URL}/RELEASES.json` + : FEED_BASE_URL, + headers: { + "X-App-Version": app.getVersion(), + "User-Agent": format( + "%s/%s (%s: %s)", + pkg.name, + pkg.version, + process.platform, + process.arch + ), + }, + serverType: process.platform === "darwin" ? "json" : "default", +}); + const main = { win: null as BrowserWindow | null, init: () => {}, @@ -386,6 +408,44 @@ main.init = async () => { app.quit(); }); + ipcMain.handle("app-check-for-updates", () => { + autoUpdater.checkForUpdates(); + }); + + ipcMain.handle("app-quit-and-install", () => { + autoUpdater.quitAndInstall(); + }); + + ipcMain.on("app-on-updater", () => { + autoUpdater.on("error", (error) => { + mainWindow.webContents.send("app-on-updater", "error", [error]); + }); + autoUpdater.on("checking-for-update", () => { + mainWindow.webContents.send("app-on-updater", "checking-for-update", []); + }); + autoUpdater.on("update-available", () => { + mainWindow.webContents.send("app-on-updater", "update-available", []); + }); + autoUpdater.on( + "update-downloaded", + (_event, releaseNotes, releaseName, releaseDate, updateURL) => { + logger.info( + "update-downloaded", + releaseNotes, + releaseName, + releaseDate, + updateURL + ); + mainWindow.webContents.send("app-on-updater", "update-downloaded", [ + releaseNotes, + releaseName, + releaseDate, + updateURL, + ]); + } + ); + }); + ipcMain.handle("app-open-dev-tools", () => { mainWindow.webContents.openDevTools(); }); @@ -646,38 +706,38 @@ ${log} { label: app.name, submenu: [ - { role: "about" }, + { role: "about", label: t("menu.about") }, { type: "separator" }, - { role: "togglefullscreen" }, - { role: "hide" }, - { role: "unhide" }, + { role: "togglefullscreen", label: t("menu.toggleFullScreen") }, + { role: "hide", label: t("menu.hide") }, + { role: "unhide", label: t("menu.unhide") }, { type: "separator" }, - { role: "quit" }, + { role: "quit", label: t("menu.quit") }, ], }, { label: "Edit", submenu: [ - { role: "undo" }, - { role: "redo" }, + { role: "undo", label: t("menu.undo") }, + { role: "redo", label: t("menu.redo") }, { type: "separator" }, - { role: "cut" }, - { role: "copy" }, - { role: "paste" }, - { role: "selectAll" }, + { role: "cut", label: t("menu.cut") }, + { role: "copy", label: t("menu.copy") }, + { role: "paste", label: t("menu.paste") }, + { role: "selectAll", label: t("menu.selectAll") }, ], }, { label: "Help", submenu: [ { - label: "Check for Updates...", + label: t("menu.checkForUpdates"), click: () => { shell.openExternal("https://1000h.org/enjoy-app/install.html"); }, }, { - label: "Report Issue...", + label: t("menu.reportIssue"), click: () => { shell.openExternal(`${REPO_URL}/issues/new`); }, diff --git a/enjoy/src/preload.ts b/enjoy/src/preload.ts index 687e5579..4f3fe840 100644 --- a/enjoy/src/preload.ts +++ b/enjoy/src/preload.ts @@ -37,6 +37,22 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", { quit: () => { ipcRenderer.invoke("app-quit"); }, + checkForUpdates: () => { + ipcRenderer.invoke("app-check-for-updates"); + }, + quitAndInstall: () => { + ipcRenderer.invoke("app-quit-and-install"); + }, + onUpdater: ( + callback: ( + event: IpcRendererEvent, + eventType: string, + args: any[] + ) => void + ) => ipcRenderer.on("app-on-updater", callback), + removeUpdaterListeners: () => { + ipcRenderer.removeAllListeners("app-on-updater"); + }, openDevTools: () => { ipcRenderer.invoke("app-open-dev-tools"); }, diff --git a/enjoy/src/renderer/components/layouts/title-bar.tsx b/enjoy/src/renderer/components/layouts/title-bar.tsx index 101a257a..f84efbb6 100644 --- a/enjoy/src/renderer/components/layouts/title-bar.tsx +++ b/enjoy/src/renderer/components/layouts/title-bar.tsx @@ -32,7 +32,6 @@ import { LightbulbIcon, LightbulbOffIcon, MaximizeIcon, - MenuIcon, MinimizeIcon, MinusIcon, SettingsIcon, @@ -40,16 +39,31 @@ import { } from "lucide-react"; import { useContext, useEffect, useState } from "react"; +const INSTALL_URL = "https://1000h.org/enjoy-app/install.html"; + export const TitleBar = () => { const [isMaximized, setIsMaximized] = useState(false); const [isFullScreen, setIsFullScreen] = useState(false); const [platform, setPlatform] = useState<"darwin" | "win32" | "linux">(); + const [updaterState, setUpdaterState] = useState< + "checking-for-update" | "update-available" | "update-downloaded" | "error" + >(); const { EnjoyApp, setDisplayPreferences, initialized } = useContext( AppSettingsProviderContext ); const { active, setActive } = useContext(CopilotProviderContext); + const checkUpdate = () => { + if (platform === "linux") { + EnjoyApp.shell.openExternal(INSTALL_URL); + } else if (updaterState === "update-downloaded") { + EnjoyApp.app.quitAndInstall(); + } else { + EnjoyApp.app.checkForUpdates(); + } + }; + const onWindowChange = ( _event: IpcRendererEvent, state: { event: string } @@ -65,14 +79,28 @@ export const TitleBar = () => { } }; + const onUpdater = ( + _event: IpcRendererEvent, + eventType: + | "checking-for-update" + | "update-available" + | "update-downloaded" + | "error", + args: any[] + ) => { + setUpdaterState(eventType); + }; + useEffect(() => { EnjoyApp.window.onChange(onWindowChange); EnjoyApp.app.getPlatformInfo().then((info) => { setPlatform(info.platform as "darwin" | "win32" | "linux"); }); + EnjoyApp.app.onUpdater(onUpdater); return () => { EnjoyApp.window.removeListener(onWindowChange); + EnjoyApp.app.removeUpdaterListeners(); }; }, []); @@ -123,9 +151,12 @@ export const TitleBar = () => { @@ -181,14 +212,20 @@ export const TitleBar = () => { - EnjoyApp.shell.openExternal( - "https://1000h.org/enjoy-app/install.html" - ) - } - className="cursor-pointer" + onClick={checkUpdate} + className="cursor-pointer relative" > - {t("checkUpdate")} + {updaterState && ( + + )} + + {updaterState === "checking-for-update" && + t("checkingForUpdate")} + {updaterState === "update-available" && t("updateAvailable")} + {updaterState === "update-downloaded" && t("quitAndInstall")} + {!updaterState || + (updaterState === "error" && t("checkUpdate"))} + diff --git a/enjoy/src/renderer/components/preferences/about.tsx b/enjoy/src/renderer/components/preferences/about.tsx index 82024d88..97faf4ce 100644 --- a/enjoy/src/renderer/components/preferences/about.tsx +++ b/enjoy/src/renderer/components/preferences/about.tsx @@ -7,7 +7,13 @@ export const About = () => { const { version, EnjoyApp } = useContext(AppSettingsProviderContext); const checkUpdate = async () => { - EnjoyApp.shell.openExternal("https://1000h.org/enjoy-app/install.html"); + const platformInfo = await EnjoyApp.app.getPlatformInfo(); + if (platformInfo.platform === "linux") { + EnjoyApp.shell.openExternal("https://1000h.org/enjoy-app/install.html"); + } else { + EnjoyApp.app.checkForUpdates(); + toast.info(t("checkingForUpdate")); + } }; return ( diff --git a/enjoy/src/types/enjoy-app.d.ts b/enjoy/src/types/enjoy-app.d.ts index db815fda..d985c10c 100644 --- a/enjoy/src/types/enjoy-app.d.ts +++ b/enjoy/src/types/enjoy-app.d.ts @@ -13,6 +13,16 @@ type EnjoyAppType = { createIssue: (title: string, body: string) => Promise; onCmdOutput: (callback: (event, output: string) => void) => void; removeCmdOutputListeners: () => void; + checkForUpdates: () => Promise; + quitAndInstall: () => Promise; + onUpdater: ( + callback: ( + event: IpcRendererEvent, + eventType: string, + args: any[] + ) => void + ) => void; + removeUpdaterListeners: () => void; diskUsage: () => Promise; version: string; }; diff --git a/yarn.lock b/yarn.lock index ec461987..e0f7b55a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12197,7 +12197,6 @@ __metadata: typescript: "npm:^5.7.2" umzug: "npm:^3.8.2" unzipper: "npm:^0.12.3" - update-electron-app: "npm:^3.0.0" vite: "npm:^6.0.3" vite-plugin-static-copy: "npm:^2.2.0" wavesurfer.js: "npm:^7.8.10" @@ -13935,15 +13934,6 @@ __metadata: languageName: node linkType: hard -"github-url-to-object@npm:^4.0.4": - version: 4.0.6 - resolution: "github-url-to-object@npm:4.0.6" - dependencies: - is-url: "npm:^1.1.0" - checksum: 10c0/b8ed9b2b93c55657299b7cc5ce9faf4441f143e59f995e126929d18f8fc692fc932d8530da9bde7655f35b29201beacbc17b066b7e26dbcc0dc983f8d3c918cf - languageName: node - linkType: hard - "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -15402,7 +15392,7 @@ __metadata: languageName: node linkType: hard -"is-url@npm:^1.1.0, is-url@npm:^1.2.4": +"is-url@npm:^1.2.4": version: 1.2.4 resolution: "is-url@npm:1.2.4" checksum: 10c0/0157a79874f8f95fdd63540e3f38c8583c2ef572661cd0693cda80ae3e42dfe8e9a4a972ec1b827f861d9a9acf75b37f7d58a37f94a8a053259642912c252bc3 @@ -23524,17 +23514,6 @@ __metadata: languageName: node linkType: hard -"update-electron-app@npm:^3.0.0": - version: 3.0.0 - resolution: "update-electron-app@npm:3.0.0" - dependencies: - github-url-to-object: "npm:^4.0.4" - is-url: "npm:^1.2.4" - ms: "npm:^2.1.1" - checksum: 10c0/5e0b9ce8fd2cbcf6ea64d8029f90d90394facf05d86c8dceda5dba79af6e21522ef4f37f212982be781f488d497882529d9b11b91174159f6567a24039c76e6a - languageName: node - linkType: hard - "uqr@npm:^0.1.2": version: 0.1.2 resolution: "uqr@npm:0.1.2"