Improved loading UI/UX on nise-replay-viewer
This commit is contained in:
parent
15dc0e90bc
commit
f6db550edb
@ -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/>
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user