Fix lint errors

This commit is contained in:
MathieuG-P
2024-07-10 21:30:05 +02:00
parent c1322c4ed7
commit 116e4d77fa
54 changed files with 270 additions and 283 deletions
@@ -190,15 +190,13 @@ export class LocalPlaylistsManagerService {
}
const songsDetails = localBPList.songs?.map(s => {
if(!s){
return undefined;
}
if(s.hash){
return this.songDetails.getSongDetails(s.hash);
}
if(s.key){
return this.songDetails.getSongDetailsById(s.key);
}
return undefined;
}).filter(Boolean);
if(songsDetails && songsDetails.length){
@@ -235,7 +233,7 @@ export class LocalPlaylistsManagerService {
});
}
public downloadPlaylistSongs(localBPList: LocalBPList, ignoreSongsHashs: string[] = [], version: BSVersion): Observable<Progression<DownloadPlaylistProgressionData>> {
public downloadPlaylistSongs(localBPList: LocalBPList, ignoreSongsHashs: string[], version: BSVersion): Observable<Progression<DownloadPlaylistProgressionData>> {
let destroyed = false;
@@ -243,7 +243,7 @@ export class LocalMapsManagerService {
(async () => {
for (const map of maps) {
let mapPath = map.path;
const mapPath = map.path;
if (pathExistsSync(mapPath)) {
await deleteFolder(mapPath);
@@ -72,6 +72,7 @@ export class SongDetailsCacheService {
private createIdIndex(songDetailsCache: Record<string, SongDetails>): Record<string, SongDetails> {
const res: Record<string, SongDetails> = {};
// eslint-disable-next-line guard-for-in
for(const hash in songDetailsCache){
res[songDetailsCache[hash].id] = songDetailsCache[hash];
}
@@ -8,7 +8,6 @@ import { BS_APP_ID, BS_EXECUTABLE, STEAMVR_APP_ID } from "../../constants";
import log from "electron-log";
import { AbstractLauncherService } from "./abstract-launcher.service";
import { CustomError } from "../../../shared/models/exceptions/custom-error.class";
import isElevated from "is-elevated";
import { UtilsService } from "../utils.service";
import { exec } from "child_process";
import fs from 'fs';
@@ -47,13 +46,6 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
});
}
private needStartBsAsAdmin(): Promise<boolean> {
return isElevated().then(elevated => {
if(elevated){ return false; }
return this.steam.isElevated();
})
}
private getStartBsAsAdminExePath(): string {
return path.join(this.util.getAssetsScriptsPath(), "start_beat_saber_admin.exe");
}
@@ -115,7 +107,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
// Linux setup
if (process.platform === "linux") {
if (launchOptions.admin == true) {
if (launchOptions.admin) {
log.warn("Launching as admin is not supported on Linux! Starting the game as a normal user.");
launchOptions.admin = false;
}
@@ -1,7 +1,7 @@
import path from "path";
import { app } from "electron";
import ElectronStore from "electron-store";
import { copyDirectoryWithJunctions, deleteFolder, ensureFolderExist, pathExist } from "../helpers/fs.helpers";
import { copyDirectoryWithJunctions, deleteFolder, ensureFolderExist } from "../helpers/fs.helpers";
import { tryit } from "../../shared/helpers/error.helpers";
import { pathExistsSync } from "fs-extra";
-1
View File
@@ -1,6 +1,5 @@
import { execOnOs } from "../../helpers/env.helpers";
import path from "path";
import { Log } from "../../decorators/log.decorator";
const { list, createKey, putValue, deleteKey, RegSzValue } = (execOnOs({ win32: () => require("regedit-rs") }, true) ?? {}) as typeof import("regedit-rs");
@@ -7,7 +7,6 @@ export class BeatModsApiService {
private readonly requestService: RequestService;
private readonly BEAT_MODS_VERSIONS = "https://versions.beatmods.com/versions.json";
private readonly BEAT_MODS_ALIAS = "https://alias.beatmods.com/aliases.json";
private readonly BEAT_MODS_API_URL = "https://beatmods.com/api/v1/";
@@ -57,7 +56,7 @@ export class BeatModsApiService {
if (Array.from(aliases.keys()).some(k => k === version.BSVersion)) {
return version;
}
const alias = Array.from(aliases.entries()).find(([key, value]) => value.find(v => v.BSVersion === version.BSVersion))?.[0];
const alias = Array.from(aliases.entries()).find(([, value]) => value.find(v => v.BSVersion === version.BSVersion))?.[0];
return { BSVersion: alias } as BSVersion;
});
}
@@ -1,6 +1,7 @@
import { BsvMapDetail } from "shared/models/maps";
import { BsvPlaylist, BsvPlaylistPage, MapFilter, PlaylistSearchParams, PlaylistSearchResponse, SearchParams, SearchResponse } from "shared/models/maps/beat-saver.model";
import { BsvPlaylistPage, MapFilter, PlaylistSearchParams, PlaylistSearchResponse, SearchParams, SearchResponse } from "shared/models/maps/beat-saver.model";
import { RequestService } from "../../request.service";
import { CustomError } from "shared/models/exceptions/custom-error.class";
export class BeatSaverApiService {
private static instance: BeatSaverApiService;
@@ -22,7 +23,7 @@ export class BeatSaverApiService {
private objectToStringRecord(obj: Record<string, any>): Record<string, string> {
return Object.fromEntries(Object.entries(obj)
.filter(([_, value]) => value !== undefined && value !== null)
.filter(([, value]) => value !== undefined && value !== null)
.map(([key, value]) => [key, String(value)] as [string, string]));
}
@@ -62,7 +63,7 @@ export class BeatSaverApiService {
public async getMapsDetailsByHashs<T extends string>(hashs: T[]): Promise<Record<Lowercase<T>, BsvMapDetail>> {
if (hashs.length > 50) {
throw "too musch map hashs";
throw new CustomError("too musch map hashs", "TOO_MUCH_MAP_HASHS");
}
const paramsHashs = hashs.join(",");
@@ -1,4 +1,4 @@
import { Dispatch, SetStateAction, createContext, useMemo, useRef, useState } from "react";
import { createContext, useRef, useState } from "react";
import { BSVersion } from "shared/bs-version.interface";
import { LocalMapsListPanel } from "./maps/local-maps-list-panel.component";
import { BsmDropdownButton, DropDownItem } from "../shared/bsm-dropdown-button.component";
@@ -22,6 +22,7 @@ import { useConstant } from "renderer/hooks/use-constant.hook";
import { LocalBPListsDetails } from "shared/models/playlists/local-playlist.models";
import { PlaylistDownloaderService } from "renderer/services/playlist-downloader.service";
import { LocalPlaylistFilter, LocalPlaylistFilterPanel } from "./playlists/local-playlist-filter-panel.component";
import { noop } from "shared/helpers/function.helpers";
type Props = {
version?: BSVersion;
@@ -76,6 +77,7 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
switch (tabIndex) {
case 0: return mapsDownloader.openDownloadMapModal(version, maps$.value);
case 1: return playlistsDownloader.openDownloadPlaylistModal(version, playlists$, maps$);
default: return noop();
}
}
@@ -219,7 +219,7 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat
)}
</motion.div>
) : (
<></>
undefined
);
}
@@ -330,24 +330,22 @@ export const isLocalMapFitMapFilter = ({filter, map, search}: { filter: MapFilte
export const isBsvMapFitMapFilter = ({filter, map, search}: { filter: MapFilter, map: BsvMapDetail, search: string }): boolean => {
console.log("LAAAAAA", map);
if (!isFitEnabledTags(filter, map.tags)) { console.log(1); return false; }
if (!isFitEnabledTags(filter, map.tags)) { return false; }
if (!isFitExcludedTags(filter, map.tags)) { return false; }
if (map.versions?.at(0)?.diffs && !map.versions.at(0).diffs.some(diff => isFitMinNps(filter, diff.nps))) { console.log(2); return false; }
if (map.versions?.at(0)?.diffs && !map.versions.at(0).diffs.some(diff => isFitMaxNps(filter, diff.nps))) { console.log(3); return false; }
if (!isFitMinDuration(filter, map.metadata.duration)) { console.log(4); return false; }
if (!isFitMaxDuration(filter, map.metadata.duration)) { console.log(5); return false; }
if (!isFitNoodle(filter, map.versions?.at(0)?.diffs.some(diff => !!diff.ne))) { console.log(6); return false; }
if (!isFitMe(filter, map.versions?.at(0)?.diffs.some(diff => !!diff.me))) { console.log(7); return false; }
if (!isFitCinema(filter, map.versions?.at(0)?.diffs.some(diff => !!diff.cinema))) { console.log(8); return false; }
if (!isFitChroma(filter, map.versions?.at(0)?.diffs.some(diff => !!diff.chroma))) { console.log(9); return false; }
if (!isFitFullSpread(filter, map.versions?.at(0)?.diffs.length)) { console.log(10); return false; }
if (!isFitAutomapper(filter, map.automapper)){ console.log(11); return false; }
if (!isFitRanked(filter, map.ranked || map.blRanked)) { console.log(12); return false; }
if (!isFitCurated(filter, !!map.curator)) { console.log(13); return false; }
if (!isFitVerified(filter, !!map.curatedAt)) { console.log(14); return false; }
if (!isFitSearch(search, {songName: map.name, songAuthorName: map.metadata.songAuthorName, levelAuthorName: map.metadata.levelAuthorName})) { console.log(15); return false; }
if (map.versions?.at(0)?.diffs && !map.versions.at(0).diffs.some(diff => isFitMinNps(filter, diff.nps))) { return false; }
if (map.versions?.at(0)?.diffs && !map.versions.at(0).diffs.some(diff => isFitMaxNps(filter, diff.nps))) { return false; }
if (!isFitMinDuration(filter, map.metadata.duration)) { return false; }
if (!isFitMaxDuration(filter, map.metadata.duration)) { return false; }
if (!isFitNoodle(filter, map.versions?.at(0)?.diffs.some(diff => !!diff.ne))) { return false; }
if (!isFitMe(filter, map.versions?.at(0)?.diffs.some(diff => !!diff.me))) { return false; }
if (!isFitCinema(filter, map.versions?.at(0)?.diffs.some(diff => !!diff.cinema))) { return false; }
if (!isFitChroma(filter, map.versions?.at(0)?.diffs.some(diff => !!diff.chroma))) { return false; }
if (!isFitFullSpread(filter, map.versions?.at(0)?.diffs.length)) { return false; }
if (!isFitAutomapper(filter, map.automapper)){ return false; }
if (!isFitRanked(filter, map.ranked || map.blRanked)) { return false; }
if (!isFitCurated(filter, !!map.curator)) { return false; }
if (!isFitVerified(filter, !!map.curatedAt)) { return false; }
if (!isFitSearch(search, {songName: map.name, songAuthorName: map.metadata.songAuthorName, levelAuthorName: map.metadata.levelAuthorName})) { return false; }
return true;
};
@@ -3,7 +3,7 @@ import { BSVersion } from "shared/bs-version.interface";
import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useState } from "react";
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
import { Subscription, BehaviorSubject } from "rxjs";
import { MapFilter, MapTag } from "shared/models/maps/beat-saver.model";
import { MapFilter } from "shared/models/maps/beat-saver.model";
import { MapsDownloaderService } from "renderer/services/maps-downloader.service";
import { last, tap } from "rxjs/operators";
import { useTranslation } from "renderer/hooks/use-translation.hook";
@@ -23,6 +23,7 @@ import { VirtualScroll } from "renderer/components/shared/virtual-scroll/virtual
import { MapItem } from "./map-item.component";
import { isLocalMapFitMapFilter } from "./filter-panel.component";
import { MapItemComponentPropsMapper } from "shared/mappers/map/map-item-component-props.mapper";
import { noop } from "shared/helpers/function.helpers";
type Props = {
version: BSVersion;
@@ -70,8 +71,9 @@ export const LocalMapsListPanel = forwardRef<unknown, Props>(({ version, classNa
}, [maps, selectedMaps]);
useOnUpdate(() => {
if(linkedState === FolderLinkState.Pending || linkedState === FolderLinkState.Processing) return () => {};
if(linkedState === FolderLinkState.Pending || linkedState === FolderLinkState.Processing) return noop;
setLinked(linkedState === FolderLinkState.Linked);
return noop;
}, [linkedState]);
useEffect(() => {
@@ -153,7 +155,7 @@ export const LocalMapsListPanel = forwardRef<unknown, Props>(({ version, classNa
}
const renderMap = useCallback((renderableMap: RenderableMap) => {
const map = renderableMap.map;
const { map } = renderableMap;
return (
<MapItem
key={map.path}
@@ -98,7 +98,7 @@ export function MapItemComponent <T = unknown>({ hash, title, autor, songAutor,
const mapUrl = mapId ? `https://beatsaver.com/maps/${mapId}` : null;
const authorUrl = autorId ? `https://beatsaver.com/profile/${autorId}` : null;
const likesText = likes ? Intl.NumberFormat(undefined, { notation: "compact" }).format(likes).split(" ").join("") : null;
const mapCoverUrl = coverUrl ? coverUrl : `https://eu.cdn.beatsaver.com/${hash}.jpg`;
const mapCoverUrl = coverUrl || `https://eu.cdn.beatsaver.com/${hash}.jpg`;
const createdDate = useConstant(() => {
if(!createdAt){ return null; }
@@ -167,16 +167,16 @@ export function MapItemComponent <T = unknown>({ hash, title, autor, songAutor,
<>
<BsmIcon className="h-4 w-4 mr-px" icon="bsMapDifficulty" />
<div className="flex py-[2px] gap-[1px] h-full">
{diffSet.map((diff, index) => (
<span key={index} className="h-full w-[6px] rounded-full" style={{ backgroundColor: MAP_DIFFICULTIES_COLORS[diff.name] }} />
{diffSet.map((diff) => (
<span key={`${diff.libelle}${diff.name}${diff.stars}`} className="h-full w-[6px] rounded-full" style={{ backgroundColor: MAP_DIFFICULTIES_COLORS[diff.name] }} />
))}
</div>
</>
);
}
if (diffSets.length > 1) {
return diffSets.map(([diffType, diffSet], index) => (
<Fragment key={index}>
return diffSets.map(([diffType, diffSet]) => (
<Fragment key={diffType}>
<BsmIcon className="h-full w-fit mr-px" icon={diffType} />
<span className="mr-2 font-bold text-[15px] h-full flex items-center pb-px">{diffSet.length}</span>
</Fragment>
@@ -205,10 +205,10 @@ export function MapItemComponent <T = unknown>({ hash, title, autor, songAutor,
<AnimatePresence>
{(diffsPanelHovered || bottomBarHovered) && (
<motion.ul key={hash} className="absolute top-[calc(100%-10px)] w-full h-fit max-h-[200%] pt-4 pb-2 px-2 overflow-y-scroll bg-light-main-color-3 dark:bg-main-color-3 text-main-color-1 dark:text-current brightness-125 rounded-md flex flex-col gap-3 scrollbar-default shadow-sm shadow-black" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }} onHoverStart={diffsPanelHoverStart} onHoverEnd={diffsPanelHoverEnd}>
{Array.from(diffs.entries()).map(([charac, diffSet], index) => (
<ol key={index} className="flex flex-col w-full gap-1">
{diffSet.map(({ name, libelle, stars }, index) => (
<li key={`${name}${libelle}${stars}${index}`} className="w-full h-4 flex items-center gap-1">
{Array.from(diffs.entries()).map(([charac, diffSet]) => (
<ol key={charac} className="flex flex-col w-full gap-1">
{diffSet.map(({ name, libelle, stars }) => (
<li key={`${name}${libelle}${stars}`} className="w-full h-4 flex items-center gap-1">
{onHighlightedDiffsChange && (
<Tippy content={t("maps.map-item.hightlight-difficulty")} placement="top" theme="default">
<div className="h-full aspect-square">
@@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import { motion } from 'framer-motion';
import { BsmImage } from 'renderer/components/shared/bsm-image.component';
import { ClockIcon } from 'renderer/components/svgs/icons/clock-icon.component';
@@ -126,7 +127,7 @@ export const PlaylistItem = memo(({ title,
withBar={false}
/>
</Tippy>
) : (<></>)}
) : (undefined)}
{onClickSync ? (
isDownloading ? (
<BsmBasicSpinner className="hover:!bg-main-color-1" spinnerClassName="brightness-75 dark:brightness-200" style={{ color }} thikness="3px"/>
@@ -141,9 +142,9 @@ export const PlaylistItem = memo(({ title,
withBar={false}
/>
</Tippy>
) : (<></>)
) : undefined
) : (<></>)}
) : undefined}
{onClickDownload ? (
isDownloading ? (
<BsmBasicSpinner className="hover:!bg-main-color-1" spinnerClassName="brightness-75 dark:brightness-200" style={{ color }} thikness="3px"/>
@@ -158,9 +159,9 @@ export const PlaylistItem = memo(({ title,
withBar={false}
/>
</Tippy>
) : (<></>)
) : undefined
) : (<></>)}
) : undefined}
{onClickEdit ? (
<Tippy content={t("playlist.edit-playlist")} placement="left" theme="default">
<BsmButton
@@ -172,7 +173,7 @@ export const PlaylistItem = memo(({ title,
withBar={false}
/>
</Tippy>
) : (<></>)}
) : undefined}
{onClickOpenFile ? <Tippy content={t("playlist.open-file")} placement="left" theme="default">
<BsmButton
icon="folder"
@@ -182,7 +183,7 @@ export const PlaylistItem = memo(({ title,
onClick={onClickOpenFile}
withBar={false}
/>
</Tippy> : (<></>)}
</Tippy> : undefined}
{(onClickDelete && !isDownloading && !isInQueue) ? <Tippy content={t("misc.delete")} placement="left" theme="default">
<BsmButton
icon="trash"
@@ -191,7 +192,7 @@ export const PlaylistItem = memo(({ title,
onClick={onClickDelete}
withBar={false}
/>
</Tippy> : (<></>)}
</Tippy> : undefined}
</motion.div>
</motion.div>
</div>
@@ -7,7 +7,7 @@ import DOMPurify from 'dompurify';
import './changelog-modal.component.css';
import Tippy from "@tippyjs/react";
export const ChangelogModal: ModalComponent<void, ChangelogVersion> = ({ resolver, options: {data: changelog} }) => {
export const ChangelogModal: ModalComponent<void, ChangelogVersion> = ({ options: {data: changelog} }) => {
const linkOpener = useService(LinkOpenerService);
const openGithub = () => linkOpener.open("https://github.com/Zagrios/bs-manager");
@@ -132,7 +132,7 @@ export const DownloadMapsModal: ModalComponent<void, { version: BSVersion; owned
};
const renderMap = useCallback((downloadableMap: DownloadableMap) => {
const map = downloadableMap.map;
const { map } = downloadableMap;
const downloadable = !downloadableMap.isOwned && !downloadableMap.isInQueue;
const cancelable = downloadableMap.isInQueue && !downloadableMap.idDownloading;
@@ -274,27 +274,3 @@ type DownloadableMap = {
idDownloading: boolean;
isInQueue: boolean;
};
{/* <ul className="w-full grow flex content-start flex-wrap gap-2 pt-1.5 px-2 overflow-y-scroll overflow-x-hidden scrollbar-default z-0">
{maps.length === 0 ? (
<div className="w-full h-full flex flex-col items-center justify-center">
<img className={`w-32 h-32 ${loading && "spin-loading"}`} src={loading ? BeatWaitingImg : BeatConflictImg} alt=" " />
<span className="text-lg">
{(() => {
if (loading) {
return t("modals.download-maps.loading-maps");
}
if (isOnline) {
return t("modals.download-maps.no-maps-found");
}
return t("modals.download-maps.no-internet");
})()}
</span>
</div>
) : (
<>
{maps.map(renderMap)}
<motion.span onViewportEnter={handleLoadMore} ref={loaderRef} className="block w-full h-8" />
</>
)}
</ul> */}
@@ -70,6 +70,7 @@ export function DownloadPlaylistFilterPanel({ className, params, onChange, onSub
return <span className={`bg-inherit absolute top-[calc(100%+4px)] whitespace-nowrap h-5 font-bold rounded-md shadow-center shadow-black px-1 flex items-center ${isMax ? "text-lg" : "text-sm"}`}>{text}</span>;
};
// eslint-disable-next-line react/no-unstable-nested-components
const CustomRadio = (props: RadioProps) => {
const {children, ...otherProps} = props;
@@ -28,7 +28,7 @@ export function DownloadPlaylistModalHeader({ className, value, onSubmit }: Prop
const sortOptions: BsmSelectOption<BsvSearchOrder>[] = useConstant(() => {
return Object.values(BsvSearchOrder).reduce((acc, value) => {
if(value === BsvSearchOrder.Rating){ return acc; }
acc.push({ text: `beat-saver.maps-sorts.${value}`, value: value });
acc.push({ text: `beat-saver.maps-sorts.${value}`, value });
return acc;
}, []);
});
@@ -1,6 +1,6 @@
import { useObservable } from "renderer/hooks/use-observable.hook"
import { ModalComponent, ModalService } from "renderer/services/modale.service"
import { Observable, lastValueFrom, take } from "rxjs"
import { Observable, lastValueFrom } from "rxjs"
import { BSVersion } from "shared/bs-version.interface"
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"
import { LocalBPListsDetails } from "shared/models/playlists/local-playlist.models"
@@ -49,7 +49,7 @@ export const DownloadPlaylistModal: ModalComponent<void, {version: BSVersion, ow
setDownloadablePlaylists(() => playlists?.map(playlist => ({
playlist,
isOwned: ownedPlaylists.some(ownedPlaylist => ownedPlaylist.id === playlist.playlistId),
ownedMaps: ownedMaps
ownedMaps
})));
}, [playlists, ownedPlaylists, ownedMaps])
@@ -77,7 +77,7 @@ export const DownloadPlaylistModal: ModalComponent<void, {version: BSVersion, ow
const renderPlaylist = useCallback((downloadablePlaylists: DownloadablePlaylist) => {
const playlist = downloadablePlaylists.playlist;
const { playlist } = downloadablePlaylists;
const onClickDownload = () => {
lastValueFrom(playlistDownloader.downloadPlaylist({
@@ -115,7 +115,7 @@ export const DownloadPlaylistModal: ModalComponent<void, {version: BSVersion, ow
</div>
)
}
else if(downloadablePlaylists.length === 0){
if(downloadablePlaylists.length === 0){
return (
<div className="w-full flex flex-col justify-center items-center mt-44">
<BsmImage className="size-32" image={BeatConflict} />
@@ -10,7 +10,6 @@ import { useOnUpdate } from "renderer/hooks/use-on-update.hook";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { ModalComponent, ModalExitCode, ModalService } from "renderer/services/modale.service"
import { BehaviorSubject, Observable, lastValueFrom, map, take } from "rxjs";
import { BSVersion } from "shared/bs-version.interface";
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
import { LocalBPList } from "shared/models/playlists/local-playlist.models"
import { BsvMapDetail, SongDetails } from "shared/models/maps";
@@ -303,7 +302,7 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
) : tmpLocalMaps;
const newPlaylistMaps = {...playlistMaps$.value} ?? {};
mapsToAdd.forEach(map => newPlaylistMaps[getHashOfMap(map)] = { map });
mapsToAdd.forEach(map =>{ newPlaylistMaps[getHashOfMap(map)] = { map }; });
playlistMaps$.next(newPlaylistMaps);
availabledHashsSelected$.next([]);
@@ -345,7 +344,7 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
if(!playlistMap?.map){ return; }
const map = playlistMap.map;
const { map } = playlistMap;
if((map as BsmLocalMap).rawInfo?._levelAuthorName){
mappersSet.add((map as BsmLocalMap).rawInfo._levelAuthorName);
@@ -366,19 +365,19 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
if(!playlistMap?.map){ return 0; }
const map = playlistMap.map;
const { map } = playlistMap;
if((map as BsmLocalMap)?.songDetails?.duration){
return (map as BsmLocalMap).songDetails.duration;
}
else if((map as SongDetails)?.duration){
if((map as SongDetails)?.duration){
return (map as SongDetails).duration;
}
else if((map as BsvMapDetail)?.metadata?.duration){
if((map as BsvMapDetail)?.metadata?.duration){
return (map as BsvMapDetail).metadata.duration;
}
return 0;
}).filter(duration => !isNaN(duration));
}).filter(duration => !Number.isNaN(duration));
const totalDuration = durations.reduce((acc, duration) => acc + duration, 0);
return totalDuration > 3600 ? dateFormat(totalDuration * 1000, "H:MM:ss") : dateFormat(totalDuration * 1000, "MM:ss");
@@ -390,7 +389,7 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
if(!playlistMap?.map){ return acc; }
const map = playlistMap.map;
const { map } = playlistMap;
if(Array.isArray((map as BsmLocalMap)?.songDetails?.difficulties)){
acc.push(...(map as BsmLocalMap).songDetails.difficulties.map(diff => diff.nps));
@@ -402,7 +401,7 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
acc.push(...(map as BsvMapDetail).versions.flatMap(version => version.diffs.map(diff => diff.nps)));
}
return acc;
}, [] as number[]).filter(n => !isNaN(n));
}, [] as number[]).filter(n => !Number.isNaN(n));
const minNps = Math.min(...nps);
const maxNps = Math.max(...nps);
@@ -461,142 +460,140 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
if(!playlistMaps || !localMaps){
return <div className="flex items-center justify-center w-full h-full">{t("playlist.loading")}</div>
}
else{
return (
<div className="size-full flex flex-col justify-between">
<div className="grow flex flex-row min-h-0 gap-2.5">
<div className="flex flex-col grow basis-0 min-w-0">
<form className="h-8 flex flex-row gap-2 w-full mb-1.5 min-w-0" onSubmit={e => {e.preventDefault(); handleNewSearch()}}>
<BsmSelect className="bg-theme-1 h-full rounded-full text-center pb-0.5" options={[{ text: t("playlist.installed"), value: 0 }, { text: "BeatSaver", value: 1 }]} onChange={setAvailableMapsSource}/>
<input className="h-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2 grow pb-0.5 min-w-0" type="text" placeholder={t("pages.version-viewer.maps.search-bar.search-placeholder")} value={availableMapsSearch} onChange={e => setAvailableMapsSearch(() => e.target.value)} />
{availableMapsSource === 1 && (
<BsmButton className="h-full aspect-square z-[1] flex justify-center p-1 rounded-full min-w-0 shrink-0 !bg-light-main-color-1 dark:!bg-main-color-1" icon="search" onClick={handleNewSearch} withBar={false}/>
)}
<BsmDropdownButton ref={filterContainerRef} className="h-full aspect-square relative z-[1] flex justify-center" buttonClassName="flex items-center justify-center h-full rounded-full p-1 !bg-light-main-color-1 dark:!bg-main-color-1" icon="filter" textClassName="whitespace-nowrap" withBar={false}>
<FilterPanel className="absolute top-[calc(100%+3px)] origin-top w-[500px] h-fit p-2 rounded-md shadow-md shadow-black -translate-x-1/4 lg:translate-x-0" filter={availableMapsFilter} onChange={setAvailableMapsFilter} onApply={availableMapsSource === 1 && handleNewSearch} onClose={() => filterContainerRef.current.close()}/>
</BsmDropdownButton>
{availableMapsSource === 1 && (
<BsmSelect className="bg-theme-1 h-full rounded-full text-center pb-0.5 min-w-0 lg:min-w-fit" options={sortOptions} onChange={handleSortChange}/>
)}
</form>
return (
<div className="size-full flex flex-col justify-between">
<div className="grow flex flex-row min-h-0 gap-2.5">
<div className="flex flex-col grow basis-0 min-w-0">
<form className="h-8 flex flex-row gap-2 w-full mb-1.5 min-w-0" onSubmit={e => {e.preventDefault(); handleNewSearch()}}>
<BsmSelect className="bg-theme-1 h-full rounded-full text-center pb-0.5" options={[{ text: t("playlist.installed"), value: 0 }, { text: "BeatSaver", value: 1 }]} onChange={setAvailableMapsSource}/>
<input className="h-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2 grow pb-0.5 min-w-0" type="text" placeholder={t("pages.version-viewer.maps.search-bar.search-placeholder")} value={availableMapsSearch} onChange={e => setAvailableMapsSearch(() => e.target.value)} />
{availableMapsSource === 1 && (
<BsmButton className="h-full aspect-square z-[1] flex justify-center p-1 rounded-full min-w-0 shrink-0 !bg-light-main-color-1 dark:!bg-main-color-1" icon="search" onClick={handleNewSearch} withBar={false}/>
)}
<BsmDropdownButton ref={filterContainerRef} className="h-full aspect-square relative z-[1] flex justify-center" buttonClassName="flex items-center justify-center h-full rounded-full p-1 !bg-light-main-color-1 dark:!bg-main-color-1" icon="filter" textClassName="whitespace-nowrap" withBar={false}>
<FilterPanel className="absolute top-[calc(100%+3px)] origin-top w-[500px] h-fit p-2 rounded-md shadow-md shadow-black -translate-x-1/4 lg:translate-x-0" filter={availableMapsFilter} onChange={setAvailableMapsFilter} onApply={availableMapsSource === 1 && handleNewSearch} onClose={() => filterContainerRef.current.close()}/>
</BsmDropdownButton>
{availableMapsSource === 1 && (
<BsmSelect className="bg-theme-1 h-full rounded-full text-center pb-0.5 min-w-0 lg:min-w-fit" options={sortOptions} onChange={handleSortChange}/>
)}
</form>
<div className="overflow-hidden size-full bg-theme-1 rounded-md ">
{(() => {
if((availableMapsSource === 1 && !Array.isArray(bsvMaps)) || (availableMapsSource === 0 && !Array.isArray(localMaps))){
return (
<div className="flex flex-col items-center justify-center size-full">
<BsmImage className="size-24 spin-loading" image={BeatWaiting}/>
<span className="text-sm italic leading-4">{t("playlist.loading")}</span>
</div>
);
}
if((availableMapsSource === 1 && bsvMaps.length === 0) || (availableMapsSource === 0 && localMaps.length === 0)){
return (
<div className="flex flex-col items-center justify-center size-full">
<BsmImage className="size-20" image={BeatConflict}/>
<span className="text-sm italic leading-4 w-3/4 text-center">{t("playlist.no-map-found")}</span>
</div>
);
}
if(availableMapsSource === 0){
return renderList(localMaps.filter(map => {
if(playlistMaps?.[map.hash]){ return false; }
return isMapFitFilter({ map, filter: availableMapsFilter, search: availableMapsSearch });
}), renderAvailableMapItem);
}
return renderList(bsvMaps, renderBsvMapItem, { onScrollEnd: loadMoreBsvMaps });
})()}
</div>
<div className="w-full h-4 flex justify-start">
<span className="text-xs italic leading-4">{t("playlist.edit-playlist-shortcuts")}</span>
</div>
</div>
<div className="shrink-0 flex flex-col gap-2.5 pb-4 pt-10">
<Tippy content={t("playlist.add-to-playlist")} theme="default" placement="left">
<button className="grow w-9 rounded-md cursor-pointer transition-transform duration-150 hover:brightness-110 active:scale-95" style={{backgroundColor: color, color: getCorrectTextColor(color)}} type="button" onClick={addMapsToPlaylist}>
<ChevronTopIcon className="origin-center rotate-90"/>
</button>
</Tippy>
<Tippy content={t("playlist.remove-from-playlist")} theme="default" placement="right">
<button className="grow w-9 rounded-md cursor-pointer transition-transform duration-150 hover:brightness-110 active:scale-95" style={{backgroundColor: color, color: getCorrectTextColor(color)}} type="button" onClick={removeMapsFromPlaylist}>
<ChevronTopIcon className="origin-center -rotate-90"/>
</button>
</Tippy>
</div>
<div className="flex flex-col grow basis-0">
<div className="h-8 flex flex-row gap-2 w-full mb-1.5">
<input className="h-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2 grow pb-0.5" type="text" placeholder={t("pages.version-viewer.maps.search-bar.search-placeholder")} value={playlistMapsSearch} onChange={e => setPlaylistMapsSearch(() => e.target.value)} />
<BsmDropdownButton className="h-full aspect-square relative z-[1] flex justify-center" buttonClassName="flex items-center justify-center h-full rounded-full p-1 !bg-light-main-color-1 dark:!bg-main-color-1" icon="filter" textClassName="whitespace-nowrap" withBar={false}>
<FilterPanel className="absolute top-[calc(100%+3px)] origin-top w-[500px] h-fit p-2 rounded-md shadow-md shadow-black -translate-x-[40%]" filter={playlistMapsFilter} onChange={setPlaylistMapsFilter}/>
</BsmDropdownButton>
</div>
<div className="overflow-hidden size-full bg-theme-1 rounded-md">
{(() => {
if(!playlistMaps){
return(
<div className="flex flex-col items-center justify-center size-full">
<BsmImage className="size-24 spin-loading" image={BeatWaiting}/>
<span className="text-sm italic leading-4">{t("playlist.loading")}</span>
</div>
);
}
if(Object.keys(playlistMaps).length === 0){
return (
<div className="flex flex-col items-center justify-center size-full">
<BsmImage className="size-20" image={BeatConflict}/>
<span className="text-sm italic leading-4 w-3/4 text-center">{t("playlist.playlist-is-empty")}</span>
</div>
);
}
<div className="overflow-hidden size-full bg-theme-1 rounded-md ">
{(() => {
if((availableMapsSource === 1 && !Array.isArray(bsvMaps)) || (availableMapsSource === 0 && !Array.isArray(localMaps))){
return (
<DraggableVirtualScroll
classNames={{
mainDiv: "size-full min-w-0",
rows: "py-2.5 px-2.5"
}}
itemHeight={110}
items={displayablePlaylistMaps}
isDragDisabled={!!Object.keys(playlistMapsFilter).length || !!playlistMapsSearch}
renderItem={renderPlaylistMapItem}
onDragEnd={handlePlaylistMapDragEnd}
/>
<div className="flex flex-col items-center justify-center size-full">
<BsmImage className="size-24 spin-loading" image={BeatWaiting}/>
<span className="text-sm italic leading-4">{t("playlist.loading")}</span>
</div>
);
}
})()}
if((availableMapsSource === 1 && bsvMaps.length === 0) || (availableMapsSource === 0 && localMaps.length === 0)){
return (
<div className="flex flex-col items-center justify-center size-full">
<BsmImage className="size-20" image={BeatConflict}/>
<span className="text-sm italic leading-4 w-3/4 text-center">{t("playlist.no-map-found")}</span>
</div>
);
}
if(availableMapsSource === 0){
return renderList(localMaps.filter(map => {
if(playlistMaps?.[map.hash]){ return false; }
return isMapFitFilter({ map, filter: availableMapsFilter, search: availableMapsSearch });
}), renderAvailableMapItem);
}
return renderList(bsvMaps, renderBsvMapItem, { onScrollEnd: loadMoreBsvMaps });
})()}
</div>
<div className="w-full h-4 flex justify-start">
<span className="text-xs italic leading-4">{t("playlist.edit-playlist-shortcuts")}</span>
</div>
</div>
<div className="shrink-0 flex flex-col gap-2.5 pb-4 pt-10">
<Tippy content={t("playlist.add-to-playlist")} theme="default" placement="left">
<button className="grow w-9 rounded-md cursor-pointer transition-transform duration-150 hover:brightness-110 active:scale-95" style={{backgroundColor: color, color: getCorrectTextColor(color)}} type="button" onClick={addMapsToPlaylist}>
<ChevronTopIcon className="origin-center rotate-90"/>
</button>
</Tippy>
<Tippy content={t("playlist.remove-from-playlist")} theme="default" placement="right">
<button className="grow w-9 rounded-md cursor-pointer transition-transform duration-150 hover:brightness-110 active:scale-95" style={{backgroundColor: color, color: getCorrectTextColor(color)}} type="button" onClick={removeMapsFromPlaylist}>
<ChevronTopIcon className="origin-center -rotate-90"/>
</button>
</Tippy>
</div>
<div className="flex flex-col grow basis-0">
<div className="h-8 flex flex-row gap-2 w-full mb-1.5">
<input className="h-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2 grow pb-0.5" type="text" placeholder={t("pages.version-viewer.maps.search-bar.search-placeholder")} value={playlistMapsSearch} onChange={e => setPlaylistMapsSearch(() => e.target.value)} />
<BsmDropdownButton className="h-full aspect-square relative z-[1] flex justify-center" buttonClassName="flex items-center justify-center h-full rounded-full p-1 !bg-light-main-color-1 dark:!bg-main-color-1" icon="filter" textClassName="whitespace-nowrap" withBar={false}>
<FilterPanel className="absolute top-[calc(100%+3px)] origin-top w-[500px] h-fit p-2 rounded-md shadow-md shadow-black -translate-x-[40%]" filter={playlistMapsFilter} onChange={setPlaylistMapsFilter}/>
</BsmDropdownButton>
</div>
<div className="overflow-hidden size-full bg-theme-1 rounded-md">
{(() => {
if(!playlistMaps){
return(
<div className="flex flex-col items-center justify-center size-full">
<BsmImage className="size-24 spin-loading" image={BeatWaiting}/>
<span className="text-sm italic leading-4">{t("playlist.loading")}</span>
</div>
);
}
if(Object.keys(playlistMaps).length === 0){
return (
<div className="flex flex-col items-center justify-center size-full">
<BsmImage className="size-20" image={BeatConflict}/>
<span className="text-sm italic leading-4 w-3/4 text-center">{t("playlist.playlist-is-empty")}</span>
</div>
);
}
return (
<DraggableVirtualScroll
classNames={{
mainDiv: "size-full min-w-0",
rows: "py-2.5 px-2.5"
}}
itemHeight={110}
items={displayablePlaylistMaps}
isDragDisabled={!!Object.keys(playlistMapsFilter).length || !!playlistMapsSearch}
renderItem={renderPlaylistMapItem}
onDragEnd={handlePlaylistMapDragEnd}
/>
);
})()}
</div>
<div className="w-full h-4 flex justify-end gap-3 mt-px">
<div className="h-full flex justify-center items-center gap-0.5">
<MapIcon className='h-full aspect-square mt-0.5'/>
<span className="text-xs italic leading-4">{Object.keys(playlistMaps ?? {}).length}</span>
</div>
<div className="w-full h-4 flex justify-end gap-3 mt-px">
<div className="h-full flex justify-center items-center gap-0.5">
<MapIcon className='h-full aspect-square mt-0.5'/>
<span className="text-xs italic leading-4">{Object.keys(playlistMaps ?? {}).length}</span>
</div>
<div className="h-full flex justify-center items-center gap-0.5">
<PersonIcon className='h-full aspect-square mt-0.5'/>
<span className="text-xs italic leading-4">{playlistNbMappers}</span>
</div>
<div className="h-full flex justify-center items-center gap-0.5">
<ClockIcon className='h-full aspect-square mt-0.5'/>
<span className="text-xs italic leading-4">{playlistDuration}</span>
</div>
<div className="h-full flex justify-center items-center gap-0.5">
<NpsIcon className='h-full aspect-square mt-0.5'/>
<span className="text-xs italic leading-4">{`${playlistMinNps} - ${playlistMaxNps}`}</span>
</div>
<div className="h-full flex justify-center items-center gap-0.5">
<PersonIcon className='h-full aspect-square mt-0.5'/>
<span className="text-xs italic leading-4">{playlistNbMappers}</span>
</div>
<div className="h-full flex justify-center items-center gap-0.5">
<ClockIcon className='h-full aspect-square mt-0.5'/>
<span className="text-xs italic leading-4">{playlistDuration}</span>
</div>
<div className="h-full flex justify-center items-center gap-0.5">
<NpsIcon className='h-full aspect-square mt-0.5'/>
<span className="text-xs italic leading-4">{`${playlistMinNps} - ${playlistMaxNps}`}</span>
</div>
</div>
</div>
<footer className="flex justify-center items-center gap-2 h-8 mt-2.5">
<BsmButton className="rounded-md text-center h-full grow basis-0 flex justify-center items-center" typeColor="cancel" text="misc.cancel" withBar={false} onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })}/>
<BsmButton className="rounded-md text-center h-full grow basis-0 flex justify-center items-center" typeColor="primary" text="playlist.continue" withBar={false} onClick={handleContinue}/>
</footer>
</div>
)
}
<footer className="flex justify-center items-center gap-2 h-8 mt-2.5">
<BsmButton className="rounded-md text-center h-full grow basis-0 flex justify-center items-center" typeColor="cancel" text="misc.cancel" withBar={false} onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })}/>
<BsmButton className="rounded-md text-center h-full grow basis-0 flex justify-center items-center" typeColor="primary" text="playlist.continue" withBar={false} onClick={handleContinue}/>
</footer>
</div>
);
})()}
</div>
)
@@ -66,7 +66,7 @@ export const BsvPlaylistDetailsModal: ModalComponent<void, Props> = ({ resolver,
}, [playlistMaps, currentMapDownload, downloadingMaps, installedMaps])
const renderMapItem = useCallback((downloadableMap: DownloadableMap) => {
const map = downloadableMap.map;
const { map } = downloadableMap;
const downloadable = !downloadableMap.isOwned && !downloadableMap.isInQueue;
const cancelable = downloadableMap.isInQueue && !downloadableMap.idDownloading;
@@ -81,7 +81,7 @@ export const LocalPlaylistDetailsModal: ModalComponent<void, Props> = ({resolver
if (!Array.isArray(installedMaps) || !localPlaylist) {
return (
<div className="grow bg-red-400">
Error
</div>
);
}
@@ -60,7 +60,7 @@ export function Modal() {
return (
<AnimatePresence>
{currentModal ? <motion.span key={crypto.randomUUID()} onClick={() => currentModal.resolver({ exitCode: ModalExitCode.NO_CHOICE })} className="fixed size-full bg-black z-[90]" initial={{ opacity: 0 }} animate={{ opacity: currentModal && 0.6 }} exit={{ opacity: 0 }} transition={{ duration: 0.2 }} /> : <></>}
{currentModal ? <motion.span key={crypto.randomUUID()} onClick={() => currentModal.resolver({ exitCode: ModalExitCode.NO_CHOICE })} className="fixed size-full bg-black z-[90]" initial={{ opacity: 0 }} animate={{ opacity: currentModal && 0.6 }} exit={{ opacity: 0 }} transition={{ duration: 0.2 }} /> : undefined}
{modals?.map(modal => (
<motion.div key={crypto.randomUUID()} className="fixed z-[90] top-1/2 left-1/2" initial={{ y: "100vh", x: "-50%" }} animate={{y: "-50%", scale: modal === currentModal ? 1 : 0, opacity: modal === currentModal ? 1 : 0, display: modal === currentModal ? "block" : ["block", "none"]}} exit={{ y: "100vh" }}>
{renderModal(modal)}
@@ -19,6 +19,7 @@ import { ModelsDownloaderService } from "renderer/services/models-management/mod
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { BsContentLoader } from "../shared/bs-content-loader.component";
import { VirtualScroll } from "../shared/virtual-scroll/virtual-scroll.component";
import { noop } from "shared/helpers/function.helpers";
type Props = {
className?: string;
@@ -96,7 +97,7 @@ export const ModelsGrid = forwardRef<unknown, Props>(({ className, version, type
useOnUpdate(() => {
if (!active && !models) {
return;
return noop;
}
const onLinkStateChangeCb = (action: VersionLinkerAction) => {
@@ -204,7 +205,7 @@ export const ModelsGrid = forwardRef<unknown, Props>(({ className, version, type
const renderModel = useCallback((renderableModel: RenderableModel) => {
const model = renderableModel.model;
const { model } = renderableModel;
return (
<ModelItem
@@ -1,12 +1,13 @@
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { BsmButton } from "../../shared/bsm-button.component";
import { MouseEventHandler } from "react";
type Props = {
children: JSX.Element;
isDownloading?: boolean;
progress?: number;
isActive?: boolean;
onCancel?: (e: React.MouseEvent) => void;
onCancel?: MouseEventHandler<HTMLDivElement>;
};
export function NavBarItem({ progress, isDownloading, children, isActive, onCancel }: Props) {
@@ -15,7 +15,7 @@ export function SettingContainer({ id, className, title, minorTitle, description
const t = useTranslation();
if (os && os !== window.electron.platform) {
return <></>;
return undefined;
}
return (
@@ -1,3 +1,4 @@
import { MouseEventHandler } from "react";
import { LinkBtnProps, LinkButton } from "renderer/components/shared/link-button.component";
import { SvgIcon } from "renderer/components/svgs/svg-icon.type";
import { useTranslation } from "renderer/hooks/use-translation.hook";
@@ -17,7 +18,7 @@ export const BsContentTabItem: BsContentTabItemComponent = ({ text, icon: Icon,
const t = useTranslation();
const handleClick = (e: React.MouseEvent<HTMLLIElement, MouseEvent>) => {
const handleClick: MouseEventHandler<HTMLLIElement> = (e) => {
e.preventDefault();
e.stopPropagation();
onClick(value);
@@ -1,4 +1,4 @@
import { forwardRef, useImperativeHandle, useRef, useState } from "react";
import { forwardRef, LegacyRef, useImperativeHandle, useRef, useState } from "react";
import { BsmIconType, BsmIcon } from "../svgs/bsm-icon.component";
import { BsmButton } from "./bsm-button.component";
import { useTranslation } from "renderer/hooks/use-translation.hook";
@@ -62,10 +62,8 @@ export const BsmDropdownButton = forwardRef(({ className, items, align, withBar
return "right-0 origin-top-right";
})();
console.log("alignClass", alignClass);
return (
<div ref={ref as unknown as React.LegacyRef<HTMLDivElement>} className={className}>
<div ref={ref as unknown as LegacyRef<HTMLDivElement>} className={className}>
<BsmButton onClick={() => setExpanded(!expanded)} className={buttonClassName ?? defaultButtonClassName} icon={icon} active={expanded} textClassName={textClassName} onClickOutside={handleClickOutside} withBar={withBar} text={text} />
<div className={`py-1 w-fit absolute cursor-pointer top-[calc(100%-4px)] rounded-md bg-inherit text-sm text-gray-800 dark:text-gray-200 shadow-md shadow-black transition-[scale] duration-150 ease-in-out ${alignClass}`} style={{ scale: expanded ? "1" : "0", translate: `0 ${menuTranslationY}` }}>
{items?.map(
@@ -1,3 +1,4 @@
import { CSSProperties, ReactNode } from "react";
import { useService } from "renderer/hooks/use-service.hook";
import { LinkOpenerService } from "renderer/services/link-opener.service";
@@ -5,8 +6,8 @@ type Props = {
className?: string;
href?: string;
internal?: boolean;
children?: React.ReactNode;
style?: React.CSSProperties;
children?: ReactNode;
style?: CSSProperties;
};
export function BsmLink({ className, href, children, style, internal }: Props) {
@@ -15,8 +16,6 @@ export function BsmLink({ className, href, children, style, internal }: Props) {
const openLink = () => {
console.log("Opening link", href, internal);
if (!href) {
return;
}
@@ -1,4 +1,4 @@
import { DetailedHTMLProps, Fragment } from "react";
import { DetailedHTMLProps, Fragment, HTMLAttributes } from "react";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { LaserSlider } from "./laser-slider.component";
@@ -8,7 +8,7 @@ type Props = {
tabsText: string[];
onTabChange: (index: number) => void;
className?: string;
renderTab?: (props: DetailedHTMLProps<React.HTMLAttributes<any>, any>, text: string, index?: number) => JSX.Element;
renderTab?: (props: DetailedHTMLProps<HTMLAttributes<any>, any>, text: string, index?: number) => JSX.Element;
};
export function TabNavBar(props: Props) {
@@ -1,4 +1,6 @@
export function ChevronTopIcon(props: React.ComponentProps<"svg">) {
import { ComponentProps } from "react";
export function ChevronTopIcon(props: ComponentProps<"svg">) {
return (
<svg {...props} fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path d="M260.811-366.051q-11.021-11.021-11.355-26.521-.333-15.5 10.689-26.522l193.333-193.333q5.892-5.892 12.475-8.457 6.583-2.565 14.047-2.565t14.047 2.565q6.583 2.565 12.475 8.457l193.667 193q11.021 10.514 10.981 26.054-.039 15.54-11.315 26.656-11.021 11.021-26.855 11.021-15.833 0-26.855-11.021L480-532.196 313.521-365.051q-10.355 11.022-25.894 10.649-15.54-.373-26.816-11.649Z" />
@@ -1,7 +1,7 @@
import { BsmButton } from "renderer/components/shared/bsm-button.component";
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
import { Mod } from "shared/models/mods/mod.interface";
import { CSSProperties, useRef } from "react";
import { CSSProperties, MouseEvent, useRef } from "react";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { BsModsManagerService } from "renderer/services/bs-mods-manager.service";
import { useObservable } from "renderer/hooks/use-observable.hook";
@@ -34,11 +34,11 @@ export function ModItem({ className, mod, installedVersion, isDependency, isSele
modsManager.uninstallMod(mod, pageState.getState());
};
const handleWantInfo = (e: React.MouseEvent<Element, MouseEvent>) => {
const handleWantInfo = (e: MouseEvent<Element, MouseEvent>) => {
e.preventDefault();
onWantInfo(mod);
};
const handleOnChange = (e: React.MouseEvent<Element, MouseEvent>) => {
const handleOnChange = (e: MouseEvent<Element, MouseEvent>) => {
e.preventDefault();
onChange(!isChecked);
};
+2 -2
View File
@@ -1,6 +1,6 @@
import { MutableRefObject, useEffect } from "react";
import { ComponentProps, MutableRefObject, useEffect } from "react";
export function useClickOutside(ref: MutableRefObject<any>, handler: React.ComponentProps<any>["onClick"]) {
export function useClickOutside(ref: MutableRefObject<any>, handler: ComponentProps<any>["onClick"]) {
useEffect(() => {
if (!handler) {
return () => {};
+1 -1
View File
@@ -1,4 +1,4 @@
import { RefObject, useEffect, useState } from "react";
import { RefObject, useEffect } from "react";
import { useConstant } from "./use-constant.hook";
import { BehaviorSubject, debounceTime, distinctUntilChanged } from "rxjs";
import { useObservable } from "./use-observable.hook";
@@ -1,13 +1,14 @@
import { Observable } from "rxjs";
import { useState, useEffect, Dispatch, SetStateAction } from "react";
import { noop } from "shared/helpers/function.helpers";
export function useSwitchableObservable<T>(observable?: Observable<T>, clearOnSwitch = true): [T, React.Dispatch<React.SetStateAction<Observable<T>>>, Observable<T>, Dispatch<SetStateAction<T>>] {
export function useSwitchableObservable<T>(observable?: Observable<T>, clearOnSwitch = true): [T, Dispatch<SetStateAction<Observable<T>>>, Observable<T>, Dispatch<SetStateAction<T>>] {
const [currentObs, setCurrentObs] = useState(observable);
const [obsValue, setObsValue] = useState<T>();
useEffect(() => {
if (!currentObs) {
return;
return noop;
}
if (clearOnSwitch) {
setObsValue(() => null);
@@ -1,3 +1,4 @@
/* eslint-disable no-redeclare */
import { DefaultConfigKey } from "renderer/config/default-configuration.config";
import { ConfigurationService } from "renderer/services/configuration.service";
import { useObservable } from "./use-observable.hook";
@@ -11,6 +11,7 @@ import { useService } from "renderer/hooks/use-service.hook";
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";
export const AvailableVersionsContext = createContext<{ selectedVersion: BSVersion; setSelectedVersion: (version: BSVersion) => void }>(null);
@@ -28,7 +29,7 @@ export function AvailableVersionsList() {
const startDownload = async () => {
return bsDownloader.downloadVersion(selectedVersion)
.catch(console.log)
.catch(logRenderError)
.finally(() => setSelectedVersion(null));
};
@@ -40,19 +41,19 @@ export function AvailableVersionsList() {
return Promise.all([
versionManager.askAvailableVersions(),
versionManager.askInstalledVersions()
]).catch(console.log);
]).catch(logRenderError);
}
return (
<div className="relative h-full w-full flex items-center flex-col pt-2">
<Slideshow className="absolute w-full h-full top-0" />
<h1 className="text-gray-100 text-2xl mb-4 z-[1]">{t("pages.available-versions.title")}</h1>
<AvailableVersionsContext.Provider value={contextValue}>
<AvailableVersionsSlider />
</AvailableVersionsContext.Provider>
<AnimatePresence>
{selectedVersion && !downloading && (
<motion.div initial={{ y: "150%" }} animate={{ y: "0%" }} exit={{ y: "150%" }} className="absolute bottom-5" onClick={startDownload}>
+14 -4
View File
@@ -163,7 +163,7 @@ export function SettingsPage() {
return;
}
ipcService.sendV2<{ canceled: boolean; filePaths: string[] }>("choose-file").toPromise().then(res => {
lastValueFrom(ipcService.sendV2("choose-file")).then(res => {
if (!res.canceled && res.filePaths?.length) {
const protonPath = res.filePaths[0];
setProtonPath(protonPath);
@@ -243,9 +243,14 @@ export function SettingsPage() {
const switchDeepLink = async (manager: MapsManagerService | PlaylistsManagerService | ModelsManagerService, enable: boolean, showNotification: boolean, setter: Dispatch<SetStateAction<boolean>>) => {
const res = await (enable ? manager.enableDeepLink() : manager.disableDeepLink()).then(() => true).catch(() => false);
if(showNotification){
res ? showDeepLinkSuccess(enable) : showDeepLinkError(enable);
if(showNotification && res){
showDeepLinkSuccess(enable)
}
else if(showNotification && !res){
showDeepLinkError(enable);
}
const isEnable = await manager.isDeepLinksEnabled();
setter(() => isEnable);
return res;
@@ -257,7 +262,12 @@ export function SettingsPage() {
const toogleAllDeepLinks = async () => {
const res = (await Promise.all([switchDeepLink(mapsManager, !allDeepLinkEnabled, false, setMapDeepLinksEnabled), switchDeepLink(playlistsManager, !allDeepLinkEnabled, false, setPlaylistsDeepLinkEnabled), switchDeepLink(modelsManager, !allDeepLinkEnabled, false, setModelsDeepLinkEnabled)])).every(activation => activation === true);
res ? showDeepLinkSuccess(allDeepLinkEnabled) : showDeepLinkError(allDeepLinkEnabled);
if(res){
showDeepLinkSuccess(allDeepLinkEnabled);
}
else{
showDeepLinkError(allDeepLinkEnabled);
}
};
return (
@@ -1,5 +1,6 @@
import { BehaviorSubject, Observable, map } from 'rxjs';
import { ConfigurationService } from './configuration.service';
import { logRenderError } from 'renderer';
interface PlayerVolume {
volume: number;
@@ -51,7 +52,7 @@ export class AudioPlayerService {
if(!sound){ return; }
this.player.src = sound.src;
this._bpm$.next(sound.bpm || 0);
this.player.play().catch(error => console.error('Error playing sound:', error));
this.player.play().catch(logRenderError);
});
this._volume$.subscribe(volume => {
@@ -79,7 +80,7 @@ export class AudioPlayerService {
return this.player.play().then(() => {
this._playing$.next(true);
}).catch(error => console.error('Error resuming sound:', error));
}).catch(logRenderError);
}
public setVolume(volume: number): void {
@@ -1,5 +1,5 @@
import { BSVersion } from "shared/bs-version.interface";
import { BehaviorSubject, Observable, Subscription, lastValueFrom, map, shareReplay, throwError } from "rxjs";
import { BehaviorSubject, Observable, Subscription, lastValueFrom, shareReplay, throwError } from "rxjs";
import { IpcService } from "./ipc.service";
import { ModalExitCode, ModalService } from "./modale.service";
import { NotificationService } from "./notification.service";
+4 -1
View File
@@ -81,10 +81,13 @@ export class I18nService {
translated = getProperty(this.dictionary, translationKey) ?? translationKey;
this.cache.set(translationKey, translated);
}
args &&
if (args) {
Object.keys(args).forEach(key => {
translated = translated.replaceAll(`{${key}}`, args[key]);
});
}
return translated;
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ export class ModalService {
const promise = new Promise<ModalResponse<T>>(resolve => {
resolver = resolve as (value: ModalResponse | PromiseLike<ModalResponse>) => void;
});
const modalObj = {modal: modal as ModalComponent, resolver: resolver, options};
const modalObj = {modal: modal as ModalComponent, resolver, options};
this._modalToShow$.next([...this._modalToShow$.getValue(), modalObj]);
promise.then(() => {
@@ -1,6 +1,6 @@
import { MSModelType } from "shared/models/models/model-saber.model";
import { IpcService } from "../ipc.service";
import { FolderLinkState, VersionFolderLinkerService, VersionLinkerActionListener, VersionLinkerActionType } from "../version-folder-linker.service";
import { FolderLinkState, VersionFolderLinkerService, VersionLinkerActionListener } from "../version-folder-linker.service";
import { MODEL_TYPE_FOLDERS } from "shared/models/models/constants";
import { Observable, lastValueFrom, map } from "rxjs";
import { BSVersion } from "shared/bs-version.interface";
@@ -1,4 +1,4 @@
import { BehaviorSubject, Observable, Subject, Subscription, distinctUntilChanged, filter, lastValueFrom, map, shareReplay, take, takeUntil, takeWhile, tap } from "rxjs";
import { BehaviorSubject, Observable, Subject, Subscription, distinctUntilChanged, filter, lastValueFrom, map, shareReplay, take, takeUntil, tap } from "rxjs";
import { BPList, DownloadPlaylistProgressionData } from "shared/models/playlists/playlist.interface";
import { IpcService } from "./ipc.service";
import { ProgressBarService } from "./progress-bar.service";
@@ -43,7 +43,7 @@ export class PlaylistDownloaderService {
return new Observable<Progression<DownloadPlaylistProgressionData>>(subscriber => {
let subs: Subscription[] = [];
const subs: Subscription[] = [];
let canShowProgress = false;
(async () => {
@@ -1,6 +1,6 @@
import { BSVersion } from "shared/bs-version.interface";
import { IpcService } from "./ipc.service";
import { Observable, lastValueFrom, of, switchMap } from "rxjs";
import { Observable, lastValueFrom } from "rxjs";
import { FolderLinkState, VersionFolderLinkerService } from "./version-folder-linker.service";
import { Progression } from "main/helpers/fs.helpers";
import { LocalBPList, LocalBPListsDetails } from "shared/models/playlists/local-playlist.models";
@@ -5,7 +5,6 @@ import { NotificationService } from "./notification.service";
import { CSSProperties } from "react";
import { ProgressionInterface } from "shared/models/progress-bar";
import { Progression } from "main/helpers/fs.helpers";
import { satisfies } from "semver";
export class ProgressBarService {
private static instance: ProgressBarService;
@@ -1,4 +1,4 @@
import { LinkOptions, UnlinkOptions } from "main/services/folder-linker.service";
import { LinkOptions } from "main/services/folder-linker.service";
import { map, distinctUntilChanged, filter, mergeMap, shareReplay } from "rxjs/operators";
import { BehaviorSubject, Observable, of } from "rxjs";
import { BSVersion } from "shared/bs-version.interface";
@@ -33,12 +33,9 @@ export default function OneClickDownloadMap() {
progressBar.open();
console.log("AAAA", mapId, isHash);
const promise = (async () => {
const mapDetails = isHash === "true" ? (await bsv.getMapDetailsFromHashs([mapId])).at(0) : await bsv.getMapDetailsById(mapId);
console.log(mapDetails);
setMapInfo(() => mapDetails);
@@ -11,7 +11,6 @@ import { map, filter, take, lastValueFrom } from "rxjs";
import defaultImage from "../../../../assets/images/default-version-img.jpg";
import { useService } from "renderer/hooks/use-service.hook";
import { useConstant } from "renderer/hooks/use-constant.hook";
import { BPList } from "shared/models/playlists/playlist.interface";
import { useOnUpdate } from "renderer/hooks/use-on-update.hook";
import { useWindowArgs } from "renderer/hooks/use-window-args.hook";
import { useWindowControls } from "renderer/hooks/use-window-controls.hook";
@@ -104,11 +104,11 @@ export abstract class MapItemComponentPropsMapper {
public static from(mapDetails: BsmLocalMap|BsvMapDetail|SongDetails): MapItemComponentProps<BsmLocalMap|BsvMapDetail|SongDetails> {
if ((mapDetails as BsmLocalMap).rawInfo) {
return MapItemComponentPropsMapper.fromBsmLocalMap(mapDetails as BsmLocalMap) as MapItemComponentProps<BsmLocalMap|BsvMapDetail|SongDetails>;
} else if ((mapDetails as BsvMapDetail).metadata) {
return MapItemComponentPropsMapper.fromBsvMapDetail(mapDetails as BsvMapDetail) as MapItemComponentProps<BsmLocalMap|BsvMapDetail|SongDetails>;;
} else {
return MapItemComponentPropsMapper.fromSongDetails(mapDetails as SongDetails) as MapItemComponentProps<BsmLocalMap|BsvMapDetail|SongDetails>;;
}
if ((mapDetails as BsvMapDetail).metadata) {
return MapItemComponentPropsMapper.fromBsvMapDetail(mapDetails as BsvMapDetail) as MapItemComponentProps<BsmLocalMap|BsvMapDetail|SongDetails>;
}
return MapItemComponentPropsMapper.fromSongDetails(mapDetails as SongDetails) as MapItemComponentProps<BsmLocalMap|BsvMapDetail|SongDetails>;
}
}
+1
View File
@@ -116,6 +116,7 @@ export interface IpcChannelMapping {
/* ** os-controls-ipcs ** */
"new-window": { request: string, response: void };
"choose-folder": { request: string, response: OpenDialogReturnValue };
"choose-file": { request: string, response: OpenDialogReturnValue }
"choose-image": { request: { multiple?: boolean, base64?: boolean }, response: string[] }
"window.progression": { request: number, response: void };
"save-file": { request: { filename?: string; filters?: FileFilter[] }, response: string };
+1 -1
View File
@@ -216,7 +216,7 @@ export enum MapSpecificity {
}
//[ Admin, Uploader, SageScore, None ]
// [ Admin, Uploader, SageScore, None ]
export const BsvDeclaredAi = {
Admin: "Admin",
Uploader: "Uploader",
+3 -1
View File
@@ -1,4 +1,6 @@
import { FileFilter } from "electron";
export interface OpenSaveDialogOption {
filename?: string;
filters?: Electron.FileFilter[];
filters?: FileFilter[];
}