diff --git a/src/UI/components/muralView.tsx b/src/UI/components/muralView.tsx index 40a0fc8..d5b9745 100644 --- a/src/UI/components/muralView.tsx +++ b/src/UI/components/muralView.tsx @@ -10,10 +10,11 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Coordinates, CordType } from "../../lib/coordinates"; -import { TEXT_FORMATS } from "../importMural"; +import { BIN_FORMATS } from "../importMural"; import saveAs from "file-saver"; import { Popup } from "./Popup"; import { Palette } from "../../lib/palette"; +import { serializeMural } from "../serializer"; const Margin = styled.div` margin: 2px; @@ -218,8 +219,14 @@ export class MuralView extends React.Component { y: this.props.mural.y, pixels: this.props.mural.pixels, }; - const blob = new Blob([JSON.stringify(rawMural)], { type: "application/json;charset=utf-8" }); - saveAs(blob, `${rawMural.name}.${TEXT_FORMATS[0]}`); + const buffer = serializeMural(rawMural); + const blob = new Blob([buffer], { type: "octet/stream" }); + + const saveName = rawMural.name + .replace(/ /, "_") + .replace(/[^a-zA-Z0-9-_]/g, ""); + + saveAs(blob, `${saveName}.${BIN_FORMATS[0]}`); }; onDelete = async () => { if (await Popup.confirm(`Are you sure you want to remove "${this.props.mural.name}"`)) { diff --git a/src/UI/importMural.tsx b/src/UI/importMural.tsx index 85cfac1..ea728b9 100644 --- a/src/UI/importMural.tsx +++ b/src/UI/importMural.tsx @@ -3,6 +3,7 @@ import React from "react"; import { Mural, RGB } from "../interfaces"; import { canvasToImageData, get2DArrHeight, get2DArrWidth, getColorScore, getExtension, imageDataToPaletteIndices, imageToCanvas, loadImageSource, processNumberEvent, + readAsArrayBuffer, readAsDataUrl, readAsString, resize, rgb, validateMural } from "../lib/utils"; import styled from "styled-components"; import { Popup } from "./components/Popup"; @@ -13,8 +14,14 @@ import { Palette } from "../lib/palette"; import { FileInput } from "../lib/fileinput"; import { Store } from "../lib/store"; import { Coordinates } from "../lib/coordinates"; +import { deserializeMural } from "./serializer"; export const TEXT_FORMATS = ["muraljson", "json"]; +export const BIN_FORMATS = ["pcm", "bin"]; + +// TODO: Add bundle +export const BIN_BUNDLE_FORMATS = ["pcmb"]; +export const ZIP_FORMATS = ["zip"]; enum RetrieveType { @@ -465,15 +472,16 @@ export function imageDataToCanvas(imageData: ImageData) { async function importFile() { const fileInput = new FileInput(); - fileInput.setAcceptType(["png", "jpg", "jpeg", ...TEXT_FORMATS]); + fileInput.setAcceptType(["png", "jpg", "jpeg", ...TEXT_FORMATS, ...BIN_FORMATS]); const files = await fileInput.show(); const fileData = files[0]; if (!fileData) { throw new Error("Empty file"); } const ex = getExtension(fileData.name); + const etn = ex.ex.toLowerCase(); const name = ex.text; - if (TEXT_FORMATS.includes(ex.ex)) { + if (TEXT_FORMATS.includes(etn)) { const content = await readAsString(fileData); const mural = JSON.parse(content) as Mural; if (!mural.name) { @@ -485,6 +493,14 @@ async function importFile() { type: "mural", data: mural, }; + } else if (BIN_FORMATS.includes(etn)) { + const buffer = await readAsArrayBuffer(fileData); + const mural = deserializeMural(buffer); + validateMural(mural); + return { + type: "mural", + data: mural, + }; } else { const readData = await readAsDataUrl(fileData); return { diff --git a/src/UI/serializer.tsx b/src/UI/serializer.tsx index aee9b92..49b3bcf 100644 --- a/src/UI/serializer.tsx +++ b/src/UI/serializer.tsx @@ -1,5 +1,4 @@ import { Mural } from "../interfaces"; -import { waitForDraw } from "../lib/utils"; const MAGIC_REPEATING = 200; const TRANSPARENT = 201; @@ -44,10 +43,9 @@ export function decompressRepeatingArray(buffer: ArrayLike) { return output; } -export async function serializeMural(mural: Mural) { +export function serializeMural(mural: Mural) { const encoder = new TextEncoder(); const nameBinary = encoder.encode(mural.name); - await waitForDraw(); const array: number[] = []; for (let i = 0; i < mural.pixels.length; i++) { for (let j = 0; j < mural.pixels[i].length; j++) { @@ -80,7 +78,7 @@ export async function serializeMural(mural: Mural) { return buffer; } -export async function deserializeMural(buffer: ArrayBuffer): Promise { +export function deserializeMural(buffer: ArrayBuffer): Mural { const view = new DataView(buffer); const version = view.getUint8(0); if (version !== VERSION) { @@ -95,14 +93,12 @@ export async function deserializeMural(buffer: ArrayBuffer): Promise { const decoder = new TextDecoder(); const name = decoder.decode(nameBytes); const pixelBuffer = new Uint8Array(buffer, 12 + nameLength); - await waitForDraw(); const pixelArray: number[] = decompressRepeatingArray(pixelBuffer); const pixels: number[][] = []; leg: for (;;) { - await waitForDraw(); if (!pixelArray.length) { break; } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 1834264..eb3b643 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -91,6 +91,20 @@ export function readAsDataUrl(blob: Blob) { }); } +export function readAsArrayBuffer(blob: Blob) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.addEventListener("load", () => { + resolve(reader.result as ArrayBuffer ); + }); + reader.addEventListener("error", err => { + reject(err); + }); + reader.readAsArrayBuffer(blob); + }); +} + + export function loadImageSource(name: string, data: string): Promise { return new Promise((resolve, reject) => { const image = new Image();