Add binary save and load
This commit is contained in:
@@ -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<Props, State> {
|
||||
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}"`)) {
|
||||
|
||||
+18
-2
@@ -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 {
|
||||
|
||||
@@ -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<number>) {
|
||||
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<Mural> {
|
||||
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<Mural> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -91,6 +91,20 @@ export function readAsDataUrl(blob: Blob) {
|
||||
});
|
||||
}
|
||||
|
||||
export function readAsArrayBuffer(blob: Blob) {
|
||||
return new Promise<ArrayBuffer >((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<HTMLImageElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
|
||||
Reference in New Issue
Block a user