diff --git a/assets/jsons/changelogs.json b/assets/jsons/changelogs.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/assets/jsons/changelogs.json @@ -0,0 +1 @@ +{} diff --git a/assets/jsons/patreons.json b/assets/jsons/patreons.json index a6eaed66..dba45720 100644 --- a/assets/jsons/patreons.json +++ b/assets/jsons/patreons.json @@ -73,5 +73,11 @@ }, { "username": "Minescence" + }, + { + "username": "Finlay" + }, + { + "username": "mereknom" } ] diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 47911e4a..0e4ec724 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -191,6 +191,10 @@ "report-bug": "Fehler melden", "open-logs": "Protokolle öffnen" } + }, + "changelogs": { + "open" : "🚀 Neues im Changelog entdecken!", + "not_founds": "😕 Hoppla, kein Changelog zu sehen!" } } }, diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index d916d46d..c3acdaca 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -191,6 +191,10 @@ "report-bug": "Report a bug", "open-logs": "Open logs" } + }, + "changelogs": { + "open" : "🚀 Check Out the Changelog!", + "not-founds": "😕 Oops, No Changelog Here!" } } }, diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 6e7948cf..3dd0c6d9 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -191,6 +191,10 @@ "report-bug": "Informar de un error", "open-logs": "Abrir los registros" } + }, + "changelogs": { + "open" : "🚀 ¡Descubre lo nuevo en el Changelog!", + "not_founds": "😕 Uy, ¡Changelog no encontrado!" } } }, diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index b0e0f3e6..4bdc643f 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -191,6 +191,10 @@ "report-bug": "Signaler un bug", "open-logs": "Ouvrir les logs" } + }, + "changelogs": { + "open" : "🚀 Jetez un œil au Changelog !", + "not_founds": "😕 Oups, Changelog introuvable !" } } }, diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 04766a8d..6066151f 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -191,6 +191,10 @@ "report-bug": "バグを報告", "open-logs": "ログを表示" } + }, + "changelogs": { + "open" : "🚀 チェンジログを覗いてみて!", + "not_founds": "😕 あれ、チェンジログが見当たらない!" } } }, diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 34321568..bce2403d 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -191,6 +191,10 @@ "report-bug": "Сообщить о баге", "open-logs": "Открыть логи" } + }, + "changelogs": { + "open" : "🚀 Изучаем Changelog!", + "not_founds": "😕 Ой, Changelog нигде не видно!" } } }, diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 06a6d8cc..e05baa8b 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -191,6 +191,10 @@ "report-bug": "報告 bug", "open-logs": "打開日誌" } + }, + "changelogs": { + "open" : "🚀 探索更新日誌!", + "not_founds": "😕 噢,找不到更新日誌!" } } }, diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index ce678890..c1a3724c 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -191,6 +191,10 @@ "report-bug": "报告 bug", "open-logs": "打开日志" } + }, + "changelogs": { + "open" : "🚀 探索更新日志!", + "not_founds": "😕 哎呀,找不到更新日志!" } } }, diff --git a/package-lock.json b/package-lock.json index 655240b7..2d14e785 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "color": "^4.2.3", "crypto-js": "^4.2.0", "dateformat": "^5.0.3", + "dompurify": "^3.0.8", "dot-prop": "^8.0.2", "electron-debug": "^3.2.0", "electron-log": "^4.4.8", @@ -33,6 +34,7 @@ "pako": "^2.1.0", "protobufjs": "^7.2.6", "qrcode.react": "^3.1.0", + "query-process": "^0.0.3", "react": "^18.2.0", "react-colorful": "^5.6.1", "react-dom": "^18.2.0", @@ -66,6 +68,7 @@ "@types/color": "^3.0.3", "@types/crypto-js": "^4.2.1", "@types/dateformat": "^5.0.0", + "@types/dompurify": "^3.0.5", "@types/got": "^9.6.12", "@types/jest": "^29.5.11", "@types/node": "20.11.15", @@ -2794,6 +2797,15 @@ "@types/ms": "*" } }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/eslint": { "version": "8.4.10", "dev": true, @@ -3181,6 +3193,12 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true + }, "node_modules/@types/use-double-click": { "version": "1.0.4", "dev": true, @@ -6884,6 +6902,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.9.tgz", + "integrity": "sha512-uyb4NDIvQ3hRn6NiC+SIFaP4mJ/MdXlvtunaqK9Bn6dD3RuB/1S/gasEjDHD8eiaqdSael2vBv+hOs7Y+jhYOQ==" + }, "node_modules/domutils": { "version": "2.8.0", "dev": true, @@ -14976,6 +14999,160 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-process": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/query-process/-/query-process-0.0.3.tgz", + "integrity": "sha512-6WRue0P+c84abP0o8MkDvigoS/gkkJzsSBt10PsOoBkP7Yodo4DducFsn9XyS4S+LuVYPAE5Hr4+kVNR5Onykg==", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "query-process-android-arm-eabi": "0.0.3", + "query-process-android-arm64": "0.0.3", + "query-process-linux-arm-gnueabihf": "0.0.3", + "query-process-linux-arm64-gnu": "0.0.3", + "query-process-linux-arm64-musl": "0.0.3", + "query-process-linux-x64-gnu": "0.0.3", + "query-process-linux-x64-musl": "0.0.3", + "query-process-win32-arm64-msvc": "0.0.3", + "query-process-win32-x64-msvc": "0.0.3" + } + }, + "node_modules/query-process-android-arm-eabi": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/query-process-android-arm-eabi/-/query-process-android-arm-eabi-0.0.3.tgz", + "integrity": "sha512-NB+9T+/poBcygDiG+7d2lJSeioP5ZyLT0HwUBdnLgNWvNCpzbJxydup1SLpnt5G2NF6nlTgbUZlH4hvxY7t/bw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/query-process-android-arm64": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/query-process-android-arm64/-/query-process-android-arm64-0.0.3.tgz", + "integrity": "sha512-TiAw2yO62vQzM0s02471LToHyUs/PRkLGGlA715z10BPIM5NckzIe245AI45CDyH6tE/3nVHUd0LX/dfjQtTcw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/query-process-linux-arm-gnueabihf": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/query-process-linux-arm-gnueabihf/-/query-process-linux-arm-gnueabihf-0.0.3.tgz", + "integrity": "sha512-w7NLHBKt7qselCDoh/PVWyeuEb2NiiDDf1A9UB4K3Ns/H24hK3NbfMMBm8mCFxUnFUqoVX8O+VMs7dD/FNlTxA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/query-process-linux-arm64-gnu": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/query-process-linux-arm64-gnu/-/query-process-linux-arm64-gnu-0.0.3.tgz", + "integrity": "sha512-nrPHjnSqCBuuN7IZkZWOnBpiJjyKOjfpT0Xt2EjSGp+I7C0CHbnhCL/vG1hBzaWZn0mxJ5nf0c5N0Fja7krnpQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/query-process-linux-arm64-musl": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/query-process-linux-arm64-musl/-/query-process-linux-arm64-musl-0.0.3.tgz", + "integrity": "sha512-vk8xMYTnS1PRKhwJgqLKyBAGIFP2CYZW2BsaKuNgQKUrDv7NnBaG7yukZT8niHNO1ycSrFpR/xqeJ8R23hO07A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/query-process-linux-x64-gnu": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/query-process-linux-x64-gnu/-/query-process-linux-x64-gnu-0.0.3.tgz", + "integrity": "sha512-j1tGcNnGyVCABJ1PHZD1gKuyuomNzsPow3NDuZzUGjPqvKu67RsmKJJJOhMyT1SvUkcqdLFdtS1KTD20I4GS5Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/query-process-linux-x64-musl": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/query-process-linux-x64-musl/-/query-process-linux-x64-musl-0.0.3.tgz", + "integrity": "sha512-AOGrrI/Qcb30iKFstiW60zNG/HQevCY0Q38eMHGd7LrBZ5sEN7NzZOjzKp43KeNHejuZBDMxf0gdGAqDznOAng==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/query-process-win32-arm64-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/query-process-win32-arm64-msvc/-/query-process-win32-arm64-msvc-0.0.3.tgz", + "integrity": "sha512-LHvxbzMFwPPUXHe4bDCAqs1bYbp8j0bMW8XyJGPZnIYrnwHz6g8VE5WQEtXQxSznsbfVxAKJXUtn088EjDVazw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/query-process-win32-x64-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/query-process-win32-x64-msvc/-/query-process-win32-x64-msvc-0.0.3.tgz", + "integrity": "sha512-LfHAKmfqyKCgl9K+sTz0KyNwMwB79VcXZUy/Et9iQKLuZt7PDYpHWcFdhrfHw2qqIdKWTED8KmnmsOLhUn/2OA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "funding": [ diff --git a/package.json b/package.json index ac84ad1b..d7a2f6c2 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,7 @@ "@types/crypto-js": "^4.2.1", "@types/dateformat": "^5.0.0", "@types/got": "^9.6.12", + "@types/dompurify": "^3.0.5", "@types/jest": "^29.5.11", "@types/node": "20.11.15", "@types/node-fetch": "^2.6.3", @@ -252,6 +253,7 @@ "color": "^4.2.3", "crypto-js": "^4.2.0", "dateformat": "^5.0.3", + "dompurify": "^3.0.8", "dot-prop": "^8.0.2", "electron-debug": "^3.2.0", "electron-log": "^4.4.8", @@ -271,6 +273,7 @@ "pako": "^2.1.0", "protobufjs": "^7.2.6", "qrcode.react": "^3.1.0", + "query-process": "^0.0.3", "react": "^18.2.0", "react-colorful": "^5.6.1", "react-dom": "^18.2.0", diff --git a/src/main/ipcs/bs-launcher-ipcs.ts b/src/main/ipcs/bs-launcher-ipcs.ts index c96c6a9b..53614fb1 100644 --- a/src/main/ipcs/bs-launcher-ipcs.ts +++ b/src/main/ipcs/bs-launcher-ipcs.ts @@ -32,7 +32,6 @@ ipc.on("create-launch-shortcut", (req, reply) => { reply(from(bsLauncher.createLaunchShortcut(req.args))); }); - ipc.on("bs-launch.restore-steamvr", (_, reply) => { const steamLauncher = SteamLauncherService.getInstance(); reply(from(steamLauncher.restoreSteamVR())); @@ -43,4 +42,4 @@ ipc.on("restore-original-oculus-folder", (_, reply) => { reply(from( oculusLauncher.deleteBsSymlinks().then(() => oculusLauncher.restoreOriginalBeatSaber()) )); -}) +}); diff --git a/src/main/services/bs-launcher/oculus-launcher.service.ts b/src/main/services/bs-launcher/oculus-launcher.service.ts index b71cd2a9..d6edc3ed 100644 --- a/src/main/services/bs-launcher/oculus-launcher.service.ts +++ b/src/main/services/bs-launcher/oculus-launcher.service.ts @@ -11,6 +11,7 @@ import { AbstractLauncherService } from "./abstract-launcher.service"; import { taskRunning } from "../../helpers/os.helpers"; import { CustomError } from "../../../shared/models/exceptions/custom-error.class"; import { InstallationLocationService } from "../installation-location.service"; +import { ensurePathNotAlreadyExist } from "../../helpers/fs.helpers"; export class OculusLauncherService extends AbstractLauncherService implements StoreLauncherInterface { @@ -86,7 +87,7 @@ export class OculusLauncherService extends AbstractLauncherService implements St private async backupOriginalBeatSaber(): Promise{ const bsFolder = await this.oculus.getGameFolder(OCULUS_BS_DIR); if(!bsFolder){ return; } - const backupPath = path.join(bsFolder, "..", OCULUS_BS_BACKUP_DIR); + const backupPath = await ensurePathNotAlreadyExist(path.join(bsFolder, "..", OCULUS_BS_BACKUP_DIR)); log.info("Backing up original Beat Saber", bsFolder, backupPath); return rename(bsFolder, backupPath); } diff --git a/src/renderer/components/modal/modal-types/chabgelog-modal/changelog-modal.component.css b/src/renderer/components/modal/modal-types/chabgelog-modal/changelog-modal.component.css new file mode 100644 index 00000000..fa08a62b --- /dev/null +++ b/src/renderer/components/modal/modal-types/chabgelog-modal/changelog-modal.component.css @@ -0,0 +1,3 @@ +.content * { + all: revert; +} diff --git a/src/renderer/components/modal/modal-types/chabgelog-modal/changelog-modal.component.tsx b/src/renderer/components/modal/modal-types/chabgelog-modal/changelog-modal.component.tsx new file mode 100644 index 00000000..4c461e22 --- /dev/null +++ b/src/renderer/components/modal/modal-types/chabgelog-modal/changelog-modal.component.tsx @@ -0,0 +1,46 @@ +import { BsmButton } from "renderer/components/shared/bsm-button.component"; +import { ChangelogVersion } from "renderer/services/auto-updater.service"; +import { LinkOpenerService } from "renderer/services/link-opener.service"; +import { useService } from "renderer/hooks/use-service.hook"; +import { ModalComponent } from "renderer/services/modale.service"; +import DOMPurify from 'dompurify'; +import './changelog-modal.component.css'; +import Tippy from "@tippyjs/react"; + +export const ChangelogModal: ModalComponent = ({ resolver, data: changelog }) => { + + const linkOpener = useService(LinkOpenerService); + const openGithub = () => linkOpener.open("https://github.com/Zagrios/bs-manager"); + const openTwitter = () => linkOpener.open("https://twitter.com/BSManager_"); + const openSupportPage = () => linkOpener.open("https://www.patreon.com/bsmanager"); + const openDiscord = () => linkOpener.open("https://discord.gg/uSqbHVpKdV"); + const date = changelog?.timestamp ? new Date(changelog.timestamp * 1000).toLocaleDateString() : ''; + + return ( +
+

{changelog?.title}

+
+ +
+
+ {date} + v{changelog?.version} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ + ) +} diff --git a/src/renderer/components/shared/bsm-button.component.tsx b/src/renderer/components/shared/bsm-button.component.tsx index 351ef8a8..9e051db7 100644 --- a/src/renderer/components/shared/bsm-button.component.tsx +++ b/src/renderer/components/shared/bsm-button.component.tsx @@ -6,7 +6,7 @@ import { useClickOutside } from "renderer/hooks/use-click-outside.hook"; import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; import { getCorrectTextColor } from "renderer/helpers/correct-text-color"; -type BsmButtonType = "primary" | "secondary" | "success" | "cancel" | "error"; +type BsmButtonType = "primary" | "secondary" | "success" | "cancel" | "error" | "none"; type Props = { className?: string; @@ -50,7 +50,7 @@ export function BsmButton({ className, style, imgClassName, iconClassName, icon, if (primaryColor) { return getCorrectTextColor(primaryColor); } - return typeColor ? "white" : undefined; + return typeColor && typeColor !== "none" ? "white" : undefined; })(); const renderTypeColor = (() => { diff --git a/src/renderer/components/svgs/icons/patreon-icon.component.tsx b/src/renderer/components/svgs/icons/patreon-icon.component.tsx index 360bf1e0..c770bba6 100644 --- a/src/renderer/components/svgs/icons/patreon-icon.component.tsx +++ b/src/renderer/components/svgs/icons/patreon-icon.component.tsx @@ -2,9 +2,8 @@ import { CSSProperties } from "react"; export default function PatreonIcon(props: { className?: string; style?: CSSProperties }) { return ( - - - + + ); } diff --git a/src/renderer/components/svgs/icons/twitter-icon.component.tsx b/src/renderer/components/svgs/icons/twitter-icon.component.tsx index be0b4f68..ddc467cd 100644 --- a/src/renderer/components/svgs/icons/twitter-icon.component.tsx +++ b/src/renderer/components/svgs/icons/twitter-icon.component.tsx @@ -2,10 +2,8 @@ import { CSSProperties } from "react"; export function TwitterIcon(props: { className?: string; style?: CSSProperties }) { return ( - - - - + + ); } diff --git a/src/renderer/pages/settings-page.component.tsx b/src/renderer/pages/settings-page.component.tsx index 9420f7b4..5ca7155b 100644 --- a/src/renderer/pages/settings-page.component.tsx +++ b/src/renderer/pages/settings-page.component.tsx @@ -40,9 +40,12 @@ import { BsStore } from "shared/models/bs-store.enum"; import { SteamIcon } from "renderer/components/svgs/icons/steam-icon.component"; import { OculusIcon } from "renderer/components/svgs/icons/oculus-icon.component"; import { BsDownloaderService } from "renderer/services/bs-version-download/bs-downloader.service"; +import { AutoUpdaterService } from "renderer/services/auto-updater.service"; +import BeatWaitingImg from "../../../assets/images/apngs/beat-waiting.png"; + export function SettingsPage() { - + const configService = useService(ConfigurationService); const themeService = useService(ThemeService); const ipcService = useService(IpcService); @@ -58,6 +61,7 @@ export function SettingsPage() { const playlistsManager = useService(PlaylistsManagerService); const modelsManager = useService(ModelsManagerService); const versionLinker = useService(VersionFolderLinkerService); + const autoUpdater = useService(AutoUpdaterService); const { firstColor, secondColor } = useThemeColor(); @@ -89,6 +93,9 @@ export function SettingsPage() { const [hasDownloaderSession, setHasDownloaderSession] = useState(false); const appVersion = useObservable(() => ipcService.sendV2("current-version")); + const [isChangelogAvailable, setIsChangelogAvailable] = useState(true); + const [changlogsLoading, setChanglogsLoading] = useState(false); + useEffect(() => { loadInstallationFolder(); loadDownloadersSession(); @@ -137,6 +144,23 @@ export function SettingsPage() { i18nService.setLanguage(item.value); }; + const handleVersionClick = async () => { + let isChangelogResolved = false; + const timeoutId = setTimeout(() => setChanglogsLoading(() => !isChangelogResolved), 100); + + await autoUpdater.showChangelog(await lastValueFrom(autoUpdater.getAppVersion())) + .then(() => { + setIsChangelogAvailable(() => true); + }) + .catch(() => { + setIsChangelogAvailable(() => false); + }) + .finally(() => { isChangelogResolved = true; }); + + setChanglogsLoading(() => false); + clearTimeout(timeoutId); + }; + const setDefaultInstallationFolder = () => { if (!progressBarService.require()) { return; @@ -232,7 +256,7 @@ export function SettingsPage() { - + }, @@ -240,7 +264,7 @@ export function SettingsPage() { { id: 0, text: t("pages.settings.steam-and-oculus.download-platform.always-ask"), value: null, }, ]} selectedItemValue={downloadStore} onItemSelected={handleChangeBsStore}/> - + @@ -426,7 +450,7 @@ export function SettingsPage() {
- +
@@ -442,10 +466,13 @@ export function SettingsPage() {
- - v{appVersion} + +
+ +
+
+ - ); diff --git a/src/renderer/services/auto-updater.service.ts b/src/renderer/services/auto-updater.service.ts index 7c92dcb1..d4de5b2e 100644 --- a/src/renderer/services/auto-updater.service.ts +++ b/src/renderer/services/auto-updater.service.ts @@ -1,8 +1,21 @@ -import { Observable, lastValueFrom } from "rxjs"; import { map } from "rxjs/operators"; import { IpcService } from "./ipc.service"; import { ProgressBarService } from "./progress-bar.service"; +import { ModalService } from "renderer/services/modale.service"; +import { ChangelogModal } from "renderer/components/modal/modal-types/chabgelog-modal/changelog-modal.component"; +import { ConfigurationService } from "./configuration.service"; +import { Observable, lastValueFrom } from "rxjs"; + +export interface Changelog { + [version: string]: ChangelogVersion; +} +export interface ChangelogVersion { + htmlBody: string; + title: string; + timestamp : number; + version: string; +} export class AutoUpdaterService { private static instance: AutoUpdaterService; @@ -11,6 +24,12 @@ export class AutoUpdaterService { private downloadProgress$: Observable; + private modal: ModalService; + + private configurationService: ConfigurationService; + + private cacheChangelog: Changelog; + public static getInstance(): AutoUpdaterService { if (!AutoUpdaterService.instance) { AutoUpdaterService.instance = new AutoUpdaterService(); @@ -21,6 +40,8 @@ export class AutoUpdaterService { private constructor() { this.progressService = ProgressBarService.getInstance(); this.ipcService = IpcService.getInstance(); + this.modal = ModalService.getInstance(); + this.configurationService = ConfigurationService.getInstance(); this.downloadProgress$ = this.ipcService.watch("update-download-progress").pipe(map(res => (res.success ? res.data : 0))); } @@ -35,11 +56,59 @@ export class AutoUpdaterService { }); this.progressService.show(this.downloadProgress$, true); - return promise; } public quitAndInstall() { this.ipcService.sendLazy("install-update"); } + + public getLastAppVersion(): string { + return this.configurationService.get("last-app-version"); + } + + public setLastAppVersion(value : string){ + this.configurationService.set("last-app-version", value); + } + + private async getChangelog(): Promise { + if (this.cacheChangelog) { + return this.cacheChangelog; + } + + const path = `https://raw.githubusercontent.com/Zagrios/bs-manager/master/assets/jsons/changelogs.json` + const response = await fetch(path); + if (!response.ok) { + throw new Error(`Failed to fetch changelogs (${response.status})`); + } + + const data = await response.json(); + if (!data) { + throw new Error(`Failed to parse changelogs`); + } + + this.cacheChangelog = data; + return data; + } + + private async getChangelogVersion(version:string): Promise { + const changelogs = await this.getChangelog(); + + const changelogVersion = changelogs[version]; + if (!changelogVersion) { + throw new Error(`No changelog found for this version (${version})`); + } + + return changelogVersion; + } + + public getAppVersion() : Observable { + return this.ipcService.sendV2("current-version"); + } + + public async showChangelog(version:string): Promise{ + const changelog = await this.getChangelogVersion(version); + + this.modal.openModal(ChangelogModal, changelog); + } } diff --git a/src/renderer/windows/App.tsx b/src/renderer/windows/App.tsx index 3c70c7fa..6ea15823 100644 --- a/src/renderer/windows/App.tsx +++ b/src/renderer/windows/App.tsx @@ -18,10 +18,12 @@ import { MapsManagerService } from "renderer/services/maps-manager.service"; import { PlaylistsManagerService } from "renderer/services/playlists-manager.service"; import { ModelsManagerService } from "renderer/services/models-management/models-manager.service"; import { NotificationService } from "renderer/services/notification.service"; -import { timer } from "rxjs"; +import { lastValueFrom, timer } from "rxjs"; import { ConfigurationService } from "renderer/services/configuration.service"; import { OsDiagnosticService } from "renderer/services/os-diagnostic.service"; import { useService } from "renderer/hooks/use-service.hook"; +import { AutoUpdaterService } from "renderer/services/auto-updater.service"; +import { gt } from "semver" export default function App() { useService(OsDiagnosticService); @@ -31,14 +33,27 @@ export default function App() { const models = useService(ModelsManagerService); const notification = useService(NotificationService); const config = useService(ConfigurationService); + const autoUpdater = useService(AutoUpdaterService); const location = useLocation(); const navigate = useNavigate(); useEffect(() => { + checkIsUpdated(); checkOneClicks(); }, []); + const checkIsUpdated = async () => { + const appVersion = await lastValueFrom(autoUpdater.getAppVersion()); + const lastAppVersion = autoUpdater.getLastAppVersion(); + + autoUpdater.setLastAppVersion(appVersion); + + if (lastAppVersion && gt(appVersion, lastAppVersion)) { + autoUpdater.showChangelog(appVersion); + } + }; + const checkOneClicks = async () => { if (config.get("not-remind-oneclick") === true) { return; diff --git a/src/renderer/windows/Launcher.tsx b/src/renderer/windows/Launcher.tsx index 270056ef..155b10f8 100644 --- a/src/renderer/windows/Launcher.tsx +++ b/src/renderer/windows/Launcher.tsx @@ -9,7 +9,6 @@ import { WindowManagerService } from "../services/window-manager.service"; import { useService } from "renderer/hooks/use-service.hook"; export default function Launcher() { - const updaterService = useService(AutoUpdaterService); const windowService = useService(WindowManagerService);