Merge pull request #52 from Zagrios/master

merge master
This commit is contained in:
Gaëtan Gerard
2022-12-30 05:42:16 +01:00
committed by GitHub
20 changed files with 262 additions and 85 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

+22 -1
View File
@@ -8,7 +8,9 @@
"playlists": "Playlists",
"models": "Models",
"cancel": "Cancel",
"delete": "Delete"
"delete": "Delete",
"accept": "Accept",
"refuse": "Refuse"
},
"pages": {
"version-viewer": {
@@ -134,6 +136,9 @@
"supporters": "Supporters"
}
},
"discord": {
"description": "Join the BSManager community by joining our Discord!"
},
"contribution": {
"description": "Suggest new features or report a bug to help improve BSManager!",
"buttons": {
@@ -249,6 +254,14 @@
"error":{
"description":"An unknown error occurred."
}
},
"check-all-enabled":{
"title": "OneClick Disabled",
"description": "One or more OneClick installations are disabled. Go to settings to enable them.",
"actions":{
"settings": "Settings",
"not-remind": "Do not remind me"
}
}
}
}
@@ -481,6 +494,14 @@
"download-maps": {
"search-btn": "Search",
"loading-maps": "Loading maps..."
},
"mods-disclaimer":{
"title": "disclaimer",
"p-1": "By choosing to use mods, you understand that:",
"li-1": "You may experience problems that don't exist in the vanilla game. 99.9% of bugs, crashes, and lag are due to mods.",
"li-2": "Mods are subject to being broken by updates and that's normal - be patient and respectful when this happens, as modders are volunteers with real lives.",
"li-3": "Beat Games aren't purposefully trying to break mods. They wish to work on the codebase and sometimes this breaks mods, but they are not out to kill mods.",
"p-2": "Do not attack the game developers for issues related to mods, and vice versa - modders and game developers are two separate groups. Just don't be a jerk ok."
}
},
"maps": {
+22 -1
View File
@@ -8,7 +8,9 @@
"playlists": "Playlists",
"models": "Modelos",
"cancel": "Cancelar",
"delete": "Borrar"
"delete": "Borrar",
"accept": "Aceptar",
"refuse": "Rechazar"
},
"pages": {
"version-viewer": {
@@ -134,6 +136,9 @@
"supporters": "Apoyos"
}
},
"discord": {
"description": "¡Únete a la comunidad de BSManager uniéndote a nuestro Discord!"
},
"contribution": {
"description": "¡Sugiera nuevas características o reporte un error para ayudar a mejorar BSManager!",
"buttons": {
@@ -249,6 +254,14 @@
"error":{
"description":"Ocurrió un error desconocido."
}
},
"check-all-enabled":{
"title": "OneClick deshabilitado",
"description": "Una o más instalaciones de OneClick están deshabilitadas. Vaya a la configuración para habilitarlas.",
"actions":{
"settings": "Ajustes",
"not-remind": "No volver a recordarme"
}
}
}
}
@@ -481,6 +494,14 @@
"download-maps": {
"search-btn": "buscar",
"loading-maps": "Cargando mapas..."
},
"mods-disclaimer":{
"title": "descargo de responsabilidad",
"p-1": "Al elegir usar mods, entiendes y aceptas que:",
"li-1": "Es posible que encuentres problemas que no existen en el juego base. El 99,9% de los errores, bloqueos y ralentizaciones son debidos a los mods.",
"li-2": "Es probable que los mods dejen de funcionar debido a las actualizaciones y eso es normal. Sé paciente y respetuoso cuando suceda esto, ya que los modders son voluntarios y tienen una vida.",
"li-3": "Beat Games no está intencionalmente tratando de romper los mods. Quieren mejorar el juego base y a veces esto rompe los mods, pero no es su objetivo matar a los mods.",
"p-2": "No ataques a los desarrolladores por problemas relacionados con los mods y viceversa. Los modders y los desarrolladores son dos grupos distintos. No seas tonto, ¿de acuerdo?"
}
},
"maps": {
+22 -1
View File
@@ -8,7 +8,9 @@
"playlists": "Playlists",
"models": "Modèles",
"cancel": "Annuler",
"delete": "Supprimer"
"delete": "Supprimer",
"accept": "Accepter",
"refuse": "Refuser"
},
"pages": {
"version-viewer": {
@@ -134,6 +136,9 @@
"supporters": "Supporteurs"
}
},
"discord":{
"description": "Prends part à la communauté de BSManager en rejoignant notre Discord !"
},
"contribution": {
"description": "Propose de nouvelles fonctionnalités ou signale un bug pour contribuer à l'amélioration de BSManager !",
"buttons": {
@@ -249,6 +254,14 @@
"error":{
"description":"Une erreur inconnue s'est produite."
}
},
"check-all-enabled":{
"title": "OneClick désactivée(s)",
"description": "Une ou plusieurs installations OneClick sont désactivées. Rendez-vous dans les paramètres pour les activer.",
"actions":{
"settings": "Paramètres",
"not-remind": "Ne plus me rappeler"
}
}
}
}
@@ -481,6 +494,14 @@
"download-maps": {
"search-btn": "Rechercher",
"loading-maps": "Chargement des maps..."
},
"mods-disclaimer":{
"title": "avis de non-responsabilité",
"p-1": "En choisissant d'utiliser des mods, vous comprenez et acceptez que :",
"li-1": "Vous pouvez rencontrer des problèmes qui n'existent pas dans le jeu de base. 99.9% des bugs, plantages et ralentissements sont dus aux mods.",
"li-2": "Les mods sont susceptibles de ne plus fonctionner à cause des mises à jour et c'est normal - soyez patient et respectueux lorsque cela se produit, parce que les moddeurs sont bénévoles et ont une vie.",
"li-3": "Beat Games ne cherche pas volontairement à casser les mods. Ils souhaitent améliorer la base du jeu et parfois cela casse les mods, mais ce n'est pas dans leur objectif de tuer les mods.",
"p-2": "N'attaquez pas les développeurs pour des problèmes relatifs aux mods, et inversement - les moddeurs et les développeurs sont deux groupes distincts. Ne sois pas idiot, d'accord ?"
}
},
"maps": {
+5 -10
View File
@@ -10354,7 +10354,8 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true
},
"object-hash": {
"version": "3.0.0",
@@ -11280,6 +11281,7 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -11672,7 +11674,8 @@
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
"react-range": {
"version": "1.8.14",
@@ -11737,14 +11740,6 @@
}
}
},
"react-visibility-sensor": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/react-visibility-sensor/-/react-visibility-sensor-5.1.1.tgz",
"integrity": "sha512-cTUHqIK+zDYpeK19rzW6zF9YfT4486TIgizZW53wEZ+/GPBbK7cNS0EHyJVyHYacwFEvvHLEKfgJndbemWhB/w==",
"requires": {
"prop-types": "^15.7.2"
}
},
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+2 -2
View File
@@ -76,7 +76,8 @@
"target": [
"nsis"
],
"icon": "assets/favicon.ico"
"icon": "assets/favicon.ico",
"certificateSha1": "f3363a89b084f8f6b616babea3c91f9d79e15000"
},
"linux": {
"target": [
@@ -244,7 +245,6 @@
"react-dom": "^18.2.0",
"react-range": "^1.8.14",
"react-router-dom": "^6.3.0",
"react-visibility-sensor": "^5.1.1",
"regedit": "^5.1.1",
"rxjs": "^7.5.6",
"sanitize-filename": "^1.6.3",
+3 -1
View File
@@ -5,6 +5,7 @@ import { IpcRequest } from 'shared/models/ipc';
import { InstallationLocationService } from '../services/installation-location.service';
import { UtilsService } from '../services/utils.service';
import { BsmException } from 'shared/models/bsm-exception.model';
import { LocalMapsManagerService } from '../services/additional-content/local-maps-manager.service';
export interface InitDownloadInfoInterface {
@@ -25,7 +26,8 @@ export interface DownloadInfo {
}
ipcMain.on('bs-download.start', async (event, request: IpcRequest<DownloadInfo>) => {
BSInstallerService.getInstance().downloadBsVersion(request.args).then(res => {
BSInstallerService.getInstance().downloadBsVersion(request.args).then(async res => {
await LocalMapsManagerService.getInstance().linkVersionMaps(request.args.bsVersion, true).catch(e => {});
UtilsService.getInstance().ipcSend(request.responceChannel, {success: true, data: res});
}).catch(e => {
UtilsService.getInstance().ipcSend(request.responceChannel, {success: false, data: e});
@@ -23,7 +23,7 @@ export function AvailableVersionsSlider() {
return (
<div className="w-full h-fit max-h-full flex flex-col items-center grow min-h-0">
<TabNavBar className="mb-3" tabsText={availableYears} onTabChange={setSelectedYear}/>
<TabNavBar className="mb-3" 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} year={year}/>
@@ -82,7 +82,7 @@ export function MapsPlaylistsPanel({version}: Props) {
{index === 0 &&(
<div className="h-full flex absolute right-0 top-0 gap-1.5 items-center pr-2">
{isOnline && (
<motion.div whileHover={"hover"} whileTap={"tap"} className="relative h-[calc(100%-5px)] flex flex-row justify-center items-center shrink-0 rounded-full overflow-hidden pr-2" style={{color}} onClick={e => {e.stopPropagation(); onClickAdd(index)}}>
<motion.div whileHover="hover" whileTap="tap" className="relative h-[calc(100%-5px)] flex flex-row justify-center items-center shrink-0 rounded-full overflow-hidden pr-2" style={{color}} onClick={e => {e.stopPropagation(); onClickAdd(index)}}>
<span className="absolute top-0 left-0 h-full w-full brightness-50 opacity-75 dark:opacity-20 dark:filter-none" style={{backgroundColor: "currentcolor"}}/>
<motion.div className="h-full p-0.5" variants={variants}>
<BsmIcon className="block h-full aspect-square brightness-150" icon="add"/>
@@ -128,7 +128,7 @@ export function MapsPlaylistsPanel({version}: Props) {
<BsmDropdownButton className="h-full flex aspect-square relative rounded-full z-[1] bg-light-main-color-1 dark:bg-main-color-3" buttonClassName="rounded-full h-full w-full p-[6px]" icon="three-dots" withBar={false} items={dropDownItems} menuTranslationY="6px" align="center"/>
</nav>
<div className="w-full h-full flex flex-col bg-light-main-color-3 dark:bg-main-color-2 rounded-md shadow-black shadow-md overflow-hidden">
<TabNavBar className="!rounded-none shadow-sm" tabsText={["misc.maps", "misc.playlists"]} onTabChange={setTabIndex} renderTab={renderTab}/>
<TabNavBar className="!rounded-none shadow-sm" tabIndex={tabIndex} tabsText={["misc.maps", "misc.playlists"]} onTabChange={setTabIndex} renderTab={renderTab}/>
<div className="w-full grow min-h-0 flex flex-row items-center transition-transform duration-300" style={{transform: `translate(${-(tabIndex * 100)}%, 0)`}}>
<LocalMapsListPanel ref={mapsRef} className="w-full h-full shrink-0 flex flex-col" version={version} filter={mapFilter} search={mapSearch}/>
<div className="w-full h-full shrink-0 flex flex-col justify-center items-center content-center gap-2 overflow-hidden">
@@ -0,0 +1,28 @@
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 { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
import BeatConflict from '../../../../../assets/images/apngs/beat-conflict.png'
export const ModsDisclaimerModal: ModalComponent<void, void> = ({resolver}) => {
const t = useTranslation();
return (
<form className="max-w-[800px] text-gray-800 dark:text-gray-200">
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t("modals.mods-disclaimer.title")}</h1>
<BsmImage className="mx-auto h-20" image={BeatConflict} />
<p className="mb-1">{t("modals.mods-disclaimer.p-1")}</p>
<ul className="list-disc w-full pl-6">
<li>{t("modals.mods-disclaimer.li-1")}</li>
<li>{t("modals.mods-disclaimer.li-2")}</li>
<li>{t("modals.mods-disclaimer.li-3")}</li>
</ul>
<p className="mt-1">{t("modals.mods-disclaimer.p-2")}</p>
<div className="grid grid-flow-col grid-cols-2 gap-4 pt-3">
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => {resolver({exitCode: ModalExitCode.CANCELED})}} withBar={false} text="misc.refuse"/>
<BsmButton typeColor="primary" className="rounded-md text-center transition-all" onClick={() => {resolver({exitCode: ModalExitCode.COMPLETED})}} withBar={false} text="misc.accept"/>
</div>
</form>
)
}
@@ -37,7 +37,7 @@ export function NotificationItem({resolver, notification}: {resolver?: (value: N
}
return (
<motion.li layout drag="x" dragConstraints={{left: 0, right: 0}} dragElastic={{left: 0, right: .3}} onDragEnd={handleDragEnd} initial={{x: "110%"}} animate={{x: "0%"}} exit={{x:"110%"}} className="relative w-[270px] rounded-md overflow-hidden -left-[285px] mb-4 cursor-grab active:cursor-grabbing shadow-md shadow-black bg-gradient-to-br from-light-main-color-2 to-light-main-color-2 dark:from-main-color-2 dark:to-main-color-2 text-gray-800 dark:text-white">
<motion.li layout drag="x" dragConstraints={{left: 0, right: 0}} dragElastic={{left: 0, right: .3}} onDragEnd={handleDragEnd} initial={{x: "110%"}} animate={{x: "0%"}} exit={{x:"110%"}} className="relative w-[290px] rounded-md overflow-hidden -left-[300px] mb-4 cursor-grab active:cursor-grabbing shadow-md shadow-black bg-gradient-to-br from-light-main-color-2 to-light-main-color-2 dark:from-main-color-2 dark:to-main-color-2 text-gray-800 dark:text-white">
<div className="w-full flex flex-col">
<div className="flex items-center justify-start pl-1">
<BsmImage className="h-14 mr-1 pointer-events-none" image={renderImage}/>
@@ -1,12 +1,21 @@
import { ReactNode } from "react";
import { useTranslation } from "renderer/hooks/use-translation.hook";
export function SettingContainer({className, title, minorTitle, description, children}: {className?: string, title?: string, minorTitle?: string, description?: string, children?: ReactNode}) {
type Props = {
id?: string,
className?: string,
title?: string,
minorTitle?: string,
description?: string,
children?: ReactNode
}
export function SettingContainer({id, className, title, minorTitle, description, children}: Props) {
const t = useTranslation();
return (
<div className={className || "relative mb-5"}>
<div id={id} className={className || "relative mb-5"}>
{ title && <h1 className="font-bold text-2xl mb-1 tracking-wide">{t(title)}</h1> }
{ minorTitle && <h2 className="font-bold mb-1 tracking-wide text-gray-600 dark:text-gray-300">{t(minorTitle)}</h2> }
{ description && <p className="mb-3 text-sm text-gray-600 dark:text-gray-400">{t(description)}</p> }
@@ -1,21 +0,0 @@
import {motion, useCycle} from "framer-motion"
import { useState } from "react"
type Props = {className?: string, nbClickToShow?: number, children?: JSX.Element}
export function SpoilerClick({className, children, nbClickToShow = 5}: Props) {
const [remainingClicks, setRemainingClicks] = useState(nbClickToShow);
const [translateY, setTranslateY] = useState("100%");
const handleClicks = () => {
if(remainingClicks - 1 > 0){ setRemainingClicks(nb => nb - 1); }
setTranslateY(`${(((remainingClicks - 1) / nbClickToShow) * 100)}%`)
}
return (
<motion.div className={className} initial={{y: translateY}} animate={{y: translateY}} transition={{type: "spring", stiffness: 300, mass: 1 }} onClick={handleClicks}>
{children}
</motion.div>
)
}
@@ -1,8 +1,9 @@
import { DetailedHTMLProps, Fragment, useState } from "react";
import { DetailedHTMLProps, Fragment } from "react";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { useTranslation } from "renderer/hooks/use-translation.hook";
type Props = {
tabIndex: number
tabsText: string[],
onTabChange: (index: number) => void,
className?: string,
@@ -11,12 +12,12 @@ type Props = {
export function TabNavBar(props: Props) {
const [currentTabIndex, setCurrentTabIndex] = useState(0);
const currentIndex = props.tabIndex ?? 0;
const t = useTranslation();
const secondColor = useThemeColor("second-color");
const selectTab = (index: number) => {
setCurrentTabIndex(index);
props.onTabChange(index);
}
@@ -24,7 +25,7 @@ export function TabNavBar(props: Props) {
<nav className={`relative h-8 shrink-0 cursor-pointer rounded-md overflow-hidden shadow-md shadow-black bg-light-main-color-2 dark:bg-main-color-2 ${props.className}`}>
<div className="absolute w-full h-1 bottom-0" style={{color: secondColor}}>
<span className="absolute h-full w-full bg-current brightness-50" />
<span className="absolute h-full block bg-current transition-transform duration-300 shadow-center shadow-current" style={{transform: `translate(${currentTabIndex * 100}%, 0)`, width: `calc(100% / ${props.tabsText.length})`}}/>
<span className="absolute h-full block bg-current transition-transform duration-300 shadow-center shadow-current" style={{transform: `translate(${currentIndex * 100}%, 0)`, width: `calc(100% / ${props.tabsText.length})`}}/>
</div>
<ul className="grid" style={{gridTemplateColumns: `repeat(${props.tabsText.length}, minmax(0, 1fr))`}}>
{props.tabsText.map((text, index) => (
@@ -43,9 +43,10 @@ import { LinkIcon } from "./icons/link-icon.component";
import { UnlinkIcon } from "./icons/unlink-icon.component";
import { DownloadIcon } from "./icons/download-icon.component";
import { FilterIcon } from "./icons/filter-icon.component";
import DiscordIcon from "./icons/discord-icon.component";
export type BsmIconType = BsvMapCharacteristic | (
"settings"|"trash"|"favorite"|"folder"|"bsNote"|"check"|"three-dots"|"twitch"|"eye"|"play"|"checkCircleIcon"|
"settings"|"trash"|"favorite"|"folder"|"bsNote"|"check"|"three-dots"|"twitch"|"eye"|"play"|"checkCircleIcon"|"discord"|
"terminal"|"desktop"|"oculus"|"add"|"cross"|"task"|"github"|"close"|"thumbUpFill"|"timerFill"|"pause"|
"copy"|"steam"|"edit"|"export"|"patreon"|"search"|"bsMapDifficulty"|"link"|"unlink"|"download"|"filter"|
"fr-FR-flag"|"es-ES-flag"|"en-US-flag"|"en-EN-flag"
@@ -97,6 +98,7 @@ export const BsmIcon = memo(({className, icon, style}: {className?: string, icon
if(icon === "unlink"){ return <UnlinkIcon className={className} style={style}/> }
if(icon === "download"){ return <DownloadIcon className={className} style={style}/> }
if(icon === "filter"){ return <FilterIcon className={className} style={style}/> }
if(icon === "discord"){ return <DiscordIcon className={className} style={style}/> }
return <TrashIcon className={className} style={style}/>
}
@@ -0,0 +1,15 @@
import { CSSProperties } from "react";
export default function DiscordIcon(props: {className?: string, style?: CSSProperties}) {
return (
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36" fill="currentColor">
<g id="图层_2" data-name="图层 2">
<g id="Discord_Logos" data-name="Discord Logos">
<g id="Discord_Logo_-_Large_-_White" data-name="Discord Logo - Large - White">
<path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/>
</g>
</g>
</g>
</svg>
)
}
@@ -1,28 +1,32 @@
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { BsModsManagerService } from "renderer/services/bs-mods-manager.service";
import { BSVersion } from "shared/bs-version.interface";
import VisibilitySensor from 'react-visibility-sensor';
import { Mod } from "shared/models/mods/mod.interface";
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 { SpoilerClick } from "renderer/components/shared/UwU/spoiler-click.component";
import YuruYuriDance from "../../../../../../assets/images/gifs/yuruyuri-dance.gif"
import { useObservable } from "renderer/hooks/use-observable.hook";
import { skip, filter } from "rxjs/operators";
import { Subscription } from "rxjs";
import { useTranslation } 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";
import { ModsDisclaimerModal } from "renderer/components/modal/modal-types/mods-disclaimer-modal.component";
export function ModsSlide({version}: {version: BSVersion}) {
export function ModsSlide({version, onDisclamerDecline}: {version: BSVersion, onDisclamerDecline: () => void}) {
const ACCEPTED_DISCLAIMER_KEY = "accepted-mods-disclaimer";
const modsManager = BsModsManagerService.getInstance();
const configService = ConfigurationService.getInstance();
const linkOpener = LinkOpenerService.getInstance();
const modals = ModalService.getInsance();
const [isVisible, setIsVisible] = useState(false);
const ref = useRef(null);
const isVisible = useInView(ref, {amount: .5});
const [modsAvailable, setModsAvailable] = useState(null as Map<string, Mod[]>);
const [modsInstalled, setModsInstalled] = useState(null as Map<string, Mod[]>);
const [modsSelected, setModsSelected] = useState([] as Mod[]);
@@ -81,10 +85,27 @@ export function ModsSlide({version}: {version: BSVersion}) {
const subs: Subscription[] = [];
if(isVisible){
loadMods();
subs.push(modsManager.isUninstalling$.pipe(skip(1), filter(uninstalling => !uninstalling)).subscribe(() => {
const promise = new Promise<boolean>(async resolve => {
if(configService.get<boolean>(ACCEPTED_DISCLAIMER_KEY)){ return resolve(true); }
const res = await modals.openModal(ModsDisclaimerModal);
const haveAccepted = res.exitCode === ModalExitCode.COMPLETED;
if(haveAccepted){ configService.set(ACCEPTED_DISCLAIMER_KEY, true) }
resolve(haveAccepted);
});
promise.then(canLoad => {
if(!canLoad){ return onDisclamerDecline?.(); }
loadMods();
}))
subs.push(modsManager.isUninstalling$.pipe(skip(1), filter(uninstalling => !uninstalling)).subscribe(() => {
loadMods();
}));
});
}
return () => {
@@ -105,33 +126,28 @@ export function ModsSlide({version}: {version: BSVersion}) {
return (
<VisibilitySensor onChange={setIsVisible}>
<div className='shrink-0 w-full h-full px-8 pb-7 flex justify-center'>
<div className='relative flex flex-col grow-0 bg-light-main-color-2 dark:bg-main-color-2 h-full w-full rounded-md shadow-black shadow-center overflow-hidden'>
{modsAvailable ? (
<>
<div className="overflow-scroll w-full min-h-0 scrollbar-thin scrollbar-thumb-neutral-900 scrollbar-thumb-rounded-full">
<ModsGrid modsMap={modsAvailable} installed={modsInstalled} modsSelected={modsSelected} onModChange={handleModChange} moreInfoMod={moreInfoMod} onWantInfos={handleMoreInfo}/>
</div>
<div className="h-10 shrink-0 flex items-center justify-between px-3">
<BsmButton className="text-center rounded-md px-2 py-[2px]" text="pages.version-viewer.mods.buttons.more-infos" typeColor="cancel" withBar={false} disabled={!moreInfoMod} onClick={handleOpenMoreInfo} style={{width: downloadWith}}/>
<div ref={downloadRef}>
<BsmButton className="text-center rounded-md px-2 py-[2px]" text="pages.version-viewer.mods.buttons.install-or-update" withBar={false} disabled={installing} typeColor="primary" onClick={installMods}/>
</div>
</div>
</>
) : (
<div className="w-full h-full flex flex-col items-center justify-center relative">
<img className="w-32 h-32 spin-loading" src={BeatWaitingImg}></img>
<span className="text-xl mt-3 h-0 italic">{t("pages.version-viewer.mods.loading-mods")}</span>
<SpoilerClick className="absolute right-5 w-20 h-[120px] bottom-5 cursor-pointer">
<img className="relative w-full" src={YuruYuriDance}/>
</SpoilerClick>
<div ref={ref} className='shrink-0 w-full h-full px-8 pb-7 flex justify-center'>
<div className='relative flex flex-col grow-0 bg-light-main-color-2 dark:bg-main-color-2 h-full w-full rounded-md shadow-black shadow-center overflow-hidden'>
{modsAvailable ? (
<>
<div className="overflow-scroll w-full min-h-0 scrollbar-thin scrollbar-thumb-neutral-900 scrollbar-thumb-rounded-full">
<ModsGrid modsMap={modsAvailable} installed={modsInstalled} modsSelected={modsSelected} onModChange={handleModChange} moreInfoMod={moreInfoMod} onWantInfos={handleMoreInfo}/>
</div>
)}
</div>
<div className="h-10 shrink-0 flex items-center justify-between px-3">
<BsmButton className="text-center rounded-md px-2 py-[2px]" text="pages.version-viewer.mods.buttons.more-infos" typeColor="cancel" withBar={false} disabled={!moreInfoMod} onClick={handleOpenMoreInfo} style={{width: downloadWith}}/>
<div ref={downloadRef}>
<BsmButton className="text-center rounded-md px-2 py-[2px]" text="pages.version-viewer.mods.buttons.install-or-update" withBar={false} disabled={installing} typeColor="primary" onClick={installMods}/>
</div>
</div>
</>
) : (
<div className="w-full h-full flex flex-col items-center justify-center">
<img className="w-32 h-32 spin-loading" src={BeatWaitingImg} alt=" "/>
<span className="text-xl mt-3 h-0 italic">{t("pages.version-viewer.mods.loading-mods")}</span>
</div>
)}
</div>
</VisibilitySensor>
</div>
)
}
@@ -147,6 +147,7 @@ export function SettingsPage() {
const openGithub = () => linkOpener.open("https://github.com/Zagrios/bs-manager");
const openReportBug = () => linkOpener.open("https://github.com/Zagrios/bs-manager/issues/new?assignees=Zagrios&labels=bug&template=-bug--bug-report.md&title=%5BBUG%5D+%3A+");
const openRequestFeatures = () => linkOpener.open("https://github.com/Zagrios/bs-manager/issues/new?assignees=Zagrios&labels=enhancement&template=-feat---feature-request.md&title=%5BFEAT.%5D+%3A+");
const openDiscord = () => linkOpener.open("https://discord.gg/uSqbHVpKdV");
const openLogs = () => ipcService.sendLazy("open-logs");
@@ -228,7 +229,7 @@ export function SettingsPage() {
<SettingContainer title="pages.settings.additional-content.title" description="pages.settings.additional-content.description">
<SettingContainer minorTitle="pages.settings.additional-content.deep-links.sub-title">
<SettingContainer id="one-clicks" minorTitle="pages.settings.additional-content.deep-links.sub-title">
<ul className="w-full flex flex-col gap-1.5">
<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">
<div className="flex items-center gap-2">
@@ -283,6 +284,9 @@ export function SettingsPage() {
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 whitespace-nowrap mr-2 !text-white" iconClassName="mr-1" text="pages.settings.patreon.buttons.support" icon="patreon" color="#EC6350" withBar={false} onClick={openPatreonPage}/>
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 !text-white" withBar={false} text="pages.settings.patreon.buttons.supporters" color="#6c5ce7" onClick={toogleShowSupporters}/>
</div>
<SettingContainer className="mt-3" description="pages.settings.discord.description">
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 !text-white" withBar={false} text="Discord" icon="discord" iconClassName="p-0.5 mr-1" color="#5865f2" onClick={openDiscord}/>
</SettingContainer>
<SettingContainer className="pt-3" description="pages.settings.contribution.description">
<div className="flex items-center justify-between w-full h-8 bg-light-main-color-1 dark:bg-main-color-1 rounded-md pl-2 py-1">
<div className="flex">
@@ -57,6 +57,10 @@ export function VersionViewer() {
});
}
const handleModsDisclaimerDecline = () => {
setCurrentTabIndex(() => 0);
}
return (
<>
@@ -64,13 +68,13 @@ export function VersionViewer() {
<div className="relative flex items-center flex-col w-full h-full text-gray-200 backdrop-blur-lg">
<BsmImage className='relative object-cover h-28' image={BSLogo}/>
<h1 className='relative text-4xl font-bold italic -top-3'>{state.name ? `${state.BSVersion} - ${state.name}` : state.BSVersion}</h1>
<TabNavBar className='my-3' tabsText={["misc.launch", "misc.maps", "misc.mods"]} onTabChange={(i : number) => setCurrentTabIndex(i)}/>
<TabNavBar className='my-3' tabIndex={currentTabIndex} tabsText={["misc.launch", "misc.maps", "misc.mods"]} onTabChange={(i : number) => setCurrentTabIndex(i)}/>
<div className='mt-2 w-full min-h-0 grow flex transition-transform duration-300' style={{transform: `translate(${-(currentTabIndex * 100)}%, 0)`}}>
<LaunchSlide version={state}/>
<div className="w-full shrink-0 px-3 pb-3 flex flex-col items-center">
<MapsPlaylistsPanel version={state}/>
</div>
<ModsSlide version={state}/>
<ModsSlide version={state} onDisclamerDecline={handleModsDisclaimerDecline}/>
</div>
</div>
<BsmDropdownButton className='absolute top-5 right-5 h-9 w-9 bg-light-main-color-2 dark:bg-main-color-2 rounded-md' items={[
+60 -1
View File
@@ -1,6 +1,6 @@
import { NavBar } from "../components/nav-bar/nav-bar.component";
import TitleBar from "../components/title-bar/title-bar.component";
import { Routes, Route, useLocation } from "react-router-dom";
import { Routes, Route, useLocation, useNavigate } from "react-router-dom";
import { AvailableVersionsList } from "../pages/available-versions-list.components";
import { VersionViewer } from "../pages/version-viewer.component";
import { Modal } from "../components/modal/modal.component";
@@ -14,24 +14,83 @@ import { MapsPage } from "../pages/maps-page.component";
import "tailwindcss/tailwind.css";
import { BsmIframeView } from "../components/shared/bsm-map-preview.component";
import 'tippy.js/dist/tippy.css';
import { MapsManagerService } from "renderer/services/maps-manager.service";
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
import { ModelsManagerService } from "renderer/services/models-manager.service";
import { NotificationService } from "renderer/services/notification.service";
import { timer } from "rxjs";
import { ConfigurationService } from "renderer/services/configuration.service";
export default function App() {
const themeService = ThemeService.getInstance();
const pageState = PageStateService.getInstance();
const maps = MapsManagerService.getInstance();
const playlists = PlaylistsManagerService.getInstance();
const models = ModelsManagerService.getInstance();
const notification = NotificationService.getInstance();
const config = ConfigurationService.getInstance();
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
themeService.theme$.subscribe(() => {
if(themeService.isDark || (themeService.isOS && window.matchMedia('(prefers-color-scheme: dark)').matches)){ return document.documentElement.classList.add('dark'); }
document.documentElement.classList.remove('dark');
});
checkOneClicks();
}, []);
const checkOneClicks = async () => {
if(config.get("not-remind-oneclick") === true){ return; }
await timer(2_000).toPromise();
const oneClicks = await Promise.all([
maps.isDeepLinksEnabled(),
playlists.isDeepLinksEnabled(),
models.isDeepLinksEnabled()
]);
if(!oneClicks.some(enabled => enabled === false)){ return; }
const choice = await notification.notifyWarning({
title: "notifications.settings.additional-content.deep-link.check-all-enabled.title",
desc: "notifications.settings.additional-content.deep-link.check-all-enabled.description",
duration: 10_000,
actions: [
{id: "0", title: "notifications.settings.additional-content.deep-link.check-all-enabled.actions.settings"},
{id: "1", title: "notifications.settings.additional-content.deep-link.check-all-enabled.actions.not-remind", cancel: true}
]
});
if(choice === "0"){
return navigate("/settings#one-clicks");
}
if(choice === "1"){
config.set("not-remind-oneclick", true);
}
}
useEffect(() => {
pageState.setLocation(location);
if(!location.hash){ return; }
const hash = location.hash.replace("#", "");
const el = document.getElementById(hash);
if(!el){ return; }
el.scrollIntoView({block: "start", behavior: "smooth"});
}, [location])