diff --git a/.github/workflows/ntfy.yaml b/.github/workflows/ntfy.yaml index 4aef1a9..32d99b2 100644 --- a/.github/workflows/ntfy.yaml +++ b/.github/workflows/ntfy.yaml @@ -1,16 +1,13 @@ -name: Ntfy Deployment Notifications -on: - push: - branches: - - main +name: Deployment Notifications +on: [deployment_status] jobs: notify: - if: failure() || success() + if: github.event.deployment_status.state == "failure" | github.event.deployment_status.state == "success" runs-on: ubuntu-latest steps: - name: Notify on Success - if: success() + if: github.event.deployment_status.state == "success" run: | curl \ -H "Authorization: Bearer ${{ secrets.NTFY_TOKEN }}" \ @@ -21,7 +18,7 @@ jobs: ${{ secrets.NTFY_URL }} - name: Notify on Failure - if: failure() + if: github.event.deployment_status.state == "failure" run: | curl \ -H "Authorization: Bearer ${{ secrets.NTFY_TOKEN }}" \ diff --git a/src/components/dialogs/OpenImage.tsx b/src/components/dialogs/OpenImage.tsx index 0b2de31..0a08b23 100644 --- a/src/components/dialogs/OpenImage.tsx +++ b/src/components/dialogs/OpenImage.tsx @@ -152,8 +152,12 @@ function OpenImage({ close }: DialogProps) { })} > - -

Drag and drop your image here or click to open

+ +

+ Drag and drop your image here +
+ or click to open +

diff --git a/src/components/dialogs/OpenSchematic.tsx b/src/components/dialogs/OpenSchematic.tsx new file mode 100644 index 0000000..3f0b69b --- /dev/null +++ b/src/components/dialogs/OpenSchematic.tsx @@ -0,0 +1,160 @@ +import { useContext } from "react"; +import { useDropzone } from "react-dropzone"; +import { UploadIcon } from "lucide-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"; + +import _blockData from "@/data/blocks/data.json"; +const blockData: BlockData = _blockData; + +interface BlockPalette extends nbt.CompoundTagLike { + Name: string; + Properties?: Record; +} + +interface LitematicNBT extends nbt.ListTagLike { + Regions: { + Image: { + BlockStatePalette: BlockPalette[]; + BlockStates: BigInt64Array; + Size: { x: number; y: number; z: number }; + }; + }; +} + +function OpenSchematic({ close }: DialogProps) { + const { setBlocks } = useContext(CanvasContext); + const { setLoading } = useContext(LoadingContext); + + const { acceptedFiles, getRootProps, getInputProps } = useDropzone({ + accept: { + "application/x-gzip-compressed": [".litematic", ".schem"], + }, + }); + + const onSubmit = async () => { + const file = acceptedFiles[0]; + if (file) { + setLoading(true); + // Wait for loading indicator to appear + await new Promise((resolve) => setTimeout(resolve, 100)); + + const fileExtension = file.name.split(".").pop(); + const bytes = await file.bytes(); + const data = await nbt.read(bytes); + + if (fileExtension == "litematic") { + const litematicData = (data as nbt.NBTData).data; + const imageRegion = litematicData.Regions.Image; + + // todo: set version + const requiredBits = Math.max(Math.ceil(Math.log2(imageRegion.BlockStatePalette.length)), 2); + + const getPaletteIndex = (index: number): bigint => { + const originalY = Math.floor(index / imageRegion.Size.x); + const originalX = index % imageRegion.Size.x; + const reversedY = imageRegion.Size.y - 1 - originalY; + const reversedIndex = reversedY * imageRegion.Size.x + originalX; + + // getAt() implementation - LitematicaBitArray.java + const startOffset = reversedIndex * requiredBits; + const startArrayIndex = Math.floor(startOffset / 64); + const endArrayIndex = ((reversedIndex + 1) * requiredBits - 1) >> 6; + const bitOffset = startOffset % 64; + const mask = (1 << requiredBits) - 1; + + if (startArrayIndex === endArrayIndex) { + return (imageRegion.BlockStates[startArrayIndex] >> BigInt(bitOffset)) & BigInt(mask); + } else { + const endOffset = 64 - bitOffset; + return ( + ((imageRegion.BlockStates[startArrayIndex] >> BigInt(bitOffset)) | (imageRegion.BlockStates[endArrayIndex] << BigInt(endOffset))) & + BigInt(mask) + ); + } + }; + + // Add every block to the canvas + const blocks: Block[] = []; + let index = 0; + + for (let y = 0; y < imageRegion.Size.y; y++) { + for (let x = 0; x < imageRegion.Size.x; x++) { + const paletteIndex = Number(getPaletteIndex(index)); + const paletteBlock = imageRegion.BlockStatePalette.at(paletteIndex); + + index++; + if (!paletteBlock) continue; + + const blockId = paletteBlock.Name.replace("minecraft:", ""); + if (blockId == "air") continue; + + for (const name in blockData) { + const dataId = blockData[name].id[0]; + if (dataId !== blockId) continue; + + const paletteProperties = paletteBlock.Properties; + const dataProperties = blockData[name].properties; + + if (paletteProperties) { + if (!dataProperties) continue; + + if (!Object.keys(paletteProperties).every((key) => paletteProperties[key] === dataProperties[key])) { + continue; + } + } + + blocks.push({ x, y, name }); + break; + } + } + } + + setBlocks(blocks); + } + } + + setLoading(false); + close(); + }; + + return ( + + + Open Schematic + Open your schematic file to load into the canvas + + +
+ + +

+ Drag and drop your schematic file here +
+ or click to open +

+
+ + + + + +
+ ); +} + +export default OpenSchematic; diff --git a/src/components/dialogs/SaveLitematic.tsx b/src/components/dialogs/SaveLitematic.tsx index 640d8e2..2043a2a 100644 --- a/src/components/dialogs/SaveLitematic.tsx +++ b/src/components/dialogs/SaveLitematic.tsx @@ -47,7 +47,6 @@ function SaveLitematic({ close }: DialogProps) { new Set( blocks.map((block) => { const blockInfo = blockData[block.name.replace("minecraft:", "")]; - const returnData: { Name: string; Properties?: Record } = { Name: `minecraft:${blockInfo.id[0]}`, ...(blockInfo.properties ? { Properties: blockInfo.properties } : {}), @@ -72,6 +71,8 @@ function SaveLitematic({ close }: DialogProps) { const reversedY = height - 1 - block.y; const index = reversedY * width + block.x; + + // setAt() implementation - LitematicaBitArray.java const startOffset = index * requiredBits; const startArrayIndex = Math.floor(startOffset / 64); const endArrayIndex = ((index + 1) * requiredBits - 1) >> 6; diff --git a/src/components/menubar/FileMenu.tsx b/src/components/menubar/FileMenu.tsx index e2d0300..b16cb13 100644 --- a/src/components/menubar/FileMenu.tsx +++ b/src/components/menubar/FileMenu.tsx @@ -20,7 +20,7 @@ function FileMenu() { File - Open Schematic + openDialog("OpenSchematic")}>Open Schematic openDialog("OpenImage")}>Open Image