From fae55673dd558f89d8949306281745c1dc4cd9ea Mon Sep 17 00:00:00 2001 From: silentrald Date: Sat, 28 Dec 2024 01:02:15 +0800 Subject: [PATCH 1/9] [feat-684] added mods version comparison --- assets/jsons/translations/en.json | 10 + src/main/helpers/proxy.helpers.ts | 2 +- .../mods-disclaimer-modal.component.tsx | 6 +- .../mods-version-compare-modal.component.tsx | 338 ++++++++++++++++++ .../slides/mods/mods-grid.component.tsx | 4 +- .../slides/mods/mods-slide.component.tsx | 12 +- 6 files changed, 366 insertions(+), 6 deletions(-) rename src/renderer/components/modal/modal-types/{ => mods}/mods-disclaimer-modal.component.tsx (90%) create mode 100644 src/renderer/components/modal/modal-types/mods/mods-version-compare-modal.component.tsx diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 9deaba077..8fe6985a1 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,15 @@ }, "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" + } } }, "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..9b0cd591f --- /dev/null +++ b/src/renderer/components/modal/modal-types/mods/mods-version-compare-modal.component.tsx @@ -0,0 +1,338 @@ +import { useEffect, useState } from "react"; +import { logRenderError } from "renderer"; +import { BsmSelect, BsmSelectOption } from "renderer/components/shared/bsm-select.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 { safeLt } from "shared/helpers/semver.helpers"; +import { Mod } from "shared/models/mods"; + +enum Mode { + All = "all", + Installed = "installed", + NotInstalled = "not-installed", +} + +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(new Map()); + const [otherInstalledModsMap, setOtherInstalledModsMap] = useState(new 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 => { + let {name} = v; + if (v.steam) { + name = "Steam"; + } else if (v.oculus) { + name = "Oculus"; + } + + return { + text: `${v.BSVersion} - ${name}`, + 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)); + }, []); + + // NOTE: Can be memoized/cached + useEffect(() => { + const availableMap = new Map(); + const installedMap = new Map(); + if (!otherVersion) { + setOtherAvailableModsMap(availableMap); + setOtherInstalledModsMap(installedMap); + return; + } + + setLoading(true); + + const promises = [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]) => { + if (availableMap) { + availableMods.forEach(mod => availableMap.set( + mod.category, + [...(availableMap.get(mod.category) ?? []), mod] + )); + } + if (installedMods) { + installedMods.forEach(mod => installedMap.set( + mod.category, + [...(installedMap.get(mod.category) ?? []), mod] + )); + } + setOtherAvailableModsMap(availableMap); + setOtherInstalledModsMap(installedMap); + setLoading(false); + }); + }, [otherVersion]); + + + return { + mode, otherAvailableModsMap, otherInstalledModsMap, + + renderHeader: () =>
+
+
{version.BSVersion} - {version.name}
+ + +
+ + +
+ + }; +} + +function ModCompare({ + mod, + installed, + otherMod, + otherInstalled, + loading, +}: Readonly<{ + mod: Mod | null; + installed: boolean; + otherMod: Mod | null; + otherInstalled: boolean; + loading: boolean; +}>) { + const name = mod?.name || otherMod?.name; + + const render = (renderMod: Mod | null, installed_: boolean, loading_: boolean = false) => { + if (loading_) { + return
TODO: Rainbow Lazy Loading
+ } + + if (!renderMod) { + 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}
+
{renderMod.version}
+
+ } + + const renderSymbol = () => { + let symbol = ""; + if (mod && !otherMod) { + symbol = "-"; // Removed / Not Submitted Yet + } else if (!mod && otherMod) { + symbol = "+"; // Added + } else if (mod.version === otherMod.version) { + symbol = "="; // Equals + } else if (safeLt(mod.version, otherMod.version)) { + symbol = "^"; // Upgraded + } else { + symbol = "v"; // Downgraded + } + + return
{symbol}
+ } + + return <> + {render(mod, installed)} + {renderSymbol()} + {render(otherMod, otherInstalled, loading)} + +} + +function ModCategory({ + mode, + category, + availableMods, + installedMods, + otherAvailableMods, + otherInstalledMods, + loading, +}: Readonly<{ + mode: Mode; + category: string; + availableMods: Mod[]; + installedMods: Mod[]; + otherAvailableMods: Mod[]; + otherInstalledMods: Mod[]; + loading: boolean; +}>) { + let combinedMods: Mod[] = []; + switch (mode) { + case Mode.All: + combinedMods = [...availableMods]; + if (otherAvailableMods.length === 0) { + break; + } + + availableMods.forEach(mod => { + if (combinedMods.findIndex(cm => cm.name === mod.name) === -1) { + combinedMods.push(mod); + } + }); + combinedMods.sort((m1, m2) => m1.name.localeCompare(m2.name)); + break; + + case Mode.Installed: + combinedMods = [...installedMods]; + break; + + case Mode.NotInstalled: + combinedMods = availableMods.filter( + mod => installedMods.findIndex(im => im.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} + loading={loading} + /> + )} +
+
+} + +export const ModsVersionCompareModal: ModalComponent; + installedModsMap: Map; +}>> = ({ options: { data: { + version, + availableModsMap, + installedModsMap +} } }) => { + const { text: t } = useTranslationV2(); + + const [loading, setLoading] = useState(false); + const { + mode, otherAvailableModsMap, otherInstalledModsMap, + renderHeader, + } = useHeader({ version, loading, setLoading }); + + let categories = mode === Mode.Installed + ? [...installedModsMap.keys()] + : [...availableModsMap.keys()]; + if (otherAvailableModsMap.size > 0) { + categories = [...new Set([ + ...categories, ...otherAvailableModsMap.keys() + ])]; + categories.sort((c1, c2) => c1.localeCompare(c2)); + } + + return ( +
+

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

+ + {renderHeader()} + +
+ {categories.map(category => + + )} +
+
+ ) + } diff --git a/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx b/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx index 2410fd449..3e12c3342 100644 --- a/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx +++ b/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx @@ -18,9 +18,10 @@ type Props = { uninstallAllMods?: () => 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: "null", 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..98ca9557b 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 @@ -13,7 +13,7 @@ import { useTranslation, useTranslationV2 } from "renderer/hooks/use-translation 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} />
From 6e6602d06aa906493706bb82059c1d0a54adc0b6 Mon Sep 17 00:00:00 2001 From: silentrald Date: Sat, 28 Dec 2024 18:42:52 +0800 Subject: [PATCH 2/9] [feat-684] cached mods result for each version in comparison modal --- .../mods-version-compare-modal.component.tsx | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) 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 index 9b0cd591f..d418456d1 100644 --- 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 @@ -32,6 +32,10 @@ function useHeader({ const [otherVersion, setOtherVersion] = useState(null as BSVersion | null); const [otherAvailableModsMap, setOtherAvailableModsMap] = useState(new Map()); const [otherInstalledModsMap, setOtherInstalledModsMap] = useState(new Map()); + const [modsMapCache, setModsMapCache] = useState(new Map; + installedModsMap: Map; + }>>()); const modeOptions: BsmSelectOption[] = useConstant(() => Object.values(Mode).map(val => ({ @@ -68,7 +72,7 @@ function useHeader({ return safeLt(v1.BSVersion, v2.BSVersion) ? 1 : -1; }) .map(v => { - let {name} = v; + let { name } = v; if (v.steam) { name = "Steam"; } else if (v.oculus) { @@ -89,13 +93,18 @@ function useHeader({ .catch(error => logRenderError("Could not load versions dict", error)); }, []); - // NOTE: Can be memoized/cached useEffect(() => { - const availableMap = new Map(); - const installedMap = new Map(); if (!otherVersion) { - setOtherAvailableModsMap(availableMap); - setOtherInstalledModsMap(installedMap); + const empty = new Map(); + setOtherAvailableModsMap(empty); + setOtherInstalledModsMap(empty); + return; + } + + const cached = modsMapCache.get(otherVersion); + if (cached) { + setOtherAvailableModsMap(cached.availableModsMap); + setOtherInstalledModsMap(cached.installedModsMap); return; } @@ -107,20 +116,28 @@ function useHeader({ } Promise.all(promises).then(([availableMods, installedMods]) => { - if (availableMap) { - availableMods.forEach(mod => availableMap.set( - mod.category, - [...(availableMap.get(mod.category) ?? []), mod] - )); - } + const availableModsMap = new Map(); + const installedModsMap = new Map(); + + availableMods.forEach(mod => availableModsMap.set( + mod.category, + [...(availableModsMap.get(mod.category) ?? []), mod] + )); if (installedMods) { - installedMods.forEach(mod => installedMap.set( + installedMods.forEach(mod => installedModsMap.set( mod.category, - [...(installedMap.get(mod.category) ?? []), mod] + [...(installedModsMap.get(mod.category) ?? []), mod] )); } - setOtherAvailableModsMap(availableMap); - setOtherInstalledModsMap(installedMap); + + // Manual in-memory caching + modsMapCache.set(otherVersion, { + availableModsMap, installedModsMap, + }); + setModsMapCache(modsMapCache); + + setOtherAvailableModsMap(availableModsMap); + setOtherInstalledModsMap(installedModsMap); setLoading(false); }); }, [otherVersion]); @@ -323,6 +340,7 @@ export const ModsVersionCompareModal: ModalComponent {categories.map(category => Date: Sun, 29 Dec 2024 11:24:19 +0800 Subject: [PATCH 3/9] [feat-684] add icons for mods version comparison --- .../mods-version-compare-modal.component.tsx | 104 +++++++++++++----- .../components/svgs/bsm-icon.component.tsx | 7 +- .../svgs/icons/compare-icon.component.tsx | 15 +++ .../svgs/icons/down-icon.component.tsx | 15 +++ .../svgs/icons/equals-icon.component.tsx | 15 +++ .../svgs/icons/remove-icon.component.tsx | 15 +++ .../svgs/icons/up-icon.component.tsx | 15 +++ .../slides/mods/mods-grid.component.tsx | 2 +- 8 files changed, 158 insertions(+), 30 deletions(-) create mode 100644 src/renderer/components/svgs/icons/compare-icon.component.tsx create mode 100644 src/renderer/components/svgs/icons/down-icon.component.tsx create mode 100644 src/renderer/components/svgs/icons/equals-icon.component.tsx create mode 100644 src/renderer/components/svgs/icons/remove-icon.component.tsx create mode 100644 src/renderer/components/svgs/icons/up-icon.component.tsx 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 index d418456d1..1a14db7bc 100644 --- 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 @@ -1,6 +1,11 @@ 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"; @@ -131,10 +136,11 @@ function useHeader({ } // Manual in-memory caching - modsMapCache.set(otherVersion, { + const newCache = new Map(modsMapCache); + newCache.set(otherVersion, { availableModsMap, installedModsMap, }); - setModsMapCache(modsMapCache); + setModsMapCache(newCache); setOtherAvailableModsMap(availableModsMap); setOtherInstalledModsMap(installedModsMap); @@ -144,7 +150,8 @@ function useHeader({ return { - mode, otherAvailableModsMap, otherInstalledModsMap, + mode, + otherVersion, otherAvailableModsMap, otherInstalledModsMap, renderHeader: () =>
@@ -165,7 +172,6 @@ function useHeader({ onChange={setOtherVersion} />
- }; } @@ -174,65 +180,101 @@ function ModCompare({ installed, otherMod, otherInstalled, + otherInstalledLocal, loading, }: Readonly<{ mod: Mod | null; installed: boolean; otherMod: Mod | null; otherInstalled: boolean; + // Check if the other version is installed locally + otherInstalledLocal: boolean; loading: boolean; }>) { const name = mod?.name || otherMod?.name; - const render = (renderMod: Mod | null, installed_: boolean, loading_: boolean = false) => { - if (loading_) { + 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 (!renderMod) { + if (!otherMod) { return
{name}
} let modClass = "flex justify-between py-1 px-2 gap-x-2"; - if (installed_) { + if (!otherInstalledLocal) { + modClass += " bg-blue-700"; // Change, just for visual prototyping + } else if (otherInstalled) { modClass += " bg-green-700"; } else { modClass += " bg-red-700"; } - return
+ return
{name}
-
{renderMod.version}
+
{otherMod.version}
} - const renderSymbol = () => { - let symbol = ""; + const renderIcon = () => { + if (loading) { + return
+ } + if (mod && !otherMod) { - symbol = "-"; // Removed / Not Submitted Yet - } else if (!mod && otherMod) { - symbol = "+"; // Added - } else if (mod.version === otherMod.version) { - symbol = "="; // Equals - } else if (safeLt(mod.version, otherMod.version)) { - symbol = "^"; // Upgraded - } else { - symbol = "v"; // Downgraded + // Removed / Not Submitted Yet + return + } + + if (!mod && otherMod) { + // Added + return + } + + if (mod.version === otherMod.version) { + // Equals + return } - return
{symbol}
+ if (safeLt(mod.version, otherMod.version)) { + // Upgraded + return + } + + // Downgraded + return } return <> - {render(mod, installed)} - {renderSymbol()} - {render(otherMod, otherInstalled, loading)} + {renderMod()} + {renderIcon()} + {renderOtherMod()} } function ModCategory({ mode, category, + otherVersion, availableMods, installedMods, otherAvailableMods, @@ -241,12 +283,15 @@ function ModCategory({ }: Readonly<{ mode: Mode; category: string; + otherVersion: BSVersion; availableMods: Mod[]; installedMods: Mod[]; otherAvailableMods: Mod[]; otherInstalledMods: Mod[]; loading: boolean; }>) { + const otherInstalledLocal = !!otherVersion?.path; + let combinedMods: Mod[] = []; switch (mode) { case Mode.All: @@ -255,7 +300,7 @@ function ModCategory({ break; } - availableMods.forEach(mod => { + otherAvailableMods.forEach(mod => { if (combinedMods.findIndex(cm => cm.name === mod.name) === -1) { combinedMods.push(mod); } @@ -286,7 +331,7 @@ function ModCategory({
{combinedMods.map(mod => 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} /> )} @@ -315,7 +361,8 @@ export const ModsVersionCompareModal: ModalComponent { // 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..465a2e11e --- /dev/null +++ b/src/renderer/components/svgs/icons/compare-icon.component.tsx @@ -0,0 +1,15 @@ +import { CSSProperties } from "react"; + +export function CompareIcon(props: Readonly<{ className?: string; style?: CSSProperties }>) { + 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..5abec0a5b --- /dev/null +++ b/src/renderer/components/svgs/icons/down-icon.component.tsx @@ -0,0 +1,15 @@ +import { CSSProperties } from "react"; + +export function DownIcon(props: Readonly<{ className?: string; style?: CSSProperties }>) { + 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..69b51d629 --- /dev/null +++ b/src/renderer/components/svgs/icons/equals-icon.component.tsx @@ -0,0 +1,15 @@ +import { CSSProperties } from "react"; + +export function EqualIcon(props: Readonly<{ className?: string; style?: CSSProperties }>) { + 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..c035ec268 --- /dev/null +++ b/src/renderer/components/svgs/icons/remove-icon.component.tsx @@ -0,0 +1,15 @@ +import { CSSProperties } from "react"; + +export function RemoveIcon(props: Readonly<{ className?: string; style?: CSSProperties }>) { + 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..f8bca33a5 --- /dev/null +++ b/src/renderer/components/svgs/icons/up-icon.component.tsx @@ -0,0 +1,15 @@ +import { CSSProperties } from "react"; + +export function UpIcon(props: Readonly<{ className?: string; style?: CSSProperties }>) { + return ( + + + + ); +} diff --git a/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx b/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx index 3e12c3342..d6e1dc77d 100644 --- a/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx +++ b/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx @@ -72,7 +72,7 @@ export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreIn openModsDropZone?.() }, - { text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.compare-mods", icon: "null", onClick: () => openModsVersionCompare?.() }, + { 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?.() } ]} /> From 8f99d3b02b0259032010bf7160d6c596b93e3cf5 Mon Sep 17 00:00:00 2001 From: silentrald Date: Sun, 29 Dec 2024 12:05:11 +0800 Subject: [PATCH 4/9] [translation-684] added translation for mods version comparison --- assets/jsons/translations/de.json | 10 ++++++++++ assets/jsons/translations/es.json | 10 ++++++++++ assets/jsons/translations/fr.json | 10 ++++++++++ assets/jsons/translations/ja.json | 10 ++++++++++ assets/jsons/translations/ko.json | 10 ++++++++++ assets/jsons/translations/ru.json | 10 ++++++++++ assets/jsons/translations/zh-tw.json | 10 ++++++++++ assets/jsons/translations/zh.json | 10 ++++++++++ 8 files changed, 80 insertions(+) diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 5f662c4e9..027e94f7c 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" } @@ -649,6 +650,15 @@ "do-not-remind": "Nicht mehr daran erinnern", "ok": "Ok" } + }, + "mods-version-compare": { + "title": "Vergleich der Mod-Versionen", + "select-version": "Wählen Sie eine Version", + "mod-types": { + "all": "Alle", + "installed": "Installiert", + "not-installed": "Nicht installiert" + } } }, "modals": { diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 528e4939e..14bdf7041 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" } @@ -649,6 +650,15 @@ "do-not-remind": "No volver a recordármelo", "ok": "Ok" } + }, + "mods-version-compare": { + "title": "Comparación de versiones de mods", + "select-version": "Seleccione una versión", + "mod-types": { + "all": "Todo", + "installed": "Instalado", + "not-installed": "No instalado" + } } }, "modals": { diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index e0e3c7690..cb63127f3 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" } @@ -650,6 +651,15 @@ "do-not-remind": "Ne plus me rappeler", "ok": "Ok" } + }, + "mods-version-compare": { + "title": "Comparaison des versions des mods", + "select-version": "Sélectionnez une version", + "mod-types": { + "all": "Tout", + "installed": "Installé", + "not-installed": "Non installé" + } } }, "modals": { diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 61ffe3a7a..27a0464c7 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": "すべて選択解除" } @@ -649,6 +650,15 @@ "do-not-remind": "再度通知しない", "ok": "OK" } + }, + "mods-version-compare": { + "title": "MODのバージョン比較", + "select-version": "バージョンを選択", + "mod-types": { + "all": "全て", + "installed": "インストール済み", + "not-installed": "インストールされていません" + } } }, "modals": { diff --git a/assets/jsons/translations/ko.json b/assets/jsons/translations/ko.json index e296c2afe..69052ee4c 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": "모두 선택 해제" } @@ -649,6 +650,15 @@ "do-not-remind": "다시 알리지 않기", "ok": "확인" } + }, + "mods-version-compare": { + "title": "Mods 버전 비교", + "select-version": "버전 선택", + "mod-types": { + "all": "모두", + "installed": "설치됨", + "not-installed": "설치되지 않음" + } } }, "modals": { diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 651e11a9b..0051a7b1d 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": "Снять все выделения" } @@ -649,6 +650,15 @@ "do-not-remind": "Больше не напоминать", "ok": "Ок" } + }, + "mods-version-compare": { + "title": "Сравнение версий модов", + "select-version": "Выберите версию", + "mod-types": { + "all": "Все", + "installed": "Установлено", + "not-installed": "Не установлено" + } } }, "modals": { diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 668517a52..3007695f9 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": "取消全選" } @@ -649,6 +650,15 @@ "do-not-remind": "不要再提醒我", "ok": "確定" } + }, + "mods-version-compare": { + "title": "Mod 版本比較", + "select-version": "選擇版本", + "mod-types": { + "all": "全部", + "installed": "已安裝", + "not-installed": "未安裝" + } } }, "modals": { diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index ace849b71..64149f57f 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": "取消全选" } @@ -649,6 +650,15 @@ "do-not-remind": "不再提醒", "ok": "确定" } + }, + "mods-version-compare": { + "title": "Mod 版本比较", + "select-version": "选择版本", + "mod-types": { + "all": "全部", + "installed": "已安装", + "not-installed": "未安装" + } } }, "modals": { From ce19458e40d7e5426acccf60b86c0153ab2dc3d3 Mon Sep 17 00:00:00 2001 From: silentrald Date: Wed, 1 Jan 2025 11:37:13 +0800 Subject: [PATCH 5/9] [feat-684] updated icon component to use createSvgIcon --- .../components/svgs/icons/compare-icon.component.tsx | 11 ++++++----- .../components/svgs/icons/down-icon.component.tsx | 10 +++++----- .../components/svgs/icons/equals-icon.component.tsx | 8 +++++--- .../components/svgs/icons/remove-icon.component.tsx | 10 +++++----- .../components/svgs/icons/up-icon.component.tsx | 10 +++++----- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/renderer/components/svgs/icons/compare-icon.component.tsx b/src/renderer/components/svgs/icons/compare-icon.component.tsx index 465a2e11e..a5fd2d463 100644 --- a/src/renderer/components/svgs/icons/compare-icon.component.tsx +++ b/src/renderer/components/svgs/icons/compare-icon.component.tsx @@ -1,10 +1,10 @@ -import { CSSProperties } from "react"; +import { createSvgIcon } from "../svg-icon.type"; -export function CompareIcon(props: Readonly<{ className?: string; style?: CSSProperties }>) { +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 index 5abec0a5b..486e512bd 100644 --- a/src/renderer/components/svgs/icons/down-icon.component.tsx +++ b/src/renderer/components/svgs/icons/down-icon.component.tsx @@ -1,10 +1,10 @@ -import { CSSProperties } from "react"; +import { createSvgIcon } from "../svg-icon.type"; -export function DownIcon(props: Readonly<{ className?: string; style?: CSSProperties }>) { +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 index 69b51d629..a354b286d 100644 --- a/src/renderer/components/svgs/icons/equals-icon.component.tsx +++ b/src/renderer/components/svgs/icons/equals-icon.component.tsx @@ -1,8 +1,10 @@ -import { CSSProperties } from "react"; +import { createSvgIcon } from "../svg-icon.type"; -export function EqualIcon(props: Readonly<{ className?: string; style?: CSSProperties }>) { +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 index c035ec268..d7ffa9569 100644 --- a/src/renderer/components/svgs/icons/remove-icon.component.tsx +++ b/src/renderer/components/svgs/icons/remove-icon.component.tsx @@ -1,10 +1,10 @@ -import { CSSProperties } from "react"; +import { createSvgIcon } from "../svg-icon.type" -export function RemoveIcon(props: Readonly<{ className?: string; style?: CSSProperties }>) { +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 index f8bca33a5..1bcd60ae5 100644 --- a/src/renderer/components/svgs/icons/up-icon.component.tsx +++ b/src/renderer/components/svgs/icons/up-icon.component.tsx @@ -1,10 +1,10 @@ -import { CSSProperties } from "react"; +import { createSvgIcon } from "../svg-icon.type"; -export function UpIcon(props: Readonly<{ className?: string; style?: CSSProperties }>) { +export const UpIcon = createSvgIcon((props, ref) => { return ( ); -} +}); From 54b1d15661f301939d8ef1db887951e5cf5cca75 Mon Sep 17 00:00:00 2001 From: silentrald Date: Wed, 1 Jan 2025 15:13:17 +0800 Subject: [PATCH 6/9] [feat-684] fixed some map key element issues --- .../mods/mods-version-compare-modal.component.tsx | 10 ++++------ .../slides/launch/launch-slide.component.tsx | 1 + 2 files changed, 5 insertions(+), 6 deletions(-) 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 index 1a14db7bc..e35841764 100644 --- 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 @@ -300,11 +300,9 @@ function ModCategory({ break; } - otherAvailableMods.forEach(mod => { - if (combinedMods.findIndex(cm => cm.name === mod.name) === -1) { - combinedMods.push(mod); - } - }); + combinedMods.push(...otherAvailableMods.filter( + mod => combinedMods.findIndex(cm => cm.name === mod.name) === -1 + )); combinedMods.sort((m1, m2) => m1.name.localeCompare(m2.name)); break; @@ -335,7 +333,7 @@ function ModCategory({ }}> {combinedMods.map(mod => am.name === mod.name)} installed={installedMods.findIndex(im => im.name === mod.name) > -1} otherMod={otherAvailableMods.find(oam => oam.name === mod.name)} 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 ( Date: Sat, 4 Jan 2025 12:14:55 +0800 Subject: [PATCH 7/9] [feat-684] applied changes with BadBeatMods API migration --- assets/jsons/translations/en.json | 3 +- .../mods-version-compare-modal.component.tsx | 165 +++++++++++------- .../slides/mods/mods-slide.component.tsx | 4 +- src/shared/helpers/bs-version.helpers.ts | 10 ++ 4 files changed, 114 insertions(+), 68 deletions(-) create mode 100644 src/shared/helpers/bs-version.helpers.ts diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 8fe6985a1..30d9fb1d2 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -909,7 +909,8 @@ "mod-types": { "all": "All", "installed": "Installed", - "not-installed": "Not Installed" + "not-installed": "Not Installed", + "missing": "Missing" } } }, 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 index e35841764..57b289ab8 100644 --- 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 @@ -13,15 +13,30 @@ 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 { Mod } from "shared/models/mods"; +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, @@ -35,11 +50,11 @@ function useHeader({ const [mode, setMode] = useState(Mode.All); const [otherVersion, setOtherVersion] = useState(null as BSVersion | null); - const [otherAvailableModsMap, setOtherAvailableModsMap] = useState(new Map()); - const [otherInstalledModsMap, setOtherInstalledModsMap] = useState(new Map()); + const [otherAvailableModsMap, setOtherAvailableModsMap] = useState(null as Map); + const [otherInstalledModsMap, setOtherInstalledModsMap] = useState(null as Map); const [modsMapCache, setModsMapCache] = useState(new Map; - installedModsMap: Map; + availableModsMap: Map; + installedModsMap: Map; }>>()); const modeOptions: BsmSelectOption[] = useConstant(() => @@ -76,19 +91,10 @@ function useHeader({ return v1.name.localeCompare(v2.name); return safeLt(v1.BSVersion, v2.BSVersion) ? 1 : -1; }) - .map(v => { - let { name } = v; - if (v.steam) { - name = "Steam"; - } else if (v.oculus) { - name = "Oculus"; - } - - return { - text: `${v.BSVersion} - ${name}`, - value: v - }; - }), + .map(v => ({ + text: getVersionName(v), + value: v + })), ...availableVersions .filter(v => v.BSVersion !== version.BSVersion) .map(v => ({ text: v.BSVersion, value: v })) @@ -100,39 +106,53 @@ function useHeader({ useEffect(() => { if (!otherVersion) { - const empty = new Map(); - setOtherAvailableModsMap(empty); - setOtherInstalledModsMap(empty); + setOtherAvailableModsMap(null); + setOtherInstalledModsMap(null); return; } - const cached = modsMapCache.get(otherVersion); - if (cached) { - setOtherAvailableModsMap(cached.availableModsMap); - setOtherInstalledModsMap(cached.installedModsMap); + const cache = modsMapCache.get(otherVersion); + if (cache) { + setOtherAvailableModsMap(cache.availableModsMap); + setOtherInstalledModsMap(cache.installedModsMap); return; } setLoading(true); - const promises = [lastValueFrom(ipc.sendV2("bs-mods.get-available-mods", otherVersion))]; + 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.forEach(mod => availableModsMap.set( - mod.category, - [...(availableModsMap.get(mod.category) ?? []), mod] + 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.forEach(mod => installedModsMap.set( - mod.category, - [...(installedModsMap.get(mod.category) ?? []), mod] - )); + (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 @@ -148,14 +168,13 @@ function useHeader({ }); }, [otherVersion]); - return { mode, otherVersion, otherAvailableModsMap, otherInstalledModsMap, renderHeader: () =>
-
{version.BSVersion} - {version.name}
+
{getVersionName(version)}
) { const otherInstalledLocal = !!otherVersion?.path; - let combinedMods: Mod[] = []; + let combinedMods: ModCompareType[] = []; switch (mode) { case Mode.All: combinedMods = [...availableMods]; @@ -301,13 +320,15 @@ function ModCategory({ } combinedMods.push(...otherAvailableMods.filter( - mod => combinedMods.findIndex(cm => cm.name === mod.name) === -1 + mod => combinedMods.findIndex(cm => cm.id === mod.id) === -1 )); combinedMods.sort((m1, m2) => m1.name.localeCompare(m2.name)); break; case Mode.Installed: - combinedMods = [...installedMods]; + combinedMods = availableMods.filter( + mod => installedMods.findIndex(im => im.name === mod.name) > -1 + ); break; case Mode.NotInstalled: @@ -316,6 +337,15 @@ function ModCategory({ ); break; + case Mode.Missing: + if (otherAvailableMods.length === 0) { + break; + } + combinedMods = otherAvailableMods.filter( + mod => availableMods.findIndex(am => am.name === mod.name) === -1 + ); + break; + default: } @@ -324,7 +354,7 @@ function ModCategory({ } return
-

+

{category}

@@ -348,8 +378,8 @@ function ModCategory({ export const ModsVersionCompareModal: ModalComponent; - installedModsMap: Map; + availableModsMap: Map; + installedModsMap: Map; }>> = ({ options: { data: { version, availableModsMap, @@ -357,6 +387,21 @@ export const ModsVersionCompareModal: ModalComponent { 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, @@ -364,16 +409,6 @@ export const ModsVersionCompareModal: ModalComponent 0) { - categories = [...new Set([ - ...categories, ...otherAvailableModsMap.keys() - ])]; - categories.sort((c1, c2) => c1.localeCompare(c2)); - } - return (

@@ -383,16 +418,16 @@ export const ModsVersionCompareModal: ModalComponent - {categories.map(category => + {Object.values(BbmCategories).map(category => )} 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 98ca9557b..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,7 +9,7 @@ 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"; @@ -319,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..63cd92455 --- /dev/null +++ b/src/shared/helpers/bs-version.helpers.ts @@ -0,0 +1,10 @@ +import { BSVersion } from "shared/bs-version.interface"; + +export function getVersionName(version: BSVersion) { + let { name } = version; + if (!name) { + name = version.steam + ? "Steam" : "Oculus"; + } + return `${version.BSVersion} - ${name}`; +} From 41d76e1f2c9fad9ef7bebc4161edf24ff1014da8 Mon Sep 17 00:00:00 2001 From: silentrald Date: Sat, 4 Jan 2025 12:35:50 +0800 Subject: [PATCH 8/9] [translation-684] fixed translation --- assets/jsons/translations/de.json | 19 +++++----- assets/jsons/translations/es.json | 19 +++++----- assets/jsons/translations/fr.json | 19 +++++----- assets/jsons/translations/it.json | 57 +++++++++++++++++----------- assets/jsons/translations/ja.json | 19 +++++----- assets/jsons/translations/ko.json | 21 +++++----- assets/jsons/translations/ru.json | 21 +++++----- assets/jsons/translations/zh-tw.json | 19 +++++----- assets/jsons/translations/zh.json | 21 +++++----- 9 files changed, 117 insertions(+), 98 deletions(-) diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 027e94f7c..60c165910 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -650,15 +650,6 @@ "do-not-remind": "Nicht mehr daran erinnern", "ok": "Ok" } - }, - "mods-version-compare": { - "title": "Vergleich der Mod-Versionen", - "select-version": "Wählen Sie eine Version", - "mod-types": { - "all": "Alle", - "installed": "Installiert", - "not-installed": "Nicht installiert" - } } }, "modals": { @@ -916,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/es.json b/assets/jsons/translations/es.json index 14bdf7041..799338604 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -650,15 +650,6 @@ "do-not-remind": "No volver a recordármelo", "ok": "Ok" } - }, - "mods-version-compare": { - "title": "Comparación de versiones de mods", - "select-version": "Seleccione una versión", - "mod-types": { - "all": "Todo", - "installed": "Instalado", - "not-installed": "No instalado" - } } }, "modals": { @@ -916,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 cb63127f3..175ab7b10 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -651,15 +651,6 @@ "do-not-remind": "Ne plus me rappeler", "ok": "Ok" } - }, - "mods-version-compare": { - "title": "Comparaison des versions des mods", - "select-version": "Sélectionnez une version", - "mod-types": { - "all": "Tout", - "installed": "Installé", - "not-installed": "Non installé" - } } }, "modals": { @@ -917,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 27a0464c7..211ec725c 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -650,15 +650,6 @@ "do-not-remind": "再度通知しない", "ok": "OK" } - }, - "mods-version-compare": { - "title": "MODのバージョン比較", - "select-version": "バージョンを選択", - "mod-types": { - "all": "全て", - "installed": "インストール済み", - "not-installed": "インストールされていません" - } } }, "modals": { @@ -916,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 69052ee4c..2b32d36d0 100644 --- a/assets/jsons/translations/ko.json +++ b/assets/jsons/translations/ko.json @@ -123,7 +123,7 @@ "description": "설명", "dropdown": { "import-mods": "모드 가져오기", - "compare-mods": "모드 비교", + "compare-mods": "모드 비교하기", "uninstall-all": "모두 제거", "unselect-all": "모두 선택 해제" } @@ -650,15 +650,6 @@ "do-not-remind": "다시 알리지 않기", "ok": "확인" } - }, - "mods-version-compare": { - "title": "Mods 버전 비교", - "select-version": "버전 선택", - "mod-types": { - "all": "모두", - "installed": "설치됨", - "not-installed": "설치되지 않음" - } } }, "modals": { @@ -916,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 0051a7b1d..edef57d5a 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -123,7 +123,7 @@ "description": "Описание", "dropdown": { "import-mods": "Импортировать моды", - "compare-mods": "равнить моды", + "compare-mods": "Сравнить моды", "uninstall-all": "Удалить всё", "unselect-all": "Снять все выделения" } @@ -650,15 +650,6 @@ "do-not-remind": "Больше не напоминать", "ok": "Ок" } - }, - "mods-version-compare": { - "title": "Сравнение версий модов", - "select-version": "Выберите версию", - "mod-types": { - "all": "Все", - "installed": "Установлено", - "not-installed": "Не установлено" - } } }, "modals": { @@ -915,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 3007695f9..48653102f 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -650,15 +650,6 @@ "do-not-remind": "不要再提醒我", "ok": "確定" } - }, - "mods-version-compare": { - "title": "Mod 版本比較", - "select-version": "選擇版本", - "mod-types": { - "all": "全部", - "installed": "已安裝", - "not-installed": "未安裝" - } } }, "modals": { @@ -916,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 64149f57f..0986b7cab 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -123,7 +123,7 @@ "description": "描述", "dropdown": { "import-mods": "导入模组", - "compare-mods": "比较模组", + "compare-mods": "比较模", "uninstall-all": "全部卸载", "unselect-all": "取消全选" } @@ -650,15 +650,6 @@ "do-not-remind": "不再提醒", "ok": "确定" } - }, - "mods-version-compare": { - "title": "Mod 版本比较", - "select-version": "选择版本", - "mod-types": { - "all": "全部", - "installed": "已安装", - "not-installed": "未安装" - } } }, "modals": { @@ -916,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": { From c2f9139f88808b7bb6ff169e8ae180187c0507ec Mon Sep 17 00:00:00 2001 From: silentrald Date: Sat, 4 Jan 2025 12:48:45 +0800 Subject: [PATCH 9/9] [feat-684] changed logic for getVersionName --- src/shared/helpers/bs-version.helpers.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/shared/helpers/bs-version.helpers.ts b/src/shared/helpers/bs-version.helpers.ts index 63cd92455..555116250 100644 --- a/src/shared/helpers/bs-version.helpers.ts +++ b/src/shared/helpers/bs-version.helpers.ts @@ -1,10 +1,14 @@ import { BSVersion } from "shared/bs-version.interface"; export function getVersionName(version: BSVersion) { - let { name } = version; - if (!name) { - name = version.steam - ? "Steam" : "Oculus"; + if (version.steam) { + return `${version.BSVersion} - Steam`; } - return `${version.BSVersion} - ${name}`; + + if (version.oculus) { + return `${version.BSVersion} - Oculus`; + } + + const { name } = version; + return name ? `${version.BSVersion} - ${name}` : version.BSVersion; }