[feature-136] add models in shared contents + open download models on click "download models" when no models + fix version undefined when go in shared maps

This commit is contained in:
MathieuG-P
2023-05-21 01:38:48 +02:00
parent 474deaedcf
commit b9359d6b9c
19 changed files with 129 additions and 86 deletions
+7 -3
View File
@@ -16,7 +16,11 @@
},
"nav-bar":{
"add-version": "Version Hinzufügen",
"settings": "Einstellungen"
"settings": "Einstellungen",
"shared": {
"text": "Geteilt",
"tooltip": "Geteilte Inhalte"
}
},
"pages": {
"shared-maps":{
@@ -674,7 +678,6 @@
}
},
"models":{
"models": "Modelle",
"types": {
"singular": {
"avatar": "Avatar",
@@ -825,7 +828,8 @@
"prevent-for-mods": {
"title": "Erforderliche Mods",
"desc": "Stellen Sie sicher, dass Sie die notwendigen Mods installiert haben, um Modelle in BeatSaber zu verwenden",
"go-to-mods": "Gehe zu Mods"
"go-to-mods": "Gehe zu Mods",
"not-remind": "Erinnere mich nicht"
},
"export-success": {
"title": "Export abgeschlossen 🎉"
+7 -3
View File
@@ -16,7 +16,11 @@
},
"nav-bar":{
"add-version": "Add a version",
"settings": "Settings"
"settings": "Settings",
"shared": {
"text": "Shared",
"tooltip": "Shared contents"
}
},
"pages": {
"shared-maps":{
@@ -674,7 +678,6 @@
}
},
"models":{
"models": "Models",
"types": {
"singular": {
"avatar": "avatar",
@@ -825,7 +828,8 @@
"prevent-for-mods": {
"title": "Required mods",
"desc": "Make sure you've installed the necessary mods to use models in BeatSaber",
"go-to-mods": "Go to mods"
"go-to-mods": "Go to mods",
"not-remind": "Do not remind me"
},
"export-success": {
"title": "Export completed 🎉"
+7 -3
View File
@@ -16,7 +16,11 @@
},
"nav-bar":{
"add-version": "Agregar una versión",
"settings": "Ajustes"
"settings": "Ajustes",
"shared": {
"text": "Compartidos",
"tooltip": "Contenidos compartidos"
}
},
"pages": {
"shared-maps":{
@@ -673,7 +677,6 @@
}
},
"models":{
"models": "Modelos",
"types": {
"singular": {
"avatar": "avatar",
@@ -824,7 +827,8 @@
"prevent-for-mods": {
"title": "Mods necesarios",
"desc": "Asegúrate de haber instalado los mods necesarios para usar modelos en BeatSaber",
"go-to-mods": "Ir a mods"
"go-to-mods": "Ir a mods",
"not-remind": "No volver a recordarme"
},
"export-success": {
"title": "Exportación completada 🎉"
+7 -3
View File
@@ -18,7 +18,11 @@
},
"nav-bar":{
"add-version": "Ajouter une version",
"settings": "Paramètres"
"settings": "Paramètres",
"shared": {
"text": "Partagés",
"tooltip": "Contenus partagés"
}
},
"pages": {
"shared-maps":{
@@ -675,7 +679,6 @@
}
},
"models":{
"models": "Modèles",
"types": {
"singular": {
"avatar": "avatar",
@@ -826,7 +829,8 @@
"prevent-for-mods": {
"title": "Mods nécessaires",
"desc": "Assurez-toi d'avoir installé les mods nécessaires pour utiliser des modèles dans BeatSaber",
"go-to-mods": "Aller aux mods"
"go-to-mods": "Aller aux mods",
"not-remind": "Ne plus me rappeler"
},
"export-success": {
"title": "Export terminé 🎉"
@@ -80,6 +80,7 @@ export class VersionFolderLinkerService {
}
public async isFolderLinked(version: BSVersion, relativeFolder: string): Promise<boolean>{
if(!version){ return Promise.reject("no version provided") }
const versionPath = await this.localVersion.getVersionPath(version);
const folderPath = this.relativeToFullPath(versionPath, relativeFolder);
return this.folderLinker.isFolderSymlink(folderPath);
@@ -43,6 +43,8 @@ export function MapsPlaylistsPanel({version}: Props) {
const mapsRef = useRef<any>();
useEffect(() => {
if(!version){ return; }
loadMapIsLinked();
const sub = mapsService.$mapsLinkingPending(version).pipe(debounceTime(50)).subscribe(setLinkingPending);
@@ -145,8 +147,8 @@ export function MapsPlaylistsPanel({version}: Props) {
})()
return (
<div className="w-full h-full flex flex-col items-center justify-center">
<nav className="w-full shrink-0 flex h-9 justify-center px-40 gap-2 mb-3 text-main-color-1 dark:text-white">
<div className="w-full h-full flex flex-col items-center justify-center gap-4">
<nav className="w-full shrink-0 flex h-9 justify-center px-40 gap-2 text-main-color-1 dark:text-white">
<div className="h-full rounded-full bg-light-main-color-2 dark:bg-main-color-2 grow p-[6px]">
<input type="text" className="h-full w-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2" placeholder={t("pages.version-viewer.maps.search-bar.search-placeholder")} value={tabIndex === 0 ? mapSearch : playlistSearch} onChange={e => handleSearch(e.target.value)} tabIndex={-1}/>
</div>
@@ -27,10 +27,11 @@ type Props = {
version?: BSVersion,
type: MSModelType
search?: string,
active: boolean
active: boolean,
downloadModels?: () => void
}
export const ModelsGrid = forwardRef(({className, version, type, search, active}: Props, forwardRef) => {
export const ModelsGrid = forwardRef(({className, version, type, search, active, downloadModels}: Props, forwardRef) => {
const modelsManager = useService(ModelsManagerService);
const modelsDownloader = useService(ModelsDownloaderService);
@@ -80,6 +81,11 @@ export const ModelsGrid = forwardRef(({className, version, type, search, active}
else {
loadModels();
}
}, [version, type]);
useOnUpdate(() => {
if(!active){ return; }
const onLinkStateChangeCb = (action: VersionLinkerAction) => {
if(!equal(version, action.version) || !action.relativeFolder.includes(MODEL_TYPE_FOLDERS[type])){ return; }
@@ -100,7 +106,7 @@ export const ModelsGrid = forwardRef(({className, version, type, search, active}
sub.unsubscribe();
}
}, [version, type]);
}, [version, type, active]);
const loadModels = () => {
const modelsObs$ = modelsManager.$getModels(type, version);
@@ -170,7 +176,7 @@ export const ModelsGrid = forwardRef(({className, version, type, search, active}
<div className="h-full flex flex-col items-center justify-center flex-wrap gap-1 text-gray-800 dark:text-gray-200">
<BsmImage className="h-32" image={BeatConflict}/>
<span className="font-bold">{t("models.panel.grid.no-models")}</span>
<BsmButton className="font-bold rounded-md p-2" text="models.panel.grid.download-models" typeColor="primary" withBar={false} onClick={e => {e.preventDefault();}}/>
<BsmButton className="font-bold rounded-md p-2" text="models.panel.grid.download-models" typeColor="primary" withBar={false} onClick={e => {e.preventDefault(); downloadModels?.()}}/>
</div>
)
}
@@ -14,7 +14,7 @@ import { NotificationService } from "renderer/services/notification.service";
import { ConfigurationService } from "renderer/services/configuration.service";
import { useTranslation } from "renderer/hooks/use-translation.hook";
export function ModelsPanel({version, isActive, goToMods}: {version?: BSVersion, isActive: boolean, goToMods: () => void}) {
export function ModelsPanel({version, isActive, goToMods}: {version?: BSVersion, isActive: boolean, goToMods?: () => void}) {
const modelsManager = useService(ModelsManagerService);
const modelDownloader = useService(ModelsDownloaderService);
@@ -33,12 +33,14 @@ export function ModelsPanel({version, isActive, goToMods}: {version?: BSVersion,
const [search, setSearch] = useState<string>("");
useOnUpdate(() => {
if(!isActive){ return; }
if(config.get("prevented-for-mods-models")){ return; }
config.set("prevented-for-mods-models", true);
notification.notifyWarning({ title: "models.notifications.prevent-for-mods.title", desc: "models.notifications.prevent-for-mods.desc", actions: [{id: "0", title: "models.notifications.prevent-for-mods.go-to-mods"}], duration: 9_000 }).then(res => {
if(res !== "0"){ return; }
goToMods();
if(!isActive || !goToMods){ return; }
if(config.get("not-remind-mods-models")){ return; }
notification.notifyWarning({ title: "models.notifications.prevent-for-mods.title", desc: "models.notifications.prevent-for-mods.desc", actions: [
{id: "0", title: "models.notifications.prevent-for-mods.go-to-mods"},
{id: "1", title: "models.notifications.prevent-for-mods.not-remind", cancel: true}
], duration: 12_000 }).then(res => {
if(res === "0"){ return goToMods(); }
if(res === "1"){ config.set("not-remind-mods-models", true); }
});
}, [isActive]);
@@ -63,8 +65,8 @@ export function ModelsPanel({version, isActive, goToMods}: {version?: BSVersion,
];
return (
<div ref={ref} className="w-full h-full flex flex-col items-center justify-center">
<div className="w-full shrink-0 flex h-9 justify-center px-40 gap-2 mb-3 text-main-color-1 dark:text-white">
<div ref={ref} className="w-full h-full flex flex-col items-center justify-center gap-4">
<div className="w-full shrink-0 flex h-9 justify-center px-40 gap-2 text-main-color-1 dark:text-white">
<BsmButton className="flex items-center justify-center w-fit rounded-full px-2 py-1 font-bold" icon="add" text="misc.add" typeColor="primary" withBar={false} onClick={e => {e.preventDefault(); openDownloadModal()}}/>
<div className="h-full rounded-full bg-light-main-color-2 dark:bg-main-color-2 grow p-[6px]">
<input type="text" className="h-full w-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2" placeholder={t("models.panel.actions.search")} onChange={e => setSearch(e.target.value)} tabIndex={-1}/>
@@ -75,10 +77,10 @@ export function ModelsPanel({version, isActive, goToMods}: {version?: BSVersion,
<ModelsTabsNavbar className="flex-shrink-0" version={version} tabIndex={currentTabIndex} onTabChange={(index, tab) => {setModelTypeTab(() => tab.extra), setCurrentTabIndex(() => index)}}/>
<div className="flex-grow h-full flex flex-col transition-all duration-300" style={{translate: `0 ${0 - currentTabIndex * 100}%`}}>
<ModelsGrid ref={modelsGridRefs[0]} version={version} type={MSModelType.Avatar} active={isActive && modelTypeTab === MSModelType.Avatar} search={search}/>
<ModelsGrid ref={modelsGridRefs[1]} version={version} type={MSModelType.Saber} active={isActive && modelTypeTab === MSModelType.Saber} search={search}/>
<ModelsGrid ref={modelsGridRefs[2]} version={version} type={MSModelType.Platfrom} active={isActive && modelTypeTab === MSModelType.Platfrom} search={search}/>
<ModelsGrid ref={modelsGridRefs[3]} version={version} type={MSModelType.Bloq} active={isActive && modelTypeTab === MSModelType.Bloq} search={search}/>
<ModelsGrid ref={modelsGridRefs[0]} version={version} type={MSModelType.Avatar} active={isActive && modelTypeTab === MSModelType.Avatar} search={search} downloadModels={openDownloadModal}/>
<ModelsGrid ref={modelsGridRefs[1]} version={version} type={MSModelType.Saber} active={isActive && modelTypeTab === MSModelType.Saber} search={search} downloadModels={openDownloadModal}/>
<ModelsGrid ref={modelsGridRefs[2]} version={version} type={MSModelType.Platfrom} active={isActive && modelTypeTab === MSModelType.Platfrom} search={search} downloadModels={openDownloadModal}/>
<ModelsGrid ref={modelsGridRefs[3]} version={version} type={MSModelType.Bloq} active={isActive && modelTypeTab === MSModelType.Bloq} search={search} downloadModels={openDownloadModal}/>
</div>
</div>
</div>
@@ -50,7 +50,7 @@ function ModelTab({version, modelType, active, onClick}: TabProps){
const [linkBtnDisabled, setLinkBtnDisabled] = useState(false);
useOnUpdate(() => {
if(!version){ return null; }
if(!version){ return; }
const sub = modelsManager.$modelsLinkingPending(version, modelType).subscribe(async (pending) => {
if(pending){ return setLinkBtnDisabled(() => pending); }
@@ -98,7 +98,7 @@ export function BsVersionItem(props: {version: BSVersion}) {
return (
<NavBarItem onCancel={cancel} progress={downloading ? downloadPercent : 0} isActive={isActive() && !downloading} isDownloading={downloading}>
{props.version.name ? (
<Tippy content={props.version.BSVersion} placement="right-end" arrow={false} className="font-bold !bg-main-color-3" duration={[100, 0]} animation="shift-away-subtle">
<Tippy content={props.version.BSVersion} placement="right-end" arrow={false} className="font-bold !bg-neutral-900" duration={[100, 0]} animation="shift-away-subtle">
<Link onDoubleClick={handleDoubleClick} to={`/bs-version/${props.version.BSVersion}`} state={props.version} className="w-full flex items-center justify-start content-center max-w-full">
{renderIcon()}
{renderVersionText()}
@@ -1,24 +0,0 @@
import { Link } from "react-router-dom";
import { BsmIcon } from "renderer/components/svgs/bsm-icon.component";
import { useObservable } from "renderer/hooks/use-observable.hook";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { PageStateService } from "renderer/services/page-state.service";
import { NavBarItem } from "./nav-bar-item.component";
export function MapsNavBarItem() {
const pageState = PageStateService.getInstance();
const route = useObservable(pageState.route$);
const color = useThemeColor("first-color");
return (
<NavBarItem isActive={route === "/maps"}>
<Link to="maps" className="w-full flex items-center justify-start content-center max-w-full h-[30px]">
<BsmIcon className="w-[19px] h-[19px] mr-[5px] shrink-0 brightness-125" icon="bsMapDifficulty" style={{color}}/>
<span className="dark:text-gray-200 text-gray-800 font-bold tracking-wide">Maps</span>
</Link>
</NavBarItem>
)
}
@@ -0,0 +1,29 @@
import { Link } from "react-router-dom";
import { BsmIcon } from "renderer/components/svgs/bsm-icon.component";
import { useObservable } from "renderer/hooks/use-observable.hook";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { PageStateService } from "renderer/services/page-state.service";
import { NavBarItem } from "./nav-bar-item.component";
import { useService } from "renderer/hooks/use-service.hook";
import Tippy from "@tippyjs/react";
import { useTranslation } from "renderer/hooks/use-translation.hook";
export function SharedNavBarItem() {
const pageState = useService(PageStateService);
const t = useTranslation();
const route = useObservable(pageState.route$);
const color = useThemeColor("first-color");
return (
<NavBarItem isActive={route === "/shared"}>
<Tippy content={t("nav-bar.shared.tooltip")} className="font-bold !bg-neutral-900" placement="right-end" arrow={false} duration={[100, 0]} animation="shift-away-subtle">
<Link to="shared" className="w-full flex items-center justify-start content-center max-w-full h-[30px]">
<BsmIcon className="w-[19px] h-[19px] mr-[5px] shrink-0 brightness-125" icon="link" style={{color}}/>
<span className="dark:text-gray-200 text-gray-800 font-bold tracking-wide">{t("nav-bar.shared.text")}</span>
</Link>
</Tippy>
</NavBarItem>
)
}
@@ -5,7 +5,7 @@ import { Link } from 'react-router-dom';
import { BsmIcon } from '../svgs/bsm-icon.component';
import { useObservable } from 'renderer/hooks/use-observable.hook';
import { BsManagerIcon } from './bsmanager-icon.component';
import { MapsNavBarItem } from './nav-bar-items/maps-nav-bar-item.component';
import { SharedNavBarItem } from './nav-bar-items/shared-nav-bar-item.component';
import { NavBarSpliter } from './nav-bar-spliter.component';
import { useThemeColor } from 'renderer/hooks/use-theme-color.hook';
import Tippy from '@tippyjs/react';
@@ -26,18 +26,18 @@ export function NavBar() {
<nav id='nav-bar' className='z-10 flex flex-col h-full max-h-full items-center p-1'>
<BsManagerIcon className='relative aspect-square w-16 h-16 mb-3'/>
<ol id='versions' className='w-fit max-w-[120px] relative left-[2px] grow overflow-y-hidden scrollbar-track-transparent scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900 hover:overflow-y-scroll'>
<MapsNavBarItem/>
<SharedNavBarItem/>
<NavBarSpliter/>
{installedVersions && installedVersions.map((version) => <BsVersionItem key={JSON.stringify(version)} version={version}/>)}
</ol>
<NavBarSpliter/>
<div className='w-full pb-2 flex flex-col items-center content-center justify-start gap-1'>
<Tippy placement="right" content={t("nav-bar.add-version")}>
<Tippy placement="right" content={t("nav-bar.add-version")} className='!bg-neutral-900' arrow={false}>
<Link className='rounded-md w-9 h-9 flex justify-center items-center hover:bg-light-main-color-3 dark:hover:bg-main-color-3' to="blah">
<BsmIcon icon='add' className='text-blue-500 h-[34px]' style={{color}}/>
</Link>
</Tippy>
<Tippy placement="right" content={t("nav-bar.settings")}>
<Tippy placement="right" content={t("nav-bar.settings")} className='!bg-neutral-900' arrow={false}>
<Link className='rounded-md w-9 h-9 flex justify-center items-center hover:bg-light-main-color-3 dark:hover:bg-main-color-3' to="settings">
<BsmIcon icon='settings' className='text-blue-500 h-7' style={{color}}/>
</Link>
@@ -1,18 +0,0 @@
import { MapsPlaylistsPanel } from "renderer/components/maps-mangement-components/maps-playlists-panel.component";
import { Slideshow } from "renderer/components/slideshow/slideshow.component";
import { useTranslation } from "renderer/hooks/use-translation.hook";
export function MapsPage() {
const t = useTranslation();
return (
<div className="w-full h-full flex items-center flex-col pt-2">
<Slideshow className="absolute w-full h-full top-0"/>
<h1 className="text-gray-100 text-2xl z-[1]">{t("pages.shared-maps.title")}</h1>
<div className="w-full min-h-0 grow p-10 flex flex-col items-center justify-start text-white relative">
<MapsPlaylistsPanel/>
</div>
</div>
)
}
@@ -0,0 +1,28 @@
import { useState } from "react";
import { MapsPlaylistsPanel } from "renderer/components/maps-mangement-components/maps-playlists-panel.component";
import { ModelsPanel } from "renderer/components/models-management/models-panel.component";
import { TabNavBar } from "renderer/components/shared/tab-nav-bar.component";
import { Slideshow } from "renderer/components/slideshow/slideshow.component";
import { useTranslation } from "renderer/hooks/use-translation.hook";
export function SharedContentsPage() {
const t = useTranslation();
const [tabIndex, setTabIndex] = useState(0);
return (
<div className="relative flex items-center flex-col w-full h-full text-gray-200 backdrop-blur-lg">
<Slideshow className="absolute w-full h-full top-0"/>
<TabNavBar className='my-4' tabIndex={tabIndex} tabsText={["misc.maps", "misc.models"]} onTabChange={setTabIndex}/>
<div className='w-full min-h-0 grow flex transition-transform duration-300' style={{transform: `translate(${-(tabIndex * 100)}%, 0)`}}>
<div className="w-full shrink-0 px-3 pb-3 flex flex-col items-center">
<MapsPlaylistsPanel/>
</div>
<div className="w-full shrink-0 px-3 pb-3 flex flex-col items-center">
<ModelsPanel isActive={tabIndex === 1}/>
</div>
</div>
</div>
)
}
@@ -76,8 +76,8 @@ export function VersionViewer() {
<>
<BsmImage className="absolute w-full h-full top-0 left-0 object-cover" image={state.ReleaseImg || DefautVersionImage} errorImage={DefautVersionImage}/>
<div className="relative flex items-center flex-col w-full h-full text-gray-200 backdrop-blur-lg">
<TabNavBar className='my-3' tabIndex={currentTabIndex} tabsText={["misc.launch", "misc.maps", "Models", "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)`}}>
<TabNavBar className='my-4' tabIndex={currentTabIndex} tabsText={["misc.launch", "misc.maps", "misc.models", "misc.mods"]} onTabChange={(i : number) => setCurrentTabIndex(i)}/>
<div className='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}/>
@@ -88,7 +88,7 @@ export function VersionViewer() {
<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={[
<BsmDropdownButton className='absolute top-3 right-4 h-9 w-9 bg-light-main-color-2 dark:bg-main-color-2 rounded-md' items={[
{text: "pages.version-viewer.dropdown.open-folder", icon: "folder", onClick: openFolder},
((!state.steam && !state.oculus) && {text: "pages.version-viewer.dropdown.verify-files", icon: "task", onClick: verifyFiles}),
((!state.steam && !state.oculus) && {text: "pages.version-viewer.dropdown.edit", icon: "edit", onClick: edit}),
@@ -139,6 +139,7 @@ export class ModelsManagerService {
const types = Array.from(new Set(models.map(m => m.type)));
const linked = await (async () => {
if(!version){ return true; }
for(const type of types){
if(!(await this.isModelsLinked(version, type))){ continue; }
return true;
@@ -1,5 +1,5 @@
import { LinkOptions, UnlinkOptions } from "main/services/folder-linker.service";
import { map, distinctUntilChanged, filter, mergeMap, share } from "rxjs/operators";
import { map, distinctUntilChanged, filter, mergeMap, share, shareReplay } from "rxjs/operators";
import { BehaviorSubject, Observable } from "rxjs";
import { BSVersion } from "shared/bs-version.interface";
import { IpcService } from "./ipc.service";
@@ -141,7 +141,7 @@ export class VersionFolderLinkerService{
public $isVersionFolderPending(version: BSVersion, relativeFolder: string): Observable<boolean>{
return this.queue$.pipe(mergeMap(async queue => {
return queue.some(q => q.relativeFolder.includes(relativeFolder) && equal(q.version, version));
}), distinctUntilChanged(), share());
}), distinctUntilChanged(), shareReplay(1));
}
public get currentAction$(): Observable<VersionLinkerAction>{
+2 -2
View File
@@ -10,7 +10,7 @@ import { useEffect } from "react";
import { ThemeService } from "../services/theme.service";
import { NotificationOverlay } from "../components/notification/notification-overlay.component";
import { PageStateService } from "../services/page-state.service";
import { MapsPage } from "../pages/maps-page.component";
import { SharedContentsPage } from "../pages/shared-contents-page.component";
import "tailwindcss/tailwind.css";
import { BsmIframeView } from "../components/shared/iframe-view.component";
import 'tippy.js/dist/tippy.css';
@@ -109,7 +109,7 @@ export default function App() {
<div className="bg-light-main-color-2 dark:bg-main-color-2 relative rounded-tl-lg grow overflow-hidden max-w-full">
<Routes>
<Route path="/bs-version/:versionNumber" element={<VersionViewer/>}/>
<Route path="/maps" element={<MapsPage/>}/>
<Route path="/shared" element={<SharedContentsPage/>}/>
<Route path="/settings" element={<SettingsPage/>}/>
<Route path="*" element={<AvailableVersionsList/>}/>
</Routes>