mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
add info and optimize rendering
This commit is contained in:
Generated
+19
-14
@@ -1713,7 +1713,7 @@
|
||||
},
|
||||
"@types/plist": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types%2fplist/-/plist-3.0.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.2.tgz",
|
||||
"integrity": "sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
@@ -1878,7 +1878,7 @@
|
||||
},
|
||||
"@types/verror": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@types%2fverror/-/verror-1.10.6.tgz",
|
||||
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz",
|
||||
"integrity": "sha512-NNm+gdePAX1VGvPcGZCDKQZKYSiAWigKhKaz5KF94hG6f2s8de9Ow5+7AbXoeKxL8gavZfk4UquSAygOF2duEQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
@@ -2721,7 +2721,7 @@
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
|
||||
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@@ -3146,13 +3146,13 @@
|
||||
"buffer-equal": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz",
|
||||
"integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=",
|
||||
"integrity": "sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"buffer-fill": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
|
||||
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
|
||||
"integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"buffer-from": {
|
||||
@@ -3449,7 +3449,7 @@
|
||||
"chromium-pickle-js": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz",
|
||||
"integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=",
|
||||
"integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==",
|
||||
"dev": true
|
||||
},
|
||||
"ci-info": {
|
||||
@@ -3615,7 +3615,7 @@
|
||||
"colors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
|
||||
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
|
||||
"integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==",
|
||||
"dev": true
|
||||
},
|
||||
"combined-stream": {
|
||||
@@ -3642,7 +3642,7 @@
|
||||
"compare-version": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
|
||||
"integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=",
|
||||
"integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==",
|
||||
"dev": true
|
||||
},
|
||||
"compress-brotli": {
|
||||
@@ -4440,7 +4440,7 @@
|
||||
"commander": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
|
||||
"integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
|
||||
"integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-readlink": ">= 1.0.0"
|
||||
@@ -4893,7 +4893,7 @@
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -6865,7 +6865,7 @@
|
||||
"graceful-readlink": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
||||
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
|
||||
"integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==",
|
||||
"dev": true
|
||||
},
|
||||
"grapheme-splitter": {
|
||||
@@ -12257,7 +12257,7 @@
|
||||
"truncate-utf8-bytes": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||
"integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=",
|
||||
"integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"utf8-byte-length": "^1.0.1"
|
||||
@@ -12558,6 +12558,11 @@
|
||||
"prepend-http": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"use-delayed-state": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/use-delayed-state/-/use-delayed-state-1.2.0.tgz",
|
||||
"integrity": "sha512-i/7nyyHZsJ+2qGwbb9XGTViBDs43j/57pxdbhbyhTD6XNTzqCvaZQSfIa/SLAGR7eFPK9+UV62Dfzia/aYdwPw=="
|
||||
},
|
||||
"use-double-click": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/use-double-click/-/use-double-click-1.0.5.tgz",
|
||||
@@ -12566,7 +12571,7 @@
|
||||
"utf8-byte-length": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
|
||||
"integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=",
|
||||
"integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==",
|
||||
"dev": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
@@ -12635,7 +12640,7 @@
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
"semver": "^7.3.8",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"tailwindcss-scoped-groups": "^2.0.0",
|
||||
"use-delayed-state": "^1.2.0",
|
||||
"use-double-click": "^1.0.5",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
|
||||
@@ -5,12 +5,14 @@ import { BsmLink } from "../shared/bsm-link.component";
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component";
|
||||
import { BsmButton } from "../shared/bsm-button.component";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
import { useState, Fragment } from "react";
|
||||
import { LinkOpenerService } from "renderer/services/link-opener.service";
|
||||
import dateFormat from "dateformat";
|
||||
import { AudioPlayerService } from "renderer/services/audio-player.service";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { map } from "rxjs/operators";
|
||||
import useDelayedState from "use-delayed-state";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export type ParsedMapDiff = {type: BsvMapDifficultyType, name: string, stars: number}
|
||||
|
||||
@@ -42,8 +44,8 @@ export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autor
|
||||
const color = useThemeColor("first-color");
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const [bottomBarHovered, setBottomBarHovered] = useState(false);
|
||||
const [diffsPanelHovered, setDiffsPanelHoverd] = useState(false);
|
||||
const [bottomBarHovered, setBottomBarHovered, cancelBottomBarHovered] = useDelayedState(false);
|
||||
const [diffsPanelHovered, setDiffsPanelHovered] = useState(false);
|
||||
|
||||
const songPlaying = useObservable(audioPlayer.playing$.pipe(map(playing => playing && audioPlayer.src === songUrl)));
|
||||
|
||||
@@ -59,9 +61,12 @@ export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autor
|
||||
const date = new Date(0);
|
||||
date.setSeconds(duration);
|
||||
return duration > 3600 ? dateFormat(date, "h:MM:ss") : dateFormat(date, "MM:ss");
|
||||
})()
|
||||
})();
|
||||
|
||||
console.log(bottomBarHovered, diffsPanelHovered);
|
||||
const parseDiffLabel = (diffLabel: string) => {
|
||||
if(diffLabel === "ExpertPlus"){ return "Expert+" }
|
||||
return diffLabel;
|
||||
}
|
||||
|
||||
const openPreview = () => linkOpener.open(previewUrl, true);
|
||||
const copyBsr = () => navigator.clipboard.writeText(`!bsr ${mapId}`);
|
||||
@@ -75,6 +80,20 @@ export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autor
|
||||
audioPlayer.play(songUrl, bpm);
|
||||
}
|
||||
|
||||
const bottomBarHoverStart = () => {
|
||||
cancelBottomBarHovered();
|
||||
setBottomBarHovered(true, (diffsPanelHovered || bottomBarHovered) ? 0 : 300);
|
||||
}
|
||||
|
||||
const bottomBarHoverEnd = () => {
|
||||
cancelBottomBarHovered();
|
||||
setBottomBarHovered(false, 100);
|
||||
}
|
||||
|
||||
const diffsPanelHoverStart = () => setDiffsPanelHovered(true);
|
||||
const diffsPanelHoverEnd = () => setDiffsPanelHovered(false);
|
||||
|
||||
|
||||
const renderDiffPreview = () => {
|
||||
|
||||
const diffSets = Array.from(diffs.entries());
|
||||
@@ -87,7 +106,7 @@ export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autor
|
||||
<BsmIcon className="h-4 w-4 mr-px" icon="bsMapDifficulty"/>
|
||||
<div className="flex py-[2px] gap-[1px] h-full">
|
||||
{diffSet.map(diff => (
|
||||
<span key={diff.type} className="h-full w-[6px] brightness-200 saturate-150 rounded-full" style={{backgroundColor: diffColors[diff.type]}}/>
|
||||
<span key={diff.type} className="h-full w-[6px] rounded-full" style={{backgroundColor: diffColors[diff.type]}}/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
@@ -95,35 +114,52 @@ export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autor
|
||||
}
|
||||
if(diffSets.length > 1){
|
||||
return diffSets.map(([diffType, diffSet]) => (
|
||||
<>
|
||||
<Fragment key={uuidv4()}>
|
||||
<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>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(coverUrl);
|
||||
|
||||
return (
|
||||
<motion.li className="relative h-[100px] min-w-[400px] shrink grow basis-0 text-white group" onHoverStart={() => setHovered(true)} onHoverEnd={() => setHovered(false)} style={{zIndex: (bottomBarHovered || diffsPanelHovered) && 5}}>
|
||||
<motion.li className="relative h-[100px] min-w-[400px] shrink grow basis-0 text-white group" onHoverStart={() => setHovered(true)} onHoverEnd={() => setHovered(false)} style={{zIndex: hovered && 5}}>
|
||||
<AnimatePresence>
|
||||
{(diffsPanelHovered || bottomBarHovered) && (
|
||||
<motion.div className="absolute w-full top-[calc(100%-10px)] h-fit bg-main-color-3 brightness-125 rounded-md pointer-events-auto" onHoverStart={() => setDiffsPanelHoverd(true)} onHoverEnd={() => setDiffsPanelHoverd(false)}>
|
||||
aaeaze<br/>azeaze
|
||||
</motion.div>
|
||||
<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-main-color-3 brightness-125 rounded-md flex flex-col gap-3 scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900 shadow-sm shadow-black" initial={{opacity: 0}} animate={{opacity: 1}} exit={{opacity: 0}} transition={{duration: .15}} onHoverStart={diffsPanelHoverStart} onHoverEnd={diffsPanelHoverEnd}>
|
||||
{Array.from(diffs.entries()).map(([charac, diffSet]) => (
|
||||
<ol key={uuidv4()} className="flex flex-col w-full gap-1">
|
||||
{diffSet.map(({type, name, stars}) => (
|
||||
<li key={`${type}${name}${stars}`} className="w-full h-4 flex items-center gap-1">
|
||||
<BsmIcon className="h-full w-fit p-px" icon={charac}/>
|
||||
<span className="h-full px-2 flex items-center text-xs font-bold bg-current rounded-full" style={{color: diffColors[type]}}>
|
||||
{
|
||||
stars ? (
|
||||
<span className="h-full block brightness-[.25]">★ {stars}</span>
|
||||
) : (
|
||||
<span className="h-full brightness-[.25] leading-4 pb-[2px]">{parseDiffLabel(type)}</span>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
<span className="text-sm leading-4 pb-[2px]">{parseDiffLabel(name)}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
))}
|
||||
</motion.ul>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<div className="h-full w-full relative pl-[100px] rounded-md overflow-hidden flex flex-row justify-end">
|
||||
<div className="absolute top-0 left-0 h-full aspect-square">
|
||||
<div className="absolute top-0 left-0 h-full aspect-square cursor-pointer">
|
||||
<BsmImage image={coverUrl}/>
|
||||
<span className="absolute flex justify-center items-center w-full h-full pr-1 bg-transparent top-0 left-0 group-hover:bg-black group-hover:bg-opacity-40" style={{color}} onClick={(e) => {e.stopPropagation(); toogleMusic()}}>
|
||||
<BsmIcon className="w-full h-full p-7 opacity-0 group-hover:opacity-100 text-white hover:text-current" icon={songPlaying ? "pause" : "play"}/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-full w-full z-[1] rounded-md overflow-hidden -translate-x-1" style={{backgroundImage: `url('${coverUrl}')`, backgroundPosition: "center", backgroundSize: "cover"}}>
|
||||
<div className="relative h-full w-full z-[1] rounded-md overflow-hidden -translate-x-1">
|
||||
<BsmImage className="absolute top-0 left-0 w-full h-full -z-[1] object-cover" image={coverUrl}/>
|
||||
<div className="pt-1 pl-2 pr-7 top-0 left-0 w-full h-full bg-gray-600 bg-opacity-80 flex flex-col justify-between group-hover:bg-main-color-1 group-hover:bg-opacity-80">
|
||||
<h1 className="font-bold whitespace-nowrap text-ellipsis overflow-hidden w-full leading-5 tracking-wide text-lg"><BsmLink className="hover:underline" href={mapUrl}>{title}</BsmLink></h1>
|
||||
<h1 className="font-bold whitespace-nowrap text-ellipsis overflow-hidden w-full leading-5 tracking-wide text-lg" title={title}><BsmLink className="hover:underline" href={mapUrl}>{title}</BsmLink></h1>
|
||||
<h2 className="font-bold whitespace-nowrap text-ellipsis overflow-hidden w-full text-sm mb-[3px]">par {songAutor}</h2>
|
||||
<h3 className="font-bold whitespace-nowrap text-ellipsis overflow-hidden w-full text-xs">{autor && (<> mappée par <BsmLink href={authorUrl} className="brightness-150 saturate-50 hover:underline" style={{color}}>{autor}</BsmLink></>)}</h3>
|
||||
<div className="w-full h-4 text-xs gap-2 flex opacity-0 group-hover:opacity-100">
|
||||
@@ -146,35 +182,37 @@ export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autor
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<motion.div className="w-full h-5 pb-1 pr-7 flex items-center gap-1" onHoverStart={() => setBottomBarHovered(true)} onHoverEnd={() => setBottomBarHovered(false)}>
|
||||
<div className="text-yellow-300 bg-current rounded-full px-1 h-full flex items-center justify-center">
|
||||
<span className="uppercase text-xs font-bold tracking-wide brightness-[.25]">classée</span>
|
||||
</div>
|
||||
<motion.div className="w-full h-5 pb-1 pr-7 flex items-center gap-1" onHoverStart={bottomBarHoverStart} onHoverEnd={bottomBarHoverEnd}>
|
||||
{ranked && (
|
||||
<div className="text-yellow-300 bg-current rounded-full px-1 h-full flex items-center justify-center">
|
||||
<span className="uppercase text-xs font-bold tracking-wide brightness-[.25]">classée</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="h-full grow flex items-start content-start">
|
||||
{renderDiffPreview()}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
<motion.div className="absolute bg-main-color-3 top-0 right-0 h-full z-[1]" initial={{width: "8px"}} animate={{width: hovered ? "30px" : "8px"}}>
|
||||
<div className="absolute bg-main-color-3 top-0 right-0 h-full z-[1] w-[30px] translate-x-5 group-hover:translate-x-0 transition-transform">
|
||||
<span className="absolute w-[10px] h-[10px] top-0 right-full bg-inherit" style={{clipPath: 'path("M11 -1 L11 10 L10 10 A10 10 0 0 0 0 0 L0 -1 Z")'}}/>
|
||||
<span className="absolute w-[10px] h-[10px] bottom-0 right-full bg-inherit" style={{clipPath: 'path("M11 11 L11 0 L10 0 A10 10 0 0 1 0 10 L 0 11 Z")'}}/>
|
||||
|
||||
<div className="flex flex-col justify-center items-center gap-1 w-full h-full overflow-hidden transition-opacity" style={{opacity: hovered ? 1 : 0}}>
|
||||
<div className="flex flex-col justify-center items-center gap-1 w-full h-full overflow-hidden transition-opacity opacity-0 group-hover:opacity-100">
|
||||
{onDelete && <BsmButton className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:!bg-main-color-2" iconClassName="w-full h-full brightness-150 saturate-50" iconColor={color} icon="trash" withBar={false} onClick={e => {e.stopPropagation(); openPreview()}}/>}
|
||||
<BsmButton className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:!bg-main-color-2" iconClassName="w-full h-full brightness-150 saturate-50" iconColor={color} icon="eye" withBar={false} onClick={e => {e.stopPropagation(); openPreview()}}/>
|
||||
{mapId && <BsmButton className="w-6 h-6 p-1 rounded-md !bg-inherit hover:!bg-main-color-2" iconClassName="w-full h-full brightness-150 saturate-50" iconColor={color} icon="twitch" withBar={false} onClick={e => {e.stopPropagation(); copyBsr()}}/>}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.li>
|
||||
)
|
||||
}
|
||||
|
||||
const diffColors: Record<BsvMapDifficultyType, string> = {
|
||||
Easy: "#008055",
|
||||
Normal: "#1268a1",
|
||||
Hard: "#bd5500",
|
||||
Expert: "#b52a1c",
|
||||
ExpertPlus: "#7646af"
|
||||
Easy: "#00FF9E",
|
||||
Normal: "#00FFFF",
|
||||
Hard: "#FFA700",
|
||||
Expert: "#FF4319",
|
||||
ExpertPlus: "#FF7EFF"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user