mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature-274] Add setting to select the default platfrom to download BS
This commit is contained in:
+12
-3
@@ -6,11 +6,14 @@ import { BsStore } from "shared/models/bs-store.enum";
|
||||
import { useState } from "react";
|
||||
import tailwindConfig from "../../../../../../tailwind.config";
|
||||
import Color from "color";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export const ChooseStore: ModalComponent<BsStore> = ({ resolver }) => {
|
||||
|
||||
const t = useTranslation();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [oculusHover, setOculusHover] = useState(false);
|
||||
const [steamHover, setSteamHover] = useState(false);
|
||||
|
||||
@@ -18,6 +21,11 @@ export const ChooseStore: ModalComponent<BsStore> = ({ resolver }) => {
|
||||
resolver({ exitCode: ModalExitCode.COMPLETED, data: store });
|
||||
}
|
||||
|
||||
const goToSettings = () => {
|
||||
resolver({ exitCode: ModalExitCode.CANCELED });
|
||||
navigate("/settings#choose-default-store");
|
||||
}
|
||||
|
||||
const isDarkMode = document.documentElement.classList.contains("dark");
|
||||
|
||||
const bg = (() => {
|
||||
@@ -37,10 +45,10 @@ export const ChooseStore: ModalComponent<BsStore> = ({ resolver }) => {
|
||||
// TODO : Translate
|
||||
|
||||
return (
|
||||
<form className="flex flex-col gap-3">
|
||||
<form className="flex flex-col gap-3 max-w-sm">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t("which platform ?")}</h1>
|
||||
<p className="max-w-sm text-gray-800 dark:text-gray-200 text-center">Select the platfrom where you own Beat Saber or from which you want to download it.</p>
|
||||
<div className="flex flex-row w-full flex-grow gap-4">
|
||||
<p className="w-auto text-gray-800 dark:text-gray-200 text-center">Select the platfrom where you own Beat Saber or from which you want to download it.</p>
|
||||
<div className="flex flex-row w-full flex-grow gap-3">
|
||||
<div className="flex flex-col flex-grow basis-0 gap-2 text-center px-5 pt-3 pb-1 rounded-md border-main-color-3 border-2 cursor-pointer" onMouseEnter={() => setOculusHover(true)} onMouseLeave={() => setOculusHover(false)} onClick={() => chooseStore(BsStore.OCULUS)} style={{backgroundColor: oculusHover ? bg.dim : bg.bright}}>
|
||||
<OculusIcon className="flex-grow aspect-square text-black bg-white rounded-full p-5"/>
|
||||
<h2 className="font-bold">Oculus Store (PC)</h2>
|
||||
@@ -50,6 +58,7 @@ export const ChooseStore: ModalComponent<BsStore> = ({ resolver }) => {
|
||||
<h2 className="font-bold">Steam</h2>
|
||||
</div>
|
||||
</div>
|
||||
<p onClick={goToSettings} className="text-sm italic underline cursor-pointer">Définir la platforme par défaut dans les paramètres</p>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,24 +1,37 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { BsmIcon, BsmIconType } from "../svgs/bsm-icon.component";
|
||||
import { CSSProperties } from "react"
|
||||
|
||||
export function SettingRadioArray({ items, selectedItem = items[0].id, onItemSelected }: { items: RadioItem[]; selectedItem?: number; onItemSelected?: (id: number) => void }) {
|
||||
type Props<T> = {
|
||||
id?: HTMLDivElement["id"];
|
||||
items: RadioItem<T>[];
|
||||
selectedItemId?: number;
|
||||
selectedItemValue?: T;
|
||||
direction?: CSSProperties["flexDirection"],
|
||||
onItemSelected?: (item: RadioItem<T>) => void
|
||||
};
|
||||
|
||||
export function SettingRadioArray<T>({ id, items, selectedItemId, selectedItemValue, onItemSelected, direction = "column" }: Props<T>) {
|
||||
const t = useTranslation();
|
||||
|
||||
const isSelected = (item: RadioItem<T>) => {
|
||||
return item.id === selectedItemId || item.value === selectedItemValue;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div id={id} className="w-full flex gap-1.5" style={{flexDirection: direction}}>
|
||||
{items.map(i => (
|
||||
<div onClick={() => onItemSelected(i.id)} key={i.id} className={`py-3 my-[6px] w-full flex cursor-pointer justify-between items-center rounded-md px-2 transition-colors duration-200 ${i.id === selectedItem ? "bg-light-main-color-3 dark:bg-main-color-3" : "bg-light-main-color-1 dark:bg-main-color-1"}`}>
|
||||
<div onClick={() => onItemSelected(i)} key={i.id} className={`py-3 w-full flex cursor-pointer justify-between items-center rounded-md px-2 transition-colors duration-200 ${isSelected(i) ? "bg-light-main-color-3 dark:bg-main-color-3" : "bg-light-main-color-1 dark:bg-main-color-1"} ${i.className}`}>
|
||||
<div className="flex items-center">
|
||||
<div className="h-5 rounded-full aspect-square border-2 border-gray-800 dark:border-white p-[3px] mr-2">
|
||||
<motion.span initial={{ scale: 0 }} animate={{ scale: i.id === selectedItem ? 1 : 0 }} className="h-full w-full block bg-gray-800 dark:bg-white rounded-full" />
|
||||
<motion.span initial={{ scale: 0 }} animate={{ scale: isSelected(i) ? 1 : 0 }} className="h-full w-full block bg-gray-800 dark:bg-white rounded-full" />
|
||||
</div>
|
||||
<h2 className="font-extrabold">{t(i.text)}</h2>
|
||||
</div>
|
||||
{i.icon && (
|
||||
<div className="flex items-center">
|
||||
{i.textIcon && <span className="text-sm">{t(i.textIcon)}</span>}
|
||||
<BsmIcon icon={i.icon} className="max-h-5 w-fit ml-2" />
|
||||
{i.icon}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -27,10 +40,11 @@ export function SettingRadioArray({ items, selectedItem = items[0].id, onItemSel
|
||||
);
|
||||
}
|
||||
|
||||
export interface RadioItem {
|
||||
export interface RadioItem<T> {
|
||||
id: number;
|
||||
text: string;
|
||||
icon?: BsmIconType;
|
||||
icon?: JSX.Element;
|
||||
textIcon?: string;
|
||||
value?: any;
|
||||
className?: string;
|
||||
value?: T;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { forwardRef, HTMLProps } from "react";
|
||||
|
||||
export const OculusIcon = forwardRef<SVGSVGElement, HTMLProps<SVGSVGElement>>((props, ref) => {
|
||||
export function OculusIcon(props: HTMLProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M24.182 18.599c-0.427 0.297-0.901 0.474-1.411 0.552-0.51 0.083-1.016 0.068-1.521 0.068h-10.5c-0.51 0-1.016 0.021-1.526-0.068-0.51-0.083-0.979-0.255-1.411-0.552-0.854-0.604-1.365-1.563-1.365-2.604 0-1.057 0.516-2.016 1.37-2.599 0.417-0.297 0.896-0.479 1.396-0.557 0.5-0.083 1-0.083 1.526-0.083h10.5c0.5 0 1.016-0.016 1.516 0.063s0.984 0.26 1.401 0.542c0.865 0.578 1.365 1.557 1.365 2.599 0 1.036-0.526 2-1.38 2.599zM28.411 8.526c-1.125-0.906-2.417-1.531-3.818-1.865-0.802-0.193-1.604-0.281-2.432-0.307-0.599-0.021-1.198-0.010-1.818-0.010h-8.661c-0.609 0-1.224-0.010-1.833 0.010-0.823 0.026-1.63 0.109-2.432 0.307-1.401 0.339-2.698 0.964-3.818 1.865-2.281 1.823-3.599 4.568-3.599 7.474 0 2.911 1.318 5.656 3.583 7.474 1.13 0.906 2.422 1.531 3.823 1.87 0.802 0.193 1.609 0.281 2.432 0.302 0.599 0.021 1.198 0.016 1.818 0.016h8.661c0.599 0 1.219 0.005 1.818-0.016 0.823-0.021 1.62-0.109 2.417-0.302 1.401-0.344 2.682-0.969 3.823-1.87 2.307-1.823 3.625-4.568 3.625-7.474s-1.318-5.656-3.589-7.474z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -28,14 +28,7 @@ export function AvailableVersionsList() {
|
||||
|
||||
const startDownload = async () => {
|
||||
|
||||
const store = await (async () => {
|
||||
if(!selectedVersion.OculusBinaryId) { return BsStore.STEAM; }
|
||||
return bsDownloader.getLastStoreDownloadedFrom() ?? await bsDownloader.chooseStoreToDownloadFrom().catch(() => null);
|
||||
})();
|
||||
|
||||
if(!store){ return; }
|
||||
|
||||
return bsDownloader.downloadVersion(selectedVersion, store)
|
||||
return bsDownloader.downloadVersion(selectedVersion)
|
||||
.catch(console.log)
|
||||
.finally(() => setSelectedVersion(null));
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import SettingColorChooser from "renderer/components/settings/setting-color-choo
|
||||
import { SettingContainer } from "renderer/components/settings/setting-container.component";
|
||||
import { RadioItem, SettingRadioArray } from "renderer/components/settings/setting-radio-array.component";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmIconType } from "renderer/components/svgs/bsm-icon.component";
|
||||
import { BsmIcon, BsmIconType } from "renderer/components/svgs/bsm-icon.component";
|
||||
import { DefaultConfigKey, ThemeConfig } from "renderer/config/default-configuration.config";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { SteamDownloaderService } from "renderer/services/bs-version-download/steam-downloader.service";
|
||||
@@ -36,6 +36,10 @@ import { lastValueFrom } from "rxjs";
|
||||
import { BsmException } from "shared/models/bsm-exception.model";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { OculusDownloaderService } from "renderer/services/bs-version-download/oculus-downloader.service";
|
||||
import { BsStore } from "shared/models/bs-store.enum";
|
||||
import { SteamIcon } from "renderer/components/svgs/icons/steam-icon.component";
|
||||
import { OculusIcon } from "renderer/components/svgs/icons/oculus-icon.component";
|
||||
import { BsDownloaderService } from "renderer/services/bs-version-download/bs-downloader.service";
|
||||
|
||||
export function SettingsPage() {
|
||||
|
||||
@@ -43,6 +47,7 @@ export function SettingsPage() {
|
||||
const themeService = useService(ThemeService);
|
||||
const ipcService = useService(IpcService);
|
||||
const modalService = useService(ModalService);
|
||||
const bsDownloader = useService(BsDownloaderService);
|
||||
const steamDownloader = useService(SteamDownloaderService);
|
||||
const oculusDownloader = useService(OculusDownloaderService);
|
||||
const progressBarService = useService(ProgressBarService);
|
||||
@@ -56,24 +61,25 @@ export function SettingsPage() {
|
||||
|
||||
const { firstColor, secondColor } = useThemeColor();
|
||||
|
||||
const themeItem: RadioItem[] = [
|
||||
const themeItem: RadioItem<ThemeConfig>[] = [
|
||||
{ id: 0, text: "pages.settings.appearance.themes.dark", value: "dark" as ThemeConfig },
|
||||
{ id: 1, text: "pages.settings.appearance.themes.light", value: "light" as ThemeConfig },
|
||||
{ id: 3, text: "pages.settings.appearance.themes.os", value: "os" as ThemeConfig },
|
||||
];
|
||||
|
||||
const languagesItems: RadioItem[] = i18nService
|
||||
const languagesItems: RadioItem<string>[] = i18nService
|
||||
.getSupportedLanguages()
|
||||
.map((l, index) => {
|
||||
return { id: index, text: `pages.settings.language.languages.${l}`, value: l, textIcon: `pages.settings.language.languages.translated.${l}`, icon: `${l}-flag` as BsmIconType };
|
||||
return { id: index, text: `pages.settings.language.languages.${l}`, value: l, textIcon: `pages.settings.language.languages.translated.${l}`, icon: <BsmIcon icon={`${l}-flag` as BsmIconType} className="max-h-5 w-fit ml-2"/> };
|
||||
})
|
||||
.sort((a, b) => a.text.localeCompare(b.text));
|
||||
|
||||
const nav = useNavigate();
|
||||
const t = useTranslation();
|
||||
|
||||
const [themeIdSelected, setThemeIdSelected] = useState(themeItem.find(e => e.value === themeService.getTheme()).id);
|
||||
const [languageSelected, setLanguageSelected] = useState(languagesItems.find(e => e.value === i18nService.currentLanguage).id);
|
||||
const themeSelected = useObservable(themeService.theme$, "os");
|
||||
const languageSelected = useObservable(i18nService.currentLanguage$, i18nService.getFallbackLanguage());
|
||||
const downloadStore = useObservable(bsDownloader.defaultStore$);
|
||||
const [installationFolder, setInstallationFolder] = useState(null);
|
||||
const [showSupporters, setShowSupporters] = useState(false);
|
||||
const [mapDeepLinksEnabled, setMapDeepLinksEnabled] = useState(false);
|
||||
@@ -118,15 +124,16 @@ export function SettingsPage() {
|
||||
const setFirstColorSetting = (hex: string) => configService.set("first-color", hex);
|
||||
const setSecondColorSetting = (hex: string) => configService.set("second-color", hex);
|
||||
|
||||
const handleChangeTheme = (id: number) => {
|
||||
setThemeIdSelected(id);
|
||||
themeService.setTheme(themeItem.find(e => e.id === id).value);
|
||||
const handleChangeBsStore = (item: RadioItem<BsStore|undefined>) => {
|
||||
bsDownloader.setDefaultStore(item.value);
|
||||
}
|
||||
|
||||
const handleChangeTheme = (item: RadioItem<ThemeConfig>) => {
|
||||
themeService.setTheme(item.value);
|
||||
};
|
||||
|
||||
const handleChangeLanguage = (id: number) => {
|
||||
const selectedLanguage = languagesItems.find(l => l.id === id).value;
|
||||
i18nService.setLanguage(selectedLanguage);
|
||||
setLanguageSelected(languagesItems.find(l => l.value === i18nService.currentLanguage).id);
|
||||
const handleChangeLanguage = (item: RadioItem<string>) => {
|
||||
i18nService.setLanguage(item.value);
|
||||
};
|
||||
|
||||
const setDefaultInstallationFolder = () => {
|
||||
@@ -224,7 +231,15 @@ export function SettingsPage() {
|
||||
|
||||
<SettingContainer title="pages.settings.steam-and-oculus.title" description="pages.settings.steam-and-oculus.description">
|
||||
<BsmButton onClick={clearDownloadersSession} className="w-fit px-3 py-[2px] text-white rounded-md" withBar={false} text="pages.settings.steam-and-oculus.logout" typeColor="error" disabled={!hasDownloaderSession}/>
|
||||
</SettingContainer>
|
||||
<SettingContainer id="choose-default-store" minorTitle="Platforme par défaut" description="Choisi la platfrome par défaut qui sera utilisé pour télécharger les versions de Beat Saber." className="mt-3">
|
||||
{/* TODO: Translate */}
|
||||
<SettingRadioArray items={[
|
||||
{ id: 1, text: "Steam", value: BsStore.STEAM, icon: <SteamIcon className="h-6 w-6 float-left"/> },
|
||||
{ id: 2, text: "Oculus", value: BsStore.OCULUS, icon: <OculusIcon className="h-6 w-6 float-left bg-white text-black rounded-full p-0.5"/>},
|
||||
{ id: 0, text: "Toujours demander", value: undefined },
|
||||
]} selectedItemValue={downloadStore} onItemSelected={handleChangeBsStore} direction="row"/>
|
||||
</SettingContainer>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.appearance.title" description="pages.settings.appearance.description">
|
||||
<div className="relative w-full h-8 bg-light-main-color-1 dark:bg-main-color-1 flex justify-center rounded-md py-1">
|
||||
@@ -235,7 +250,7 @@ export function SettingsPage() {
|
||||
</div>
|
||||
</div>
|
||||
<SettingContainer minorTitle="pages.settings.appearance.sub-title" className="mt-3">
|
||||
<SettingRadioArray items={themeItem} selectedItem={themeIdSelected} onItemSelected={handleChangeTheme} />
|
||||
<SettingRadioArray items={themeItem} selectedItemValue={themeSelected} onItemSelected={handleChangeTheme} />
|
||||
</SettingContainer>
|
||||
</SettingContainer>
|
||||
|
||||
@@ -398,7 +413,7 @@ export function SettingsPage() {
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.language.title" description="pages.settings.language.description">
|
||||
<SettingRadioArray items={languagesItems} selectedItem={languageSelected} onItemSelected={handleChangeLanguage} />
|
||||
<SettingRadioArray items={languagesItems} selectedItemValue={languageSelected} onItemSelected={handleChangeLanguage} />
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.patreon.title" description="pages.settings.patreon.description">
|
||||
|
||||
@@ -8,6 +8,7 @@ import { OculusDownloaderService } from "./oculus-downloader.service";
|
||||
import { DownloaderServiceInterface } from "./bs-store-downloader.interface";
|
||||
import { AbstractBsDownloaderService } from "./abstract-bs-downloader.service";
|
||||
import { BSVersionManagerService } from "../bs-version-manager.service";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
export class BsDownloaderService extends AbstractBsDownloaderService {
|
||||
|
||||
@@ -27,6 +28,8 @@ export class BsDownloaderService extends AbstractBsDownloaderService {
|
||||
private readonly oculusDownloader: OculusDownloaderService;
|
||||
private readonly versionManager: BSVersionManagerService;
|
||||
|
||||
private readonly SELECTED_STORE_TO_DOWNLOAD_KET = "selectedStoreToDownload";
|
||||
|
||||
private constructor(){
|
||||
super();
|
||||
this.config = ConfigurationService.getInstance();
|
||||
@@ -52,8 +55,11 @@ export class BsDownloaderService extends AbstractBsDownloaderService {
|
||||
this._isVerifying$.next(false);
|
||||
}
|
||||
|
||||
public getLastStoreDownloadedFrom(): BsStore | undefined {
|
||||
return this.config.get<BsStore>("lastStoreDownloadedFrom");
|
||||
public get defaultStore(): BsStore | undefined { return this.config.get<BsStore>(this.SELECTED_STORE_TO_DOWNLOAD_KET); }
|
||||
public get defaultStore$(): Observable<BsStore | undefined> { return this.config.watch(this.SELECTED_STORE_TO_DOWNLOAD_KET); }
|
||||
|
||||
public setDefaultStore(store: BsStore|undefined): void {
|
||||
this.config.set(this.SELECTED_STORE_TO_DOWNLOAD_KET, store);
|
||||
}
|
||||
|
||||
public async chooseStoreToDownloadFrom(): Promise<BsStore | undefined> {
|
||||
@@ -66,8 +72,15 @@ export class BsDownloaderService extends AbstractBsDownloaderService {
|
||||
return res.data;
|
||||
}
|
||||
|
||||
public downloadVersion(version: BSVersion, from: BsStore): Promise<BSVersion> {
|
||||
return this.getStoreDownloader(from).downloadBsVersion(version).finally(() => {
|
||||
public async downloadVersion(version: BSVersion, from?: BsStore): Promise<BSVersion> {
|
||||
|
||||
if(!from){
|
||||
from = this.defaultStore ?? await this.chooseStoreToDownloadFrom();
|
||||
}
|
||||
|
||||
return this.getStoreDownloader(from).downloadBsVersion(version).then(() => {
|
||||
return this.downloadingVersion;
|
||||
}).finally(() => {
|
||||
this.resetDownloadState();
|
||||
this.versionManager.askInstalledVersions();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
import { DefaultConfigKey, defaultConfiguration } from "renderer/config/default-configuration.config";
|
||||
import { tryit } from "shared/helpers/error.helpers";
|
||||
|
||||
export class ConfigurationService {
|
||||
private static instance: ConfigurationService;
|
||||
@@ -27,15 +28,22 @@ export class ConfigurationService {
|
||||
}
|
||||
|
||||
public get<Type>(key: string | DefaultConfigKey): Type {
|
||||
const t = JSON.parse(window.sessionStorage.getItem(key) || window.localStorage.getItem(key));
|
||||
if (!t) {
|
||||
const rawValue = (window.sessionStorage.getItem(key) ?? window.localStorage.getItem(key));
|
||||
const tryParse = tryit<Type>(() => JSON.parse(rawValue));
|
||||
if (!tryParse.result) {
|
||||
return defaultConfiguration[key as DefaultConfigKey];
|
||||
}
|
||||
return t;
|
||||
return (tryParse.result ?? rawValue) as Type;
|
||||
}
|
||||
|
||||
public set(key: string, value: unknown, persistant = true) {
|
||||
this.getPropperStorage(persistant).setItem(key, JSON.stringify(value));
|
||||
|
||||
if(value != null){
|
||||
this.getPropperStorage(persistant).setItem(key, JSON.stringify(value));
|
||||
} else {
|
||||
this.getPropperStorage(persistant).removeItem(key);
|
||||
}
|
||||
|
||||
this.emitChange(key);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user