Add binary save and load
This commit is contained in:
@@ -10,10 +10,11 @@ import {
|
|||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Coordinates, CordType } from "../../lib/coordinates";
|
import { Coordinates, CordType } from "../../lib/coordinates";
|
||||||
import { TEXT_FORMATS } from "../importMural";
|
import { BIN_FORMATS } from "../importMural";
|
||||||
import saveAs from "file-saver";
|
import saveAs from "file-saver";
|
||||||
import { Popup } from "./Popup";
|
import { Popup } from "./Popup";
|
||||||
import { Palette } from "../../lib/palette";
|
import { Palette } from "../../lib/palette";
|
||||||
|
import { serializeMural } from "../serializer";
|
||||||
|
|
||||||
const Margin = styled.div`
|
const Margin = styled.div`
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
@@ -218,8 +219,14 @@ export class MuralView extends React.Component<Props, State> {
|
|||||||
y: this.props.mural.y,
|
y: this.props.mural.y,
|
||||||
pixels: this.props.mural.pixels,
|
pixels: this.props.mural.pixels,
|
||||||
};
|
};
|
||||||
const blob = new Blob([JSON.stringify(rawMural)], { type: "application/json;charset=utf-8" });
|
const buffer = serializeMural(rawMural);
|
||||||
saveAs(blob, `${rawMural.name}.${TEXT_FORMATS[0]}`);
|
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 () => {
|
onDelete = async () => {
|
||||||
if (await Popup.confirm(`Are you sure you want to remove "${this.props.mural.name}"`)) {
|
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 { Mural, RGB } from "../interfaces";
|
||||||
import { canvasToImageData, get2DArrHeight, get2DArrWidth, getColorScore, getExtension,
|
import { canvasToImageData, get2DArrHeight, get2DArrWidth, getColorScore, getExtension,
|
||||||
imageDataToPaletteIndices, imageToCanvas, loadImageSource, processNumberEvent,
|
imageDataToPaletteIndices, imageToCanvas, loadImageSource, processNumberEvent,
|
||||||
|
readAsArrayBuffer,
|
||||||
readAsDataUrl, readAsString, resize, rgb, validateMural } from "../lib/utils";
|
readAsDataUrl, readAsString, resize, rgb, validateMural } from "../lib/utils";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Popup } from "./components/Popup";
|
import { Popup } from "./components/Popup";
|
||||||
@@ -13,8 +14,14 @@ import { Palette } from "../lib/palette";
|
|||||||
import { FileInput } from "../lib/fileinput";
|
import { FileInput } from "../lib/fileinput";
|
||||||
import { Store } from "../lib/store";
|
import { Store } from "../lib/store";
|
||||||
import { Coordinates } from "../lib/coordinates";
|
import { Coordinates } from "../lib/coordinates";
|
||||||
|
import { deserializeMural } from "./serializer";
|
||||||
|
|
||||||
export const TEXT_FORMATS = ["muraljson", "json"];
|
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 {
|
enum RetrieveType {
|
||||||
@@ -465,15 +472,16 @@ export function imageDataToCanvas(imageData: ImageData) {
|
|||||||
|
|
||||||
async function importFile() {
|
async function importFile() {
|
||||||
const fileInput = new FileInput();
|
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 files = await fileInput.show();
|
||||||
const fileData = files[0];
|
const fileData = files[0];
|
||||||
if (!fileData) {
|
if (!fileData) {
|
||||||
throw new Error("Empty file");
|
throw new Error("Empty file");
|
||||||
}
|
}
|
||||||
const ex = getExtension(fileData.name);
|
const ex = getExtension(fileData.name);
|
||||||
|
const etn = ex.ex.toLowerCase();
|
||||||
const name = ex.text;
|
const name = ex.text;
|
||||||
if (TEXT_FORMATS.includes(ex.ex)) {
|
if (TEXT_FORMATS.includes(etn)) {
|
||||||
const content = await readAsString(fileData);
|
const content = await readAsString(fileData);
|
||||||
const mural = JSON.parse(content) as Mural;
|
const mural = JSON.parse(content) as Mural;
|
||||||
if (!mural.name) {
|
if (!mural.name) {
|
||||||
@@ -485,6 +493,14 @@ async function importFile() {
|
|||||||
type: "mural",
|
type: "mural",
|
||||||
data: 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 {
|
} else {
|
||||||
const readData = await readAsDataUrl(fileData);
|
const readData = await readAsDataUrl(fileData);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Mural } from "../interfaces";
|
import { Mural } from "../interfaces";
|
||||||
import { waitForDraw } from "../lib/utils";
|
|
||||||
|
|
||||||
const MAGIC_REPEATING = 200;
|
const MAGIC_REPEATING = 200;
|
||||||
const TRANSPARENT = 201;
|
const TRANSPARENT = 201;
|
||||||
@@ -44,10 +43,9 @@ export function decompressRepeatingArray(buffer: ArrayLike<number>) {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function serializeMural(mural: Mural) {
|
export function serializeMural(mural: Mural) {
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const nameBinary = encoder.encode(mural.name);
|
const nameBinary = encoder.encode(mural.name);
|
||||||
await waitForDraw();
|
|
||||||
const array: number[] = [];
|
const array: number[] = [];
|
||||||
for (let i = 0; i < mural.pixels.length; i++) {
|
for (let i = 0; i < mural.pixels.length; i++) {
|
||||||
for (let j = 0; j < mural.pixels[i].length; j++) {
|
for (let j = 0; j < mural.pixels[i].length; j++) {
|
||||||
@@ -80,7 +78,7 @@ export async function serializeMural(mural: Mural) {
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deserializeMural(buffer: ArrayBuffer): Promise<Mural> {
|
export function deserializeMural(buffer: ArrayBuffer): Mural {
|
||||||
const view = new DataView(buffer);
|
const view = new DataView(buffer);
|
||||||
const version = view.getUint8(0);
|
const version = view.getUint8(0);
|
||||||
if (version !== VERSION) {
|
if (version !== VERSION) {
|
||||||
@@ -95,14 +93,12 @@ export async function deserializeMural(buffer: ArrayBuffer): Promise<Mural> {
|
|||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
const name = decoder.decode(nameBytes);
|
const name = decoder.decode(nameBytes);
|
||||||
const pixelBuffer = new Uint8Array(buffer, 12 + nameLength);
|
const pixelBuffer = new Uint8Array(buffer, 12 + nameLength);
|
||||||
await waitForDraw();
|
|
||||||
const pixelArray: number[] = decompressRepeatingArray(pixelBuffer);
|
const pixelArray: number[] = decompressRepeatingArray(pixelBuffer);
|
||||||
|
|
||||||
const pixels: number[][] = [];
|
const pixels: number[][] = [];
|
||||||
|
|
||||||
leg:
|
leg:
|
||||||
for (;;) {
|
for (;;) {
|
||||||
await waitForDraw();
|
|
||||||
if (!pixelArray.length) {
|
if (!pixelArray.length) {
|
||||||
break;
|
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> {
|
export function loadImageSource(name: string, data: string): Promise<HTMLImageElement> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
|||||||
Reference in New Issue
Block a user