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 {OsuRenderer} from "@/osu/OsuRenderer";
import {Stats} from "@/interface/composites/stats";
import {LoadingDialog} from "@/interface/composites/loading-dialog";
export function App() {
@ -38,6 +39,7 @@ export function App() {
<>
<Navbar/>
<AboutDialog/>
<LoadingDialog/>
<SongSlider/>
<Stats/>
<Helper/>

View File

@ -29,8 +29,8 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { hideCloseButton?: boolean } // Add hideCloseButton prop
>(({ className, children, hideCloseButton, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
@ -42,14 +42,17 @@ const DialogContent = React.forwardRef<
{...props}
>
{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">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
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() {
for (const imageName of Object.keys(Drawer.images)) {
Drawer.images[imageName as keyof typeof Drawer.images] =
await loadImageAsync(`/${imageName}.png`);
const imageLoadPromises = Object.keys(Drawer.images).map(imageName =>
loadImageAsync(`/${imageName}.png`).then(
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) {
@ -97,7 +105,7 @@ export class Drawer {
// Drawer.p.strokeWeight(2);
// Drawer.p.line(barX + barWidth / 2, barY - 5, barX + barWidth / 2, barY + barHeight + 5);
Drawer.p.pop();
// Drawer.p.pop();
}
//@ts-ignore

View File

@ -12,7 +12,7 @@ import {
} from "osu-standard-stable";
import {Drawer} from "./Drawer";
import {Vec2} from "@osujs/math";
import {clamp, getBeatmap, getReplay} from "@/utils";
import {clamp, getBeatmap, getReplay, state} from "@/utils";
import EventEmitter from "eventemitter3";
import {toast} from "sonner";
import p5 from "p5";
@ -303,6 +303,11 @@ export class OsuRenderer {
const i_beatmap = await getBeatmap(beatmap, i_replay);
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);
}

View File

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

View File

@ -3,7 +3,7 @@
font-style: normal;
font-display: swap;
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 {
@ -11,7 +11,7 @@
font-style: normal;
font-display: swap;
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 {

View File

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