mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
add filters panel (partial)
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { MapFilter, MapTag } from "shared/models/maps/beat-saver.model"
|
||||
import {motion} from "framer-motion"
|
||||
import { MutableRefObject, useRef } from "react"
|
||||
import { MAP_TYPES } from "renderer/partials/map-tags/map-types"
|
||||
import { MAP_STYLES } from "renderer/partials/map-tags/map-styles"
|
||||
|
||||
export type Props = {
|
||||
className?: string,
|
||||
ref?: MutableRefObject<undefined>
|
||||
playlist?: boolean,
|
||||
filter?: MapFilter
|
||||
onChange?: () => void
|
||||
}
|
||||
|
||||
export function FilterPanel({className, ref, playlist = false, filter, onChange}: Props) {
|
||||
|
||||
return !playlist ? (
|
||||
<motion.div ref={ref} className={className} initial={{scale: 0}} animate={{scale: 1}} exit={{scale: 0}}>
|
||||
<div className="w-full h-full">
|
||||
<div className="flex flex-row flex-wrap grow">
|
||||
{[...MAP_TYPES, ...MAP_STYLES].map(tag => (
|
||||
<span>{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
@@ -43,6 +43,8 @@ export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autor
|
||||
const linkOpener = LinkOpenerService.getInstance();
|
||||
const audioPlayer = AudioPlayerService.getInstance();
|
||||
|
||||
|
||||
|
||||
const color = useThemeColor("first-color");
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
+14
-17
@@ -1,9 +1,10 @@
|
||||
import { useState } from "react"
|
||||
import { BSVersion } from "shared/bs-version.interface"
|
||||
import { TabNavBar } from "../shared/tab-nav-bar.component"
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component"
|
||||
import { LocalMapsListPanel } from "./local-maps-list-panel.component"
|
||||
import {AnimatePresence, motion} from "framer-motion"
|
||||
import { BsmDropdownButton } from "../shared/bsm-dropdown-button.component"
|
||||
import { FilterPanel } from "./filter-panel.component"
|
||||
|
||||
type Props = {
|
||||
oneBlock?: boolean,
|
||||
@@ -16,25 +17,21 @@ export function MapsPlaylistsPanel({version, oneBlock = false}: Props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!oneBlock && <TabNavBar className="mb-8 w-72" tabsText={["Maps", "Playlists"]} onTabChange={setTabIndex}/>}
|
||||
{!oneBlock && <TabNavBar className="mb-8 w-72" tabsText={["misc.maps", "Playlists"]} onTabChange={setTabIndex}/>}
|
||||
<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">
|
||||
<motion.div layout className="h-full rounded-full bg-main-color-2 grow flex items-center gap-1 px-2">
|
||||
<input type="text" name="" id="" className="bg-main-color-1 rounded-full grow px-2" placeholder="Rechercher" />
|
||||
<BsmIcon className="h-full w-fit py-1" icon="trash"/>
|
||||
</motion.div>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{tabIndex === 0 && (
|
||||
<motion.div layout className="h-full rounded-full bg-main-color-2 flex justify-center items-center px-2" initial={{scale: tabIndex === 0 ? 1 : 0}} animate={{scale: tabIndex === 0 ? 1 : 0}} exit={{scale: 0}}>
|
||||
<BsmIcon className="h-full w-fit py-1" icon="trash"/>
|
||||
<span>Filtres</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<nav className="w-full shrink-0 flex h-[35px] justify-center px-40 gap-2 mb-3">
|
||||
<div className="h-full rounded-full bg-main-color-2 grow p-[6px]">
|
||||
<input type="text" name="" id="" className="h-full w-full bg-main-color-1 rounded-full px-2" placeholder="Rechercher" />
|
||||
</div>
|
||||
<BsmDropdownButton className="h-full relative z-10 flex justify-center" buttonClassName="flex items-center justify-center h-full rounded-full px-2 py-1" icon="search" text="Filtres" withBar={false}>
|
||||
<FilterPanel className="absolute top-[calc(100%+3px)] bg-main-color-3 origin-top w-[400px]"/>
|
||||
</BsmDropdownButton>
|
||||
<BsmDropdownButton className="h-full flex aspect-square relative rounded-full z-10" buttonClassName="rounded-full h-full w-full p-[6px]" icon="three-dots" withBar={false}>
|
||||
<></>
|
||||
</BsmDropdownButton>
|
||||
</nav>
|
||||
<div className="w-full h-full flex flex-col bg-main-color-2 rounded-md shadow-black shadow-md overflow-hidden">
|
||||
{oneBlock && <TabNavBar className="!rounded-none shadow-sm" tabsText={["Maps", "Playlists"]} onTabChange={setTabIndex}/>}
|
||||
{oneBlock && <TabNavBar className="!rounded-none shadow-sm" tabsText={["misc.maps", "Playlists"]} onTabChange={setTabIndex}/>}
|
||||
<div className="w-full grow min-h-0 flex flex-row items-center transition-transform duration-300" style={{transform: `translate(${-(tabIndex * 100)}%, 0)`}}>
|
||||
<LocalMapsListPanel className="w-full h-full shrink-0 flex flex-col" version={version}/>
|
||||
<div className="w-full h-full grow shrink-0">b</div>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { HexColorPicker } from "react-colorful";
|
||||
import { motion, AnimatePresence } from "framer-motion"
|
||||
import OutsideClickHandler from "react-outside-click-handler";
|
||||
import { useClickOutside } from "renderer/hooks/use-click-outside.hook";
|
||||
|
||||
export default function SettingColorChooser({color, onChange, pickerClassName}: {color?: string, onChange?: (color: string) => void, pickerClassName?: string}) {
|
||||
|
||||
const [colorVisible, setColorVisible] = useState(false);
|
||||
const ref = useRef(null);
|
||||
useClickOutside(ref, (e) => setColorVisible(false));
|
||||
|
||||
return (
|
||||
<OutsideClickHandler onOutsideClick={() => setColorVisible(false)}>
|
||||
<div className="relative cursor-pointer mx-3 h-full aspect-square flex flex-col items-center">
|
||||
return (
|
||||
<div ref={ref} className="relative cursor-pointer mx-3 h-full aspect-square flex flex-col items-center">
|
||||
<span className="z-[1] block h-full w-full border-2 border-white rounded-full" onClick={() => setColorVisible(!colorVisible)} style={{backgroundColor: color}}/>
|
||||
<AnimatePresence>
|
||||
{colorVisible &&
|
||||
<motion.div initial={{opacity: 0}} animate={{opacity: 1}} transition={{duration: .1}} exit={{opacity: 0}} className="fixed flex items-center justify-center translate-y-9 shadow-lg rounded-lg shadow-black">
|
||||
<div className="absolute w-2/4 aspect-square rotate-45 bg-light-main-color-3 dark:bg-main-color-3 -translate-y-12"></div>
|
||||
<div className="absolute w-2/4 aspect-square rotate-45 bg-light-main-color-3 dark:bg-main-color-3 -translate-y-12"/>
|
||||
<HexColorPicker color={color} onChange={onChange} className={pickerClassName} />
|
||||
</motion.div>
|
||||
}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</OutsideClickHandler>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { BsmIcon, BsmIconType } from "../svgs/bsm-icon.component"
|
||||
import OutsideClickHandler from 'react-outside-click-handler';
|
||||
import React, { MutableRefObject, Ref } from "react";
|
||||
import { useEffect, useRef, CSSProperties } from "react";
|
||||
import { BsmImage } from "./bsm-image.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useClickOutside } from "renderer/hooks/use-click-outside.hook";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { getCorrectTextColor } from "renderer/helpers/correct-text-color";
|
||||
|
||||
type BsmButtonType = "primary"|"success"|"cancel"|"error";
|
||||
|
||||
type propsType = {
|
||||
type Props = {
|
||||
className?: string,
|
||||
style?: React.CSSProperties,
|
||||
style?: CSSProperties,
|
||||
imgClassName?: string,
|
||||
iconClassName?: string,
|
||||
icon?: BsmIconType,
|
||||
@@ -26,43 +26,43 @@ type propsType = {
|
||||
color?: string,
|
||||
title?: string,
|
||||
iconColor?: string,
|
||||
textClassName?: string
|
||||
}
|
||||
|
||||
export function BsmButton({className, style, imgClassName, iconClassName, icon, image, text, type, active, withBar = true, disabled, onClickOutside, onClick, typeColor, color, title, iconColor}: propsType) {
|
||||
export function BsmButton({className, style, imgClassName, iconClassName, icon, image, text, type, active, withBar = true, disabled, onClickOutside, onClick, typeColor, color, title, iconColor, textClassName}: Props) {
|
||||
|
||||
const t = useTranslation();
|
||||
const secondColor = useThemeColor("second-color");
|
||||
const t = useTranslation();
|
||||
const secondColor = useThemeColor("second-color");
|
||||
const ref = useRef(null);
|
||||
useClickOutside(ref, onClickOutside);
|
||||
|
||||
const primaryColor = typeColor === "primary" && useThemeColor("first-color");
|
||||
const primaryColor = typeColor === "primary" && useThemeColor("first-color");
|
||||
|
||||
const textColor = (() => {
|
||||
if(primaryColor){ return getCorrectTextColor(primaryColor); }
|
||||
return typeColor ? "white" : undefined;
|
||||
})();
|
||||
const textColor = (() => {
|
||||
if(primaryColor){ return getCorrectTextColor(primaryColor); }
|
||||
return typeColor ? "white" : undefined;
|
||||
})();
|
||||
|
||||
const renderTypeColor = (() => {
|
||||
if(typeColor === "primary"){ return ""; }
|
||||
if(!typeColor){ return `bg-light-main-color-2 dark:bg-main-color-2 ${!withBar && "hover:bg-light-main-color-3 dark:hover:bg-main-color-3"}`; }
|
||||
if(typeColor === "cancel"){ return "bg-gray-500"; }
|
||||
if(typeColor === "error"){ return "bg-red-500"; }
|
||||
if(typeColor === "success"){ return "bg-green-500"; }
|
||||
return "";
|
||||
})()
|
||||
const renderTypeColor = (() => {
|
||||
if(typeColor === "primary"){ return ""; }
|
||||
if(!typeColor){ return `bg-light-main-color-2 dark:bg-main-color-2 ${!withBar && "hover:bg-light-main-color-3 dark:hover:bg-main-color-3"}`; }
|
||||
if(typeColor === "cancel"){ return "bg-gray-500"; }
|
||||
if(typeColor === "error"){ return "bg-red-500"; }
|
||||
if(typeColor === "success"){ return "bg-green-500"; }
|
||||
return "";
|
||||
})();
|
||||
|
||||
return (
|
||||
<OutsideClickHandler onOutsideClick={e => onClickOutside && onClickOutside(e)}>
|
||||
<div onClick={onClick} title={t(title)} className={`${className} overflow-hidden cursor-pointer group ${(!withBar && !disabled && (!!typeColor || !!color)) && "hover:brightness-[1.15]"} ${disabled && "brightness-75 cursor-not-allowed"} ${renderTypeColor}`} style={{...style, backgroundColor: primaryColor || color}}>
|
||||
{ image && <BsmImage image={image} className={imgClassName}/> }
|
||||
{ icon && <BsmIcon icon={icon} className={iconClassName ?? "h-full w-full text-gray-800 dark:text-white"} style={{color: iconColor}}/> }
|
||||
{text && (type === "submit" ? <button type="submit" className="w-full h-full" style={{...(!!textColor && {color: textColor})}}>{t(text)}</button> : <span style={{...(!!textColor && {color: `${textColor}`})}}>{t(text)}</span>)}
|
||||
{ withBar && (
|
||||
<div className="absolute bottom-0 left-0 w-full h-1 bg-current" style={{color: secondColor}}>
|
||||
<div className="absolute top-0 left-0 h-full w-full bg-current brightness-50"/>
|
||||
<div className={`absolute top-0 left-0 h-full w-full bg-inherit -translate-x-full group-hover:translate-x-0 transition-transform shadow-center shadow-current ${active && "translate-x-0"}`}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</OutsideClickHandler>
|
||||
|
||||
)
|
||||
return (
|
||||
<div ref={ref} onClick={onClick} title={t(title)} className={`${className} overflow-hidden cursor-pointer group ${(!withBar && !disabled && (!!typeColor || !!color)) && "hover:brightness-[1.15]"} ${disabled && "brightness-75 cursor-not-allowed"} ${renderTypeColor}`} style={{...style, backgroundColor: primaryColor || color}}>
|
||||
{ image && <BsmImage image={image} className={imgClassName}/> }
|
||||
{ icon && <BsmIcon icon={icon} className={iconClassName ?? "h-full w-full text-gray-800 dark:text-white"} style={{color: iconColor}}/> }
|
||||
{text && (type === "submit" ? <button type="submit" className={textClassName || "h-full w-full"} style={{...(!!textColor && {color: textColor})}}>{t(text)}</button> : <span style={{...(!!textColor && {color: `${textColor}`})}}>{t(text)}</span>)}
|
||||
{ withBar && (
|
||||
<div className="absolute bottom-0 left-0 w-full h-1 bg-current" style={{color: secondColor}}>
|
||||
<div className="absolute top-0 left-0 h-full w-full bg-current brightness-50"/>
|
||||
<div className={`absolute top-0 left-0 h-full w-full bg-inherit -translate-x-full group-hover:translate-x-0 transition-transform shadow-center shadow-current ${active && "translate-x-0"}`}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,31 +1,58 @@
|
||||
import { useState } from "react"
|
||||
import { BsmIconType } from "../svgs/bsm-icon.component"
|
||||
import { useRef, useState } from "react"
|
||||
import { BsmIconType, BsmIcon } from "../svgs/bsm-icon.component"
|
||||
import { BsmButton } from "./bsm-button.component"
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component"
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook"
|
||||
import { AnimatePresence } from "framer-motion"
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useClickOutside } from "renderer/hooks/use-click-outside.hook"
|
||||
|
||||
export interface DropDownItem {text: string, icon?: BsmIconType, onClick?: () => void}
|
||||
|
||||
type Props = {className?: string, items?: DropDownItem[], align?: "left"|"right", withBar?: boolean, icon?: BsmIconType, buttonClassName?: string, menuTranslationY?: string|number}
|
||||
type Props = {
|
||||
className?: string,
|
||||
items?: DropDownItem[],
|
||||
align?: "left"|"right",
|
||||
withBar?: boolean,
|
||||
icon?: BsmIconType,
|
||||
buttonClassName?: string,
|
||||
menuTranslationY?: string|number
|
||||
children?: JSX.Element,
|
||||
text?: string,
|
||||
textClassName?: string
|
||||
}
|
||||
|
||||
export function BsmDropdownButton({className, items, align, withBar = true, icon = "settings", buttonClassName, menuTranslationY}: Props) {
|
||||
export function BsmDropdownButton({className, items, align, withBar = true, icon = "settings", buttonClassName, menuTranslationY, children, text, textClassName}: Props) {
|
||||
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const t = useTranslation();
|
||||
const ref = useRef(null)
|
||||
useClickOutside(ref, () => setExpanded(false));
|
||||
|
||||
const defaultButtonClassName = "relative z-[1] p-1 rounded-md text-inherit w-full h-full shadow-md shadow-black"
|
||||
|
||||
const handleClickOutside = () => {
|
||||
if(children){ return; }
|
||||
setExpanded(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
<BsmButton onClick={() => setExpanded(!expanded)} className={buttonClassName ?? defaultButtonClassName} icon={icon} active={expanded} onClickOutside={() => {setExpanded(false)}} withBar={withBar}/>
|
||||
<div ref={ref} className={className}>
|
||||
<BsmButton onClick={() => setExpanded(!expanded)} className={buttonClassName ?? defaultButtonClassName} icon={icon} active={expanded} 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] ease-in-out ${align === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"}`} style={{scale: expanded ? "1" : "0", translate: `0 ${menuTranslationY}`}}>
|
||||
{ items?.map((i, index) => !!i &&(
|
||||
<div key={index} onClick={!!i.onClick ? i.onClick : undefined} className="flex w-full px-3 py-2 hover:backdrop-brightness-150">
|
||||
{i.icon && <BsmIcon icon={i.icon} className="h-5 w-5 mr-1 text-inherit"></BsmIcon>}
|
||||
{ items?.map((i) => (
|
||||
<div key={uuidv4()} onClick={() => i.onClick?.()} className="flex w-full px-3 py-2 hover:backdrop-brightness-150">
|
||||
{i.icon && <BsmIcon icon={i.icon} className="h-5 w-5 mr-1 text-inherit"/>}
|
||||
<span className="w-max">{t(i.text)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{!!children && (
|
||||
<AnimatePresence>
|
||||
{expanded && (
|
||||
children
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { MutableRefObject, useEffect } from "react";
|
||||
|
||||
export function useClickOutside(ref: MutableRefObject<any>, handler: (e: MouseEvent) => void) {
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if(!handler){ return () => {}; }
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(event.target)) {
|
||||
handler?.(event);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
};
|
||||
|
||||
}, [ref]);
|
||||
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export function VersionViewer() {
|
||||
<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} oneBlock></MapsPlaylistsPanel>
|
||||
<MapsPlaylistsPanel version={state} oneBlock/>
|
||||
</div>
|
||||
<ModsSlide version={state}/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { MapStyle } from "shared/models/maps/beat-saver.model";
|
||||
|
||||
export const MAP_STYLES: MapStyle[] = [
|
||||
"DanceStyle",
|
||||
"Swing",
|
||||
"Nightcore",
|
||||
"Folk",
|
||||
"Family",
|
||||
"Ambient",
|
||||
"Funk",
|
||||
"Jazz",
|
||||
"Classical",
|
||||
"Soul",
|
||||
"Speedcore",
|
||||
"Punk",
|
||||
"RB",
|
||||
"Holiday",
|
||||
"Vocaloid",
|
||||
"JRock",
|
||||
"Trance",
|
||||
"DrumBass",
|
||||
"Comedy",
|
||||
"Instrumental",
|
||||
"Hardcore",
|
||||
"KPop",
|
||||
"Indie",
|
||||
"Techno",
|
||||
"House",
|
||||
"Game",
|
||||
"Film",
|
||||
"Alt",
|
||||
"Dubstep",
|
||||
"Metal",
|
||||
"Anime",
|
||||
"HipHop",
|
||||
"JPop",
|
||||
"Rock",
|
||||
"Pop",
|
||||
"Electronic"
|
||||
];
|
||||
@@ -0,0 +1,11 @@
|
||||
import { MapType } from "shared/models/maps/beat-saver.model";
|
||||
|
||||
export const MAP_TYPES: MapType[] = [
|
||||
"Accuracy",
|
||||
"Balanced",
|
||||
"Challenge",
|
||||
"Dance",
|
||||
"Fitness",
|
||||
"Speed",
|
||||
"Tech"
|
||||
];
|
||||
@@ -14,6 +14,7 @@ export class BeatSaverService {
|
||||
|
||||
private readonly bsaverApi: BeatSaverApiService;
|
||||
|
||||
|
||||
private constructor(){
|
||||
this.bsaverApi = BeatSaverApiService.getInstance();
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface BsvMapDetail {
|
||||
qualified: boolean,
|
||||
ranked: boolean,
|
||||
stats: BsvMapStats,
|
||||
tags: string[],
|
||||
tags: MapTag[],
|
||||
updatedAt: BsvInstant,
|
||||
uploaded: BsvInstant,
|
||||
uploader: BsvUserDetail,
|
||||
@@ -141,4 +141,27 @@ export interface BsvMapParitySummary {
|
||||
}
|
||||
|
||||
export type BsvMapCharacteristic = ("Standard" | "OneSaber" | "NoArrows" | "90Degree" | "360Degree" | "Lightshow" | "Lawless")
|
||||
export type BsvMapDifficultyType = ("Easy" | "Normal" | "Hard" | "Expert" | "ExpertPlus")
|
||||
export type BsvMapDifficultyType = ("Easy" | "Normal" | "Hard" | "Expert" | "ExpertPlus")
|
||||
|
||||
export type MapStyle = ("DanceStyle" | "Swing" | "Nightcore" | "Folk" | "Family" | "Ambient" | "Funk" | "Jazz" | "Classical" | "Soul" | "Speedcore" | "Punk" | "RB" | "Holiday" | "Vocaloid" | "JRock" | "Trance" | "DrumBass" | "Comedy" | "Instrumental" | "Hardcore" | "KPop" | "Indie" | "Techno" | "House" | "Game" | "Film" | "Alt" | "Dubstep" | "Metal" | "Anime" | "HipHop" | "JPop" | "Rock" | "Pop" | "Electronic")
|
||||
export type MapType = ("Accuracy" | "Balanced" | "Challenge" | "Dance" | "Fitness" | "Speed" | "Tech")
|
||||
export type MapTag = MapStyle | MapType
|
||||
|
||||
export interface MapFilter {
|
||||
automapper?: boolean,
|
||||
chroma?: boolean,
|
||||
cinema?: boolean,
|
||||
noodle?: boolean,
|
||||
curated?: boolean,
|
||||
verified?: boolean,
|
||||
from?: number,
|
||||
to?: number,
|
||||
fullSpread?: boolean,
|
||||
ranked?: boolean,
|
||||
minDuration?: number,
|
||||
maxDuration?: number,
|
||||
minNps?: number,
|
||||
maxNps?: number,
|
||||
enabledTags?: MapTag[],
|
||||
excludedTags?: MapTag[]
|
||||
}
|
||||
Reference in New Issue
Block a user