From 41ab349e43fe39818fd3cd216644199ab15e054f Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Sun, 22 Dec 2024 21:17:43 +0000 Subject: [PATCH] feat: add loading indicator --- src/assets/loading.svg | 1 + src/components/canvas/Blocks.tsx | 4 +++- src/components/canvas/Canvas.tsx | 3 +++ src/components/dialogs/OpenImage.tsx | 13 +++++++++--- src/components/dialogs/SaveLitematic.tsx | 8 ++++++++ src/context/Loading.tsx | 26 ++++++++++++++++++++++++ src/context/Textures.tsx | 12 +++++------ src/pages/AppPage.tsx | 25 +++++++++++++---------- 8 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 src/assets/loading.svg create mode 100644 src/context/Loading.tsx diff --git a/src/assets/loading.svg b/src/assets/loading.svg new file mode 100644 index 0000000..90b9303 --- /dev/null +++ b/src/assets/loading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/canvas/Blocks.tsx b/src/components/canvas/Blocks.tsx index 4974a5a..a766f49 100644 --- a/src/components/canvas/Blocks.tsx +++ b/src/components/canvas/Blocks.tsx @@ -14,12 +14,13 @@ interface Props { imageDimensions: Dimension; coords: Position; scale: number; + setLoading: React.Dispatch>; } // 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(); @@ -109,6 +110,7 @@ function Blocks({ blocks, setBlocks, textures, image, imageDimensions, coords, s } setBlocks(newBlocks); + setLoading(false); } }, [image, imageDimensions]); diff --git a/src/components/canvas/Canvas.tsx b/src/components/canvas/Canvas.tsx index f033659..04b919c 100644 --- a/src/components/canvas/Canvas.tsx +++ b/src/components/canvas/Canvas.tsx @@ -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} /> diff --git a/src/components/dialogs/OpenImage.tsx b/src/components/dialogs/OpenImage.tsx index 136784b..e4c60df 100644 --- a/src/components/dialogs/OpenImage.tsx +++ b/src/components/dialogs/OpenImage.tsx @@ -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); } }; diff --git a/src/components/dialogs/SaveLitematic.tsx b/src/components/dialogs/SaveLitematic.tsx index f6db3f4..fab4d13 100644 --- a/src/components/dialogs/SaveLitematic.tsx +++ b/src/components/dialogs/SaveLitematic.tsx @@ -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`; diff --git a/src/context/Loading.tsx b/src/context/Loading.tsx new file mode 100644 index 0000000..c6f4ecf --- /dev/null +++ b/src/context/Loading.tsx @@ -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>, +}); + +export const LoadingProvider = ({ children }: Props) => { + const [loading, setLoading] = useState(true); + + return ( + + {loading && ( +
+ +
+ )} + {children} +
+ ); +}; diff --git a/src/context/Textures.tsx b/src/context/Textures.tsx index aeb504c..8a906f3 100644 --- a/src/context/Textures.tsx +++ b/src/context/Textures.tsx @@ -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); export const TexturesProvider = ({ children }: Props) => { const [textures, setTextures] = useState>({}); - 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

Loading...

; - } - return {children}; }; diff --git a/src/pages/AppPage.tsx b/src/pages/AppPage.tsx index 1684d43..d72f566 100644 --- a/src/pages/AppPage.tsx +++ b/src/pages/AppPage.tsx @@ -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 ( - - - -
- - - -
-
-
-
+ + + + +
+ + + +
+
+
+
+
);