feat: add loading indicator
This commit is contained in:
parent
a93073caf9
commit
41ab349e43
8 changed files with 70 additions and 22 deletions
1
src/assets/loading.svg
Normal file
1
src/assets/loading.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_aj0A{transform-origin:center;animation:spinner_KYSC .75s infinite linear}@keyframes spinner_KYSC{100%{transform:rotate(360deg)}}</style><path d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z" class="spinner_aj0A"/></svg>
|
||||
|
After Width: | Height: | Size: 434 B |
|
|
@ -14,12 +14,13 @@ interface Props {
|
|||
imageDimensions: Dimension;
|
||||
coords: Position;
|
||||
scale: number;
|
||||
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
// Lifts 16,000 tiles limit
|
||||
settings.use32bitIndex = true;
|
||||
|
||||
function Blocks({ blocks, setBlocks, textures, image, imageDimensions, coords, scale }: Props) {
|
||||
function Blocks({ blocks, setBlocks, textures, image, imageDimensions, coords, scale, setLoading }: Props) {
|
||||
const app = useApp();
|
||||
const [missingTexture, setMissingTexture] = useState<PIXI.Texture>();
|
||||
|
||||
|
|
@ -109,6 +110,7 @@ function Blocks({ blocks, setBlocks, textures, image, imageDimensions, coords, s
|
|||
}
|
||||
|
||||
setBlocks(newBlocks);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [image, imageDimensions]);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Container, Stage } from "@pixi/react";
|
|||
|
||||
import { CanvasContext } from "@/context/Canvas";
|
||||
import { ImageContext } from "@/context/Image";
|
||||
import { LoadingContext } from "@/context/Loading";
|
||||
import { SettingsContext } from "@/context/Settings";
|
||||
import { TexturesContext } from "@/context/Textures";
|
||||
import { ToolContext } from "@/context/Tool";
|
||||
|
|
@ -25,6 +26,7 @@ PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
|
|||
function Canvas() {
|
||||
const { stageSize, canvasSize, blocks, coords, scale, setStageSize, setBlocks, setCoords, setScale } = useContext(CanvasContext);
|
||||
const { image, imageDimensions } = useContext(ImageContext);
|
||||
const { setLoading } = useContext(LoadingContext);
|
||||
const { settings } = useContext(SettingsContext);
|
||||
const textures = useContext(TexturesContext);
|
||||
const { tool, selectedBlock, cssCursor, setTool, setCssCursor } = useContext(ToolContext);
|
||||
|
|
@ -252,6 +254,7 @@ function Canvas() {
|
|||
imageDimensions={imageDimensions}
|
||||
coords={coords}
|
||||
scale={scale}
|
||||
setLoading={setLoading}
|
||||
/>
|
||||
|
||||
<Container x={coords.x} y={coords.y} scale={scale}>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ import { Input } from "@/components/ui/input";
|
|||
import { Label } from "@/components/ui/label";
|
||||
|
||||
import { ImageContext } from "@/context/Image";
|
||||
import { LoadingContext } from "@/context/Loading";
|
||||
|
||||
function OpenImage({ close }: DialogProps) {
|
||||
const { setLoading } = useContext(LoadingContext);
|
||||
const { setImage: setContextImage, setImageDimensions: setContextImageDimensions } = useContext(ImageContext);
|
||||
|
||||
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
|
||||
|
|
@ -50,9 +52,14 @@ function OpenImage({ close }: DialogProps) {
|
|||
|
||||
const onSubmit = () => {
|
||||
if (image) {
|
||||
setContextImage(image);
|
||||
setContextImageDimensions(imageDimensions);
|
||||
close();
|
||||
setLoading(true);
|
||||
|
||||
// Wait for loading indicator to appear
|
||||
setTimeout(() => {
|
||||
setContextImage(image);
|
||||
setContextImageDimensions(imageDimensions);
|
||||
close();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useContext, useState } from "react";
|
|||
import * as nbt from "nbtify";
|
||||
|
||||
import { CanvasContext } from "@/context/Canvas";
|
||||
import { LoadingContext } from "@/context/Loading";
|
||||
|
||||
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -12,10 +13,15 @@ const blockData: BlockData = _blockData;
|
|||
|
||||
function SaveLitematic({ close }: DialogProps) {
|
||||
const { canvasSize, blocks } = useContext(CanvasContext);
|
||||
const { setLoading } = useContext(LoadingContext);
|
||||
|
||||
const [fileName, setFileName] = useState("blockmatic");
|
||||
|
||||
const onSubmit = async () => {
|
||||
setLoading(true);
|
||||
// Wait for loading indicator to appear
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// todo: check if file name input is empty/valid
|
||||
const width = canvasSize.maxX - canvasSize.minX;
|
||||
const height = canvasSize.maxY - canvasSize.minY;
|
||||
|
|
@ -130,6 +136,8 @@ function SaveLitematic({ close }: DialogProps) {
|
|||
const blob = new Blob([compressed], { type: "application/x-gzip" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
setLoading(false);
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `${fileName}.litematic`;
|
||||
|
|
|
|||
26
src/context/Loading.tsx
Normal file
26
src/context/Loading.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { createContext, ReactNode, useState } from "react";
|
||||
import LoadingIndicator from "@/assets/loading.svg?react";
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const LoadingContext = createContext({
|
||||
loading: false,
|
||||
setLoading: ((value: boolean) => {}) as React.Dispatch<React.SetStateAction<boolean>>,
|
||||
});
|
||||
|
||||
export const LoadingProvider = ({ children }: Props) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
return (
|
||||
<LoadingContext.Provider value={{ loading, setLoading }}>
|
||||
{loading && (
|
||||
<div className="absolute w-full h-full z-[9999] backdrop-brightness-50 flex justify-center items-center gap-4">
|
||||
<LoadingIndicator fill="white" className="w-16 h-16" />
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</LoadingContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import { createContext, ReactNode, useEffect, useState } from "react";
|
||||
import { createContext, ReactNode, useContext, useEffect, useState } from "react";
|
||||
import { BaseTexture, Spritesheet, Texture } from "pixi.js";
|
||||
|
||||
import { LoadingContext } from "./Loading";
|
||||
|
||||
import spritesheet from "@/data/blocks/programmer-art/spritesheet.json";
|
||||
|
||||
interface Props {
|
||||
|
|
@ -11,19 +13,15 @@ export const TexturesContext = createContext({} as Record<string, Texture>);
|
|||
|
||||
export const TexturesProvider = ({ children }: Props) => {
|
||||
const [textures, setTextures] = useState<Record<string, Texture>>({});
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const { setLoading } = useContext(LoadingContext);
|
||||
|
||||
useEffect(() => {
|
||||
const sheet = new Spritesheet(BaseTexture.from("/blocks/programmer-art/spritesheet.png"), spritesheet);
|
||||
sheet.parse().then((t) => {
|
||||
setTextures(t);
|
||||
setLoaded(true);
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!loaded) {
|
||||
return <h1>Loading...</h1>;
|
||||
}
|
||||
|
||||
return <TexturesContext.Provider value={textures}>{children}</TexturesContext.Provider>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { CanvasProvider } from "@/context/Canvas";
|
||||
import { ImageProvider } from "../context/Image";
|
||||
import { LoadingProvider } from "@/context/Loading";
|
||||
import { SettingsProvider } from "../context/Settings";
|
||||
import { TexturesProvider } from "../context/Textures";
|
||||
import { ToolProvider } from "../context/Tool";
|
||||
|
|
@ -12,17 +13,19 @@ function AppPage() {
|
|||
return (
|
||||
<CanvasProvider>
|
||||
<ImageProvider>
|
||||
<SettingsProvider>
|
||||
<TexturesProvider>
|
||||
<ToolProvider>
|
||||
<main className="h-screen grid grid-rows-[2.5rem_1fr] grid-cols-[2.5rem_1fr]">
|
||||
<Menubar />
|
||||
<Toolbar />
|
||||
<Canvas />
|
||||
</main>
|
||||
</ToolProvider>
|
||||
</TexturesProvider>
|
||||
</SettingsProvider>
|
||||
<LoadingProvider>
|
||||
<SettingsProvider>
|
||||
<TexturesProvider>
|
||||
<ToolProvider>
|
||||
<main className="h-screen grid grid-rows-[2.5rem_1fr] grid-cols-[2.5rem_1fr]">
|
||||
<Menubar />
|
||||
<Toolbar />
|
||||
<Canvas />
|
||||
</main>
|
||||
</ToolProvider>
|
||||
</TexturesProvider>
|
||||
</SettingsProvider>
|
||||
</LoadingProvider>
|
||||
</ImageProvider>
|
||||
</CanvasProvider>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue