mirror of
https://github.com/trafficlunar/blockmatic.git
synced 2026-08-12 12:29:42 +00:00
feat: update to react 19, pixi.js v8, tailwind v4, shad-cn
Also: - center canvas on startup - add version 1.21.9
This commit is contained in:
parent
4193ebcaf4
commit
5eedff5bab
58 changed files with 3819 additions and 4765 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React, { createContext, ReactNode, useContext, useEffect, useMemo, useState } from "react";
|
||||
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { HistoryContext } from "./History";
|
||||
import welcomeBlocksData from "@/data/welcome.json";
|
||||
|
|
@ -22,6 +22,7 @@ interface Props {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const CanvasContext = createContext<Context>({} as Context);
|
||||
|
||||
export const CanvasProvider = ({ children }: Props) => {
|
||||
|
|
@ -31,7 +32,7 @@ export const CanvasProvider = ({ children }: Props) => {
|
|||
const [blocks, setBlocks] = useState<Block[]>(welcomeBlocksData);
|
||||
const [coords, setCoords] = useState<Position>({ x: 0, y: 0 });
|
||||
const [scale, setScale] = useState(1);
|
||||
const [version, setVersion] = useState(1214);
|
||||
const [version, setVersion] = useState(1219);
|
||||
|
||||
// Get the farthest away blocks in each direction
|
||||
const canvasSize = useMemo(() => {
|
||||
|
|
@ -64,7 +65,7 @@ export const CanvasProvider = ({ children }: Props) => {
|
|||
};
|
||||
}, [blocks]);
|
||||
|
||||
const centerCanvas = () => {
|
||||
const centerCanvas = useCallback(() => {
|
||||
// Margin of 8 blocks on each side
|
||||
const margin = 8 * 16;
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ export const CanvasProvider = ({ children }: Props) => {
|
|||
|
||||
setScale(newScale);
|
||||
setCoords({ x: newX, y: newY });
|
||||
};
|
||||
}, [canvasSize, stageSize]);
|
||||
|
||||
useEffect(() => {
|
||||
addHistory(
|
||||
|
|
@ -93,6 +94,7 @@ export const CanvasProvider = ({ children }: Props) => {
|
|||
() => setBlocks(welcomeBlocksData),
|
||||
() => setBlocks([])
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ interface Props {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const DialogContext = createContext<Context>({} as Context);
|
||||
|
||||
export const DialogProvider = ({ children }: Props) => {
|
||||
|
|
@ -34,7 +35,7 @@ export const DialogProvider = ({ children }: Props) => {
|
|||
<DialogContext.Provider value={openDialog}>
|
||||
<Dialog open={open} onOpenChange={(value) => setOpen(value)}>
|
||||
{LazyDialogContent && (
|
||||
<Suspense fallback={<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">Loading dialog...</div>}>
|
||||
<Suspense>
|
||||
<LazyDialogContent close={() => setOpen(false)} registerSubmit={(fn) => (onSubmitRef.current = fn)} dialogKeyHandler={dialogKeyHandler} />
|
||||
</Suspense>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ interface Props {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const HistoryContext = createContext<Context>({} as Context);
|
||||
|
||||
export const HistoryProvider = ({ children }: Props) => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ interface Props {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const LoadingContext = createContext<Context>({} as Context);
|
||||
|
||||
export const LoadingProvider = ({ children }: Props) => {
|
||||
|
|
@ -18,10 +19,10 @@ export const LoadingProvider = ({ children }: Props) => {
|
|||
return (
|
||||
<LoadingContext.Provider value={{ loading, setLoading }}>
|
||||
{loading && (
|
||||
<div className="absolute w-full h-full cursor-wait flex justify-center items-center">
|
||||
<div className="absolute size-full cursor-wait flex justify-center items-center">
|
||||
{/* Keep loading indicator outside of div with backdrop-filter due to Chrome */}
|
||||
<LoadingIndicator fill="white" className="w-16 h-16 z-[10000]" />
|
||||
<div className="absolute w-full h-full z-[9999] backdrop-brightness-50 flex justify-center items-center gap-4"></div>
|
||||
<LoadingIndicator fill="white" className="w-16 h-16 z-10000" />
|
||||
<div className="absolute size-full z-9999 backdrop-brightness-50 flex justify-center items-center gap-4"></div>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import React, { createContext, ReactNode, useRef, useState } from "react";
|
|||
interface Context {
|
||||
selectionCoords: CoordinateArray;
|
||||
selectionLayerBlocks: Block[];
|
||||
confirmHistoryEntryNameRef: React.MutableRefObject<string>;
|
||||
confirmHistoryEntryNameRef: React.RefObject<string>;
|
||||
setSelectionCoords: React.Dispatch<React.SetStateAction<CoordinateArray>>;
|
||||
setSelectionLayerBlocks: React.Dispatch<React.SetStateAction<Block[]>>;
|
||||
isInSelection: (x: number, y: number) => boolean;
|
||||
|
|
@ -13,6 +13,7 @@ interface Props {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const SelectionContext = createContext<Context>({} as Context);
|
||||
|
||||
export const SelectionProvider = ({ children }: Props) => {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const defaultSettings: Settings = {
|
|||
blockSelector: true,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const SettingsContext = createContext<Context>({} as Context);
|
||||
|
||||
export const SettingsProvider = ({ children }: Props) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createContext, ReactNode, useContext, useEffect, useRef } from "react";
|
||||
import * as PIXI from "pixi.js";
|
||||
import { Assets, Spritesheet, Texture } from "pixi.js";
|
||||
|
||||
import { LoadingContext } from "./Loading";
|
||||
|
||||
|
|
@ -7,48 +7,57 @@ import spritesheet from "@/data/blocks/spritesheet.json";
|
|||
import programmerArtSpritesheet from "@/data/blocks/programmer-art/spritesheet.json";
|
||||
|
||||
interface Context {
|
||||
missingTexture: PIXI.Texture;
|
||||
textures: Record<string, PIXI.Texture>;
|
||||
programmerArtTextures: Record<string, PIXI.Texture>;
|
||||
missingTexture: Texture;
|
||||
textures: Record<string, Texture>;
|
||||
programmerArtTextures: Record<string, Texture>;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const TexturesContext = createContext<Context>({} as Context);
|
||||
|
||||
export const TexturesProvider = ({ children }: Props) => {
|
||||
const { setLoading } = useContext(LoadingContext);
|
||||
|
||||
// Load missing texture through data string just incase of network errors
|
||||
const missingTextureRef = useRef<PIXI.Texture>(
|
||||
new PIXI.Texture(
|
||||
new PIXI.BaseTexture(
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGUlEQVR42mPABX4w/MCKaKJhVMPgcOuoBgDZRfgBVl5QdQAAAABJRU5ErkJggg=="
|
||||
)
|
||||
)
|
||||
);
|
||||
const texturesRef = useRef<Record<string, PIXI.Texture>>({});
|
||||
const programmerArtTexturesRef = useRef<Record<string, PIXI.Texture>>({});
|
||||
// Load missing texture through data string just in case of network errors
|
||||
const missingTextureRef = useRef<Texture>(Texture.EMPTY);
|
||||
const texturesRef = useRef<Record<string, Texture>>({});
|
||||
const programmerArtTexturesRef = useRef<Record<string, Texture>>({});
|
||||
|
||||
// Load textures
|
||||
useEffect(() => {
|
||||
// Add air texture
|
||||
const airBaseTexture = new PIXI.BaseTexture("/blocks/air.png");
|
||||
const airTexture = new PIXI.Texture(airBaseTexture);
|
||||
const loadTextures = async () => {
|
||||
try {
|
||||
// Load base textures
|
||||
missingTextureRef.current = await Assets.load(
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGUlEQVR42mPABX4w/MCKaKJhVMPgcOuoBgDZRfgBVl5QdQAAAABJRU5ErkJggg=="
|
||||
);
|
||||
const airBaseTexture = await Assets.load("/blocks/air.png");
|
||||
const sheetTexture = await Assets.load("/blocks/spritesheet.png");
|
||||
const programmerArtSheetTexture = await Assets.load("/blocks/programmer-art/spritesheet.png");
|
||||
|
||||
const sheet = new PIXI.Spritesheet(PIXI.BaseTexture.from("/blocks/spritesheet.png"), spritesheet);
|
||||
sheet.parse().then((t) => {
|
||||
texturesRef.current = { ...t, "air.png": airTexture };
|
||||
});
|
||||
// Create and parse main spritesheet
|
||||
const sheet = new Spritesheet(sheetTexture, spritesheet);
|
||||
await sheet.parse();
|
||||
texturesRef.current = { ...sheet.textures, "air.png": airBaseTexture };
|
||||
|
||||
const programmerArtSheet = new PIXI.Spritesheet(PIXI.BaseTexture.from("/blocks/programmer-art/spritesheet.png"), programmerArtSpritesheet);
|
||||
programmerArtSheet.parse().then((t) => {
|
||||
programmerArtTexturesRef.current = { ...t, "air.png": airTexture };
|
||||
});
|
||||
setLoading(false);
|
||||
}, []);
|
||||
// Create and parse programmer art spritesheet
|
||||
const programmerArtSheet = new Spritesheet(programmerArtSheetTexture, programmerArtSpritesheet);
|
||||
await programmerArtSheet.parse();
|
||||
programmerArtTexturesRef.current = { ...programmerArtSheet.textures, "air.png": airBaseTexture };
|
||||
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to load textures:", error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadTextures();
|
||||
}, [setLoading]);
|
||||
|
||||
return (
|
||||
<TexturesContext.Provider
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ interface Context {
|
|||
setTheme: (theme: Theme) => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const ThemeContext = createContext<Context>({} as Context);
|
||||
|
||||
export function ThemeProvider({ children, defaultTheme = "system", storageKey = "vite-ui-theme", ...props }: Props) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ interface Props {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const ToolContext = createContext<Context>({} as Context);
|
||||
|
||||
export const ToolProvider = ({ children }: Props) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue