[feat-994] Add support for alternative mod repos (#995)

* [feat] Add support for alternative mod repos

* Fix lint errors

* Simplify code + Fix potential issues

---------

Co-authored-by: Zagrios <40181755+Zagrios@users.noreply.github.com>
This commit is contained in:
frto027
2026-01-26 23:06:44 +08:00
committed by GitHub
parent 49ef8203c8
commit 57ca031e70
22 changed files with 260 additions and 8 deletions
@@ -23,6 +23,7 @@ import { Dropzone } from "renderer/components/shared/dropzone.component";
import { ModsGridStatus } from "shared/models/mods/mod-ipc.model";
import { BsmLink } from "renderer/components/shared/bsm-link.component";
import { DISCORD_URL, GITHUB_URL } from "shared/constants";
import { ModRepo } from "shared/models/mods/repo.model";
export type ModsSlideRef = {
loadMods: () => Promise<void>;
@@ -55,6 +56,8 @@ export const ModsSlide = forwardRef<ModsSlideRef, Props>(({ version, isActive, o
const downloadRef = useRef(null);
const [downloadWith, setDownloadWidth] = useState(0);
const [selectedModRepo, setSelectedModRepo] = useState(null as ModRepo)
const modsToCategoryMap = (mods: BbmFullMod[]): Map<BbmCategories, BbmFullMod[]> => {
if (!mods) {
return new Map<BbmCategories, BbmFullMod[]>();
@@ -218,6 +221,10 @@ export const ModsSlide = forwardRef<ModsSlideRef, Props>(({ version, isActive, o
loadMods
}), [version]);
useEffect(()=>{
modsManager.getSelectedModRepo().then(repo=>setSelectedModRepo(repo));
}, []);
useEffect(() => {
let isCancelled = false;
@@ -284,6 +291,18 @@ export const ModsSlide = forwardRef<ModsSlideRef, Props>(({ version, isActive, o
const renderStatus = () => {
if (gridStatus === ModsGridStatus.BEATMODS_DOWN) {
if(selectedModRepo && selectedModRepo.id !== "beatmods"){
return <ModStatus image={BeatConflictImg}>
<span className="text-xl text-center px-2 mt-3 italic">
{
t("pages.version-viewer.mods.notifications.third-party-mod-source-not-avaliable.description",
{name:selectedModRepo ? selectedModRepo.display_name : "null"}
)
}
</span>
</ModStatus>
}
return <ModStatus image={BeatConflictImg}>
<span className="text-xl text-center px-2 mt-3 italic">
{te("pages.version-viewer.mods.status.beatmods-down", {links: (<>
@@ -49,6 +49,8 @@ import { AutoUpdaterService } from "renderer/services/auto-updater.service";
import { OculusDownloaderService } from "renderer/services/bs-version-download/oculus-downloader.service";
import { DISCORD_URL } from "shared/constants";
import { AutoUpdate } from "shared/models/config";
import { BsModsManagerService } from "renderer/services/bs-mods-manager.service";
import { ModRepo } from "shared/models/mods/repo.model";
export function SettingsPage() {
@@ -69,6 +71,7 @@ export function SettingsPage() {
const versionLinker = useService(VersionFolderLinkerService);
const staticConfig = useService(StaticConfigurationService);
const installationLocationService = useService(InstallationLocationService);
const bsModManagerService = useService(BsModsManagerService);
const autoUpdater = useService(AutoUpdaterService);
const { firstColor, secondColor } = useThemeColor();
@@ -100,6 +103,8 @@ export function SettingsPage() {
const [playlistsDeepLinkEnabled, setPlaylistsDeepLinkEnabled] = useState(false);
const [modelsDeepLinkEnabled, setModelsDeepLinkEnabled] = useState(false);
const [hasDownloaderSession, setHasDownloaderSession] = useState(false);
const [modRepoList, setModRepoList] = useState([] as ModRepo[]);
const [modRepo, setModRepo] = useState("");
const appVersion = useObservable(() => autoUpdater.getAppVersion());
useEffect(() => {
@@ -109,6 +114,15 @@ export function SettingsPage() {
playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeepLinkEnabled(() => enabled));
modelsManager.isDeepLinksEnabled().then(enabled => setModelsDeepLinkEnabled(() => enabled));
bsModManagerService.getModRepoList().then(list =>{
setModRepoList(list);
bsModManagerService.getSelectedModRepo().then(repo => {
setModRepo(repo.id);
})
});
staticConfig.get("proton-folder").then(setProtonFolder);
}, []);
@@ -157,6 +171,13 @@ export function SettingsPage() {
i18nService.setLanguage(item.value);
};
const handleChangeModRepo = (repo: RadioItem<string>) => {
bsModManagerService.selectModRepo(repo.value).then(result=>{
if(result){
setModRepo(repo.value)
}
})
}
const setDefaultProtonFolder = async () => {
if (!progressBarService.require()) {
return;
@@ -508,6 +529,17 @@ export function SettingsPage() {
<SettingRadioArray items={languagesItems} selectedItemValue={languageSelected} onItemSelected={handleChangeLanguage} columnCount={2} />
</SettingContainer>
<SettingContainer title="pages.settings.mod-repos.title" description="pages.settings.mod-repos.description">
<SettingRadioArray items={modRepoList.map((repo,index)=>({
id:index,
value: repo.id,
text: repo.display_name,
icon: repo.website ?
<BsmButton onClick={()=>linkOpener.open(repo.website, false)} className="px-2 font-bold italic text-sm rounded-md" text="pages.settings.mod-repos.website" withBar={false} />
: null
}))} selectedItemValue={modRepo} onItemSelected={handleChangeModRepo} />
</SettingContainer>
<SettingContainer title="pages.settings.patreon.title" description="pages.settings.patreon.description">
<div className="flex gap-2">
<BsmButton color="#ef4444" className="flex w-fit rounded-md h-8 px-2 font-bold py-1 whitespace-nowrap !text-white" text="pages.settings.patreon.buttons.support" withBar={false} onClick={openSupportPage} />
@@ -1,4 +1,4 @@
import { Observable, BehaviorSubject, throwError, of, lastValueFrom } from "rxjs";
import { Observable, BehaviorSubject, throwError, of, lastValueFrom, from } from "rxjs";
import { catchError, map, tap } from "rxjs/operators";
import { BSVersion } from "shared/bs-version.interface";
import { IpcService } from "./ipc.service";
@@ -10,6 +10,7 @@ import { BbmFullMod, BbmModVersion } from "shared/models/mods/mod.interface";
import { logRenderError } from "renderer";
import { ModsGridStatus } from "shared/models/mods/mod-ipc.model";
import { LinuxService } from "./linux.service";
import { ModRepo } from "shared/models/mods/repo.model";
export class BsModsManagerService {
private static instance: BsModsManagerService;
@@ -226,4 +227,18 @@ export class BsModsManagerService {
return ModsGridStatus.OK;
}
public async getModRepoList(): Promise<ModRepo[]> {
return lastValueFrom(this.ipcService.sendV2("bs-mods.mod-repo.get-repo-list")).catch(() => [] as ModRepo[]);
}
public async getSelectedModRepo(): Promise<ModRepo> {
return lastValueFrom(this.ipcService.sendV2("bs-mods.mod-repo.get-name").pipe(
catchError(() => from(this.getModRepoList()).pipe(map(list => list.find(repo => repo.default) || list[0])))
));
}
public async selectModRepo(name:string): Promise<boolean> {
return lastValueFrom(this.ipcService.sendV2("bs-mods.mod-repo.select-name", name)).catch(() => false);
}
}