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 (
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
);