Change storage to use indexDB within binary format
This commit is contained in:
Generated
+46
@@ -21,6 +21,7 @@
|
|||||||
"copy-dir": "^1.3.0",
|
"copy-dir": "^1.3.0",
|
||||||
"esbuild": "^0.20.0",
|
"esbuild": "^0.20.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -2259,6 +2260,12 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immediate": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
@@ -2734,6 +2741,24 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lie": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"immediate": "~3.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/localforage": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lie": "3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
@@ -5457,6 +5482,11 @@
|
|||||||
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
|
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"immediate": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
|
||||||
|
},
|
||||||
"import-fresh": {
|
"import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
@@ -5797,6 +5827,22 @@
|
|||||||
"type-check": "~0.4.0"
|
"type-check": "~0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lie": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
||||||
|
"requires": {
|
||||||
|
"immediate": "~3.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"localforage": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
||||||
|
"requires": {
|
||||||
|
"lie": "3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"locate-path": {
|
"locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"copy-dir": "^1.3.0",
|
"copy-dir": "^1.3.0",
|
||||||
"esbuild": "^0.20.0",
|
"esbuild": "^0.20.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import { Coordinates } from "../../lib/coordinates";
|
|||||||
import { Palette } from "../../lib/palette";
|
import { Palette } from "../../lib/palette";
|
||||||
import { Menu } from "./menu";
|
import { Menu } from "./menu";
|
||||||
import { MovableWindow } from "./movableWindow";
|
import { MovableWindow } from "./movableWindow";
|
||||||
import { SelectedMural } from "../../interfaces";
|
|
||||||
import { Minimap } from "./minimap";
|
import { Minimap } from "./minimap";
|
||||||
import { Overlay } from "./overlay";
|
import { Overlay } from "./overlay";
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import { TC_LOGO } from "../../constants";
|
import { TC_LOGO } from "../../constants";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import { MuralEx } from "../../interfaces";
|
||||||
|
|
||||||
const Img = styled.img`
|
const Img = styled.img`
|
||||||
width: 25px;
|
width: 25px;
|
||||||
@@ -33,7 +33,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface State extends StoreSettings{
|
interface State extends StoreSettings{
|
||||||
selected?: SelectedMural;
|
selected?: MuralEx;
|
||||||
overlays: number[];
|
overlays: number[];
|
||||||
phantomOverlay: number;
|
phantomOverlay: number;
|
||||||
overlayModify?: OverlayReturn;
|
overlayModify?: OverlayReturn;
|
||||||
@@ -127,14 +127,14 @@ export class Main extends React.Component<Props, State> {
|
|||||||
opacity={this.state.opacity}
|
opacity={this.state.opacity}
|
||||||
cords={this.props.cords}
|
cords={this.props.cords}
|
||||||
palette={this.props.palette}
|
palette={this.props.palette}
|
||||||
mural={this.props.store.murals[o]}
|
muralExtended={this.props.store.murals[o]}
|
||||||
/> )}
|
/> )}
|
||||||
{this.props.store.murals[this.state.phantomOverlay] ? <Overlay
|
{this.props.store.murals[this.state.phantomOverlay] ? <Overlay
|
||||||
storage={this.props.storage}
|
storage={this.props.storage}
|
||||||
opacity={50}
|
opacity={50}
|
||||||
cords={this.props.cords}
|
cords={this.props.cords}
|
||||||
palette={this.props.palette}
|
palette={this.props.palette}
|
||||||
mural={this.props.store.murals[this.state.phantomOverlay]}
|
muralExtended={this.props.store.murals[this.state.phantomOverlay]}
|
||||||
/> : null }
|
/> : null }
|
||||||
{this.state.overlayModify ? <Overlay
|
{this.state.overlayModify ? <Overlay
|
||||||
muralObj={this.state.overlayModify.muralObj}
|
muralObj={this.state.overlayModify.muralObj}
|
||||||
@@ -142,7 +142,7 @@ export class Main extends React.Component<Props, State> {
|
|||||||
storage={this.props.storage}
|
storage={this.props.storage}
|
||||||
cords={this.props.cords}
|
cords={this.props.cords}
|
||||||
palette={this.props.palette}
|
palette={this.props.palette}
|
||||||
mural={this.props.store.overlayModify!.pixels}
|
muralExtended={this.props.store.overlayModify!.mural}
|
||||||
onChange={(name, x, y, confirm) => {
|
onChange={(name, x, y, confirm) => {
|
||||||
this.state.overlayModify!.cb(name, x, y, confirm);
|
this.state.overlayModify!.cb(name, x, y, confirm);
|
||||||
}} /> : null}
|
}} /> : null}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { MuralEx, SelectedMural } from "../../interfaces";
|
import { MuralEx } from "../../interfaces";
|
||||||
import { Store, StoreEvents } from "../../lib/store";
|
import { Store, StoreEvents } from "../../lib/store";
|
||||||
import { MuralView } from "./muralView";
|
import { MuralView } from "./muralView";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
@@ -38,7 +38,7 @@ interface Props {
|
|||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
murals: MuralEx[];
|
murals: MuralEx[];
|
||||||
selected?: SelectedMural;
|
selected?: MuralEx;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MuralList extends React.Component<Props, State> {
|
export class MuralList extends React.Component<Props, State> {
|
||||||
@@ -63,9 +63,12 @@ export class MuralList extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
render() {
|
render() {
|
||||||
return <ScrollContainer> {this.state.murals.map((m,i) => {
|
return <ScrollContainer> {this.state.murals.map((m,i) => {
|
||||||
return <MuralView key={i} mural={m} cords={this.props.cords}
|
return <MuralView
|
||||||
palette={this.props.palette} store={this.props.store}
|
key={i}
|
||||||
selected={m === this.state.selected?.m} />;
|
muralExtended={m} cords={this.props.cords}
|
||||||
|
palette={this.props.palette}
|
||||||
|
store={this.props.store}
|
||||||
|
selected={m && m === this.state.selected} />;
|
||||||
})}</ScrollContainer>;
|
})}</ScrollContainer>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Store } from "../../lib/store";
|
import { Store } from "../../lib/store";
|
||||||
import { MuralEx, MuralOld, MuralStatus } from "../../interfaces";
|
import { MuralEx, MuralStatus } from "../../interfaces";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { A, Border, Btn, Flex, SELECTED_COLOR } from "../styles";
|
import { A, Border, Btn, Flex, SELECTED_COLOR } from "../styles";
|
||||||
import { CanvasToCanvasJSX } from "./canvasToCanvasJSX";
|
import { CanvasToCanvasJSX } from "./canvasToCanvasJSX";
|
||||||
import { convertOldMuralToNewMural, formatNumber, getMuralHeight, getMuralWidth, getPixelStatusMural } from "../../lib/utils";
|
import { formatNumber, getPixelStatusMural } from "../../lib/utils";
|
||||||
import {
|
import {
|
||||||
IconDefinition, faDownload, faLayerGroup, faLocation, faPenToSquare, faRefresh, faTrash
|
IconDefinition, faDownload, faLayerGroup, faLocation, faPenToSquare, faRefresh, faTrash
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
@@ -66,7 +66,7 @@ export const BtnSmall = styled.button`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mural: MuralEx;
|
muralExtended: MuralEx;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
store: Store;
|
store: Store;
|
||||||
palette: Palette;
|
palette: Palette;
|
||||||
@@ -102,7 +102,7 @@ export class MuralView extends React.Component<Props, State> {
|
|||||||
this.props.cords.off(CordType.Url, this.onCordUpdate);
|
this.props.cords.off(CordType.Url, this.onCordUpdate);
|
||||||
}
|
}
|
||||||
componentDidUpdate(prevProps: Readonly<Props> ) {
|
componentDidUpdate(prevProps: Readonly<Props> ) {
|
||||||
if (this.props.mural.ref !== prevProps.mural.ref) {
|
if (this.props.muralExtended.ref !== prevProps.muralExtended.ref) {
|
||||||
this.updateSize();
|
this.updateSize();
|
||||||
if (typeof this.state.progress === "function") {
|
if (typeof this.state.progress === "function") {
|
||||||
typeof this.state.progress();
|
typeof this.state.progress();
|
||||||
@@ -116,8 +116,8 @@ export class MuralView extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateSize = () => {
|
updateSize = () => {
|
||||||
const w = this.props.mural.ref.width;
|
const w = this.props.muralExtended.ref.width;
|
||||||
const h = this.props.mural.ref.height;
|
const h = this.props.muralExtended.ref.height;
|
||||||
|
|
||||||
let width = w;
|
let width = w;
|
||||||
let height = h;
|
let height = h;
|
||||||
@@ -153,26 +153,26 @@ export class MuralView extends React.Component<Props, State> {
|
|||||||
if ("tagName" in tr) {
|
if ("tagName" in tr) {
|
||||||
const ignore = ["button", "svg", "path"];
|
const ignore = ["button", "svg", "path"];
|
||||||
if (!ignore.includes(tr.tagName.toLowerCase())) {
|
if (!ignore.includes(tr.tagName.toLowerCase())) {
|
||||||
this.s.select(this.props.selected ? undefined : this.props.mural );
|
this.s.select(this.props.selected ? undefined : this.props.muralExtended );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
onModify = async () => {
|
onModify = async () => {
|
||||||
const mural = this.props.mural;
|
const muralExtended = this.props.muralExtended;
|
||||||
this.props.store.setOverlayModify({
|
this.props.store.setOverlayModify({
|
||||||
pixels: this.props.mural.pixels,
|
mural: muralExtended,
|
||||||
cb: (name, x, y, confirm) => {
|
cb: (name, x, y, confirm) => {
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
mural.name = name;
|
muralExtended.mural.name = name;
|
||||||
mural.x = x;
|
muralExtended.mural.x = x;
|
||||||
mural.y = y;
|
muralExtended.mural.y = y;
|
||||||
this.props.store.updateMural(mural);
|
this.props.store.updateMural(muralExtended.mural);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
muralObj: {
|
muralObj: {
|
||||||
name: this.props.mural.name,
|
name: this.props.muralExtended.mural.name,
|
||||||
x: this.props.mural.x,
|
x: this.props.muralExtended.mural.x,
|
||||||
y: this.props.mural.y
|
y: this.props.muralExtended.mural.y
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -212,39 +212,35 @@ export class MuralView extends React.Component<Props, State> {
|
|||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
onExport = async () => {
|
onExport = async () => {
|
||||||
const rawMural: MuralOld = {
|
const mural = this.props.muralExtended.mural;
|
||||||
name: this.props.mural.name,
|
|
||||||
x: this.props.mural.x,
|
|
||||||
y: this.props.mural.y,
|
|
||||||
pixels: this.props.mural.pixels,
|
|
||||||
};
|
|
||||||
const mural = convertOldMuralToNewMural(rawMural);
|
|
||||||
const buffer = await mural.getBuffer();
|
const buffer = await mural.getBuffer();
|
||||||
const blob = new Blob([buffer], { type: "octet/stream" });
|
const blob = new Blob([buffer], { type: "octet/stream" });
|
||||||
|
|
||||||
const saveName = rawMural.name
|
const saveName = mural.name
|
||||||
.replace(/ /, "_")
|
.replace(/ /, "_")
|
||||||
.replace(/[^a-zA-Z0-9-_]/g, "");
|
.replace(/[^a-zA-Z0-9-_]/g, "");
|
||||||
|
|
||||||
saveAs(blob, `${saveName}.${BIN_FORMATS[0]}`);
|
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
|
||||||
this.s.remove(this.props.mural);
|
.confirm(`Are you sure you want to remove "${this.props.muralExtended.mural.name}"`)
|
||||||
|
) {
|
||||||
|
this.s.remove(this.props.muralExtended);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
onEnter = () => {
|
onEnter = () => {
|
||||||
this.props.store.addPhantomOverlay(this.props.mural);
|
this.props.store.addPhantomOverlay(this.props.muralExtended);
|
||||||
};
|
};
|
||||||
onLeave = () => {
|
onLeave = () => {
|
||||||
this.props.store.removePhantomOverlay();
|
this.props.store.removePhantomOverlay();
|
||||||
};
|
};
|
||||||
|
|
||||||
get link() {
|
get link() {
|
||||||
const weight = getMuralWidth(this.props.mural);
|
const weight = this.props.muralExtended.mural.w;
|
||||||
const height = getMuralHeight(this.props.mural);
|
const height = this.props.muralExtended.mural.h;
|
||||||
const x = this.props.mural.x + Math.round(weight / 2);
|
const x = this.props.muralExtended.mural.x + Math.round(weight / 2);
|
||||||
const y = this.props.mural.y + Math.round(height / 2);
|
const y = this.props.muralExtended.mural.y + Math.round(height / 2);
|
||||||
const o = origin === "null" ? `${location.href}` : `${origin}/`;
|
const o = origin === "null" ? `${location.href}` : `${origin}/`;
|
||||||
return `${o}@${x},${y},${this.props.cords.uScale}`;
|
return `${o}@${x},${y},${this.props.cords.uScale}`;
|
||||||
}
|
}
|
||||||
@@ -254,10 +250,10 @@ export class MuralView extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onPreview = () => {
|
onPreview = () => {
|
||||||
if (this.props.store.hasOverlay(this.props.mural)) {
|
if (this.props.store.hasOverlay(this.props.muralExtended)) {
|
||||||
this.props.store.removeOverlay(this.props.mural);
|
this.props.store.removeOverlay(this.props.muralExtended);
|
||||||
} else {
|
} else {
|
||||||
this.props.store.addOverlay(this.props.mural);
|
this.props.store.addOverlay(this.props.muralExtended);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -267,7 +263,9 @@ export class MuralView extends React.Component<Props, State> {
|
|||||||
canceled = true;
|
canceled = true;
|
||||||
};
|
};
|
||||||
this.setState({progress: cancelFn});
|
this.setState({progress: cancelFn});
|
||||||
const data = await getPixelStatusMural(this.props.mural, this.props.palette.palette);
|
const data = await getPixelStatusMural(
|
||||||
|
this.props.muralExtended.mural, this.props.palette.palette
|
||||||
|
);
|
||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
this.setState({progress: data});
|
this.setState({progress: data});
|
||||||
}
|
}
|
||||||
@@ -293,18 +291,21 @@ export class MuralView extends React.Component<Props, State> {
|
|||||||
cursor: "pointer" }}
|
cursor: "pointer" }}
|
||||||
onMouseEnter={this.onEnter} onMouseLeave={this.onLeave}>
|
onMouseEnter={this.onEnter} onMouseLeave={this.onLeave}>
|
||||||
<Flex>
|
<Flex>
|
||||||
<CanvasToCanvasJSX canvas={this.props.mural.ref} height={this.state.height} width={this.state.width}/>
|
<CanvasToCanvasJSX canvas={this.props.muralExtended.ref} height={this.state.height} width={this.state.width}/>
|
||||||
<Margin style={{flex: "1"}}>
|
<Margin style={{flex: "1"}}>
|
||||||
{this.renderInfoLine("Name", this.props.mural.name)}
|
{this.renderInfoLine("Name", this.props.muralExtended.mural.name)}
|
||||||
{this.renderInfoLine("Pixel count", formatNumber(this.props.mural.pixelCount))}
|
{this.renderInfoLine("Pixel count", formatNumber(this.props.muralExtended.pixelCount))}
|
||||||
{this.renderInfoLine("X", this.props.mural.x.toString() )}
|
{this.renderInfoLine("X", this.props.muralExtended.mural.x.toString() )}
|
||||||
{this.renderInfoLine("Y", this.props.mural.y.toString() )}
|
{this.renderInfoLine("Y", this.props.muralExtended.mural.y.toString() )}
|
||||||
{this.renderInfoLine("Size", `${getMuralWidth(this.props.mural)}x${getMuralHeight(this.props.mural)}`)}
|
{this.renderInfoLine(
|
||||||
|
"Size",
|
||||||
|
`${this.props.muralExtended.mural.w}x${this.props.muralExtended.mural.w}`
|
||||||
|
)}
|
||||||
{this.renderInfoLine("Progress", this.renderProgress())}
|
{this.renderInfoLine("Progress", this.renderProgress())}
|
||||||
</Margin>
|
</Margin>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex>
|
<Flex>
|
||||||
{this.btn("Preview", faLayerGroup, this.onPreview, this.props.store.hasOverlay(this.props.mural))}
|
{this.btn("Preview", faLayerGroup, this.onPreview, this.props.store.hasOverlay(this.props.muralExtended))}
|
||||||
{this.btn("Modify", faPenToSquare, this.onModify)}
|
{this.btn("Modify", faPenToSquare, this.onModify)}
|
||||||
{this.btn("Export", faDownload, this.onExport)}
|
{this.btn("Export", faDownload, this.onExport)}
|
||||||
{this.btn("Delete", faTrash, this.onDelete)}
|
{this.btn("Delete", faTrash, this.onDelete)}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Mural, MuralEx } from "../../interfaces";
|
import { MuralEx } from "../../interfaces";
|
||||||
import { canvasFromMural, get2DArrHeight, get2DArrWidth } from "../../lib/utils";
|
|
||||||
import { Coordinates, CordType } from "../../lib/coordinates";
|
import { Coordinates, CordType } from "../../lib/coordinates";
|
||||||
import { Palette } from "../../lib/palette";
|
import { Palette } from "../../lib/palette";
|
||||||
import { MovableWindow } from "./movableWindow";
|
import { MovableWindow } from "./movableWindow";
|
||||||
import { Storage } from "../../lib/storage";
|
import { Storage } from "../../lib/storage";
|
||||||
import { MuralEditor } from "./muralEditor";
|
import { MuralEditor } from "./muralEditor";
|
||||||
|
import { Mural } from "../../lib/mural";
|
||||||
|
|
||||||
const Canvas = styled.canvas`
|
const Canvas = styled.canvas`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -15,7 +15,7 @@ const Canvas = styled.canvas`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mural: MuralEx | Mural | number[][];
|
muralExtended: MuralEx;
|
||||||
muralObj?: Partial<Mural>;
|
muralObj?: Partial<Mural>;
|
||||||
onChange?: (name: string, x: number, y:number, confirm?: boolean) => void;
|
onChange?: (name: string, x: number, y:number, confirm?: boolean) => void;
|
||||||
cords: Coordinates;
|
cords: Coordinates;
|
||||||
@@ -61,7 +61,7 @@ export class Overlay extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>) {
|
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>) {
|
||||||
if (prevProps.mural !== this.props.mural) {
|
if (prevProps.muralExtended !== this.props.muralExtended) {
|
||||||
this._refImage = undefined;
|
this._refImage = undefined;
|
||||||
this.setState({
|
this.setState({
|
||||||
x: 0, y: 0
|
x: 0, y: 0
|
||||||
@@ -72,43 +72,21 @@ export class Overlay extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private isPixels(pixels: MuralEx | Mural | number[][]): pixels is number[][] {
|
|
||||||
return Array.isArray(pixels);
|
|
||||||
}
|
|
||||||
|
|
||||||
get pixels() {
|
get pixels() {
|
||||||
if (this.isPixels(this.props.mural)) {
|
return this.props.muralExtended.mural.pixelBuffer;
|
||||||
return this.props.mural;
|
|
||||||
} else {
|
|
||||||
return this.props.mural.pixels;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get x() {
|
get x() {
|
||||||
if (this.isPixels(this.props.mural)) {
|
return this.props.muralExtended.mural.x;
|
||||||
return this.state.x;
|
|
||||||
} else {
|
|
||||||
return this.props.mural.x;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get y() {
|
get y() {
|
||||||
if (this.isPixels(this.props.mural)) {
|
return this.props.muralExtended.mural.y;
|
||||||
return this.state.y;
|
|
||||||
} else {
|
|
||||||
return this.props.mural.y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get refImg() {
|
get refImg() {
|
||||||
if (!Array.isArray(this.props.mural) && "ref" in this.props.mural) {
|
return this.props.muralExtended.ref;
|
||||||
return this.props.mural.ref;
|
|
||||||
}
|
|
||||||
if (this._refImage) {
|
|
||||||
return this._refImage;
|
|
||||||
}
|
|
||||||
this._refImage = canvasFromMural(this.pixels, this.props.palette.hex).canvas;
|
|
||||||
return this._refImage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
draw = () => {
|
draw = () => {
|
||||||
@@ -122,8 +100,8 @@ export class Overlay extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
const { x, y } = cords;
|
const { x, y } = cords;
|
||||||
|
|
||||||
const width = get2DArrWidth(this.pixels) * scale;
|
const width = this.props.muralExtended.mural.w * scale;
|
||||||
const height = get2DArrHeight(this.pixels) * scale;
|
const height = this.props.muralExtended.mural.h * scale;
|
||||||
if (width !== canvas.width || height !== canvas.height ) {
|
if (width !== canvas.width || height !== canvas.height ) {
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
|
|||||||
+18
-46
@@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { MuralOld, RGB } from "../interfaces";
|
import { ImportOutput, MuralOld, RGB } from "../interfaces";
|
||||||
import { canvasToImageData, get2DArrHeight, get2DArrWidth, getColorScore, getExtension,
|
import { canvasToImageData, convertOldMuralToNewMural, getColorScore, getExtension,
|
||||||
imageDataToPaletteIndices, imageToCanvas, loadImageSource, processNumberEvent,
|
imageDataToPaletteIndices, imageToCanvas, loadImageSource, processNumberEvent,
|
||||||
readAsArrayBuffer,
|
readAsArrayBuffer,
|
||||||
readAsDataUrl, readAsString, resize, rgb, validateMural } from "../lib/utils";
|
readAsDataUrl, readAsString, resize, rgb } from "../lib/utils";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Popup } from "./components/Popup";
|
import { Popup } from "./components/Popup";
|
||||||
import RgbQuant, { DitheringKernel, RGBQuantOptions } from "rgbquant";
|
import RgbQuant, { DitheringKernel, RGBQuantOptions } from "rgbquant";
|
||||||
@@ -56,7 +56,7 @@ type DitheringKernelEx = DitheringKernel | "Flat";
|
|||||||
interface QuantResult {
|
interface QuantResult {
|
||||||
type: DitheringKernelEx;
|
type: DitheringKernelEx;
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
indices: number[][];
|
indices: Int8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
type DitherSetting = DitheringKernel | "Flat" | "show-all";
|
type DitherSetting = DitheringKernel | "Flat" | "show-all";
|
||||||
@@ -85,33 +85,35 @@ export interface ImageToMuralOptions {
|
|||||||
quantizerSetting: DitherSetting;
|
quantizerSetting: DitherSetting;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function importArtWorks(store: Store, cords: Coordinates, palette: Palette) {
|
export async function importArtWorks(
|
||||||
|
store: Store, cords: Coordinates, palette: Palette
|
||||||
|
): Promise<Mural[]> {
|
||||||
const files = await importFiles();
|
const files = await importFiles();
|
||||||
const output: MuralOld[] = [];
|
const output: Mural[] = [];
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.type === "mural") {
|
if (file.type === "mural") {
|
||||||
output.push(file.data);
|
output.push(file.data);
|
||||||
} else {
|
} else {
|
||||||
const img = file.data as HTMLImageElement;
|
const img = file.data;
|
||||||
const pixels = await imageToMural(img, palette);
|
const pixels = await imageToMural(img, palette);
|
||||||
const mural = await new Promise<MuralOld>((resolve, reject)=> {
|
const mural = await new Promise<Mural>((resolve, reject)=> {
|
||||||
store.setOverlayModify({
|
store.setOverlayModify({
|
||||||
pixels,
|
pixels,
|
||||||
muralObj: {
|
muralObj: {
|
||||||
name: img.alt,
|
name: img.alt,
|
||||||
x: Math.floor(cords.ux - (get2DArrWidth(pixels) / 2)),
|
x: Math.floor(cords.ux - (img.width / 2)),
|
||||||
y: Math.floor(cords.uy - (get2DArrHeight(pixels) / 2)),
|
y: Math.floor(cords.uy - (img.height / 2)),
|
||||||
},
|
},
|
||||||
cb: (name, x, y, confirm) => {
|
cb: (name, x, y, confirm) => {
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
resolve({ name, pixels, x, y });
|
const mural = new Mural(name, x,y,img.width, img.height, pixels);
|
||||||
|
resolve(mural);
|
||||||
} else {
|
} else {
|
||||||
reject(new Error("Canceled by user"));
|
reject(new Error("Canceled by user"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
validateMural(mural);
|
|
||||||
output.push(mural);
|
output.push(mural);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -472,7 +474,7 @@ export function imageDataToCanvas(imageData: ImageData) {
|
|||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function importFiles() {
|
async function importFiles(): Promise<ImportOutput[]> {
|
||||||
const fileInput = new FileInput();
|
const fileInput = new FileInput();
|
||||||
const images = ["png", "jpg", "jpeg"];
|
const images = ["png", "jpg", "jpeg"];
|
||||||
fileInput.setAcceptType([...images, ...TEXT_FORMATS, ...BIN_FORMATS]);
|
fileInput.setAcceptType([...images, ...TEXT_FORMATS, ...BIN_FORMATS]);
|
||||||
@@ -489,13 +491,7 @@ async function importFiles() {
|
|||||||
throw new Error("Only one image files allowed to be imported at the time");
|
throw new Error("Only one image files allowed to be imported at the time");
|
||||||
}
|
}
|
||||||
|
|
||||||
const output: ({
|
const output: ImportOutput[] = [];
|
||||||
type: "mural",
|
|
||||||
data: MuralOld
|
|
||||||
} | {
|
|
||||||
type: "image",
|
|
||||||
data: HTMLImageElement
|
|
||||||
}) [] = [];
|
|
||||||
for (const file of filteredFiles) {
|
for (const file of filteredFiles) {
|
||||||
const ex = getExtension(file.name);
|
const ex = getExtension(file.name);
|
||||||
const etn = ex.ex.toLowerCase();
|
const etn = ex.ex.toLowerCase();
|
||||||
@@ -510,38 +506,14 @@ async function importFiles() {
|
|||||||
|
|
||||||
output.push({
|
output.push({
|
||||||
type: "mural",
|
type: "mural",
|
||||||
data: mural,
|
data: convertOldMuralToNewMural(mural),
|
||||||
});
|
});
|
||||||
} else if (BIN_FORMATS.includes(etn)) {
|
} else if (BIN_FORMATS.includes(etn)) {
|
||||||
const buffer = await readAsArrayBuffer(file);
|
const buffer = await readAsArrayBuffer(file);
|
||||||
const mural = await Mural.from(new Uint8Array(buffer));
|
const mural = await Mural.from(new Uint8Array(buffer));
|
||||||
const pixelBuffer = mural.pixelBuffer;
|
|
||||||
const pixels = Array.from({ length: mural.h }, () => Array(mural.w).fill(0));
|
|
||||||
let i = 0;
|
|
||||||
let yy = -1;
|
|
||||||
leg:
|
|
||||||
for (;;) {
|
|
||||||
yy++;
|
|
||||||
if (pixelBuffer[i] === undefined) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// const ref = [];
|
|
||||||
// pixels.push(ref);
|
|
||||||
for (let xx = 0; xx < mural.w; xx++) {
|
|
||||||
const item = pixelBuffer[i++];
|
|
||||||
if (item != undefined) {
|
|
||||||
pixels[yy][xx] = item;
|
|
||||||
//ref.push(item);
|
|
||||||
} else {
|
|
||||||
break leg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output.push({
|
output.push({
|
||||||
type: "mural",
|
type: "mural",
|
||||||
data: {
|
data: mural,
|
||||||
name, x: mural.x, y: mural.y, pixels,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const readData = await readAsDataUrl(file);
|
const readData = await readAsDataUrl(file);
|
||||||
|
|||||||
+16
-7
@@ -1,3 +1,5 @@
|
|||||||
|
import { Mural } from "./lib/mural";
|
||||||
|
|
||||||
export interface RGB {
|
export interface RGB {
|
||||||
r: number;
|
r: number;
|
||||||
g: number;
|
g: number;
|
||||||
@@ -22,13 +24,8 @@ export interface LoadUnload {
|
|||||||
unload: () => Promise<void> | void;
|
unload: () => Promise<void> | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectedMural {
|
export interface MuralEx {
|
||||||
m: MuralEx;
|
mural: Mural;
|
||||||
w: number;
|
|
||||||
h: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MuralEx extends MuralOld {
|
|
||||||
ref: HTMLCanvasElement;
|
ref: HTMLCanvasElement;
|
||||||
pixelCount: number;
|
pixelCount: number;
|
||||||
}
|
}
|
||||||
@@ -44,3 +41,15 @@ export interface Rect {
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImportOutputMural {
|
||||||
|
type: "mural",
|
||||||
|
data: Mural
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImportOutputImage {
|
||||||
|
type: "image",
|
||||||
|
data: HTMLImageElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ImportOutput = ImportOutputImage | ImportOutputMural;
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ export class Mural {
|
|||||||
this.validate();
|
this.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new Mural(this.name, this.x, this.y, this.w, this.h, this._b.slice());
|
||||||
|
}
|
||||||
|
|
||||||
getBuffer() {
|
getBuffer() {
|
||||||
this.validate();
|
this.validate();
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ export class Storage {
|
|||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
deleteItem(key: string) {
|
removeItem(key: string) {
|
||||||
if (this.chrome) {
|
if (this.chrome) {
|
||||||
return new Promise<void>(r => {
|
return new Promise<void>(r => {
|
||||||
chrome.storage.local.remove(key, r);
|
chrome.storage.local.remove(key, r);
|
||||||
|
|||||||
+53
-30
@@ -1,8 +1,13 @@
|
|||||||
import { BasicEventEmitter, Listener } from "./eventEmitter";
|
import { BasicEventEmitter, Listener } from "./eventEmitter";
|
||||||
import { LoadUnload, Mural, MuralEx, SelectedMural } from "../interfaces";
|
import { LoadUnload, MuralEx, MuralOld } from "../interfaces";
|
||||||
import { Palette } from "./palette";
|
import { Palette } from "./palette";
|
||||||
import { Storage } from "./storage";
|
import { Storage } from "./storage";
|
||||||
import { canvasFromMural, getMuralHeight, getMuralWidth, pushUnique, removeItem } from "./utils";
|
import localforage from "localforage";
|
||||||
|
import {
|
||||||
|
convertOldMuralToNewMural, createMuralExtended,
|
||||||
|
pushUnique, removeItem
|
||||||
|
} from "./utils";
|
||||||
|
import { Mural } from "./mural";
|
||||||
|
|
||||||
export enum StoreEvents {
|
export enum StoreEvents {
|
||||||
MuralAdd = 1,
|
MuralAdd = 1,
|
||||||
@@ -15,7 +20,7 @@ export enum StoreEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface OverlayReturn {
|
export interface OverlayReturn {
|
||||||
pixels: number[][];
|
mural: MuralEx;
|
||||||
muralObj?: Partial<Mural>;
|
muralObj?: Partial<Mural>;
|
||||||
cb: (name: string, x: number, y: number, confirm?: boolean) => void;
|
cb: (name: string, x: number, y: number, confirm?: boolean) => void;
|
||||||
}
|
}
|
||||||
@@ -24,23 +29,53 @@ export class Store implements LoadUnload {
|
|||||||
private readonly STORAGE_KEY_MURAL = "_murals";
|
private readonly STORAGE_KEY_MURAL = "_murals";
|
||||||
private readonly STORAGE_KEY_SELECTED = "_mural";
|
private readonly STORAGE_KEY_SELECTED = "_mural";
|
||||||
private _murals: MuralEx[] = [];
|
private _murals: MuralEx[] = [];
|
||||||
private _selected: SelectedMural | undefined;
|
private _selected?: MuralEx;
|
||||||
private emitter = new BasicEventEmitter();
|
private emitter = new BasicEventEmitter();
|
||||||
private _overlayIndices: number[] = [];
|
private _overlayIndices: number[] = [];
|
||||||
private _phantomOverlay = -1;
|
private _phantomOverlay = -1;
|
||||||
private _overlayModify?: OverlayReturn;
|
private _overlayModify?: OverlayReturn;
|
||||||
|
private db = localforage.createInstance({
|
||||||
|
name: "pixel-canvas-overlay"
|
||||||
|
});
|
||||||
|
|
||||||
constructor(private storage: Storage, private palette: Palette) {}
|
constructor(private storage: Storage, private palette: Palette) {}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this._murals = await this.storage.getItem<MuralEx[]>(this.STORAGE_KEY_MURAL) || [];
|
console.log("loaded");
|
||||||
for (const mural of this._murals) {
|
this._murals = [];
|
||||||
const { canvas, pixels } = canvasFromMural(mural.pixels, this.palette.hex);
|
const rawMurals = await this.db.getItem<Uint8Array[]>(this.STORAGE_KEY_MURAL) || [];
|
||||||
mural.ref = canvas;
|
|
||||||
mural.pixelCount = pixels;
|
const murals = await Promise.all(rawMurals.map(e => Mural.from(e)));
|
||||||
|
// backwards compatibility
|
||||||
|
const oldMurals = await this.storage.getItem<MuralOld[]>(this.STORAGE_KEY_MURAL);
|
||||||
|
if (oldMurals) {
|
||||||
|
for (const old of oldMurals) {
|
||||||
|
const mural = convertOldMuralToNewMural(old);
|
||||||
|
murals.push(mural);
|
||||||
|
}
|
||||||
|
const muralBuffer = await Promise.all(murals.map(e => e.getBuffer()));
|
||||||
|
this.storage.removeItem(this.STORAGE_KEY_MURAL);
|
||||||
|
this.db.setItem(this.STORAGE_KEY_MURAL, muralBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//const murals = await this.storage.getItem<Uint8Array[]>(this.STORAGE_KEY_MURAL);
|
||||||
|
|
||||||
|
for (const mural of murals) {
|
||||||
|
this._murals.push(createMuralExtended(mural, this.palette.hex));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const index = await this.storage.getItem<number>(this.STORAGE_KEY_SELECTED) ?? -1;
|
const index = await this.storage.getItem<number>(this.STORAGE_KEY_SELECTED) ?? -1;
|
||||||
this.select(this._murals[index]);
|
if (index != null) {
|
||||||
|
this.select(this._murals[index]);
|
||||||
|
this.storage.removeItem(this.STORAGE_KEY_SELECTED);
|
||||||
|
await this.db.setItem(this.STORAGE_KEY_SELECTED, index);
|
||||||
|
} else {
|
||||||
|
const index = await this.db.getItem<number>(this.STORAGE_KEY_SELECTED);
|
||||||
|
if (index != null) {
|
||||||
|
this.select(this._murals[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateMural(_mural: Mural) {
|
updateMural(_mural: Mural) {
|
||||||
this.save();
|
this.save();
|
||||||
@@ -50,10 +85,7 @@ export class Store implements LoadUnload {
|
|||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
add(mural: Mural) {
|
add(mural: Mural) {
|
||||||
const m = mural as MuralEx;
|
const m = createMuralExtended(mural, this.palette.hex);
|
||||||
const { canvas, pixels } = canvasFromMural(mural.pixels, this.palette.hex);
|
|
||||||
m.ref = canvas;
|
|
||||||
m.pixelCount = pixels;
|
|
||||||
this._murals.push(m);
|
this._murals.push(m);
|
||||||
this.emit(StoreEvents.Any);
|
this.emit(StoreEvents.Any);
|
||||||
this.save();
|
this.save();
|
||||||
@@ -63,7 +95,7 @@ export class Store implements LoadUnload {
|
|||||||
this._overlayIndices = [];
|
this._overlayIndices = [];
|
||||||
this._overlayModify = undefined;
|
this._overlayModify = undefined;
|
||||||
this._phantomOverlay = -1;
|
this._phantomOverlay = -1;
|
||||||
if (this._selected && this._selected?.m === mural) {
|
if (this._selected && this._selected === mural) {
|
||||||
this._selected = undefined;
|
this._selected = undefined;
|
||||||
}
|
}
|
||||||
removeItem(this._murals, mural);
|
removeItem(this._murals, mural);
|
||||||
@@ -89,7 +121,7 @@ export class Store implements LoadUnload {
|
|||||||
this.emit(StoreEvents.MuralOverlay);
|
this.emit(StoreEvents.MuralOverlay);
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
setOverlayModify(overlay: OverlayReturn) {
|
setOverlayModify(overlay: OverlayReturn) { // WTF?
|
||||||
this._overlayModify = overlay;
|
this._overlayModify = overlay;
|
||||||
const cb = overlay.cb;
|
const cb = overlay.cb;
|
||||||
overlay.cb = (name, x, y, done) => {
|
overlay.cb = (name, x, y, done) => {
|
||||||
@@ -105,25 +137,16 @@ export class Store implements LoadUnload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
select(mural: MuralEx | undefined) {
|
select(mural: MuralEx | undefined) {
|
||||||
if (mural) {
|
this._selected = mural;
|
||||||
this._selected = {
|
|
||||||
m: mural,
|
|
||||||
h: getMuralHeight(mural),
|
|
||||||
w: getMuralWidth(mural),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this._selected = undefined;
|
|
||||||
}
|
|
||||||
this.emit(StoreEvents.MuralSelect);
|
this.emit(StoreEvents.MuralSelect);
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
await this.storage
|
const muralBuffer = await Promise.all(this._murals.map(e => e.mural.getBuffer()));
|
||||||
.setItem(this.STORAGE_KEY_MURAL, this._murals
|
this.db.setItem(this.STORAGE_KEY_MURAL, muralBuffer);
|
||||||
.map(m => ({ name: m.name, pixels: m.pixels, x: m.x, y: m.y } as Mural)));
|
const selected = this._selected ? this._murals.indexOf(this._selected) : -1;
|
||||||
const selected = this._selected?.m ? this._murals.indexOf(this._selected?.m!) : -1;
|
await this.db.setItem(this.STORAGE_KEY_SELECTED, selected);
|
||||||
await this.storage.setItem(this.STORAGE_KEY_SELECTED, selected);
|
|
||||||
}
|
}
|
||||||
addPhantomOverlay(mural: MuralEx) {
|
addPhantomOverlay(mural: MuralEx) {
|
||||||
const index = this._murals.indexOf(mural);
|
const index = this._murals.indexOf(mural);
|
||||||
|
|||||||
+37
-37
@@ -1,5 +1,5 @@
|
|||||||
import { clone, isInteger } from "lodash";
|
import { clone, isInteger } from "lodash";
|
||||||
import { MuralOld, MuralStatus, RGB } from "../interfaces";
|
import { MuralEx, MuralOld, MuralStatus, RGB } from "../interfaces";
|
||||||
import { fetchCombineTiledImage } from "./canvashot";
|
import { fetchCombineTiledImage } from "./canvashot";
|
||||||
import { Mural } from "./mural";
|
import { Mural } from "./mural";
|
||||||
|
|
||||||
@@ -240,12 +240,12 @@ export function flatQuantizeImageData(imageData: ImageData, palette: RGB[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPixelStatusMural(mural: MuralOld, palette: RGB[]): Promise<MuralStatus> {
|
export async function getPixelStatusMural(mural: Mural, palette: RGB[]): Promise<MuralStatus> {
|
||||||
const width = getMuralWidth(mural);
|
const width = mural.w;
|
||||||
const height = getMuralHeight(mural);
|
const height = mural.h;
|
||||||
const tile = await fetchCombineTiledImage(mural.x, mural.y, width, height);
|
const tile = await fetchCombineTiledImage(mural.x, mural.y, width, height);
|
||||||
const imageData = tile.getContext("2d")?.getImageData(0, 0, width, height);
|
const imageData = tile.getContext("2d")?.getImageData(0, 0, width, height);
|
||||||
const buffer = mural.pixels.flat();
|
const buffer = mural.pixelBuffer;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let bad = 0;
|
let bad = 0;
|
||||||
let good = 0;
|
let good = 0;
|
||||||
@@ -272,36 +272,31 @@ export async function getPixelStatusMural(mural: MuralOld, palette: RGB[]): Prom
|
|||||||
|
|
||||||
export function imageDataToPaletteIndices(imageData: ImageData, palette: RGB[]) {
|
export function imageDataToPaletteIndices(imageData: ImageData, palette: RGB[]) {
|
||||||
const { height, width } = imageData;
|
const { height, width } = imageData;
|
||||||
const pixels: number[][] = [];
|
let k = 0;
|
||||||
for (let i = 0; i < height; i++) {
|
const pixels = new Int8Array(height * width);
|
||||||
pixels.push([]);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||||
const r = imageData.data[i + 0] ?? 0;
|
const r = imageData.data[i + 0] ?? 0;
|
||||||
const g = imageData.data[i + 1] ?? 0;
|
const g = imageData.data[i + 1] ?? 0;
|
||||||
const b = imageData.data[i + 2] ?? 0;
|
const b = imageData.data[i + 2] ?? 0;
|
||||||
const a = imageData.data[i + 3] ?? 0xff;
|
const a = imageData.data[i + 3] ?? 0xff;
|
||||||
|
|
||||||
const line = Math.floor(Math.floor(i / 4) / width);
|
|
||||||
const data = pixels[line] || [];
|
|
||||||
if (a < 25) {
|
if (a < 25) {
|
||||||
data.push(-1);
|
pixels[k++] = -1;
|
||||||
} else {
|
} else {
|
||||||
const index = findClosestFormArray(rgb(r, g, b), palette);
|
const index = findClosestFormArray(rgb(r, g, b), palette);
|
||||||
data.push(index);
|
pixels[k++] = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pixels;
|
return pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findClosestFormArray(rgbO: RGB, palette: RGB[]) {
|
export function findClosestFormArray(rgbO: RGB, palette: RGB[]) {
|
||||||
const scores: number[] = [];
|
const scores: number[] = new Array(palette.length).fill(0);
|
||||||
|
|
||||||
for (const rgb of palette) {
|
for (let i = 0; i < palette.length; i++) {
|
||||||
const r = getColorScore(rgbO.r, rgb.r);
|
const r = getColorScore(rgbO.r, palette[i].r);
|
||||||
const g = getColorScore(rgbO.g, rgb.g);
|
const g = getColorScore(rgbO.g, palette[i].g);
|
||||||
const b = getColorScore(rgbO.b, rgb.b);
|
const b = getColorScore(rgbO.b, palette[i].b);
|
||||||
scores.push(r + g + b);
|
scores[i] = (r + g + b);
|
||||||
}
|
}
|
||||||
const lowest = Math.min(...scores);
|
const lowest = Math.min(...scores);
|
||||||
const index = scores.indexOf(lowest);
|
const index = scores.indexOf(lowest);
|
||||||
@@ -318,24 +313,20 @@ export function imageToCanvas(image: HTMLImageElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function drawPixelsOntoCanvas(
|
export function drawPixelsOntoCanvas(
|
||||||
canvas: HTMLCanvasElement, pixelsData: number[][], palette: string[], pixelSize = 1
|
canvas: HTMLCanvasElement, mural: Mural, palette: string[], pixelSize = 1
|
||||||
) {
|
) {
|
||||||
const ctx = canvas.getContext("2d")!;
|
const ctx = canvas.getContext("2d")!;
|
||||||
if (!ctx) return 0;
|
if (!ctx) return 0;
|
||||||
const data = pixelsData;
|
|
||||||
const height = data.length * pixelSize;
|
|
||||||
const width = data[0] ? data[0].length * pixelSize : 0;
|
|
||||||
if (!(canvas instanceof OffscreenCanvas)){
|
if (!(canvas instanceof OffscreenCanvas)){
|
||||||
canvas.width = width;
|
canvas.width = mural.w;
|
||||||
canvas.height = height;
|
canvas.height = mural.h;
|
||||||
canvas.style.width = `${width}px`;
|
canvas.style.width = `${mural.w}px`;
|
||||||
canvas.style.height = `${height}px`;
|
canvas.style.height = `${mural.h}px`;
|
||||||
}
|
}
|
||||||
let pixels = 0;
|
let pixels = 0;
|
||||||
for (let x = 0; x < width; x ++) {
|
for (let x = 0; x < mural.w; x ++) {
|
||||||
for (let y = 0; y < height; y++) {
|
for (let y = 0; y < mural.h; y++) {
|
||||||
const yd = data[y];
|
const index = mural.getPixel(x, y);
|
||||||
const index = (yd && yd[x]);
|
|
||||||
if (index != null && index > -1) {
|
if (index != null && index > -1) {
|
||||||
pixels++;
|
pixels++;
|
||||||
const aColor = palette[index]!;
|
const aColor = palette[index]!;
|
||||||
@@ -343,7 +334,6 @@ export function drawPixelsOntoCanvas(
|
|||||||
throw new Error(`Unknown color at ${index}`);
|
throw new Error(`Unknown color at ${index}`);
|
||||||
}
|
}
|
||||||
ctx.fillStyle = aColor;
|
ctx.fillStyle = aColor;
|
||||||
|
|
||||||
ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
|
ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,14 +363,24 @@ export function toChunkY(y: number) {
|
|||||||
return Math.floor(y / CHUNK_SIZE) * CHUNK_SIZE;
|
return Math.floor(y / CHUNK_SIZE) * CHUNK_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canvasFromMural(pixelsToDraw: number[][], hex: string[]) {
|
export function canvasFromMural(mural: Mural, palette: string[]) {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = get2DArrWidth(pixelsToDraw);
|
canvas.width = mural.w;
|
||||||
canvas.height = get2DArrHeight(pixelsToDraw);
|
canvas.height = mural.h;
|
||||||
const pixels = drawPixelsOntoCanvas(canvas, pixelsToDraw, hex);
|
const pixels = drawPixelsOntoCanvas(canvas, mural, palette);
|
||||||
return { canvas, pixels };
|
return { canvas, pixels };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function createMuralExtended(mural: Mural, palette: string[]): MuralEx {
|
||||||
|
const { canvas, pixels } = canvasFromMural(mural, palette);
|
||||||
|
return {
|
||||||
|
mural,
|
||||||
|
pixelCount: pixels,
|
||||||
|
ref: canvas
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let numberFormatter: Intl.NumberFormat;
|
let numberFormatter: Intl.NumberFormat;
|
||||||
try {
|
try {
|
||||||
const userLocale = navigator.language || (navigator as any).userLanguage;
|
const userLocale = navigator.language || (navigator as any).userLanguage;
|
||||||
|
|||||||
Reference in New Issue
Block a user