import { Mural } from "../interfaces";
import { canvasToImageData, createCanvas, flatQuantizeImageData,
getExtension, getMuralHeight, getMuralWidth, imageDataToPaletteIndices,
imageToCanvas, loadImageSource, readAsDataUrl, readAsString, resize, validateMural } from "../utils";
import { FileInput } from "./Fileinput";
import { Palette } from "../palette";
import { Store } from "../store";
import React from "react";
import { Coordinates } from "../coordinates";
import { Storage } from "../storage";
import { Main } from "./components/main";
import { createRoot } from "react-dom/client";
const TEXT_FORMATS = ["muraljson", "json"];
export function createUI(store: Store, storage: Storage, cords: Coordinates, palette: Palette) {
return appendWindow();
}
function appendWindow(children: React.ReactNode) {
const rootElement = document.createElement("div")!;
createRoot(rootElement).render(children);
document.body.appendChild(rootElement);
return () => {
rootElement.parentElement?.removeChild(rootElement);
}
}
export class UI {
private container: HTMLDivElement;
private list: HTMLDivElement;
constructor(private store: Store, private palette: Palette) {
this.container = document.createElement("div");
const s = this.container.style;
this.container.setAttribute("mod", "pixel-canvas-overlay")
s.position = "fixed";
s.zIndex = "1000"
s.left = "0px";
s.bottom = "0px"
const container = document.createElement("div");
const ss = container.style;
this.container.appendChild(container);
ss.backgroundColor = "black";
ss.color = "white";
ss.margin = "5px";
ss.padding = "5px";
ss.border = "1px solid white";
const h = document.createElement("h3");
h.textContent = "Pixel Canvas overlay";
const input = this.btn("Import", async () => {
const mural = await this.import();
if (mural) {
this.store.add(mural);
this.renderList();
}
});
container.appendChild(h);
container.appendChild(input);
this.list = document.createElement("div");
this.list.style.display = "flex";
this.list.style.flexDirection = "column";
container.appendChild(this.list);
}
private btn(value: string, onClick: () => void) {
const button = document.createElement("button");
button.style.backgroundColor = "black";
button.style.color = "white";
button.style.border = "1px solid white";
button.style.padding = "2px";
button.textContent = value;
button.addEventListener("click", onClick);
return button;
}
import = async () => {
const file = await this.importFile();
if (file) {
if (file.type === "mural") {
return file.data as Mural;
} else {
const pixels = await this.imageToMural(file.data as HTMLImageElement);
await new Promise((resolve, reject)=> {
this.store.setOverlayModify({
pixels,
cb: (name, x, y, confirm) => {
if (confirm) {
resolve({ name, pixels, x, y })
} else {
reject(new Error("Canceled by user"));
}
}
});
})
const heights = prompt(`Enter name and location [name, x, y]`) || "";
const data = heights.split(",");
const numbers = [data[1], data[2]].map(e=> parseInt(e.trim(), 10));
if (typeof numbers[1] === "number" && typeof numbers[2] === "number") {
alert("Invalid size");
}
const mural: Mural = {
name: data[0],
x: numbers[0],
y: numbers[1],
pixels
}
try {
validateMural(mural);
} catch (error) {
alert(error.message);
}
return mural;
}
}
}
private async importFile() {
const fileInput = new FileInput();
fileInput.setAcceptType(["png", "jpg", "jpeg", ...TEXT_FORMATS]);
const files = await fileInput.show();
const fileData = files[0];
if (!fileData) {
throw new Error("Empty file");
}
const ex = getExtension(fileData.name);
const name = ex.text;
if (TEXT_FORMATS.includes(ex.ex)) {
const content = await readAsString(fileData);
const mural = JSON.parse(content) as Mural;
if (!mural.name) {
mural.name = prompt("Missing name for this mural. Please enter it manually", name) || "";
}
validateMural(mural);
return {
type: "mural",
data: mural,
};
} else {
const readData = await readAsDataUrl(fileData);
return {
type: "image",
data: await loadImageSource(`${ex.text}.${ex.ex}`, readData),
};
}
}
private async imageToMural(image: HTMLImageElement) {
const actualImage = await this.getImageSettingFromUser(image);
const canvas = imageToCanvas(actualImage);
const imageData = canvasToImageData(canvas);
flatQuantizeImageData(imageData, this.palette.palette);
return imageDataToPaletteIndices(imageData, this.palette.palette);
}
private async getImageSettingFromUser(image: HTMLImageElement) {
const should = confirm(`You are importing image with size ${image.width}x${image.height}. Do you want to preform any image manipulations?`);
if (should) {
if (confirm(`Do you want to resize?\n Image ${image.width}x${image.height}?`)) {
const heights = prompt(`Enter new [width x height]`) || "";
const numbers = heights.split("x").map(e=> parseInt(e.trim(), 10));
if (typeof numbers[0] === "number" && typeof numbers[1] === "number" && numbers[0] > 0 && numbers[1] > 0) {
alert("Invalid size");
return image;
}
image = await resize(image, numbers[0], numbers[1]);
}
}
return image;
}
private renderList() {
while (this.list.children.length) {
this.list.removeChild(this.list.children[0]);
}
for (const mural of this.store.murals) {
const container = document.createElement("div");
container.style.border = `${this.store.selected?.m === mural ? 3 : 1}px solid white`;
container.style.padding = "2px";
const h5 = document.createElement("h5");
h5.textContent = mural.name;
const canvas = createCanvas();
canvas.style.maxHeight = "100px";
canvas.style.maxWidth = "100px";
const ctx = canvas.getContext("2d")!;
canvas.width = getMuralWidth(mural);
canvas.height = getMuralHeight(mural);
ctx.drawImage(mural.ref, 0, 0, canvas.width, canvas.height);
const wh = document.createElement("div");
wh.textContent = `Size ${getMuralWidth(mural)}x${getMuralHeight(mural)}`
const at = document.createElement("div");
at.textContent = `At: ${mural.x}x${mural.y}`;
container.appendChild(h5);
container.appendChild(wh);
container.appendChild(at);
container.appendChild(canvas);
const select = this.btn("select", () => {
this.store.select(mural);
this.renderList();
});
const del = this.btn("delete", () => {
this.store.remove(mural);
this.renderList();
});
container.appendChild(select);
container.appendChild(del);
this.list.appendChild(container);
}
}
async append() {
document.body.appendChild(this.container);
this.renderList();
}
destroy() {
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
}
}