diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index b1ad0908..f3edef0c 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -201,6 +201,34 @@ "changelogs": { "open" : "🚀 Neues im Changelog entdecken!", "not-founds": "😕 Hoppla, kein Changelog zu sehen!" + }, + "advanced": { + "title": "Erweiterte", + "description": "Erweiterte Einstellungen für BSManager.", + "hardware-acceleration": { + "title": "Hardware-Beschleunigung", + "description": "Aktivieren Sie die Hardware-Beschleunigung, um Ihre GPU zu nutzen und die Leistung von BSManager zu verbessern. Deaktivieren Sie diese Option, wenn Sie Frame-Drops erleben.", + "modal": { + "title": "Neustart erforderlich", + "body": "Das Ändern der Hardware-Beschleunigungseinstellung wird BSManager beenden und neu starten. Sind Sie sicher, dass Sie dies tun möchten?", + "confirm-btn": "Ja, ich bin sicher" + }, + "error-notification": { + "message": "Ein Fehler ist aufgetreten, die Hardware-Beschleunigung kann nicht deaktiviert werden." + } + }, + "use-symlinks": { + "title": "Symbolische Links verwenden", + "description": "Verwenden Sie symbolische Links anstelle von Verknüpfungen, um Ordner zu verknüpfen. Aktivieren Sie dies nur, wenn Sie es wirklich benötigen.", + "modal": { + "title": "Berechtigungen für symbolische Links", + "body": "Beim Erstellen symbolischer Links benötigt BSManager Administratorrechte oder den aktivierten Entwicklermodus auf Ihrem System. Sind Sie sicher, dass Sie fortfahren möchten?", + "confirm-btn": "Ja, ich bin sicher" + }, + "error-notification": { + "message": "Ein Fehler ist aufgetreten, die Einstellungen für symbolische Links können nicht geändert werden." + } + } } } }, diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 45648c95..3d9b1392 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -206,6 +206,34 @@ "changelogs": { "open" : "🚀 Check Out the Changelog!", "not-founds": "😕 Oops, No Changelog Here!" + }, + "advanced": { + "title": "Advanced", + "description": "Advanced settings for BSManager.", + "hardware-acceleration": { + "title": "Hardware Acceleration", + "description": "Enable Hardware Acceleration to use your GPU and improve BSManager's performance. Turn this off if you're experiencing frame drops.", + "modal": { + "title": "Restart Needed", + "body": "Changing hardware acceleration setting will quit and re-launch BSManager. Are you sure you want to do this?", + "confirm-btn": "Yes I'm sure" + }, + "error-notification": { + "message": "An error occur, unable to disable hardware acceleration." + } + }, + "use-symlinks": { + "title": "Use Symlinks", + "description": "Use Symlinks instead of Junctions to link folders. Turn this on only if you really need it.", + "modal": { + "title": "Symlink Permissions", + "body": "When creating symlinks, BSManager will require administrator privileges or developer mode enabled on your system. Are you sure you want to continue?", + "confirm-btn": "Yes I'm sure" + }, + "error-notification": { + "message": "An error occur, unable to change symlinks settings." + } + } } } }, diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index a075cf42..e3122355 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -201,6 +201,34 @@ "changelogs": { "open" : "🚀 ¡Descubre lo nuevo en el Changelog!", "not-founds": "😕 Uy, ¡Changelog no encontrado!" + }, + "advanced": { + "title": "Avanzados", + "description": "Configuraciones avanzadas para BSManager.", + "hardware-acceleration": { + "title": "Aceleración de hardware", + "description": "Habilite la aceleración de hardware para usar su GPU y mejorar el rendimiento de BSManager. Desactive esta opción si experimenta caídas de fotogramas.", + "modal": { + "title": "Reinicio necesario", + "body": "Cambiar la configuración de aceleración de hardware cerrará y reiniciará BSManager. ¿Estás seguro de que quieres hacer esto?", + "confirm-btn": "Sí, estoy seguro" + }, + "error-notification": { + "message": "Ocurrió un error, no se puede desactivar la aceleración de hardware." + } + }, + "use-symlinks": { + "title": "Usar enlaces simbólicos", + "description": "Utilice enlaces simbólicos en lugar de uniones para enlazar carpetas. Actívelo solo si realmente lo necesita.", + "modal": { + "title": "Permisos de enlace simbólico", + "body": "Al crear enlaces simbólicos, BSManager requerirá privilegios de administrador o el modo desarrollador activado en su sistema. ¿Estás seguro de que quieres continuar?", + "confirm-btn": "Sí, estoy seguro" + }, + "error-notification": { + "message": "Ocurrió un error, no se pueden cambiar los ajustes de los enlaces simbólicos." + } + } } } }, diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 28053b11..d2748d53 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -201,6 +201,34 @@ "changelogs": { "open" : "🚀 Jetez un œil au Changelog !", "not-founds": "😕 Oups, Changelog introuvable !" + }, + "advanced": { + "title": "Avancés", + "description": "Paramètres avancés pour BSManager.", + "hardware-acceleration": { + "title": "Accélération matérielle", + "description": "Activez l'accélération matérielle pour utiliser votre GPU et améliorer les performances de BSManager. Désactivez cette option si vous rencontrez des chutes d'IPS.", + "modal": { + "title": "Redémarrage nécessaire", + "body": "Changer le paramètre d'accélération matérielle va quitter et relancer BSManager. Êtes-vous sûr de vouloir continuer ?", + "confirm-btn": "Oui, je suis sûr" + }, + "error-notification": { + "message": "Une erreur s'est produite, impossible de désactiver l'accélération matérielle." + } + }, + "use-symlinks": { + "title": "Utiliser des liens symboliques", + "description": "Utilisez des liens symboliques au lieu de jonctions pour lier des dossiers. Activez cette option uniquement si vous en avez besoin.", + "modal": { + "title": "Permissions des liens symboliques", + "body": "Lors de la création de liens symboliques, BSManager nécessitera des privilèges administrateur ou le mode développeur activé sur votre système. Êtes-vous sûr de vouloir continuer ?", + "confirm-btn": "Oui, je suis sûr" + }, + "error-notification": { + "message": "Une erreur s'est produite, impossible de changer les paramètres des liens symboliques." + } + } } } }, diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index e1636ce9..948f31e8 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -201,6 +201,34 @@ "changelogs": { "open" : "🚀 チェンジログを覗いてみて!", "not-founds": "😕 あれ、チェンジログが見当たらない!" + }, + "advanced": { + "title": "高度な設定", + "description": "BSManagerの高度な設定。", + "hardware-acceleration": { + "title": "ハードウェアアクセラレーション", + "description": "ハードウェアアクセラレーションを有効にしてGPUを使用し、BSManagerのパフォーマンスを向上させます。フレームドロップが発生している場合は、これをオフにしてください。", + "modal": { + "title": "再起動が必要", + "body": "ハードウェアアクセラレーションの設定を変更すると、BSManagerが終了して再起動します。本当に続行しますか?", + "confirm-btn": "はい、確かです" + }, + "error-notification": { + "message": "エラーが発生しました。ハードウェアアクセラレーションを無効にできません。" + } + }, + "use-symlinks": { + "title": "シンボリックリンクを使用", + "description": "フォルダをリンクするためにジャンクションの代わりにシンボリックリンクを使用します。本当に必要な場合にのみこれをオンにしてください。", + "modal": { + "title": "シンボリックリンクの権限", + "body": "シンボリックリンクを作成する際、BSManagerは管理者権限またはシステムで有効になっている開発者モードを必要とします。本当に続行しますか?", + "confirm-btn": "はい、確かです" + }, + "error-notification": { + "message": "エラーが発生しました。シンボリックリンク設定を変更できません。" + } + } } } }, diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 1202ed50..c246de4e 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -201,6 +201,34 @@ "changelogs": { "open" : "🚀 Изучаем Changelog!", "not-founds": "😕 Ой, Changelog нигде не видно!" + }, + "advanced": { + "title": "Дополнительные", + "description": "Дополнительные настройки для BSManager.", + "hardware-acceleration": { + "title": "Аппаратное ускорение", + "description": "Включите аппаратное ускорение, чтобы использовать ваш GPU и улучшить производительность BSManager. Отключите эту опцию, если у вас возникают пропуски кадров.", + "modal": { + "title": "Требуется перезагрузка", + "body": "Изменение настройки аппаратного ускорения приведет к завершению и перезапуску BSManager. Вы уверены, что хотите это сделать?", + "confirm-btn": "Да, я уверен" + }, + "error-notification": { + "message": "Произошла ошибка, невозможно отключить аппаратное ускорение." + } + }, + "use-symlinks": { + "title": "Использовать символические ссылки", + "description": "Используйте символические ссылки вместо соединений для связывания папок. Включайте эту опцию только если это действительно необходимо.", + "modal": { + "title": "Разрешения для символических ссылок", + "body": "При создании символических ссылок BSManager потребуется права администратора или включенный режим разработчика на вашем устройстве. Вы уверены, что хотите продолжить?", + "confirm-btn": "Да, я уверен" + }, + "error-notification": { + "message": "Произошла ошибка, невозможно изменить настройки символических ссылок." + } + } } } }, diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index d0f83ef0..95ab1ff8 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -201,6 +201,34 @@ "changelogs": { "open" : "🚀 探索更新日誌!", "not-founds": "😕 噢,找不到更新日誌!" + }, + "advanced": { + "title": "高级设置", + "description": "BSManager的高级设置。", + "hardware-acceleration": { + "title": "硬體加速", + "description": "啟用硬體加速以使用您的GPU並提高BSManager的性能。如果您遇到幀丟失,請關閉此功能。", + "modal": { + "title": "需要重啟", + "body": "更改硬體加速設置將退出並重新啟動BSManager。您確定要這樣做嗎?", + "confirm-btn": "是的,我確定" + }, + "error-notification": { + "message": "發生錯誤,無法禁用硬體加速。" + } + }, + "use-symlinks": { + "title": "使用符號鏈接", + "description": "使用符號鏈接而不是連接來鏈接文件夾。僅在確實需要時才啟用此功能。", + "modal": { + "title": "符號鏈接權限", + "body": "創建符號鏈接時,BSManager將需要管理員權限或啟用開發者模式。您確定要繼續嗎?", + "confirm-btn": "是的,我確定" + }, + "error-notification": { + "message": "發生錯誤,無法更改符號鏈接設置。" + } + } } } }, diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index 4acf66d8..b052d4c2 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -201,6 +201,34 @@ "changelogs": { "open" : "🚀 探索更新日志!", "not-founds": "😕 哎呀,找不到更新日志!" + }, + "advanced": { + "title": "高级设置", + "description": "BSManager的高级设置。", + "hardware-acceleration": { + "title": "硬件加速", + "description": "启用硬件加速以使用您的GPU并提高BSManager的性能。如果您遇到帧丢失,请关闭此功能。", + "modal": { + "title": "需要重启", + "body": "更改硬件加速设置将退出并重新启动BSManager。您确定要这样做吗?", + "confirm-btn": "是的,我确定" + }, + "error-notification": { + "message": "发生错误,无法禁用硬件加速。" + } + }, + "use-symlinks": { + "title": "使用符号链接", + "description": "使用符号链接而不是联接来链接文件夹。仅在确实需要时才启用此功能。", + "modal": { + "title": "符号链接权限", + "body": "创建符号链接时,BSManager将需要管理员权限或启用开发者模式。您确定要继续吗?", + "confirm-btn": "是的,我确定" + }, + "error-notification": { + "message": "发生错误,无法更改符号链接设置。" + } + } } } }, diff --git a/src/main/helpers/fs.helpers.ts b/src/main/helpers/fs.helpers.ts index 8e663954..a24284f3 100644 --- a/src/main/helpers/fs.helpers.ts +++ b/src/main/helpers/fs.helpers.ts @@ -153,7 +153,7 @@ export async function copyDirectoryWithJunctions(src: string, dest: string, opti const symlinkTarget = await readlink(sourcePath); const relativePath = path.relative(src, symlinkTarget); const newTarget = path.join(dest, relativePath); - await symlink(newTarget, destinationPath, "junction"); + await symlink(newTarget, destinationPath, "junction"); // Only junction to avoid right issues while copying content of BSManager folder } } } diff --git a/src/main/ipcs/index.ts b/src/main/ipcs/index.ts index 2da80a78..38384c27 100644 --- a/src/main/ipcs/index.ts +++ b/src/main/ipcs/index.ts @@ -12,3 +12,4 @@ import "./bs-playlist-ipcs"; import "./model-saber.ipcs"; import "./bs-model-ipcs"; import "./bs-version-download/bs-download-ipcs"; +import "./static-configuration.ipcs"; diff --git a/src/main/ipcs/os-controls-ipcs.ts b/src/main/ipcs/os-controls-ipcs.ts index 110c3de7..4f39e4a0 100644 --- a/src/main/ipcs/os-controls-ipcs.ts +++ b/src/main/ipcs/os-controls-ipcs.ts @@ -3,6 +3,7 @@ import { NotificationService } from "../services/notification.service"; import { IpcService } from "../services/ipc.service"; import { from, of } from "rxjs"; import { readFileSync } from "fs-extra"; +import log from "electron-log"; // TODO IMPROVE WINDOW CONTROL BY USING WINDOW SERVICE @@ -16,7 +17,7 @@ ipc.on("choose-folder", (args, reply) => { reply(from(dialog.showOpenDialog({ properties: ["openDirectory"], defaultPath: args ?? "" }))); }); -ipc.on("choose-file", async (args, reply) => { +ipc.on("choose-file", async (args, reply) => { reply(from(dialog.showOpenDialog({ properties: ["openFile"], defaultPath: args ?? "" }))); }); @@ -62,3 +63,12 @@ ipc.on("choose-image", (args, reply) => { return res.filePaths; }))); }); + +ipc.on("restart-app", (_, reply) => { + log.info("App was requested to restart"); + + reply(of()); // Reply before restarting to avoid any issue + + app.relaunch(); + app.quit(); +}); diff --git a/src/main/ipcs/static-configuration.ipcs.ts b/src/main/ipcs/static-configuration.ipcs.ts new file mode 100644 index 00000000..586b04e5 --- /dev/null +++ b/src/main/ipcs/static-configuration.ipcs.ts @@ -0,0 +1,14 @@ +import { of } from "rxjs"; +import { IpcService } from "../services/ipc.service"; +import { StaticConfigurationService } from "../services/static-configuration.service"; + +const ipc = IpcService.getInstance(); +const staticConfig = StaticConfigurationService.getInstance(); + +ipc.on("static-configuration.get", (args, reply) => { + reply(of(staticConfig.get(args))); +}); + +ipc.on("static-configuration.set", (args, reply) => { + reply(of(staticConfig.set(args.key, args.value))); +}); diff --git a/src/main/main.ts b/src/main/main.ts index 8ff6f65b..42d73836 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -24,8 +24,10 @@ import { SteamLauncherService } from "./services/bs-launcher/steam-launcher.serv import { FileAssociationService } from "./services/file-association.service"; import { SongDetailsCacheService } from "./services/additional-content/maps/song-details-cache.service"; import { readdirSync, statSync, unlinkSync } from "fs-extra"; +import { StaticConfigurationService } from "./services/static-configuration.service"; const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true"; +const staticConfig = StaticConfigurationService.getInstance(); export const filterStrings = new Set(); export const filterPatterns = new Set(); @@ -37,6 +39,13 @@ initLogger(); deleteOlestLogs(); deleteOldLogs(); +staticConfig.take("disable-hadware-acceleration", disabled => { + if(disabled === true){ // strictly check for true + log.info("Disabling hardware acceleration"); + app.disableHardwareAcceleration(); + } +}); + if (process.env.NODE_ENV === "production") { const sourceMapSupport = require("source-map-support"); @@ -108,7 +117,7 @@ if (!gotTheLock) { app.whenReady().then(() => { - // C:\\Users\\Mathieu\\Desktop\\BSManager\\BSInstances\\My Version\\UserData\\SongDetailsCache.proto + app.setAppUserModelId(APP_NAME); diff --git a/src/main/services/additional-content/maps/song-details-cache.service.ts b/src/main/services/additional-content/maps/song-details-cache.service.ts index b374fd42..b8f777cf 100644 --- a/src/main/services/additional-content/maps/song-details-cache.service.ts +++ b/src/main/services/additional-content/maps/song-details-cache.service.ts @@ -1,7 +1,6 @@ import path from "path"; import { ensureDirSync, existsSync, readFile, writeFile } from "fs-extra"; import { BehaviorSubject, Observable, catchError, filter, lastValueFrom, of, take, timeout } from "rxjs"; -import { ConfigurationService } from "../../configuration.service"; import { RequestService } from "../../request.service"; import { tryit } from "shared/helpers/error.helpers"; import { CACHE_PATH, HTTP_STATUS_CODES } from "main/constants"; @@ -12,6 +11,7 @@ import { SongDetails } from "shared/models/maps/song-details-cache/song-details- import { inflate } from "pako"; import { RawSongDetailsCache } from "shared/models/maps/song-details-cache/raw-song-details-cache.model"; import { RawSongDetailsDeserializer } from "shared/models/maps/song-details-cache/raw-song-details-deserializer.class"; +import { StaticConfigurationService } from "main/services/static-configuration.service"; export class SongDetailsCacheService { @@ -32,7 +32,7 @@ export class SongDetailsCacheService { private readonly PROTO_CACHE_PATH = path.join(CACHE_PATH, "song-details-cache"); private readonly etagKey = "song-details-cache-etag"; - private readonly config: ConfigurationService; + private readonly staticConfig: StaticConfigurationService; private readonly request: RequestService; private readonly utils: UtilsService; @@ -41,7 +41,7 @@ export class SongDetailsCacheService { private readonly _loaded$ = new BehaviorSubject(null); private constructor(){ - this.config = ConfigurationService.getInstance(); + this.staticConfig = StaticConfigurationService.getInstance(); this.request = RequestService.getInstance(); this.utils = UtilsService.getInstance(); this.loadCache() @@ -49,10 +49,10 @@ export class SongDetailsCacheService { private async loadCache(): Promise { const protoCacheExists = existsSync(this.PROTO_CACHE_PATH); - const etag = protoCacheExists ? this.config.get(this.etagKey) : null; + const etag = protoCacheExists ? this.staticConfig.get(this.etagKey) : null; await this.downloadCacheFile(etag).then(etag => { - this.config.set(this.etagKey, etag); + this.staticConfig.set(this.etagKey, etag); log.info("SongDetailsCache downloaded"); this.songDetailsIdIndex = this.createIdIndex(this.songDetailsCache); log.info("SongDetailsIdIndex created"); diff --git a/src/main/services/folder-linker.service.ts b/src/main/services/folder-linker.service.ts index 42f7686c..134be320 100644 --- a/src/main/services/folder-linker.service.ts +++ b/src/main/services/folder-linker.service.ts @@ -6,6 +6,7 @@ import path from "path"; import { copy, readlink } from "fs-extra"; import { lastValueFrom } from "rxjs"; import { noop } from "shared/helpers/function.helpers"; +import { StaticConfigurationService } from "./static-configuration.service"; export class FolderLinkerService { private static instance: FolderLinkerService; @@ -18,9 +19,20 @@ export class FolderLinkerService { } private readonly installLocationService = InstallationLocationService.getInstance(); + private readonly staticConfig: StaticConfigurationService; + + private linkingType: "junction" | "symlink" = "junction"; private constructor() { this.installLocationService = InstallationLocationService.getInstance(); + this.staticConfig = StaticConfigurationService.getInstance(); + + this.linkingType = this.staticConfig.get("use-symlinks") === true ? "symlink" : "junction"; + + this.staticConfig.$watch("use-symlinks").subscribe(({ newValue: useSymlink }) => { + this.linkingType = useSymlink === true ? "symlink" : "junction"; + log.info(`Linking type set to ${this.linkingType}`); + }); } private async sharedFolder(): Promise { @@ -51,6 +63,10 @@ export class FolderLinkerService { }); } + private getLinkingType(): "junction" | undefined { + return this.linkingType === "junction" ? "junction" : undefined; + } + public async linkFolder(folderPath: string, options?: LinkOptions): Promise { const sharedPath = await this.getSharedFolder(folderPath, options?.intermediateFolder); @@ -62,7 +78,9 @@ export class FolderLinkerService { return; } await unlinkPath(folderPath); - return symlink(sharedPath, folderPath, "junction"); + + log.info(`Linking ${folderPath} to ${sharedPath}; type: ${this.linkingType}`); + return symlink(sharedPath, folderPath, this.getLinkingType()); } await ensureFolderExist(sharedPath); @@ -79,7 +97,8 @@ export class FolderLinkerService { await deleteFolder(folderPath); - return symlink(sharedPath, folderPath, "junction"); + log.info(`Linking ${folderPath} to ${sharedPath}; type: ${this.linkingType}`); + return symlink(sharedPath, folderPath, this.getLinkingType()); } public async unlinkFolder(folderPath: string, options?: UnlinkOptions): Promise { diff --git a/src/main/services/installation-location.service.ts b/src/main/services/installation-location.service.ts index d8afcef3..f1c012f1 100644 --- a/src/main/services/installation-location.service.ts +++ b/src/main/services/installation-location.service.ts @@ -1,9 +1,9 @@ import path from "path"; import { app } from "electron"; -import ElectronStore from "electron-store"; import { copyDirectoryWithJunctions, deleteFolder, ensureFolderExist } from "../helpers/fs.helpers"; import { tryit } from "../../shared/helpers/error.helpers"; import { pathExistsSync } from "fs-extra"; +import { StaticConfigurationService } from "./static-configuration.service"; export class InstallationLocationService { private static instance: InstallationLocationService; @@ -23,15 +23,15 @@ export class InstallationLocationService { private readonly STORE_INSTALLATION_PATH_KEY = "installation-folder"; - private readonly installPathConfig: ElectronStore; + private readonly staticConfig: StaticConfigurationService; private readonly updateListeners: Set = new Set(); private _installationDirectory: string; private constructor() { - this.installPathConfig = new ElectronStore({ watch: true }); + this.staticConfig = StaticConfigurationService.getInstance(); - this.installPathConfig.onDidChange(this.STORE_INSTALLATION_PATH_KEY, () => { + this.staticConfig.$watch(this.STORE_INSTALLATION_PATH_KEY).subscribe(() => { this.triggerListeners(); }); } @@ -48,7 +48,7 @@ export class InstallationLocationService { await copyDirectoryWithJunctions(oldDir, path.join(newDir, this.INSTALLATION_FOLDER), { overwrite: true }); this._installationDirectory = newDir; - this.installPathConfig.set(this.STORE_INSTALLATION_PATH_KEY, newDir); + this.staticConfig.set(this.STORE_INSTALLATION_PATH_KEY, newDir); deleteFolder(oldDir); @@ -66,8 +66,8 @@ export class InstallationLocationService { return this._installationDirectory; } - if(this.installPathConfig.has(this.STORE_INSTALLATION_PATH_KEY)) { - return this.installPathConfig.get(this.STORE_INSTALLATION_PATH_KEY) as string; + if(this.staticConfig.has(this.STORE_INSTALLATION_PATH_KEY)) { + return this.staticConfig.get(this.STORE_INSTALLATION_PATH_KEY); } const { result: oldPath } = tryit(() => path.join(app.getPath("documents"), this.INSTALLATION_FOLDER)); diff --git a/src/main/services/static-configuration.service.ts b/src/main/services/static-configuration.service.ts new file mode 100644 index 00000000..e36ea64b --- /dev/null +++ b/src/main/services/static-configuration.service.ts @@ -0,0 +1,73 @@ +import ElectronStore from "electron-store"; +import { Observable } from "rxjs"; + +export class StaticConfigurationService { + private static instance: StaticConfigurationService; + + public static getInstance(): StaticConfigurationService { + if (!StaticConfigurationService.instance) { + StaticConfigurationService.instance = new StaticConfigurationService(); + } + return StaticConfigurationService.instance; + } + + private readonly store: ElectronStore; + + private constructor() { + this.store = new ElectronStore({ watch: true }); + } + + public has(key: K): boolean { + return this.store.has(key); + } + + public get(key: K): StaticConfigKeyValues[K] { + return this.store.get(key) as StaticConfigKeyValues[K]; + } + + public take(key: K, cb: (val: StaticConfigKeyValues[K]) => void): void { + cb(this.get(key)); + } + + public set(key: K, value: StaticConfigKeyValues[K]): void { + this.store.set(key, value); + } + + public delete(key: K): void { + this.store.delete(key); + } + + public getStore(): ElectronStore { + return this.store; + } + + public $watch(key: K): Observable<{ newValue: StaticConfigKeyValues[K], oldValue: StaticConfigKeyValues[K] }> { + return new Observable(obs => { + const unsub = this.store.onDidChange(key, (newValue, oldValue) => { + obs.next({ newValue, oldValue } as { newValue: StaticConfigKeyValues[K], oldValue: StaticConfigKeyValues[K] }); + }); + + return () => unsub(); + }) + } +} + +export interface StaticConfigKeyValues { + "installation-folder": string; + "song-details-cache-etag": string; + "disable-hadware-acceleration": boolean; + "use-symlinks": boolean; +} + +export type StaticConfigKeys = keyof StaticConfigKeyValues; + +export type StaticConfigGetIpcRequestResponse = { + request: K; + response: StaticConfigKeyValues[K]; +}; + +export type StaticConfigSetIpcRequest = { + request: { key: K, value: StaticConfigKeyValues[K] }; + response: void; +} + diff --git a/src/renderer/components/modal/basic-modal.component.tsx b/src/renderer/components/modal/basic-modal.component.tsx new file mode 100644 index 00000000..0e68ab16 --- /dev/null +++ b/src/renderer/components/modal/basic-modal.component.tsx @@ -0,0 +1,42 @@ +import { BsmButton, BsmButtonType } from "renderer/components/shared/bsm-button.component"; +import { BsmImage } from "renderer/components/shared/bsm-image.component"; +import { cn } from "renderer/helpers/css-class.helpers"; +import { useTranslation } from "renderer/hooks/use-translation.hook"; +import { ModalComponent, ModalExitCode } from "renderer/services/modale.service"; + +type BasicModalOptions = { + title: string; + image: string; + body?: string; + buttons?: { + id: string; + text: string; + type: BsmButtonType, + isCancel?: boolean; + }[]; + buttonsLayout?: "row" | "column"; +}; + +export const BasicModal: ModalComponent = ({ resolver, options: { + data: { title, image, body, buttons, buttonsLayout = "column" } +} }) => { + + const t = useTranslation(); + + const handleClick = (button: BasicModalOptions["buttons"][0]) => { + resolver({ exitCode: button.isCancel ? ModalExitCode.CANCELED : ModalExitCode.COMPLETED, data: button.id }); + } + + return ( +
+

{t(title)}

+ + { body &&

{t(body)}

} +
+ {buttons.map(button => ( + handleClick(button)} withBar={false} text={button.text} /> + ))} +
+ + ); +}; diff --git a/src/renderer/components/settings/setting-toogle-switch-grid.component.tsx b/src/renderer/components/settings/setting-toogle-switch-grid.component.tsx new file mode 100644 index 00000000..cba43faf --- /dev/null +++ b/src/renderer/components/settings/setting-toogle-switch-grid.component.tsx @@ -0,0 +1,33 @@ +import { ToogleSwitch } from "../shared/toogle-switch.component"; + +type Item = { + text: string; + desc?: string; + checked?: boolean; + onChange?: (isChecked: boolean) => void|Promise; +}; + +type Props = { + items: Item[]; +}; + +export function SettingToogleSwitchGrid({ items }: Readonly) { + + const handleItemChange = (item: Item, state: boolean) => { + item.onChange?.(state); + }; + + return ( +
+ {items.map((item) => ( +
+
+

{item.text}

+

{item.desc}

+
+ handleItemChange(item, checked)}/> +
+ ))} +
+ ) +} diff --git a/src/renderer/components/shared/toogle-switch.component.tsx b/src/renderer/components/shared/toogle-switch.component.tsx new file mode 100644 index 00000000..52c460cc --- /dev/null +++ b/src/renderer/components/shared/toogle-switch.component.tsx @@ -0,0 +1,83 @@ +import { useMemo } from "react" +import { getCorrectTextColor } from "renderer/helpers/correct-text-color"; +import { cn } from "renderer/helpers/css-class.helpers"; +import { useConstant } from "renderer/hooks/use-constant.hook"; +import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; + +type Props = { + checked?: boolean; + className?: string; + classNames?: { + container?: string; + dot?: string; + active?: string; + inactive?: string; + } + bgColor?: string; + onChange?: (isChecked: boolean) => void; +} + +export function ToogleSwitch({ checked, className, classNames, bgColor, onChange }: Readonly) { + + const uuid = useConstant(() => crypto.randomUUID()); + const { firstColor } = useThemeColor(); + const backgroundColor = useMemo(() => bgColor ?? firstColor, [bgColor, firstColor]); + const dotColor = useMemo(() => { + return getCorrectTextColor(backgroundColor); + }, [backgroundColor]); + const textColor = useMemo(() => { + return getCorrectTextColor(dotColor) + }, [dotColor]); + + const handleCheckboxChange = () => { + onChange?.(!checked); + } + + return ( + + ) +} diff --git a/src/renderer/hooks/use-constant.hook.ts b/src/renderer/hooks/use-constant.hook.ts index 7f4317bc..6bfb0c74 100644 --- a/src/renderer/hooks/use-constant.hook.ts +++ b/src/renderer/hooks/use-constant.hook.ts @@ -9,5 +9,5 @@ export function useConstant(fn: () => T): T { ref.current = { v: fn() }; } - return ref.current.v; + return ref.current?.v; } diff --git a/src/renderer/pages/settings-page.component.tsx b/src/renderer/pages/settings-page.component.tsx index a90e9870..ea059fa7 100644 --- a/src/renderer/pages/settings-page.component.tsx +++ b/src/renderer/pages/settings-page.component.tsx @@ -41,8 +41,13 @@ 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"; +import BeatConflict from "../../../assets/images/apngs/beat-conflict.png"; import { logRenderError } from "renderer"; import { BSLauncherService } from "renderer/services/bs-launcher.service"; +import { SettingToogleSwitchGrid } from "renderer/components/settings/setting-toogle-switch-grid.component"; +import { BasicModal } from "renderer/components/modal/basic-modal.component"; +import { StaticConfigurationService } from "renderer/services/static-configuration.service"; +import { tryit } from "shared/helpers/error.helpers"; export function SettingsPage() { @@ -62,6 +67,7 @@ export function SettingsPage() { const modelsManager = useService(ModelsManagerService); const versionLinker = useService(VersionFolderLinkerService); const autoUpdater = useService(AutoUpdaterService); + const staticConfig = useService(StaticConfigurationService); const { firstColor, secondColor } = useThemeColor(); @@ -92,6 +98,8 @@ export function SettingsPage() { const [playlistsDeepLinkEnabled, setPlaylistsDeepLinkEnabled] = useState(false); const [modelsDeepLinkEnabled, setModelsDeepLinkEnabled] = useState(false); const [hasDownloaderSession, setHasDownloaderSession] = useState(false); + const [hardwareAccelerationEnabled, setHardwareAccelerationEnabled] = useState(true); + const [useSymlink, setUseSymlink] = useState(false); const appVersion = useObservable(() => ipcService.sendV2("current-version")); const [isChangelogAvailable, setIsChangelogAvailable] = useState(true); @@ -103,6 +111,9 @@ export function SettingsPage() { mapsManager.isDeepLinksEnabled().then(enabled => setMapDeepLinksEnabled(() => enabled)); playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeepLinkEnabled(() => enabled)); modelsManager.isDeepLinksEnabled().then(enabled => setModelsDeepLinkEnabled(() => enabled)); + + staticConfig.get("disable-hadware-acceleration").then(disabled =>setHardwareAccelerationEnabled(() => disabled !== true)); + staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks)); }, []); const allDeepLinkEnabled = mapDeepLinksEnabled && playlistsDeepLinkEnabled && modelsDeepLinkEnabled; @@ -219,6 +230,66 @@ export function SettingsPage() { }); }; + const onChangeHardwareAcceleration = async (newHardwareAccelerationEnabled: boolean) => { + if(newHardwareAccelerationEnabled === hardwareAccelerationEnabled){ return; } + + const res = await modalService.openModal(BasicModal, { data: { + title: "pages.settings.advanced.hardware-acceleration.modal.title", + body: "pages.settings.advanced.hardware-acceleration.modal.body", + image: BeatConflict, + buttons: [ + { id: "cancel", text: "misc.cancel", type: "cancel", isCancel: true }, + { id: "confirm", text: "pages.settings.advanced.hardware-acceleration.modal.confirm-btn", type: "error" } + ] + }}); + + if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; } + + const { error } = await tryit(() => staticConfig.set("disable-hadware-acceleration", !newHardwareAccelerationEnabled)); + + if(error){ + notificationService.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.hardware-acceleration.error-notification.message" }); + setHardwareAccelerationEnabled(() => !newHardwareAccelerationEnabled); + return; + } + + setHardwareAccelerationEnabled(() => newHardwareAccelerationEnabled); + + if(!progressBarService.require()){ + return; + } + + await lastValueFrom(ipcService.sendV2("restart-app")); + }; + + const onChangeUseSymlinks = async (newUseSymlink: boolean) => { + + if(newUseSymlink === useSymlink){ return; } + + if(newUseSymlink){ + const res = await modalService.openModal(BasicModal, { data: { + title: "pages.settings.advanced.use-symlinks.modal.title", + body: "pages.settings.advanced.use-symlinks.modal.body", + image: BeatConflict, + buttons: [ + { id: "cancel", text: "misc.cancel", type: "cancel", isCancel: true }, + { id: "confirm", text: "pages.settings.advanced.use-symlinks.modal.confirm-btn", type: "error" } + ] + }}); + + if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; } + } + + const { error } = await tryit(() => staticConfig.set("use-symlinks", newUseSymlink)); + + if(error){ + notificationService.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.use-symlinks.error-notification.message" }); + return; + } + + setUseSymlink(() => newUseSymlink); + } + const toogleShowSupporters = () => { setShowSupporters(show => !show); }; @@ -500,6 +571,14 @@ export function SettingsPage() { + + + + +
diff --git a/src/renderer/services/static-configuration.service.ts b/src/renderer/services/static-configuration.service.ts new file mode 100644 index 00000000..69d177a9 --- /dev/null +++ b/src/renderer/services/static-configuration.service.ts @@ -0,0 +1,30 @@ +import { StaticConfigKeys, StaticConfigKeyValues } from "main/services/static-configuration.service"; +import { IpcService } from "./ipc.service"; +import { lastValueFrom } from "rxjs"; + +export class StaticConfigurationService { + + private static instance: StaticConfigurationService; + + public static getInstance(): StaticConfigurationService { + if (!StaticConfigurationService.instance) { + StaticConfigurationService.instance = new StaticConfigurationService(); + } + return StaticConfigurationService.instance; + } + + private readonly ipc: IpcService; + + private constructor(){ + this.ipc = IpcService.getInstance(); + } + + public get(key: K): Promise { + return lastValueFrom(this.ipc.sendV2("static-configuration.get", key)) as Promise; + } + + public set(key: K, value: StaticConfigKeyValues[K]): Promise { + return lastValueFrom(this.ipc.sendV2("static-configuration.set", { key, value })); + } + +} diff --git a/src/shared/models/ipc/ipc-routes.ts b/src/shared/models/ipc/ipc-routes.ts index d32ff3cc..2589a7c0 100644 --- a/src/shared/models/ipc/ipc-routes.ts +++ b/src/shared/models/ipc/ipc-routes.ts @@ -19,6 +19,7 @@ import { SystemNotificationOptions } from "../notification/system-notification.m import { Supporter } from "../supporters"; import { AppWindow } from "../window-manager/app-window.model"; import { LocalBPList, LocalBPListsDetails } from "../playlists/local-playlist.models"; +import { StaticConfigGetIpcRequestResponse, StaticConfigKeys, StaticConfigSetIpcRequest } from "main/services/static-configuration.service"; export type IpcReplier = (data: Observable) => void; @@ -124,6 +125,7 @@ export interface IpcChannelMapping { "open-logs": { request: void, response: string }; "notify-system": { request: SystemNotificationOptions, response: void }; "view-path-in-explorer": { request: string, response: void }; + "restart-app": { request: void, response: void }; /* ** supporters-ipcs ** */ "get-supporters": { request: void, response: Supporter[] }; @@ -136,6 +138,10 @@ export interface IpcChannelMapping { "open-window-then-close-all": { request: AppWindow, response: void }; "open-window-or-focus": { request: AppWindow, response: void }; + /* ** static-configuration.ipcs ** */ + "static-configuration.get": StaticConfigGetIpcRequestResponse; + "static-configuration.set": StaticConfigSetIpcRequest; + /* ** OTHERS (if your IPC channel is not in a "-ipcs" file, put it here) ** */ "shortcut-launch-options": { request: void, response: LaunchOption }; }