diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 5f662c4e9..60c165910 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -123,6 +123,7 @@ "description": "Beschreibung", "dropdown": { "import-mods": "Mods importieren", + "compare-mods": "Mods vergleichen", "uninstall-all": "Alle deinstallieren", "unselect-all": "Alle abwählen" } @@ -906,6 +907,16 @@ }, "bs-version-outdated": { "body": "Die Version {outdatedVersion} ist veraltet, und einige Mods oder Funktionen funktionieren möglicherweise nicht mehr wie erwartet. Bitte lade die neueste empfohlene Version ({recommendedVersion}) von Beat Saber herunter, um die neuesten Funktionen und Fehlerbehebungen zu nutzen." + }, + "mods-version-compare": { + "title": "Mods Versionsvergleich", + "select-version": "Wählen Sie eine Version", + "mod-types": { + "all": "Alle", + "installed": "Installiert", + "not-installed": "Nicht installiert", + "missing": "Fehlend" + } } }, "maps": { diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 9deaba077..30d9fb1d2 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -123,6 +123,7 @@ "description": "Description", "dropdown": { "import-mods": "Import mods", + "compare-mods": "Compare mods", "uninstall-all": "Uninstall all", "unselect-all": "Unselect all" } @@ -901,6 +902,16 @@ }, "bs-version-outdated": { "body": "The version {outdatedVersion} is outdated, and some mods or features may no longer work as expected. Please download the latest recommended version ({recommendedVersion}) of Beat Saber to enjoy the latest features and bugfixes." + }, + "mods-version-compare": { + "title": "Mods Version Comparison", + "select-version": "Select a Version", + "mod-types": { + "all": "All", + "installed": "Installed", + "not-installed": "Not Installed", + "missing": "Missing" + } } }, "maps": { diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 528e4939e..799338604 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -123,6 +123,7 @@ "description": "Descripción", "dropdown": { "import-mods": "Importar mods", + "compare-mods": "Comparar mods", "uninstall-all": "Desinstalar todos", "unselect-all": "Deseleccionar todo" } @@ -906,6 +907,16 @@ }, "bs-version-outdated": { "body": "La versión {outdatedVersion} está obsoleta, y algunos mods o funciones pueden no funcionar como se espera. Por favor, descarga la última versión recomendada ({recommendedVersion}) de Beat Saber para disfrutar de las últimas funciones y correcciones de errores." + }, + "mods-version-compare": { + "title": "Comparación de versiones de mods", + "select-version": "Seleccionar una versión", + "mod-types": { + "all": "Todos", + "installed": "Instalado", + "not-installed": "No instalado", + "missing": "Faltante" + } } }, "maps": { diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index e0e3c7690..175ab7b10 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -123,6 +123,7 @@ "description": "Description", "dropdown": { "import-mods": "Importer des mods", + "compare-mods": "Comparer les mods", "uninstall-all": "Tout désinstaller", "unselect-all": "Tout désélectionner" } @@ -907,6 +908,16 @@ }, "bs-version-outdated": { "body": "La version {outdatedVersion} est obsolète, et certains mods ou fonctionnalités peuvent ne plus fonctionner comme prévu. Veuillez télécharger la dernière version recommandée ({recommendedVersion}) de Beat Saber pour profiter des dernières fonctionnalités et corrections de bugs." + }, + "mods-version-compare": { + "title": "Comparaison des versions des mods", + "select-version": "Sélectionner une version", + "mod-types": { + "all": "Tout", + "installed": "Installé", + "not-installed": "Non installé", + "missing": "Manquant" + } } }, "maps": { diff --git a/assets/jsons/translations/it.json b/assets/jsons/translations/it.json index 7de34c0c2..6a3f57dd5 100644 --- a/assets/jsons/translations/it.json +++ b/assets/jsons/translations/it.json @@ -123,6 +123,7 @@ "description": "Descrizione", "dropdown": { "import-mods": "Importa mod", + "compare-mods": "Confronta i mod", "uninstall-all": "Disinstalla tutto", "unselect-all": "Deseleziona tutto" } @@ -878,30 +879,40 @@ "valid-btn": "Valida" }, "launch-as-admin": { - "title": "Permessi di Amministratore", - "body": { - "info": "Steam è stato rilevato come in esecuzione con permessi di amministratore. Per comunicare con Steam, Beat Saber deve essere avviato anche come amministratore. Altrimenti, Beat Saber potrebbe riscontrare problemi e chiudersi dopo l'avvio.", - "info-2": "Avviare Beat Saber in modalità amministratore darà anche diritti di amministratore alle mod installate. Pertanto, è consigliato riavviare Steam senza permessi di amministratore.", - "info-3": "Si sconsiglia di dare diritti di amministratore a Steam, poiché questo influisce sui giochi e le mod installate, presentando un rischio per la sicurezza." + "title": "Permessi di Amministratore", + "body": { + "info": "Steam è stato rilevato come in esecuzione con permessi di amministratore. Per comunicare con Steam, Beat Saber deve essere avviato anche come amministratore. Altrimenti, Beat Saber potrebbe riscontrare problemi e chiudersi dopo l'avvio.", + "info-2": "Avviare Beat Saber in modalità amministratore darà anche diritti di amministratore alle mod installate. Pertanto, è consigliato riavviare Steam senza permessi di amministratore.", + "info-3": "Si sconsiglia di dare diritti di amministratore a Steam, poiché questo influisce sui giochi e le mod installate, presentando un rischio per la sicurezza." + }, + "launch-as-admin": "Avvia come Amministratore", + "not-remind-me": "Non ricordarmelo" }, - "launch-as-admin": "Avvia come Amministratore", - "not-remind-me": "Non ricordarmelo" - }, - "ask-install-path": { - "title": "Cartella di installazione", - "choose-folder-description": "Scegli la cartella che conterrà tutti i contenuti scaricati da BSManager. (versioni, mod, mappe, playlist, ecc.)", - "default": "Predefinito", - "default-tooltip": "Predefinito nella tua cartella home" - }, - "choose-proton-folder": { - "title": "Cartella di Proton", - "proton-folder-description": "Su Linux, BSManager richiede Proton per funzionare. Scegli la cartella di installazione di Proton per continuare.", - "proton-folder-placeholder": "Cartella di installazione di Proton", - "where-is-proton-installed": "Dove è installato Proton?" - }, - "bs-version-outdated": { - "body": "La versione {outdatedVersion} è obsoleta, e alcune mod o funzioni potrebbero non funzionare più come previsto. Per favore scarica l'ultima versione raccomandata ({recommendedVersion}) di Beat Saber per godere delle ultime funzioni e bugfix." - } + "ask-install-path": { + "title": "Cartella di installazione", + "choose-folder-description": "Scegli la cartella che conterrà tutti i contenuti scaricati da BSManager. (versioni, mod, mappe, playlist, ecc.)", + "default": "Predefinito", + "default-tooltip": "Predefinito nella tua cartella home" + }, + "choose-proton-folder": { + "title": "Cartella di Proton", + "proton-folder-description": "Su Linux, BSManager richiede Proton per funzionare. Scegli la cartella di installazione di Proton per continuare.", + "proton-folder-placeholder": "Cartella di installazione di Proton", + "where-is-proton-installed": "Dove è installato Proton?" + }, + "bs-version-outdated": { + "body": "La versione {outdatedVersion} è obsoleta, e alcune mod o funzioni potrebbero non funzionare più come previsto. Per favore scarica l'ultima versione raccomandata ({recommendedVersion}) di Beat Saber per godere delle ultime funzioni e bugfix." + }, + "mods-version-compare": { + "title": "Confronto delle versioni dei mod", + "select-version": "Seleziona una versione", + "mod-types": { + "all": "Tutti", + "installed": "Installato", + "not-installed": "Non installato", + "missing": "Mancante" + } + } }, "maps": { "map-filter-panel": { diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 61ffe3a7a..211ec725c 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -123,6 +123,7 @@ "description": "說明", "dropdown": { "import-mods": "モッドをインポート", + "compare-mods": "モッドを比較する", "uninstall-all": "全てアンインストールする", "unselect-all": "すべて選択解除" } @@ -906,6 +907,16 @@ }, "bs-version-outdated": { "body": "バージョン {outdatedVersion} は古いため、一部のモッドや機能が期待通りに動作しない場合があります。最新の機能やバグ修正を楽しむために、推奨される最新バージョンの Beat Saber ({recommendedVersion}) をダウンロードしてください。" + }, + "mods-version-compare": { + "title": "MODのバージョン比較", + "select-version": "バージョンを選択する", + "mod-types": { + "all": "すべて", + "installed": "インストール済み", + "not-installed": "未インストール", + "missing": "欠落している" + } } }, "maps": { diff --git a/assets/jsons/translations/ko.json b/assets/jsons/translations/ko.json index e296c2afe..2b32d36d0 100644 --- a/assets/jsons/translations/ko.json +++ b/assets/jsons/translations/ko.json @@ -123,6 +123,7 @@ "description": "설명", "dropdown": { "import-mods": "모드 가져오기", + "compare-mods": "모드 비교하기", "uninstall-all": "모두 제거", "unselect-all": "모두 선택 해제" } @@ -906,6 +907,16 @@ }, "bs-version-outdated": { "body": "버전 {outdatedVersion}은(는) 오래되어 일부 모드나 기능이 예상대로 작동하지 않을 수 있습니다. 최신 기능과 버그 수정 사항을 즐기기 위해 Beat Saber의 최신 권장 버전 ({recommendedVersion})을 다운로드하십시오." + }, + "mods-version-compare": { + "title": "모드 버전 비교", + "select-version": "버전 선택하기", + "mod-types": { + "all": "모두", + "installed": "설치됨", + "not-installed": "설치되지 않음", + "missing": "누락됨" + } } }, "maps": { diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 651e11a9b..edef57d5a 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -123,6 +123,7 @@ "description": "Описание", "dropdown": { "import-mods": "Импортировать моды", + "compare-mods": "Сравнить моды", "uninstall-all": "Удалить всё", "unselect-all": "Снять все выделения" } @@ -905,6 +906,16 @@ }, "bs-version-outdated": { "body": "Версия {outdatedVersion} устарела, и некоторые моды или функции могут работать некорректно. Пожалуйста, скачайте последнюю рекомендуемую версию ({recommendedVersion}) Beat Saber, чтобы воспользоваться новейшими функциями и исправлениями ошибок." + }, + "mods-version-compare": { + "title": "Сравнение версий модов", + "select-version": "Выберите версию", + "mod-types": { + "all": "Все", + "installed": "Установлено", + "not-installed": "Не установлено", + "missing": "Отсутствует" + } } }, "maps": { diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 668517a52..48653102f 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -123,6 +123,7 @@ "description": "描述", "dropdown": { "import-mods": "導入模組", + "compare-mods": "比較模組", "uninstall-all": "全部移除", "unselect-all": "取消全選" } @@ -906,6 +907,16 @@ }, "bs-version-outdated": { "body": "版本 {outdatedVersion} 已過時,某些模組或功能可能無法正常運作。請下載最新推薦的 Beat Saber 版本 ({recommendedVersion}),以享受最新功能和修正。" + }, + "mods-version-compare": { + "title": "模組版本比較", + "select-version": "選擇版本", + "mod-types": { + "all": "全部", + "installed": "已安裝", + "not-installed": "未安裝", + "missing": "缺少" + } } }, "maps": { diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index ace849b71..0986b7cab 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -123,6 +123,7 @@ "description": "描述", "dropdown": { "import-mods": "导入模组", + "compare-mods": "比较模", "uninstall-all": "全部卸载", "unselect-all": "取消全选" } @@ -906,6 +907,16 @@ }, "bs-version-outdated": { "body": "版本 {outdatedVersion} 已过时,某些模组或功能可能无法正常工作。请下载最新推荐的 Beat Saber 版本 ({recommendedVersion}),以享受最新功能和修复。" + }, + "mods-version-compare": { + "title": "模组版本比较", + "select-version": "选择版本", + "mod-types": { + "all": "全部", + "installed": "已安装", + "not-installed": "未安装", + "missing": "缺少" + } } }, "maps": { diff --git a/src/main/helpers/proxy.helpers.ts b/src/main/helpers/proxy.helpers.ts index 0a102b137..f4534f996 100644 --- a/src/main/helpers/proxy.helpers.ts +++ b/src/main/helpers/proxy.helpers.ts @@ -1,7 +1,7 @@ import log from 'electron-log'; import { RegDwordValue, RegSzValue } from "regedit-rs" import { execOnOs } from "./env.helpers"; -import { bootstrap } from 'global-agent'; +import { bootstrap } from "global-agent"; import { StaticConfigurationService } from "../services/static-configuration.service"; const staticConfig = StaticConfigurationService.getInstance(); diff --git a/src/renderer/components/modal/modal-types/mods-disclaimer-modal.component.tsx b/src/renderer/components/modal/modal-types/mods/mods-disclaimer-modal.component.tsx similarity index 90% rename from src/renderer/components/modal/modal-types/mods-disclaimer-modal.component.tsx rename to src/renderer/components/modal/modal-types/mods/mods-disclaimer-modal.component.tsx index 9c5382ebc..aed392727 100644 --- a/src/renderer/components/modal/modal-types/mods-disclaimer-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/mods/mods-disclaimer-modal.component.tsx @@ -1,11 +1,11 @@ import { BsmButton } from "renderer/components/shared/bsm-button.component"; import { BsmImage } from "renderer/components/shared/bsm-image.component"; -import { useTranslation } from "renderer/hooks/use-translation.hook"; +import { useTranslationV2 } from "renderer/hooks/use-translation.hook"; import { ModalComponent, ModalExitCode } from "renderer/services/modale.service"; -import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png"; +import BeatConflict from "../../../../../../assets/images/apngs/beat-conflict.png"; export const ModsDisclaimerModal: ModalComponent = ({ resolver }) => { - const t = useTranslation(); + const { text: t } = useTranslationV2(); return (
diff --git a/src/renderer/components/modal/modal-types/mods/mods-version-compare-modal.component.tsx b/src/renderer/components/modal/modal-types/mods/mods-version-compare-modal.component.tsx new file mode 100644 index 000000000..57b289ab8 --- /dev/null +++ b/src/renderer/components/modal/modal-types/mods/mods-version-compare-modal.component.tsx @@ -0,0 +1,437 @@ +import { useEffect, useState } from "react"; +import { logRenderError } from "renderer"; +import { BsmSelect, BsmSelectOption } from "renderer/components/shared/bsm-select.component"; +import { AddIcon } from "renderer/components/svgs/icons/add-icon.component"; +import { DownIcon } from "renderer/components/svgs/icons/down-icon.component"; +import { EqualIcon } from "renderer/components/svgs/icons/equals-icon.component"; +import { RemoveIcon } from "renderer/components/svgs/icons/remove-icon.component"; +import { UpIcon } from "renderer/components/svgs/icons/up-icon.component"; +import { useConstant } from "renderer/hooks/use-constant.hook"; +import { useService } from "renderer/hooks/use-service.hook"; +import { useTranslationV2 } from "renderer/hooks/use-translation.hook"; +import { IpcService } from "renderer/services/ipc.service"; +import { ModalComponent } from "renderer/services/modale.service"; +import { lastValueFrom } from "rxjs"; +import { BSVersion } from "shared/bs-version.interface"; +import { getVersionName } from "shared/helpers/bs-version.helpers"; +import { safeLt } from "shared/helpers/semver.helpers"; +import { BbmCategories, BbmFullMod, BbmModVersion } from "shared/models/mods/mod.interface"; + +enum Mode { + All = "all", + Installed = "installed", + NotInstalled = "not-installed", + Missing = "missing", // If mod is not found from the other version +} + +// To save memory when caching +export interface ModCompareType { + id: number; + name: string; + version: string; +} + +const simplifyFullMod = (mod: BbmFullMod) => ({ + id: mod.mod.id, + name: mod.mod.name, + version: mod.version.modVersion +}); + +function useHeader({ + version, + loading, + setLoading, +}: Readonly<{ + version: BSVersion; + loading: boolean; + setLoading: (loading: boolean) => void; +}>) { + const ipc = useService(IpcService); + + const [mode, setMode] = useState(Mode.All); + const [otherVersion, setOtherVersion] = useState(null as BSVersion | null); + const [otherAvailableModsMap, setOtherAvailableModsMap] = useState(null as Map); + const [otherInstalledModsMap, setOtherInstalledModsMap] = useState(null as Map); + const [modsMapCache, setModsMapCache] = useState(new Map; + installedModsMap: Map; + }>>()); + + const modeOptions: BsmSelectOption[] = useConstant(() => + Object.values(Mode).map(val => ({ + text: `modals.mods-version-compare.mod-types.${val}`, + value: val, + })) + ); + const [versionOptions, setVersionOptions] = useState( + [] as BsmSelectOption[] + ); + + useEffect(() => { + Promise.all([ + lastValueFrom(ipc.sendV2("bs-version.get-version-dict")), + lastValueFrom(ipc.sendV2("bs-version.installed-versions")), + ]) + .then(([availableVersions, installedVersions]) => { + setVersionOptions([ + { + text: "modals.mods-version-compare.select-version", + value: null + }, + ...installedVersions + .filter(v => v.BSVersion !== version.BSVersion) + .sort((v1, v2) => { + if (v1.steam) return -1; + if (v2.steam) return 1; + + if (v1.oculus) return -1; + if (v2.oculus) return 1; + + if (v1.BSVersion === v2.BSVersion) + return v1.name.localeCompare(v2.name); + return safeLt(v1.BSVersion, v2.BSVersion) ? 1 : -1; + }) + .map(v => ({ + text: getVersionName(v), + value: v + })), + ...availableVersions + .filter(v => v.BSVersion !== version.BSVersion) + .map(v => ({ text: v.BSVersion, value: v })) + .sort((v1, v2) => safeLt(v1.text, v2.text) ? 1 : -1) + ]); + }) + .catch(error => logRenderError("Could not load versions dict", error)); + }, []); + + useEffect(() => { + if (!otherVersion) { + setOtherAvailableModsMap(null); + setOtherInstalledModsMap(null); + return; + } + + const cache = modsMapCache.get(otherVersion); + if (cache) { + setOtherAvailableModsMap(cache.availableModsMap); + setOtherInstalledModsMap(cache.installedModsMap); + return; + } + + setLoading(true); + + const promises: Promise[] = [ + lastValueFrom(ipc.sendV2("bs-mods.get-available-mods", otherVersion)) + ]; + if (otherVersion.path) { + promises.push(lastValueFrom(ipc.sendV2("bs-mods.get-installed-mods", otherVersion))) + } + + Promise.all(promises).then(([availableMods, installedMods]) => { + const availableModsMap = new Map(); + const installedModsMap = new Map(); + + (availableMods as BbmFullMod[]).forEach(fullMod => availableModsMap.set( + fullMod.mod.category, + [ + ...(availableModsMap.get(fullMod.mod.category) ?? []), + simplifyFullMod(fullMod) + ] + )); + if (installedMods) { + (installedMods as BbmModVersion[]) + .map(mod => (availableMods as BbmFullMod[]) + .find(availMod => availMod.mod.id === mod.id) + ) + .forEach(fullMod => { + if (!fullMod) { return; } + installedModsMap.set( + fullMod.mod.category, + [ + ...(availableModsMap.get(fullMod.mod.category) ?? []), + simplifyFullMod(fullMod) + ] + ); + }); + } + + // Manual in-memory caching + const newCache = new Map(modsMapCache); + newCache.set(otherVersion, { + availableModsMap, installedModsMap, + }); + setModsMapCache(newCache); + + setOtherAvailableModsMap(availableModsMap); + setOtherInstalledModsMap(installedModsMap); + setLoading(false); + }); + }, [otherVersion]); + + return { + mode, + otherVersion, otherAvailableModsMap, otherInstalledModsMap, + + renderHeader: () =>
+
+
{getVersionName(version)}
+ + +
+ + +
+ }; +} + +function ModCompare({ + mod, + installed, + otherMod, + otherInstalled, + otherInstalledLocal, + loading, +}: Readonly<{ + mod: ModCompareType | null; + otherMod: ModCompareType | null; + installed: boolean; + otherInstalled: boolean; + // Check if the other version is installed locally + otherInstalledLocal: boolean; + loading: boolean; +}>) { + const name = mod?.name || otherMod?.name; + + const renderMod = () => { + if (!mod) { + return
{name}
+ } + + let modClass = "flex justify-between py-1 px-2 gap-x-2"; + if (installed) { + modClass += " bg-green-700"; + } else { + modClass += " bg-red-700"; + } + + return
+
{name}
+
{mod.version}
+
+ } + + const renderOtherMod = () => { + if (loading) { + return
TODO: Rainbow Lazy Loading
+ } + + if (!otherMod) { + return
{name}
+ } + + let modClass = "flex justify-between py-1 px-2 gap-x-2"; + if (!otherInstalledLocal) { + modClass += " bg-blue-700"; // Change, just for visual prototyping + } else if (otherInstalled) { + modClass += " bg-green-700"; + } else { + modClass += " bg-red-700"; + } + + return
+
{name}
+
{otherMod.version}
+
+ } + + const renderIcon = () => { + if (loading) { + return
+ } + + if (mod && !otherMod) { + // Removed / Not Submitted Yet + return + } + + if (!mod && otherMod) { + // Added + return + } + + if (mod.version === otherMod.version) { + // Equals + return + } + + if (safeLt(mod.version, otherMod.version)) { + // Upgraded + return + } + + // Downgraded + return + } + + return <> + {renderMod()} + {renderIcon()} + {renderOtherMod()} + +} + +function ModCategory({ + mode, + category, + otherVersion, + availableMods, + installedMods, + otherAvailableMods, + otherInstalledMods, + loading, +}: Readonly<{ + mode: Mode; + category: string; + otherVersion: BSVersion; + availableMods: ModCompareType[]; + installedMods: ModCompareType[]; + otherAvailableMods: ModCompareType[]; + otherInstalledMods: ModCompareType[]; + loading: boolean; +}>) { + const otherInstalledLocal = !!otherVersion?.path; + + let combinedMods: ModCompareType[] = []; + switch (mode) { + case Mode.All: + combinedMods = [...availableMods]; + if (otherAvailableMods.length === 0) { + break; + } + + combinedMods.push(...otherAvailableMods.filter( + mod => combinedMods.findIndex(cm => cm.id === mod.id) === -1 + )); + combinedMods.sort((m1, m2) => m1.name.localeCompare(m2.name)); + break; + + case Mode.Installed: + combinedMods = availableMods.filter( + mod => installedMods.findIndex(im => im.name === mod.name) > -1 + ); + break; + + case Mode.NotInstalled: + combinedMods = availableMods.filter( + mod => installedMods.findIndex(im => im.name === mod.name) === -1 + ); + break; + + case Mode.Missing: + if (otherAvailableMods.length === 0) { + break; + } + combinedMods = otherAvailableMods.filter( + mod => availableMods.findIndex(am => am.name === mod.name) === -1 + ); + break; + + default: + } + + if (combinedMods.length === 0) { + return
+ } + + return
+

+ {category} +

+ +
+ {combinedMods.map(mod => + am.name === mod.name)} + installed={installedMods.findIndex(im => im.name === mod.name) > -1} + otherMod={otherAvailableMods.find(oam => oam.name === mod.name)} + otherInstalled={otherInstalledMods.findIndex(oim => oim.name === mod.name) > -1} + otherInstalledLocal={otherInstalledLocal} + loading={loading} + /> + )} +
+
+} + +export const ModsVersionCompareModal: ModalComponent; + installedModsMap: Map; +}>> = ({ options: { data: { + version, + availableModsMap, + installedModsMap +} } }) => { + const { text: t } = useTranslationV2(); + + const simpleAvailableModsMap = useConstant(() => { + const map = new Map(); + availableModsMap.forEach((mods, category) => + map.set(category, mods.map(simplifyFullMod)) + ); + return map; + }); + const simpleInstalledModsMap = useConstant(() => { + const map = new Map(); + installedModsMap.forEach((mods, category) => + map.set(category, mods.map(simplifyFullMod)) + ); + return map; + }); + + const [loading, setLoading] = useState(false); + const { + mode, + otherVersion, otherAvailableModsMap, otherInstalledModsMap, + renderHeader, + } = useHeader({ version, loading, setLoading }); + + return ( +
+

+ {t("modals.mods-version-compare.title")} +

+ + {renderHeader()} + +
+ {Object.values(BbmCategories).map(category => + + )} +
+
+ ) + } diff --git a/src/renderer/components/svgs/bsm-icon.component.tsx b/src/renderer/components/svgs/bsm-icon.component.tsx index 7421a201d..3f14f7d65 100644 --- a/src/renderer/components/svgs/bsm-icon.component.tsx +++ b/src/renderer/components/svgs/bsm-icon.component.tsx @@ -68,10 +68,11 @@ import { BrowseIcon } from "./icons/browse-icon.component"; import { AddFileIcon } from "./icons/add-file-icon.component"; import { CancelIcon } from "./icons/cancel-icon.component"; import { WarningIcon } from "./icons/warning-icon.component"; +import { CompareIcon } from "./icons/compare-icon.component"; -export type BsmIconType = SongDetailDiffCharactertistic | ("settings" | "trash" | "favorite" | "folder" | "bsNote" | "check" | "three-dots" | "twitch" | "eye" | "play" | "checkCircleIcon" | "discord" | "info" | "eye-cross" | "terminal" | "desktop" | "oculus" | "add" | "cross" | "task" | "github" | "close" | "thumbUpFill" | "timerFill" | "pause" | "twitter" | "sync" | "chevron-top" | "copy" | "steam" | "edit" | "export" | "patreon" | "search" | "bsMapDifficulty" | "link" | "unlink" | "download" | "filter" | "mee6" | "volume-up" | "volume-off" | "volume-down" | "shortcut" | "backup-restore" | "web-site" | "clean" | "browse" | "add-file" | "cancel" | "warning" | "fr-FR-flag" | "es-ES-flag" | "it-IT-flag" | "en-US-flag" | "en-EN-flag" | "de-DE-flag" | "ru-RU-flag" | "zh-CN-flag" | "zh-TW-flag" | "ja-JP-flag" | "ko-KR-flag" | "null" ); +export type BsmIconType = SongDetailDiffCharactertistic | ("settings" | "trash" | "favorite" | "folder" | "bsNote" | "check" | "three-dots" | "twitch" | "eye" | "play" | "checkCircleIcon" | "discord" | "info" | "eye-cross" | "terminal" | "desktop" | "oculus" | "add" | "cross" | "task" | "github" | "close" | "thumbUpFill" | "timerFill" | "pause" | "twitter" | "sync" | "chevron-top" | "copy" | "steam" | "edit" | "export" | "patreon" | "search" | "bsMapDifficulty" | "link" | "unlink" | "download" | "filter" | "mee6" | "volume-up" | "volume-off" | "volume-down" | "shortcut" | "backup-restore" | "web-site" | "clean" | "browse" | "add-file" | "compare" | "cancel" | "warning" | "fr-FR-flag" | "es-ES-flag" | "it-IT-flag" | "en-US-flag" | "en-EN-flag" | "de-DE-flag" | "ru-RU-flag" | "zh-CN-flag" | "zh-TW-flag" | "ja-JP-flag" | "ko-KR-flag" | "null" ); export const BsmIcon = memo(({ className, icon, style }: { className?: string; icon: BsmIconType; style?: CSSProperties }) => { // TODO : Very ugly very messy, need to find a better way to do this @@ -292,6 +293,10 @@ export const BsmIcon = memo(({ className, icon, style }: { className?: string; i return } + if (icon === "compare") { + return + } + return ; diff --git a/src/renderer/components/svgs/icons/compare-icon.component.tsx b/src/renderer/components/svgs/icons/compare-icon.component.tsx new file mode 100644 index 000000000..a5fd2d463 --- /dev/null +++ b/src/renderer/components/svgs/icons/compare-icon.component.tsx @@ -0,0 +1,16 @@ +import { createSvgIcon } from "../svg-icon.type"; + +export const CompareIcon = createSvgIcon((props, ref) => { + return ( + + + + ); +}); + diff --git a/src/renderer/components/svgs/icons/down-icon.component.tsx b/src/renderer/components/svgs/icons/down-icon.component.tsx new file mode 100644 index 000000000..486e512bd --- /dev/null +++ b/src/renderer/components/svgs/icons/down-icon.component.tsx @@ -0,0 +1,15 @@ +import { createSvgIcon } from "../svg-icon.type"; + +export const DownIcon = createSvgIcon((props, ref) => { + return ( + + + + ); +}); diff --git a/src/renderer/components/svgs/icons/equals-icon.component.tsx b/src/renderer/components/svgs/icons/equals-icon.component.tsx new file mode 100644 index 000000000..a354b286d --- /dev/null +++ b/src/renderer/components/svgs/icons/equals-icon.component.tsx @@ -0,0 +1,17 @@ +import { createSvgIcon } from "../svg-icon.type"; + +export const EqualIcon = createSvgIcon((props, ref) => { + return ( + + + + ); +}); diff --git a/src/renderer/components/svgs/icons/remove-icon.component.tsx b/src/renderer/components/svgs/icons/remove-icon.component.tsx new file mode 100644 index 000000000..d7ffa9569 --- /dev/null +++ b/src/renderer/components/svgs/icons/remove-icon.component.tsx @@ -0,0 +1,15 @@ +import { createSvgIcon } from "../svg-icon.type" + +export const RemoveIcon = createSvgIcon((props, ref) => { + return ( + + + + ) +}); diff --git a/src/renderer/components/svgs/icons/up-icon.component.tsx b/src/renderer/components/svgs/icons/up-icon.component.tsx new file mode 100644 index 000000000..1bcd60ae5 --- /dev/null +++ b/src/renderer/components/svgs/icons/up-icon.component.tsx @@ -0,0 +1,15 @@ +import { createSvgIcon } from "../svg-icon.type"; + +export const UpIcon = createSvgIcon((props, ref) => { + return ( + + + + ); +}); diff --git a/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx b/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx index 9bcecbf02..2e1e5e99b 100644 --- a/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx +++ b/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx @@ -183,6 +183,7 @@ export function LaunchSlide({ version }: Props) { if(launchMod?.visible === false || !launchMod?.pinned) { return undefined; } return ( void; unselectAllMods?: () => void; openModsDropZone?: () => void; + openModsVersionCompare?: () => void; }; -export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreInfoMod, onWantInfos, disabled, uninstallMod, uninstallAllMods, unselectAllMods, openModsDropZone }: Props) { +export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreInfoMod, onWantInfos, disabled, uninstallMod, uninstallAllMods, unselectAllMods, openModsDropZone, openModsVersionCompare }: Props) { const [filter, setFilter] = useState(""); const [filterEnabled, setFilterEnabled] = useState(false); @@ -71,6 +72,7 @@ export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreIn openModsDropZone?.() }, + { text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.compare-mods", icon: "compare", onClick: () => openModsVersionCompare?.() }, { text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.unselect-all", icon: "cancel", onClick: () => unselectAllMods?.() }, { text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.uninstall-all", icon: "trash", onClick: () => uninstallAllMods?.() } ]} /> diff --git a/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx b/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx index a09422e22..0aac23038 100644 --- a/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx +++ b/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx @@ -9,11 +9,11 @@ import BeatWaitingImg from "../../../../../../assets/images/apngs/beat-waiting.p import BeatConflictImg from "../../../../../../assets/images/apngs/beat-conflict.png"; import { useObservable } from "renderer/hooks/use-observable.hook"; import { lastValueFrom } from "rxjs"; -import { useTranslation, useTranslationV2 } from "renderer/hooks/use-translation.hook"; +import { useTranslationV2 } from "renderer/hooks/use-translation.hook"; import { LinkOpenerService } from "renderer/services/link-opener.service"; import { useInView } from "framer-motion"; import { ModalExitCode, ModalService } from "renderer/services/modale.service"; -import { ModsDisclaimerModal } from "renderer/components/modal/modal-types/mods-disclaimer-modal.component"; +import { ModsDisclaimerModal } from "renderer/components/modal/modal-types/mods/mods-disclaimer-modal.component"; import { OsDiagnosticService } from "renderer/services/os-diagnostic.service"; import { lt } from "semver"; import { useService } from "renderer/hooks/use-service.hook"; @@ -21,6 +21,7 @@ import { NotificationService } from "renderer/services/notification.service"; import { noop } from "shared/helpers/function.helpers"; import { UninstallAllModsModal } from "renderer/components/modal/modal-types/uninstall-all-mods-modal.component"; import { Dropzone } from "renderer/components/shared/dropzone.component"; +import { ModsVersionCompareModal } from "renderer/components/modal/modal-types/mods/mods-version-compare-modal.component"; export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; onDisclamerDecline: () => void }) { const ACCEPTED_DISCLAIMER_KEY = "accepted-mods-disclaimer"; @@ -239,6 +240,14 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; } }, [modsAvailable]); + const openModsVersionCompare = async () => { + modals.openModal(ModsVersionCompareModal, { data: { + version, + availableModsMap: modsAvailable, + installedModsMap: modsInstalled, + }}); + }; + const renderContent = () => { if (!isOnline) { return ; @@ -269,6 +278,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; uninstallAllMods={uninstallAllMods} unselectAllMods={unselectAllMods} openModsDropZone={() => setModsDropZoneOpen(true)} + openModsVersionCompare={openModsVersionCompare} />
@@ -309,7 +319,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; } function ModStatus({ text, image, spin = false, children }: { text: string; image: string; spin?: boolean, children?: ReactNode}) { - const t = useTranslation(); + const { text: t } = useTranslationV2(); return (
diff --git a/src/shared/helpers/bs-version.helpers.ts b/src/shared/helpers/bs-version.helpers.ts new file mode 100644 index 000000000..555116250 --- /dev/null +++ b/src/shared/helpers/bs-version.helpers.ts @@ -0,0 +1,14 @@ +import { BSVersion } from "shared/bs-version.interface"; + +export function getVersionName(version: BSVersion) { + if (version.steam) { + return `${version.BSVersion} - Steam`; + } + + if (version.oculus) { + return `${version.BSVersion} - Oculus`; + } + + const { name } = version; + return name ? `${version.BSVersion} - ${name}` : version.BSVersion; +}