Improved loading UI/UX on nise-replay-viewer

This commit is contained in:
nise.moe 2024-03-10 15:56:49 +01:00
parent 15dc0e90bc
commit f6db550edb
8 changed files with 87 additions and 36 deletions

View File

@ -5,6 +5,7 @@ import {Helper} from "./composites/helper";
import {useEffect} from "react"; import {useEffect} from "react";
import {OsuRenderer} from "@/osu/OsuRenderer"; import {OsuRenderer} from "@/osu/OsuRenderer";
import {Stats} from "@/interface/composites/stats"; import {Stats} from "@/interface/composites/stats";
import {LoadingDialog} from "@/interface/composites/loading-dialog";
export function App() { export function App() {
@ -38,6 +39,7 @@ export function App() {
<> <>
<Navbar/> <Navbar/>
<AboutDialog/> <AboutDialog/>
<LoadingDialog/>
<SongSlider/> <SongSlider/>
<Stats/> <Stats/>
<Helper/> <Helper/>

View File

@ -29,8 +29,8 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef< const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { hideCloseButton?: boolean } // Add hideCloseButton prop
>(({ className, children, ...props }, ref) => ( >(({ className, children, hideCloseButton, ...props }, ref) => (
<DialogPortal> <DialogPortal>
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
@ -42,14 +42,17 @@ const DialogContent = React.forwardRef<
{...props} {...props}
> >
{children} {children}
{!hideCloseButton && ( // Conditionally render based on hideCloseButton
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</DialogPrimitive.Close> </DialogPrimitive.Close>
)}
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
)) ))
DialogContent.displayName = DialogPrimitive.Content.displayName DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ const DialogHeader = ({
className, className,

View File

@ -0,0 +1,27 @@
import { Dialog, DialogContent } from "@/interface/components/ui/dialog";
import { state } from "@/utils";
export function LoadingDialog() {
const { beatmap, replay} = state();
if (beatmap && replay) {
return;
}
return (
<Dialog
open={true}
>
<DialogContent className="sm:max-w-[425px]" hideCloseButton={true}>
<div className="bottom-0 left-0 ">
<h3 className="scroll-m-20 text-2xl font-semibold tracking-tight flex items-center gap-3">
Loading replay...
</h3>
<div className="opacity-75">
<h3 className="text-sm">Your replay is being loaded</h3>
</div>
</div>
</DialogContent>
</Dialog>
);
}

View File

@ -38,10 +38,18 @@ export class Drawer {
} }
static async loadDefaultImages() { static async loadDefaultImages() {
for (const imageName of Object.keys(Drawer.images)) { const imageLoadPromises = Object.keys(Drawer.images).map(imageName =>
Drawer.images[imageName as keyof typeof Drawer.images] = loadImageAsync(`/${imageName}.png`).then(
await loadImageAsync(`/${imageName}.png`); image => {
Drawer.images[imageName as keyof typeof Drawer.images] = image;
},
error => {
console.error(`Failed to load image ${imageName}:`, error);
} }
)
);
return Promise.allSettled(imageLoadPromises)
} }
static setImages(images: typeof this.images) { static setImages(images: typeof this.images) {
@ -97,7 +105,7 @@ export class Drawer {
// Drawer.p.strokeWeight(2); // Drawer.p.strokeWeight(2);
// Drawer.p.line(barX + barWidth / 2, barY - 5, barX + barWidth / 2, barY + barHeight + 5); // Drawer.p.line(barX + barWidth / 2, barY - 5, barX + barWidth / 2, barY + barHeight + 5);
Drawer.p.pop(); // Drawer.p.pop();
} }
//@ts-ignore //@ts-ignore

View File

@ -12,7 +12,7 @@ import {
} from "osu-standard-stable"; } from "osu-standard-stable";
import {Drawer} from "./Drawer"; import {Drawer} from "./Drawer";
import {Vec2} from "@osujs/math"; import {Vec2} from "@osujs/math";
import {clamp, getBeatmap, getReplay} from "@/utils"; import {clamp, getBeatmap, getReplay, state} from "@/utils";
import EventEmitter from "eventemitter3"; import EventEmitter from "eventemitter3";
import {toast} from "sonner"; import {toast} from "sonner";
import p5 from "p5"; import p5 from "p5";
@ -303,6 +303,11 @@ export class OsuRenderer {
const i_beatmap = await getBeatmap(beatmap, i_replay); const i_beatmap = await getBeatmap(beatmap, i_replay);
OsuRenderer.setOptions(i_beatmap, i_replay, judgements); OsuRenderer.setOptions(i_beatmap, i_replay, judgements);
state.setState({
beatmap: i_beatmap,
replay: i_replay,
mods: i_replay.info.mods?.all,
});
this.event.emit(OsuRendererEvents.LOAD); this.event.emit(OsuRendererEvents.LOAD);
} }

View File

@ -13,7 +13,7 @@ export class Renderer {
Renderer.registerEvents(); Renderer.registerEvents();
Drawer.setP(p); Drawer.setP(p);
await Drawer.loadDefaultImages().then( Drawer.loadDefaultImages().then(
() => { () => {
Renderer.areImagesLoaded = true; Renderer.areImagesLoaded = true;
OsuRenderer.setPlaying(true); OsuRenderer.setPlaying(true);

View File

@ -3,7 +3,7 @@
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
font-weight: 400; font-weight: 400;
src: url('https://replay.nise.moe/ia-quattro-400-normal.woff2') format('woff2'); src: url('/ia-quattro-400-normal.woff2') format('woff2');
} }
@font-face { @font-face {
@ -11,7 +11,7 @@
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
font-weight: 700; font-weight: 700;
src: url('https://replay.nise.moe/ia-quattro-700-normal.woff2') format('woff2'); src: url('/ia-quattro-700-normal.woff2') format('woff2');
} }
#app { #app {

View File

@ -3,7 +3,7 @@ import { ScoreDecoder } from "../osu-parsers";
import { StandardRuleset, StandardBeatmap } from "osu-standard-stable"; import { StandardRuleset, StandardBeatmap } from "osu-standard-stable";
import { IMod, Score } from "osu-classes"; import { IMod, Score } from "osu-classes";
import p5, { Image } from "p5"; import p5 from "p5";
import { create } from "zustand"; import { create } from "zustand";
const ruleset = new StandardRuleset(); const ruleset = new StandardRuleset();
@ -27,11 +27,17 @@ export async function getBeatmap(mapText: string, scoreBase: Score) {
return ruleset.applyToBeatmap(map); return ruleset.applyToBeatmap(map);
} }
export async function loadImageAsync(image: string): Promise<Image> { export async function loadImageAsync(image: string): Promise<p5.Image> {
return new Promise((res) => { return new Promise((resolve, reject) => {
p.loadImage(image, (img) => { p.loadImage(
res(img); image,
}); (img) => {
resolve(img);
},
(err) => {
reject(err);
}
);
}); });
} }