Merge branch 'master' into bugfix/527

This commit is contained in:
MathieuG-P
2024-11-16 20:17:28 +01:00
committed by GitHub
18 changed files with 243 additions and 45 deletions
+4 -2
View File
@@ -13,7 +13,9 @@ const { list } = (execOnOs({ win32: () => require("regedit-rs") }, true) ?? {})
export class SteamService {
private static readonly PROCESS_NAME = "steam";
private static readonly PROCESS_NAME: string = process.platform === "linux"
? "steam-runtime-launcher-service"
: "steam";
private static instance: SteamService;
@@ -116,7 +118,7 @@ export class SteamService {
public async openSteam(): Promise<void> {
await shell.openPath("steam://open/games");
await shell.openExternal("steam://open/games");
return new Promise((resolve, reject) => {
// Every 3 seconds check if steam is running
@@ -0,0 +1,42 @@
import { BsmButton } from "renderer/components/shared/bsm-button.component";
import { BsmImage } from "renderer/components/shared/bsm-image.component";
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
import { BSVersion } from "shared/bs-version.interface";
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
import { useState } from "react";
import { ConfigurationService } from "renderer/services/configuration.service";
import { useService } from "renderer/hooks/use-service.hook";
export const BSVersionOutdatedModal: ModalComponent<void, { outdated: BSVersion, recommended: BSVersion }> = ({ resolver, options: {data : { outdated, recommended }}}) => {
const t = useTranslationV2();
const config = useService(ConfigurationService);
const [remember, setRemember] = useState(false);
const handleContinue = () => {
if (remember) {
config.set("not-show-bs-version-outdated", true);
}
resolver({ exitCode: ModalExitCode.COMPLETED });
};
return (
<form className="text-gray-800 dark:text-gray-200 max-w-sm">
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t.text("misc.warning")}</h1>
<BsmImage className="mx-auto h-24" image={BeatConflict} />
<p>{t.element("modals.bs-version-outdated.body", { outdatedVersion: <b>{outdated.BSVersion}</b>, recommendedVersion: <b>{recommended.BSVersion}</b> })}</p>
<div className="flex items-center relative py-2 gap-1 mt-1">
<BsmCheckbox className="h-5 relative z-[1]" checked={remember} onChange={val => setRemember(() => val)} />
<span className="italic">{t.text("modals.misc.remember-my-choice")}</span>
</div>
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4 h-8">
<BsmButton typeColor="cancel" className="rounded-md flex justify-center items-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
<BsmButton typeColor="primary" className="rounded-md flex justify-center items-center transition-all" onClick={handleContinue} withBar={false} text="misc.continue" />
</div>
</form>
);
};
@@ -61,7 +61,7 @@ export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreIn
<span className="z-10 sticky flex items-center justify-end top-0 bg-inherit border-b-2 border-main-color-1 rounded-tl-md">
<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>
<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-light-main-color-1 dark: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>
<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>
@@ -1,7 +1,12 @@
import { I18nService } from "renderer/services/i18n.service";
import { useService } from "./use-service.hook";
import { useObservable } from "./use-observable.hook";
import { useConstant } from "./use-constant.hook";
import { createElement, Fragment } from "react";
/**
* @deprecated Use useTranslationV2 instead
*/
export function useTranslation(): (translationKey: string, args?: Record<string, string>) => string {
const i18nService = useService(I18nService);
@@ -15,3 +20,35 @@ export function useTranslation(): (translationKey: string, args?: Record<string,
return i18nService.translate(key, args);
};
}
type TranslateFunctions = {
text: (translationKey: string, args?: Record<string, string>) => string;
element: (translationKey: string, args?: Record<string, JSX.Element>) => JSX.Element;
}
export function useTranslationV2(): TranslateFunctions {
const i18nService = useService(I18nService);
useObservable(() => i18nService.currentLanguage$);
const text = useConstant(() => {
return (key: string, args?: Record<string, string>) => {
if (!key) {
return key;
}
return i18nService.translate(key, args);
};
});
const element = useConstant(() => {
return (key: string, args?: Record<string, JSX.Element>) => {
if (!key) {
return createElement(Fragment, null, key);
}
return i18nService.translateElement(key, args);
};
});
return { text, element };
}
@@ -12,6 +12,11 @@ import { BSVersion } from "shared/bs-version.interface";
import { useObservable } from "renderer/hooks/use-observable.hook";
import { BsDownloaderService } from "renderer/services/bs-version-download/bs-downloader.service";
import { logRenderError } from "renderer";
import { ModalExitCode, ModalService } from "renderer/services/modale.service";
import { BSVersionOutdatedModal } from "renderer/components/modal/modal-types/bs-version-outdated-modal.component";
import { ConfigurationService } from "renderer/services/configuration.service";
import { coerce, lt, valid } from "semver";
import { tryit } from "shared/helpers/error.helpers";
export const AvailableVersionsContext = createContext<{ selectedVersion: BSVersion; setSelectedVersion: (version: BSVersion) => void }>(null);
@@ -19,6 +24,8 @@ export function AvailableVersionsList() {
const bsDownloader = useService(BsDownloaderService);
const versionManager = useService(BSVersionManagerService);
const modals = useService(ModalService);
const config = useService(ConfigurationService);
const [selectedVersion, setSelectedVersion] = useState<BSVersion>(null);
const contextValue = useMemo(() => ({ selectedVersion, setSelectedVersion }), [selectedVersion]);
@@ -26,9 +33,24 @@ export function AvailableVersionsList() {
const downloading = useObservable(() => bsDownloader.downloadingVersion$.pipe(map(v => !!v)));
const t = useTranslation();
const startDownload = async () => {
const startDownload = async (version: BSVersion) => {
const showOutdated = !config.get("not-show-bs-version-outdated");
const recommendedVersion = showOutdated ? versionManager.availableVersions$.value.find(v => v.recommended) : undefined;
return bsDownloader.downloadVersion(selectedVersion)
const isVersionOutdated = recommendedVersion?.BSVersion ? (
tryit(() => lt(valid(coerce(version.BSVersion)), valid(coerce(recommendedVersion.BSVersion)))).result ?? false
) : false;
if (showOutdated && isVersionOutdated) {
const res = await modals.openModal(BSVersionOutdatedModal, {data: { outdated: version, recommended: recommendedVersion }});
if (res.exitCode !== ModalExitCode.COMPLETED) {
return;
}
}
return bsDownloader.downloadVersion(version)
.catch(logRenderError)
.finally(() => setSelectedVersion(null));
};
@@ -56,7 +78,7 @@ export function AvailableVersionsList() {
<AnimatePresence>
{selectedVersion && !downloading && (
<motion.div initial={{ y: "150%" }} animate={{ y: "0%" }} exit={{ y: "150%" }} className="absolute bottom-5" onClick={startDownload}>
<motion.div initial={{ y: "150%" }} animate={{ y: "0%" }} exit={{ y: "150%" }} className="absolute bottom-5" onClick={() => startDownload(selectedVersion)}>
<BsmButton text="misc.download" className="relative text-gray-800 dark:text-gray-100 rounded-md text-3xl font-bold italic tracking-wide px-3 pb-2 pt-1 shadow-md shadow-black" />
</motion.div>
)}
+22 -12
View File
@@ -405,7 +405,7 @@ export function SettingsPage() {
<span className="font-extrabold">{t("notifications.settings.additional-content.deep-link.select-all")}</span>
</div>
<div className="flex h-full gap-2">
<Tippy content="BeatSaver" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<Tippy content="BeatSaver" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 cursor-pointer"
image={beatSaverIcon}
@@ -415,7 +415,7 @@ export function SettingsPage() {
}}
/>
</Tippy>
<Tippy content="BeastSaber" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<Tippy content="BeastSaber" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 rounded-md cursor-pointer"
image={beastSaberIcon}
@@ -425,7 +425,7 @@ export function SettingsPage() {
}}
/>
</Tippy>
<Tippy content="ScoreSaber" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<Tippy content="ScoreSaber" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 cursor-pointer"
image={scoreSaberIcon}
@@ -435,7 +435,7 @@ export function SettingsPage() {
}}
/>
</Tippy>
<Tippy content="BeatLeader" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<Tippy content="BeatLeader" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 cursor-pointer"
image={beatleaderIcon}
@@ -445,7 +445,7 @@ export function SettingsPage() {
}}
/>
</Tippy>
<Tippy content="ModelSaber" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<Tippy content="ModelSaber" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 cursor-pointer"
image={modelSaberIcon}
@@ -464,7 +464,7 @@ export function SettingsPage() {
<span className="font-extrabold">{t("misc.maps")}</span>
</div>
<div className="flex h-full gap-2">
<Tippy content="BeatSaver" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<Tippy content="BeatSaver" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 cursor-pointer"
image={beatSaverIcon}
@@ -474,7 +474,7 @@ export function SettingsPage() {
}}
/>
</Tippy>
<Tippy content="BeastSaber" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<Tippy content="BeastSaber" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 rounded-md cursor-pointer"
image={beastSaberIcon}
@@ -484,7 +484,7 @@ export function SettingsPage() {
}}
/>
</Tippy>
<Tippy content="ScoreSaber" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<Tippy content="ScoreSaber" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 cursor-pointer"
image={scoreSaberIcon}
@@ -494,7 +494,7 @@ export function SettingsPage() {
}}
/>
</Tippy>
<Tippy content="BeatLeader" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<Tippy content="BeatLeader" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 cursor-pointer"
image={beatleaderIcon}
@@ -511,8 +511,8 @@ export function SettingsPage() {
<BsmCheckbox className="relative z-[1] h-5 w-5" onChange={() => tooglePlaylistsDeepLinks()} checked={playlistsDeepLinkEnabled} />
<span className="font-extrabold">{t("misc.playlists")}</span>
</div>
<div className="flex h-full">
<Tippy content="BeatSaver" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<div className="flex h-full gap-2">
<Tippy content="BeatSaver" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 cursor-pointer"
image={beatSaverIcon}
@@ -522,6 +522,16 @@ export function SettingsPage() {
}}
/>
</Tippy>
<Tippy content="BeatLeader" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 cursor-pointer"
image={beatleaderIcon}
onClick={e => {
e.stopPropagation();
linkOpener.open("https://www.beatleader.xyz/");
}}
/>
</Tippy>
</div>
</li>
<li className="bg-light-main-color-1 dark:bg-main-color-1 rounded-md group-one flex justify-between items-center basis-0 py-2 px-3 cursor-pointer" onClick={() => toogleModelsDeepLinks()}>
@@ -530,7 +540,7 @@ export function SettingsPage() {
<span className="font-extrabold">{t("misc.models")}</span>
</div>
<div className="flex h-full">
<Tippy content="ModelSaber" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
<Tippy content="ModelSaber" placement="top" className="font-bold bg-main-color-3" theme="default" arrow={false} duration={[200, 0]}>
<BsmImage
className="h-8 cursor-pointer"
image={modelSaberIcon}
+27
View File
@@ -4,6 +4,7 @@ import { Observable } from "rxjs";
import { ConfigurationService } from "./configuration.service";
import { getProperty } from "dot-prop";
import { i18n } from "dateformat";
import { createElement, Fragment } from "react";
export class I18nService {
private static instance: I18nService;
@@ -90,4 +91,30 @@ export class I18nService {
return translated;
}
public translateElement(translationKey: string, args?: Record<string, JSX.Element>): JSX.Element {
let translated = this.cache.get(translationKey);
if (!translated) {
translated = getProperty(this.dictionary, translationKey) ?? translationKey;
this.cache.set(translationKey, translated);
}
if (!args) {
return createElement(Fragment, null, translated);
}
// Sort keys by length in descending order to avoid partial matches within keys
const sortedKeys = Object.keys(args).sort((a, b) => b.length - a.length);
const pattern = sortedKeys.map(key => `\\{${key}\\}`).join('|');
const regex = new RegExp(`(${pattern})`, 'g');
const segments = translated.split(regex);
const children = segments.map((segment) => {
const key = segment.slice(1, -1); // Remove surrounding {}
return args[key] || segment;
});
return createElement(Fragment, null, ...children);
}
}