Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f30cad74d7 | |||
| ede82393da | |||
| 9ee468af03 | |||
| ca722b2a18 | |||
| c219e9655d | |||
| a6c1ba9fee | |||
| 6e9ea1bd9a | |||
| 84f27d571b | |||
| 51549161cd | |||
| 198002238e |
@@ -0,0 +1,32 @@
|
||||
# This workflow will...
|
||||
|
||||
name: Realease Linux
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- run: npm run build && electron-builder --linux --x64 --publish always
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release
|
||||
path: release
|
||||
@@ -679,8 +679,7 @@
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3865840827122760836",
|
||||
"ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/ae11d195bbe41a0f244d7b4d055a0ab89f88e5bb.png",
|
||||
"ReleaseDate": "1702309893",
|
||||
"year": "2023",
|
||||
"recommended": true
|
||||
"year": "2023"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.34.5",
|
||||
@@ -734,7 +733,8 @@
|
||||
"ReleaseURL": "https://store.steampowered.com/news/app/620980/view/4161968170153497460",
|
||||
"ReleaseImg": "https://clan.akamai.steamstatic.com/images//32055887/f82d34c7582137a6748a28c5b02670ff4b235356.png",
|
||||
"ReleaseDate": "1717513346",
|
||||
"year": "2024"
|
||||
"year": "2024",
|
||||
"recommended": true
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.37.1",
|
||||
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 945 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 442 KiB After Width: | Height: | Size: 442 KiB |
@@ -36,7 +36,6 @@
|
||||
},
|
||||
"build": {
|
||||
"extraResources": [
|
||||
"./assets/favicon.ico",
|
||||
"./assets/jsons/bs-versions.json",
|
||||
"./assets/jsons/patreons.json",
|
||||
"./assets/proto/song_details_cache_v1.proto"
|
||||
@@ -56,8 +55,9 @@
|
||||
"nsis",
|
||||
"nsis-web"
|
||||
],
|
||||
"icon": "assets/favicon.ico",
|
||||
"icon": "./build/icons/win/favicon.ico",
|
||||
"extraResources": [
|
||||
"./build/icons/win",
|
||||
"./assets/scripts/*.exe"
|
||||
]
|
||||
},
|
||||
@@ -65,8 +65,10 @@
|
||||
"target": [
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development",
|
||||
"icon": "./build/icons/png",
|
||||
"category": "Utility;Game;",
|
||||
"extraResources": [
|
||||
"./build/icons/png",
|
||||
"./assets/scripts/DepotDownloader"
|
||||
]
|
||||
},
|
||||
@@ -180,7 +182,7 @@
|
||||
"css-minimizer-webpack-plugin": "^6.0.0",
|
||||
"detect-port": "^1.5.1",
|
||||
"electron": "^28.2.4",
|
||||
"electron-builder": "^24.12.0",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electronmon": "^2.0.2",
|
||||
"esbuild": "^0.20.1",
|
||||
@@ -243,20 +245,20 @@
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^6.1.8",
|
||||
"electron-updater": "^6.2.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"framer-motion": "^11.2.6",
|
||||
"fs-extra": "^11.2.0",
|
||||
"got": "^14.2.0",
|
||||
"got": "^14.4.1",
|
||||
"history": "^5.3.0",
|
||||
"is-elevated": "^4.0.0",
|
||||
"jszip": "^3.10.1",
|
||||
"md5-file": "^5.0.0",
|
||||
"node-abi": "^3.56.0",
|
||||
"node-abi": "^3.65.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"pako": "^2.1.0",
|
||||
"protobufjs": "^7.3.0",
|
||||
"protobufjs": "^7.3.2",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"query-process": "^0.0.3",
|
||||
"react": "^18.2.0",
|
||||
@@ -264,17 +266,17 @@
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-range": "^1.8.14",
|
||||
"react-router-dom": "^6.22.1",
|
||||
"react-virtualized-auto-sizer": "^1.0.23",
|
||||
"react-router-dom": "^6.24.1",
|
||||
"react-virtualized-auto-sizer": "^1.0.24",
|
||||
"react-window": "^1.8.10",
|
||||
"recursive-readdir": "^2.2.3",
|
||||
"rfdc": "^1.3.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"semver": "^7.6.0",
|
||||
"semver": "^7.6.2",
|
||||
"serialize-error": "^11.0.3",
|
||||
"striptags": "^4.0.0-alpha.4",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwind-merge": "^2.4.0",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"tailwindcss-scoped-groups": "^2.0.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { BsModsManagerService } from "../services/mods/bs-mods-manager.service";
|
||||
import { IpcService } from "../services/ipc.service";
|
||||
import { from } from "rxjs";
|
||||
import { BeatModsApiService } from "main/services/mods/beat-mods-api.service";
|
||||
|
||||
const ipc = IpcService.getInstance();
|
||||
|
||||
ipc.on("get-version-mods", (args, reply) => {
|
||||
const beatMods = BeatModsApiService.getInstance();
|
||||
reply(from(beatMods.getVersionMods(args)));
|
||||
ipc.on("get-available-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(from(modsManager.getAvailableMods(args)));
|
||||
});
|
||||
|
||||
ipc.on("get-installed-mods", (args, reply) => {
|
||||
@@ -15,11 +14,6 @@ ipc.on("get-installed-mods", (args, reply) => {
|
||||
reply(from(modsManager.getInstalledMods(args)));
|
||||
});
|
||||
|
||||
ipc.on("get-version-aliases", (_, reply) => {
|
||||
const beatmods = BeatModsApiService.getInstance();
|
||||
reply(from(beatmods.getVersionAliases()));
|
||||
});
|
||||
|
||||
ipc.on("install-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(from(modsManager.installMods(args.mods, args.version)));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BSVersion, BSVersionString } from "shared/bs-version.interface";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { Mod } from "shared/models/mods/mod.interface";
|
||||
import { RequestService } from "../request.service";
|
||||
|
||||
@@ -12,8 +12,9 @@ export class BeatModsApiService {
|
||||
private readonly BEAT_MODS_API_URL = "https://beatmods.com/api/v1/";
|
||||
public readonly BEAT_MODS_URL = "https://beatmods.com";
|
||||
|
||||
private aliasesCache: Record<BSVersionString, BSVersionString[]> = {};
|
||||
private readonly versionModsCache = new Map<BSVersionString, Mod[]>();
|
||||
private readonly aliasesCache = new Map<string, BSVersion[]>();
|
||||
private readonly versionModsCache = new Map<string, Mod[]>();
|
||||
private readonly modsHashCache = new Map<string, Mod>();
|
||||
|
||||
private allModsCache: Mod[];
|
||||
|
||||
@@ -36,21 +37,29 @@ export class BeatModsApiService {
|
||||
return `${this.BEAT_MODS_API_URL}mod`;
|
||||
}
|
||||
|
||||
public async getVersionAliases(): Promise<Record<BSVersionString, BSVersionString[]>> {
|
||||
if (Object.keys(this.aliasesCache).length > 0) {
|
||||
private async getVersionAlias(): Promise<Map<string, BSVersion[]>> {
|
||||
if (this.aliasesCache.size) {
|
||||
return this.aliasesCache;
|
||||
}
|
||||
|
||||
return this.requestService.getJSON<Record<BSVersionString, BSVersionString[]>>(this.BEAT_MODS_ALIAS).then(rawAliases => {
|
||||
this.aliasesCache = rawAliases;
|
||||
return this.requestService.getJSON<Record<string, string[]>>(this.BEAT_MODS_ALIAS).then(rawAliases => {
|
||||
Object.entries(rawAliases).forEach(([key, value]) => {
|
||||
this.aliasesCache.set(
|
||||
key,
|
||||
value.map(s => ({ BSVersion: s } as BSVersion))
|
||||
);
|
||||
});
|
||||
return this.aliasesCache;
|
||||
});
|
||||
}
|
||||
|
||||
private async getAliasOfVersion(version: BSVersionString): Promise<BSVersionString> {
|
||||
return this.getVersionAliases().then(aliases => (
|
||||
aliases[version] ? version : Object.keys(aliases).find(k => aliases[k as BSVersionString].some(v => v === version)) as BSVersionString
|
||||
));
|
||||
private async getAliasOfVersion(version: BSVersion): Promise<BSVersion> {
|
||||
return this.getVersionAlias().then(aliases => {
|
||||
if (Array.from(aliases.keys()).some(k => k === version.BSVersion)) {
|
||||
return version;
|
||||
}
|
||||
const alias = Array.from(aliases.entries()).find(([, value]) => value.find(v => v.BSVersion === version.BSVersion))?.[0];
|
||||
return { BSVersion: alias } as BSVersion;
|
||||
});
|
||||
}
|
||||
|
||||
private asignDependencies(mod: Mod, mods: Mod[]): Mod {
|
||||
@@ -58,16 +67,42 @@ export class BeatModsApiService {
|
||||
return mod;
|
||||
}
|
||||
|
||||
public async getVersionMods(version: BSVersionString): Promise<Mod[]> {
|
||||
if (this.versionModsCache.has(version)) {
|
||||
return this.versionModsCache.get(version);
|
||||
private updateModsHashCache(mods: Mod[]): void {
|
||||
|
||||
if(!Array.isArray(mods)){
|
||||
return;
|
||||
}
|
||||
|
||||
for (const mod of mods) {
|
||||
for (const downloads of (mod.downloads ?? [])) {
|
||||
for (const hashMd5 of (downloads.hashMd5 ?? [])) {
|
||||
this.modsHashCache.set(hashMd5.hash, mod);
|
||||
}
|
||||
}
|
||||
|
||||
for (const dep of (mod.dependencies ?? [])) {
|
||||
for (const downloads of (dep.downloads ?? [])) {
|
||||
for (const hashMd5 of (downloads.hashMd5 ?? [])) {
|
||||
this.modsHashCache.set(hashMd5.hash, dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getVersionMods(version: BSVersion): Promise<Mod[]> {
|
||||
if (this.versionModsCache.has(version.BSVersion)) {
|
||||
return this.versionModsCache.get(version.BSVersion);
|
||||
}
|
||||
|
||||
const alias = await this.getAliasOfVersion(version);
|
||||
|
||||
return this.requestService.getJSON<Mod[]>(this.getVersionModsUrl({ BSVersion: alias })).then(mods => {
|
||||
return this.requestService.getJSON<Mod[]>(this.getVersionModsUrl(alias)).then(mods => {
|
||||
mods = mods.map(mod => this.asignDependencies(mod, mods));
|
||||
this.versionModsCache.set(version, mods);
|
||||
this.versionModsCache.set(version.BSVersion, mods);
|
||||
|
||||
this.updateModsHashCache(mods);
|
||||
|
||||
return mods;
|
||||
});
|
||||
}
|
||||
@@ -81,4 +116,15 @@ export class BeatModsApiService {
|
||||
return this.allModsCache;
|
||||
});
|
||||
}
|
||||
|
||||
public getModByHash(hash: string): Promise<Mod> {
|
||||
if (this.modsHashCache.has(hash)) {
|
||||
return Promise.resolve(this.modsHashCache.get(hash));
|
||||
}
|
||||
|
||||
return this.requestService.getJSON<Mod[]>(`${this.BEAT_MODS_API_URL}mod?hash=${hash}`).then(mods => {
|
||||
this.updateModsHashCache(mods);
|
||||
return mods.at(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,23 +48,14 @@ export class BsModsManagerService {
|
||||
}
|
||||
|
||||
private async getModFromHash(hash: string): Promise<Mod> {
|
||||
const allMods = await this.beatModsApi.getAllMods();
|
||||
return allMods.find(mod => {
|
||||
if (mod.name.toLowerCase() === "bsipa") {
|
||||
return false;
|
||||
}
|
||||
return mod.downloads.some(download => download.hashMd5.some(md5 => md5.hash === hash));
|
||||
});
|
||||
}
|
||||
|
||||
private async getIpaFromHash(hash: string): Promise<Mod> {
|
||||
const allMods = await this.beatModsApi.getAllMods();
|
||||
return allMods.find(mod => {
|
||||
if (mod.name.toLowerCase() !== "bsipa") {
|
||||
return false;
|
||||
}
|
||||
return mod.downloads.some(download => download.hashMd5.some(md5 => md5.hash === hash));
|
||||
});
|
||||
const mod = await this.beatModsApi.getModByHash(hash);
|
||||
|
||||
if(mod?.name?.toLowerCase() === "bsipa"){
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
private async getModsInDir(version: BSVersion, modsDir: ModsInstallFolder): Promise<Mod[]> {
|
||||
@@ -84,7 +75,6 @@ export class BsModsManagerService {
|
||||
return undefined;
|
||||
}
|
||||
const hash = await md5File(filePath);
|
||||
|
||||
const mod = await this.getModFromHash(hash);
|
||||
|
||||
if (!mod) {
|
||||
@@ -107,7 +97,7 @@ export class BsModsManagerService {
|
||||
});
|
||||
|
||||
const mods = await Promise.all(promises);
|
||||
return mods.filter(Boolean);
|
||||
return mods.filter(Boolean);
|
||||
}
|
||||
|
||||
private async getBsipaInstalled(version: BSVersion): Promise<Mod> {
|
||||
@@ -117,7 +107,7 @@ export class BsModsManagerService {
|
||||
return undefined;
|
||||
}
|
||||
const injectorMd5 = await md5File(injectorPath);
|
||||
return this.getIpaFromHash(injectorMd5);
|
||||
return this.beatModsApi.getModByHash(injectorMd5);
|
||||
}
|
||||
|
||||
private async downloadZip(zipUrl: string): Promise<JSZip> {
|
||||
@@ -264,7 +254,7 @@ export class BsModsManagerService {
|
||||
}
|
||||
|
||||
private async resolveDependencies(mods: Mod[], version: BSVersion): Promise<Mod[]> {
|
||||
const availableMods = await this.beatModsApi.getVersionMods(version.BSVersion);
|
||||
const availableMods = await this.beatModsApi.getVersionMods(version);
|
||||
return Array.from(
|
||||
new Map<string, Mod>(
|
||||
availableMods.reduce((res, mod) => {
|
||||
@@ -316,10 +306,16 @@ export class BsModsManagerService {
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
public getAvailableMods(version: BSVersion): Promise<Mod[]> {
|
||||
return this.beatModsApi.getVersionMods(version);
|
||||
}
|
||||
|
||||
public async getInstalledMods(version: BSVersion): Promise<Mod[]> {
|
||||
this.manifestMatches = [];
|
||||
await this.beatModsApi.getAllMods();
|
||||
|
||||
const bsipa = await this.getBsipaInstalled(version);
|
||||
|
||||
|
||||
return Promise.all([this.getModsInDir(version, ModsInstallFolder.PLUGINS_PENDING), this.getModsInDir(version, ModsInstallFolder.LIBS_PENDING), this.getModsInDir(version, ModsInstallFolder.PLUGINS), this.getModsInDir(version, ModsInstallFolder.LIBS)]).then(dirMods => {
|
||||
const modsDict = new Map<string, Mod>();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BSVersion, BSVersionString } from "shared/bs-version.interface";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { useState, memo, ComponentProps } from "react";
|
||||
import defaultImage from "../../../../assets/images/default-version-img.jpg";
|
||||
import dateFormat from "dateformat";
|
||||
@@ -9,33 +9,19 @@ import { GlowEffect } from "../shared/glow-effect.component";
|
||||
import equal from "fast-deep-equal";
|
||||
import { SteamIcon } from "../svgs/icons/steam-icon.component";
|
||||
import { useConstant } from "renderer/hooks/use-constant.hook";
|
||||
import { CpuIcon } from "../svgs/icons/cpu-icon.component";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { ModalService } from "renderer/services/modale.service";
|
||||
import { AvailableModsModal } from "../modal/modal-types/available-mods-modal.component";
|
||||
|
||||
type Props = {
|
||||
version: BSVersion;
|
||||
selected: boolean;
|
||||
beatModsVersionAlias?: BSVersionString;
|
||||
onClick: ComponentProps<"li">["onClick"];
|
||||
}
|
||||
|
||||
export const AvailableVersionItem = memo(function AvailableVersionItem({version, selected, beatModsVersionAlias, onClick}: Props) {
|
||||
export const AvailableVersionItem = memo(function AvailableVersionItem({version, selected, onClick}: Props) {
|
||||
|
||||
const t = useTranslation();
|
||||
|
||||
const modal = useService(ModalService);
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const formatedDate = useConstant(() => dateFormat(+version.ReleaseDate * 1000, "ddd. d mmm yyyy"));
|
||||
|
||||
const openAvailableMods = () => {
|
||||
if(!beatModsVersionAlias) { return; }
|
||||
modal.openModal(AvailableModsModal, beatModsVersionAlias);
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.li className="group relative w-72 h-60 active:scale-[.98]" onClick={onClick} onHoverStart={() => setHovered(true)} onHoverEnd={() => setHovered(false)}>
|
||||
<GlowEffect visible={hovered || selected} className="absolute" />
|
||||
@@ -50,22 +36,12 @@ export const AvailableVersionItem = memo(function AvailableVersionItem({version,
|
||||
<h2 className="block text-xl font-bold text-white tracking-wider">{version.BSVersion}</h2>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-400">{formatedDate}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-end gap-2">
|
||||
{version.ReleaseURL && (
|
||||
<a href={version.ReleaseURL} target="_blank" className="flex flex-row justify-between items-center rounded-full bg-black bg-opacity-30 text-white pb-px overflow-hidden hover:bg-opacity-50" tabIndex={-1}>
|
||||
<SteamIcon className="size-[25px] transition-transform group-hover:rotate-[-360deg] duration-300" />
|
||||
<span className="relative -left-px text-sm w-fit max-w-0 text-center overflow-hidden h-full whitespace-nowrap pb-[3px] transition-all group-hover:max-w-[200px] group-hover:px-1 duration-300">{t("pages.available-versions.steam-release")}</span>
|
||||
</a>
|
||||
)}
|
||||
{beatModsVersionAlias && (
|
||||
<Tippy content="Voir les mods disponnible" placement="top" theme="default">
|
||||
<button className="size-[25px] rounded-full bg-black bg-opacity-30 p-[3px] hover:bg-opacity-50" type="button" onClickCapture={openAvailableMods}>
|
||||
<CpuIcon className="text-gray-200"/>
|
||||
</button>
|
||||
</Tippy>
|
||||
)}
|
||||
</div>
|
||||
{version.ReleaseURL && (
|
||||
<a href={version.ReleaseURL} target="_blank" className="flex flex-row justify-between items-center rounded-full bg-black bg-opacity-30 text-white pb-px overflow-hidden hover:bg-opacity-50" tabIndex={-1}>
|
||||
<SteamIcon className="w-[25px] h-[25px] transition-transform group-hover:rotate-[-360deg] duration-300" />
|
||||
<span className="relative -left-px text-sm w-fit max-w-0 text-center overflow-hidden h-full whitespace-nowrap pb-[3px] transition-all group-hover:max-w-[200px] group-hover:px-1 duration-300">{t("pages.available-versions.steam-release")}</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.li>
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { useContext } from "react";
|
||||
import { AvailableVersionItem } from "./available-version-item.component";
|
||||
import { BSVersion, BSVersionString } from "shared/bs-version.interface";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { AvailableVersionsContext } from "renderer/pages/available-versions-list.components";
|
||||
import equal from "fast-deep-equal";
|
||||
|
||||
type Props = {
|
||||
versions: BSVersion[],
|
||||
versionModAliases?: Record<BSVersionString, BSVersionString[]>
|
||||
versions: BSVersion[]
|
||||
}
|
||||
|
||||
export function AvailableVersionsSlide({ versions, versionModAliases }: Props) {
|
||||
export function AvailableVersionsSlide({ versions }: Props) {
|
||||
|
||||
const context = useContext(AvailableVersionsContext);
|
||||
|
||||
@@ -29,16 +28,10 @@ export function AvailableVersionsSlide({ versions, versionModAliases }: Props) {
|
||||
return copy;
|
||||
}
|
||||
|
||||
const getVersionModAlias = (version: BSVersion): BSVersionString => {
|
||||
if(!versionModAliases) { return; }
|
||||
if(versionModAliases[version.BSVersion]) { return version.BSVersion; }
|
||||
return Object.keys(versionModAliases).find(k => versionModAliases[k as BSVersionString].some(v => v === version.BSVersion)) as BSVersionString;
|
||||
}
|
||||
|
||||
return (
|
||||
<ol className="w-full flex items-start justify-center gap-6 shrink-0 content-start flex-wrap px-3.5 py-4 overflow-x-hidden overflow-y-scroll scrollbar-default">
|
||||
{getVersions().map(version => (
|
||||
<AvailableVersionItem key={version.BSManifest} version={version} selected={equal(version, context.selectedVersion)} onClick={() => setSelectedVersion(version)} beatModsVersionAlias={getVersionModAlias(version)}/>
|
||||
<AvailableVersionItem key={version.BSManifest} version={version} selected={equal(version, context.selectedVersion)} onClick={() => setSelectedVersion(version)}/>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
|
||||
@@ -4,13 +4,10 @@ import { AvailableVersionsSlide } from "./available-versions-slide.component";
|
||||
import { TabNavBar } from "../shared/tab-nav-bar.component";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { BeatModsService } from "renderer/services/thrird-partys/beat-mods.service";
|
||||
|
||||
export function AvailableVersionsSlider() {
|
||||
const versionManagerService = useService(BSVersionManagerService);
|
||||
const beatMods = useService(BeatModsService);
|
||||
|
||||
const versionModAliases = useObservable(() => beatMods.getVersionAliases(), undefined);
|
||||
const availableVersions = useObservable(() => versionManagerService.availableVersions$);
|
||||
const [yearIndex, setYearIndex] = useState(0);
|
||||
|
||||
@@ -32,7 +29,7 @@ export function AvailableVersionsSlider() {
|
||||
<TabNavBar tabIndex={yearIndex} tabsText={availableYears} onTabChange={setSelectedYear} />
|
||||
<ol className="w-full min-h-0 flex transition-transform duration-300" style={{ transform: `translate(${-(yearIndex * 100)}%, 0)` }}>
|
||||
{availableYears.map(year => (
|
||||
<AvailableVersionsSlide key={year} versions={getVersionOfYear(year)} versionModAliases={versionModAliases} />
|
||||
<AvailableVersionsSlide key={year} versions={getVersionOfYear(year)} />
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { ModsGrid, modsArrayToCategoryMap } from "renderer/components/version-viewer/slides/mods/mods-grid.component";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useService } from "renderer/hooks/use-service.hook"
|
||||
import { ModalComponent } from "renderer/services/modale.service"
|
||||
import { BeatModsService } from "renderer/services/thrird-partys/beat-mods.service";
|
||||
import { BSVersionString } from "shared/bs-version.interface"
|
||||
|
||||
export const AvailableModsModal: ModalComponent<void, BSVersionString> = ({ data }) => {
|
||||
|
||||
const beatMods = useService(BeatModsService);
|
||||
|
||||
const availableMods = useObservable(() => beatMods.getVersionMods(data), undefined);
|
||||
|
||||
return (
|
||||
<div className="h-[85vh] overflow-y-scroll">
|
||||
{availableMods && <ModsGrid modsMap={modsArrayToCategoryMap(availableMods)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export const DownloadPlaylistModal: ModalComponent<void, {version: BSVersion, ow
|
||||
}, [searchParams]);
|
||||
|
||||
const handleNewSearch = (value: Omit<PlaylistSearchParams, "page">) => {
|
||||
setPlaylists(() => []);
|
||||
setPlaylists(() => undefined);
|
||||
setSearchParams(() => ({ ...value, page: 0}));
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createSvgIcon } from "../svg-icon.type"
|
||||
|
||||
export const CpuIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="M360-405.5v-149q0-19.15 13.17-32.33Q386.35-600 405.5-600h149q19.15 0 32.33 13.17Q600-573.65 600-554.5v149q0 19.15-13.17 32.33Q573.65-360 554.5-360h-149q-19.15 0-32.33-13.17Q360-386.35 360-405.5Zm80-34.5h80v-80h-80v80Zm-85.5 289.8v-41.67h-71.63q-37.78 0-64.39-26.61t-26.61-64.39v-71.63h-41.85q-17.78 0-29.76-12.05t-11.98-29.86q0-17.71 12.05-29.7 12.05-11.98 29.87-11.98h41.67v-83.82h-41.79q-17.77 0-29.78-12.05-12.02-12.05-12.02-29.87 0-17.71 12.05-29.69t29.87-11.98h41.67v-71.63q0-37.78 26.61-64.39t64.39-26.61h71.63v-41.85q0-17.78 12.05-29.76t29.86-11.98q17.71 0 29.7 12.02 11.98 12.01 11.98 29.78v41.79h83.82v-41.79q0-17.77 12.05-29.78 12.05-12.02 29.87-12.02 17.71 0 29.69 12.02 11.98 12.01 11.98 29.78v41.79h71.63q37.78 0 64.39 26.61t26.61 64.39v71.63h41.85q17.78 0 29.76 11.98t11.98 29.69q0 17.82-12.02 29.87-12.01 12.05-29.78 12.05h-41.79v83.82h41.79q17.77 0 29.78 11.99 12.02 11.98 12.02 29.69T839.7-366.6q-12.01 12.1-29.78 12.1h-41.79v71.63q0 37.78-26.61 64.39t-64.39 26.61H605.5v41.85q0 17.78-11.98 29.76t-29.7 11.98q-17.71 0-29.81-12.05-12.1-12.05-12.1-29.87v-41.67h-83.82v41.79q0 17.77-11.99 29.78-11.98 12.02-29.69 12.02t-29.81-12.05q-12.1-12.05-12.1-29.87Zm322.63-132.67v-394.26H282.87v394.26h394.26ZM480-480Z"/>
|
||||
</svg>
|
||||
)
|
||||
});
|
||||
@@ -10,29 +10,9 @@ import useDoubleClick from "use-double-click";
|
||||
import { gt } from "semver";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
mod: Mod;
|
||||
installedVersion: string;
|
||||
isDependency?: boolean;
|
||||
isSelected?: boolean;
|
||||
showInstalled?: boolean;
|
||||
onChange?: (val: boolean) => void;
|
||||
wantInfo?: boolean;
|
||||
onWantInfo?: (mod: Mod) => void
|
||||
};
|
||||
type Props = { className?: string; mod: Mod; installedVersion: string; isDependency?: boolean; isSelected?: boolean; onChange?: (val: boolean) => void; wantInfo?: boolean; onWantInfo?: (mod: Mod) => void };
|
||||
|
||||
export function ModItem({
|
||||
className,
|
||||
mod,
|
||||
installedVersion,
|
||||
isDependency,
|
||||
isSelected,
|
||||
showInstalled,
|
||||
onChange,
|
||||
wantInfo,
|
||||
onWantInfo
|
||||
}: Props) {
|
||||
export function ModItem({ className, mod, installedVersion, isDependency, isSelected, onChange, wantInfo, onWantInfo }: Props) {
|
||||
const modsManager = useService(BsModsManagerService);
|
||||
const pageState = useService(PageStateService);
|
||||
|
||||
@@ -68,14 +48,14 @@ export function ModItem({
|
||||
return (
|
||||
<li ref={clickRef} className={`${className} group`}>
|
||||
<div className="h-full aspect-square flex items-center justify-center p-[7px] rounded-l-md bg-inherit ml-3 border-2 border-r-0 z-[1] group-hover:brightness-90" style={wantInfoStyle}>
|
||||
{onChange && <BsmCheckbox className="h-full aspect-square z-[1] relative bg-inherit" onChange={onChange} disabled={mod.required || isDependency} checked={isChecked} />}
|
||||
<BsmCheckbox className="h-full aspect-square z-[1] relative bg-inherit" onChange={onChange} disabled={mod.required || isDependency} checked={isChecked} />
|
||||
</div>
|
||||
<span className="bg-inherit py-2 pl-3 font-bold text-sm whitespace-nowrap border-t-2 border-b-2 blur-none group-hover:brightness-90" style={wantInfoStyle}>
|
||||
{mod.name}
|
||||
</span>
|
||||
{showInstalled !== false && <span className={`min-w-0 text-center bg-inherit py-2 px-1 text-sm border-t-2 border-b-2 group-hover:brightness-90 ${installedVersion && isOutDated && "text-red-400 line-through"} ${installedVersion && !isOutDated && "text-green-400"}`} style={wantInfoStyle}>
|
||||
<span className={`min-w-0 text-center bg-inherit py-2 px-1 text-sm border-t-2 border-b-2 group-hover:brightness-90 ${installedVersion && isOutDated && "text-red-400 line-through"} ${installedVersion && !isOutDated && "text-green-400"}`} style={wantInfoStyle}>
|
||||
{installedVersion || "-"}
|
||||
</span>}
|
||||
</span>
|
||||
<span className="min-w-0 text-center bg-inherit py-2 px-1 text-sm border-t-2 border-b-2 group-hover:brightness-90" style={wantInfoStyle}>
|
||||
{mod.version}
|
||||
</span>
|
||||
|
||||
@@ -9,25 +9,10 @@ import { Mod } from "shared/models/mods/mod.interface";
|
||||
import { ModItem } from "./mod-item.component";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
|
||||
type Props = {
|
||||
modsMap: Map<string, Mod[]>;
|
||||
installed?: Map<string, Mod[]>;
|
||||
showInstalled?: boolean;
|
||||
modsSelected?: Mod[];
|
||||
onModChange?: (selected: boolean, mod: Mod) => void;
|
||||
moreInfoMod?: Mod;
|
||||
onWantInfos?: (mod: Mod) => void };
|
||||
|
||||
export function ModsGrid({
|
||||
modsMap,
|
||||
installed,
|
||||
modsSelected,
|
||||
showInstalled,
|
||||
onModChange,
|
||||
moreInfoMod,
|
||||
onWantInfos
|
||||
}: Props) {
|
||||
type Props = { modsMap: Map<string, Mod[]>; installed: Map<string, Mod[]>; modsSelected: Mod[]; onModChange: (selected: boolean, mod: Mod) => void; moreInfoMod?: Mod; onWantInfos: (mod: Mod) => void };
|
||||
|
||||
export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreInfoMod, onWantInfos }: Props) {
|
||||
|
||||
const pageState = useService(PageStateService);
|
||||
const modsManager = useService(BsModsManagerService);
|
||||
|
||||
@@ -47,7 +32,7 @@ export function ModsGrid({
|
||||
};
|
||||
|
||||
const isDependency = (mod: Mod): boolean => {
|
||||
return !!modsSelected?.some(m => {
|
||||
return modsSelected.some(m => {
|
||||
const deps = m.dependencies.map(dep =>
|
||||
Array.from(modsMap.values())
|
||||
.flat()
|
||||
@@ -60,7 +45,7 @@ export function ModsGrid({
|
||||
});
|
||||
};
|
||||
|
||||
const isSelected = (mod: Mod): boolean => !!modsSelected?.some(m => m.name === mod.name);
|
||||
const isSelected = (mod: Mod): boolean => modsSelected.some(m => m.name === mod.name);
|
||||
|
||||
const handleInput = (val: string) => setFilter(val.toLowerCase());
|
||||
|
||||
@@ -81,7 +66,7 @@ export function ModsGrid({
|
||||
<BsmButton className="rounded-full h-6 w-6 p-[2px]" withBar={false} icon="search" onClick={handleToogleFilter} />
|
||||
</span>
|
||||
<span className="z-10 sticky top-0 flex items-center bg-inherit border-main-color-1 border-b-2 h-8 px-1 whitespace-nowrap">{filterEnabled ? <motion.input autoFocus className="bg-main-color-1 rounded-md h-6 px-2" initial={{ width: 0 }} animate={{ width: "250px" }} transition={{ ease: "easeInOut", duration: 0.15 }} onChange={e => handleInput(e.target.value)} /> : <span className="w-full text-center">{t("pages.version-viewer.mods.mods-grid.header-bar.name")}</span>}</span>
|
||||
{showInstalled !== false && <span className="z-10 sticky flex items-center justify-center top-0 bg-inherit border-b-2 border-main-color-1 h-8 px-2 whitespace-nowrap">{t("pages.version-viewer.mods.mods-grid.header-bar.installed")}</span>}
|
||||
<span className="z-10 sticky flex items-center justify-center top-0 bg-inherit border-b-2 border-main-color-1 h-8 px-2 whitespace-nowrap">{t("pages.version-viewer.mods.mods-grid.header-bar.installed")}</span>
|
||||
<span className="z-10 sticky flex items-center justify-center top-0 bg-inherit border-b-2 border-main-color-1 h-8 px-2 whitespace-nowrap">{t("pages.version-viewer.mods.mods-grid.header-bar.latest")}</span>
|
||||
<span className="z-10 sticky flex items-center justify-center top-0 bg-inherit border-b-2 border-main-color-1 h-8 whitespace-nowrap">{t("pages.version-viewer.mods.mods-grid.header-bar.description")}</span>
|
||||
<span className="z-10 sticky top-0 bg-inherit border-b-2 border-main-color-1 h-8 flex justify-start items-center py-1 pl-[3px] min-w-[50px]">
|
||||
@@ -101,12 +86,3 @@ export function ModsGrid({
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function modsArrayToCategoryMap(mods: Mod[]): Map<string, Mod[]> {
|
||||
if (!mods) {
|
||||
return new Map<string, Mod[]>();
|
||||
}
|
||||
const map = new Map<string, Mod[]>();
|
||||
mods.forEach(mod => map.set(mod.category, [...(map.get(mod.category) ?? []), mod]));
|
||||
return map;
|
||||
};
|
||||
|
||||
@@ -2,15 +2,15 @@ import { ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { BsModsManagerService } from "renderer/services/bs-mods-manager.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { Mod } from "shared/models/mods/mod.interface";
|
||||
import { ModsGrid, modsArrayToCategoryMap } from "./mods-grid.component";
|
||||
import { ModsGrid } from "./mods-grid.component";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { DefaultConfigKey } from "renderer/config/default-configuration.config";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import BeatWaitingImg from "../../../../../../assets/images/apngs/beat-waiting.png";
|
||||
import BeatConflictImg from "../../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { skip, filter, map } from "rxjs/operators";
|
||||
import { Observable, Subscription, combineLatest, lastValueFrom, noop } from "rxjs";
|
||||
import { skip, filter } from "rxjs/operators";
|
||||
import { Subscription, lastValueFrom, noop } from "rxjs";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { LinkOpenerService } from "renderer/services/link-opener.service";
|
||||
import { useInView } from "framer-motion";
|
||||
@@ -41,6 +41,15 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
const downloadRef = useRef(null);
|
||||
const [downloadWith, setDownloadWidth] = useState(0);
|
||||
|
||||
const modsToCategoryMap = (mods: Mod[]): Map<string, Mod[]> => {
|
||||
if (!mods) {
|
||||
return new Map<string, Mod[]>();
|
||||
}
|
||||
const map = new Map<string, Mod[]>();
|
||||
mods.forEach(mod => map.set(mod.category, [...(map.get(mod.category) ?? []), mod]));
|
||||
return map;
|
||||
};
|
||||
|
||||
const handleModChange = (selected: boolean, mod: Mod) => {
|
||||
if (selected) {
|
||||
return setModsSelected([...modsSelected, mod]);
|
||||
@@ -87,24 +96,27 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
});
|
||||
|
||||
modsManager.installMods(modsToInstall, version).then(() => {
|
||||
lastValueFrom(loadMods());
|
||||
loadMods();
|
||||
});
|
||||
};
|
||||
|
||||
const loadMods = (): Observable<void> => {
|
||||
const loadMods = (): Promise<void> => {
|
||||
if (os.isOffline) {
|
||||
return new Observable<void>(subscriber => subscriber.complete());
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return combineLatest([
|
||||
modsManager.getAvailableMods(version),
|
||||
modsManager.getInstalledMods(version)
|
||||
]).pipe(map(([available, installed]) => {
|
||||
const promise = async () => {
|
||||
const available = await lastValueFrom(modsManager.getAvailableMods(version));
|
||||
const installed = await lastValueFrom(modsManager.getInstalledMods(version));
|
||||
return [available, installed];
|
||||
}
|
||||
|
||||
return promise().then(([available, installed]) => {
|
||||
const defaultMods = configService.get<string[]>("default_mods" as DefaultConfigKey);
|
||||
setModsAvailable(() => modsArrayToCategoryMap(available));
|
||||
setModsAvailable(() => modsToCategoryMap(available));
|
||||
setModsSelected(() => available.filter(m => m.required || defaultMods.some(d => m.name.toLowerCase() === d.toLowerCase()) || installed.some(i => m.name === i.name)));
|
||||
setModsInstalled(() => modsArrayToCategoryMap(installed));
|
||||
}));
|
||||
setModsInstalled(() => modsToCategoryMap(installed));
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -132,14 +144,14 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
return onDisclamerDecline?.();
|
||||
}
|
||||
|
||||
subs.push(loadMods().subscribe());
|
||||
loadMods();
|
||||
|
||||
subs.push(
|
||||
modsManager.isUninstalling$.pipe(
|
||||
skip(1),
|
||||
filter(uninstalling => !uninstalling)
|
||||
).subscribe(() => {
|
||||
subs.push(loadMods().subscribe());
|
||||
loadMods();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@ import { NotificationType } from "../../shared/models/notification/notification.
|
||||
import { OsDiagnosticService } from "./os-diagnostic.service";
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
import { NotificationService } from "./notification.service";
|
||||
import { BeatModsService } from "./thrird-partys/beat-mods.service";
|
||||
|
||||
export class BsModsManagerService {
|
||||
private static instance: BsModsManagerService;
|
||||
@@ -23,7 +22,6 @@ export class BsModsManagerService {
|
||||
private readonly modals: ModalService;
|
||||
private readonly notifications: NotificationService;
|
||||
private readonly os: OsDiagnosticService;
|
||||
private readonly beatMods: BeatModsService;
|
||||
|
||||
public readonly isInstalling$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
public readonly isUninstalling$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
@@ -41,11 +39,10 @@ export class BsModsManagerService {
|
||||
this.modals = ModalService.getInstance();
|
||||
this.notifications = NotificationService.getInstance();
|
||||
this.os = OsDiagnosticService.getInstance();
|
||||
this.beatMods = BeatModsService.getInstance();
|
||||
}
|
||||
|
||||
public getAvailableMods(version: BSVersion): Observable<Mod[]> {
|
||||
return this.beatMods.getVersionMods(version.BSVersion);
|
||||
return this.ipcService.sendV2("get-available-mods", version);
|
||||
}
|
||||
|
||||
public getInstalledMods(version: BSVersion): Observable<Mod[]> {
|
||||
@@ -139,8 +136,4 @@ export class BsModsManagerService {
|
||||
this.progressBar.hide();
|
||||
})
|
||||
}
|
||||
|
||||
public getVersionAliases(): Observable<Record<string, string[]>> {
|
||||
return this.ipcService.sendV2("get-version-aliases");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Observable } from "rxjs";
|
||||
import { IpcService } from "../ipc.service";
|
||||
import { BSVersionString } from "shared/bs-version.interface";
|
||||
import { Mod } from "shared/models/mods";
|
||||
|
||||
export class BeatModsService {
|
||||
|
||||
private static instance: BeatModsService;
|
||||
|
||||
public static getInstance(): BeatModsService {
|
||||
if (!BeatModsService.instance) {
|
||||
BeatModsService.instance = new BeatModsService();
|
||||
}
|
||||
return BeatModsService.instance;
|
||||
}
|
||||
|
||||
private readonly ipcService: IpcService;
|
||||
|
||||
private constructor() {
|
||||
this.ipcService = IpcService.getInstance();
|
||||
}
|
||||
|
||||
public getVersionAliases(): Observable<Record<BSVersionString, BSVersionString[]>> {
|
||||
return this.ipcService.sendV2("get-version-aliases");
|
||||
}
|
||||
|
||||
public getVersionMods(version: BSVersionString): Observable<Mod[]> {
|
||||
return this.ipcService.sendV2("get-version-mods", version);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { BsStore } from "./models/bs-store.enum";
|
||||
|
||||
export type BSVersionString = `${number}.${number}.${string}`; // 1.1.0p1
|
||||
|
||||
export interface PartialBSVersion {
|
||||
BSVersion: BSVersionString,
|
||||
BSVersion: string,
|
||||
name?: string
|
||||
ino?: number,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
import { Observable } from "rxjs";
|
||||
import { BSVersion, BSVersionString } from "shared/bs-version.interface";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BSLaunchEventData, LaunchOption } from "shared/models/bs-launch";
|
||||
import { BsvMapDetail, SongDetails } from "shared/models/maps";
|
||||
import { BsmLocalMap, BsmLocalMapsProgress, DeleteMapsProgress } from "shared/models/maps/bsm-local-map.interface";
|
||||
@@ -71,9 +71,8 @@ export interface IpcChannelMapping {
|
||||
"delete-models": { request: BsmLocalModel[], response: Progression<BsmLocalModel[]> };
|
||||
|
||||
/* ** bs-mods-ipcs ** */
|
||||
"get-version-mods": { request: BSVersionString, response: Mod[] };
|
||||
"get-available-mods": { request: BSVersion, response: Mod[] };
|
||||
"get-installed-mods": { request: BSVersion, response: Mod[] };
|
||||
"get-version-aliases": { request: void, response: Record<BSVersionString, BSVersionString[]> }
|
||||
"install-mods": { request: { mods: Mod[]; version: BSVersion }, response: InstallModsResult };
|
||||
"uninstall-mods": { request: { mods: Mod[]; version: BSVersion }, response: UninstallModsResult };
|
||||
"uninstall-all-mods": { request: BSVersion, response: UninstallModsResult };
|
||||
|
||||