Add website's pixel count tracker
This commit is contained in:
@@ -38,6 +38,9 @@ async function main(dev) {
|
|||||||
const scriptInput = path.join(process.cwd(), "src", "index.ts");
|
const scriptInput = path.join(process.cwd(), "src", "index.ts");
|
||||||
const scriptOutput = path.join(buildVersionDir, "assets", "scripts", "content.js");
|
const scriptOutput = path.join(buildVersionDir, "assets", "scripts", "content.js");
|
||||||
|
|
||||||
|
const injectScriptInput = path.join(process.cwd(), "src", "inject.ts");
|
||||||
|
const injectScriptOutput = path.join(buildVersionDir, "assets", "scripts", "inject.js");
|
||||||
|
|
||||||
deleteFolder(distDir);
|
deleteFolder(distDir);
|
||||||
ensureFolder(buildDir);
|
ensureFolder(buildDir);
|
||||||
ensureFolder(distDir);
|
ensureFolder(distDir);
|
||||||
@@ -52,6 +55,19 @@ async function main(dev) {
|
|||||||
platform: "browser",
|
platform: "browser",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await esbuild.build({
|
||||||
|
...common,
|
||||||
|
entryPoints: [injectScriptInput],
|
||||||
|
sourcemap: !dev,
|
||||||
|
define: {
|
||||||
|
DEV: JSON.stringify(dev),
|
||||||
|
ENVIRONMENT: JSON.stringify("user-script")
|
||||||
|
},
|
||||||
|
outfile: injectScriptOutput,
|
||||||
|
});
|
||||||
|
|
||||||
|
const raw = JSON.stringify(fs.readFileSync(injectScriptOutput, "utf-8"));
|
||||||
|
const injectCode = raw.substring(1, raw.length - 1);
|
||||||
await esbuild.build({
|
await esbuild.build({
|
||||||
...common,
|
...common,
|
||||||
sourcemap: !dev,
|
sourcemap: !dev,
|
||||||
@@ -75,6 +91,9 @@ async function main(dev) {
|
|||||||
outfile: userScriptOutput,
|
outfile: userScriptOutput,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[scriptOutput, userScriptOutput].forEach(path => {
|
||||||
|
fs.writeFileSync(path, fs.readFileSync(path, "utf-8").replace("/* CROSS_WORLD_INJECT_CODE */", injectCode));
|
||||||
|
});
|
||||||
const code = fs.readFileSync(userScriptOutput, "utf-8");
|
const code = fs.readFileSync(userScriptOutput, "utf-8");
|
||||||
let template = fs.readFileSync(userScriptTemplateLocation, "utf-8");
|
let template = fs.readFileSync(userScriptTemplateLocation, "utf-8");
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
|||||||
Vendored
+798
-37046
File diff suppressed because one or more lines are too long
@@ -6,7 +6,12 @@
|
|||||||
"48": "assets/images/icon_48.png",
|
"48": "assets/images/icon_48.png",
|
||||||
"128": "assets/images/icon_128.png"
|
"128": "assets/images/icon_128.png"
|
||||||
},
|
},
|
||||||
"web_accessible_resources": [],
|
"web_accessible_resources": [
|
||||||
|
{
|
||||||
|
"resources": ["assets/scripts/inject.js"],
|
||||||
|
"matches": ["<all_urls>"]
|
||||||
|
}
|
||||||
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"storage"
|
"storage"
|
||||||
],
|
],
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pixelcanvas-overlay",
|
"name": "pixelcanvas-overlay",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"description": "Pixel canvas overlay",
|
"description": "Pixel canvas overlay",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ 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 { TCLogo } from "../../constants";
|
import { TC_LOGO } from "../../constants";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
const Img = styled.img`
|
const Img = styled.img`
|
||||||
width: 25px;
|
width: 25px;
|
||||||
|
margin-right: 5px;
|
||||||
display: inline;
|
display: inline;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
@@ -98,7 +99,7 @@ export class Main extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
title() {
|
title() {
|
||||||
return <span title={"Terncode's fork"}>
|
return <span title={"Terncode's fork"}>
|
||||||
<Img src={TCLogo} alt="TC-Logo"/>
|
<Img src={TC_LOGO} alt="TC-Logo"/>
|
||||||
<span>Overlay</span>
|
<span>Overlay</span>
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { PixelPlaced } from "../../lib/pixelPlaced";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { faSquare } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { formatNumber } from "../../lib/utils";
|
||||||
|
|
||||||
|
const CountBox = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
bottom: 107px;
|
||||||
|
right: 16px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.75);
|
||||||
|
border: 2px solid black;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 5px auto;
|
||||||
|
padding: 5px;
|
||||||
|
height: 40px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Count = styled.span`
|
||||||
|
margin-left: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Icon = styled.span`
|
||||||
|
margin: 2px 5px;
|
||||||
|
width: 19px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
pixelCount: PixelPlaced;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PixelCount extends React.Component<Props, State> {
|
||||||
|
|
||||||
|
constructor(props:Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
count: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.pixelCount.count.then(countMaybe => {
|
||||||
|
if (typeof countMaybe === "number") {
|
||||||
|
this.updateCount(countMaybe);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.props.pixelCount.on(this.updateCount);
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.pixelCount.on(this.updateCount);
|
||||||
|
}
|
||||||
|
updateCount = (count: number) => {
|
||||||
|
this.setState({count});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.state.count) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CountBox>
|
||||||
|
<Count>{formatNumber(this.state.count)}</Count>
|
||||||
|
<Icon><FontAwesomeIcon icon={ faSquare }></FontAwesomeIcon></Icon>
|
||||||
|
</CountBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+10
-201
@@ -1,8 +1,3 @@
|
|||||||
import { Mural } from "../interfaces";
|
|
||||||
import { canvasToImageData, createCanvas, flatQuantizeImageData,
|
|
||||||
getExtension, getMuralHeight, getMuralWidth, imageDataToPaletteIndices,
|
|
||||||
imageToCanvas, loadImageSource, readAsDataUrl, readAsString, resize, validateMural } from "../lib/utils";
|
|
||||||
import { FileInput } from "../lib/fileinput";
|
|
||||||
import { Palette } from "../lib/palette";
|
import { Palette } from "../lib/palette";
|
||||||
import { Store } from "../lib/store";
|
import { Store } from "../lib/store";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@@ -10,11 +5,17 @@ import { Coordinates } from "../lib/coordinates";
|
|||||||
import { Storage } from "../lib/storage";
|
import { Storage } from "../lib/storage";
|
||||||
import { Main } from "./components/main";
|
import { Main } from "./components/main";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { PixelPlaced } from "../lib/pixelPlaced";
|
||||||
|
import { PixelCount } from "./components/pixelCount";
|
||||||
|
|
||||||
const TEXT_FORMATS = ["muraljson", "json"];
|
export function createUI(store: Store, storage: Storage, cords: Coordinates, palette: Palette, pixels: PixelPlaced) {
|
||||||
|
const unmounts = [
|
||||||
export function createUI(store: Store, storage: Storage, cords: Coordinates, palette: Palette) {
|
appendWindow(<PixelCount pixelCount={pixels} />),
|
||||||
return appendWindow(<Main cords={cords} store={store} storage={storage} palette={palette} />);
|
appendWindow(<Main cords={cords} store={store} storage={storage} palette={palette}/>),
|
||||||
|
];
|
||||||
|
return () => {
|
||||||
|
return unmounts.map(e => e());
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendWindow(children: React.ReactNode) {
|
function appendWindow(children: React.ReactNode) {
|
||||||
@@ -25,195 +26,3 @@ function appendWindow(children: React.ReactNode) {
|
|||||||
rootElement.parentElement?.removeChild(rootElement);
|
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<Mural>((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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-1
File diff suppressed because one or more lines are too long
+5
-2
@@ -5,19 +5,22 @@ import { createUI } from "./UI/ui";
|
|||||||
import { waitForDraw } from "./lib/utils";
|
import { waitForDraw } from "./lib/utils";
|
||||||
import { Storage } from "./lib/storage";
|
import { Storage } from "./lib/storage";
|
||||||
import { Store } from "./lib/store";
|
import { Store } from "./lib/store";
|
||||||
|
import { PixelPlaced } from "./lib/pixelPlaced";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const palette = new Palette();
|
const palette = new Palette();
|
||||||
while(!palette.init()) { await waitForDraw();}
|
while(!palette.init()) { await waitForDraw();}
|
||||||
const storage = new Storage(ENVIRONMENT === "browser-extension");
|
const storage = new Storage(ENVIRONMENT === "browser-extension");
|
||||||
|
const pixelPlaced = PixelPlaced.create(storage);
|
||||||
const store = new Store(storage, palette);
|
const store = new Store(storage, palette);
|
||||||
await store.load();
|
await store.load();
|
||||||
const coordinates = new Coordinates();
|
const coordinates = new Coordinates();
|
||||||
await coordinates.init();
|
await coordinates.init();
|
||||||
|
|
||||||
createUI(store,storage, coordinates, palette);
|
createUI(store,storage, coordinates, palette, pixelPlaced);
|
||||||
|
|
||||||
console.log("%cOverlay by 0xa663 loaded", "color: red; font-size: 20px; font-weight: bold; text-shadow: 2px 2px 4px #000000;");
|
console.log("%cOverlay by 0xa663", "color: red; font-size: 15px; font-weight: bold; text-shadow: 2px 2px 4px #000000;");
|
||||||
|
console.log("%cTerncode's fork", `color: #4a94cc; font-size: 10px; font-weight: bold; text-shadow: 1px 1px 0px #000000, -1px 1px 0px #000000, 1px -1px 0px #000000, -1px -1px 0px #000000;`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === "complete") {
|
if (document.readyState === "complete") {
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Creating artificial API between two worlds
|
||||||
|
|
||||||
|
const log = console.log;
|
||||||
|
console.log = (...args: any) => {
|
||||||
|
const firstArg = args[0];
|
||||||
|
if (firstArg && typeof firstArg === "object" && "pixelsPlaced" in firstArg) {
|
||||||
|
const pixelsPlaced = new CustomEvent("__pixelsPlaced", {
|
||||||
|
detail: firstArg.pixelsPlaced,
|
||||||
|
});
|
||||||
|
document.dispatchEvent(pixelsPlaced);
|
||||||
|
}
|
||||||
|
log(...args);
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { INJECT_SCRIPT_PIXEL_OBSERVER } from "../constants";
|
||||||
|
import { BasicEventEmitter } from "./eventEmitter";
|
||||||
|
import { Storage } from "./storage";
|
||||||
|
|
||||||
|
|
||||||
|
export class PixelPlaced {
|
||||||
|
private emitter = new BasicEventEmitter();
|
||||||
|
private static instance: PixelPlaced;
|
||||||
|
private readonly storageKey = "__pixelsPlaced";
|
||||||
|
private constructor(private storage: Storage) {
|
||||||
|
// The website is logging pixels as object onto pixelsPlaced onto console with .log.
|
||||||
|
// With extension we need to cross 2 worlds. Adding artificial api to document
|
||||||
|
|
||||||
|
const api = document.createElement("script");
|
||||||
|
api.addEventListener("load", () => {
|
||||||
|
document.body.removeChild(api);
|
||||||
|
}, { once: true });
|
||||||
|
api.addEventListener("error", error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ENVIRONMENT === "browser-extension") {
|
||||||
|
api.src = chrome.runtime.getURL("/assets/scripts/inject.js");
|
||||||
|
} else {
|
||||||
|
api.textContent = INJECT_SCRIPT_PIXEL_OBSERVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(api);
|
||||||
|
(document as any).addEventListener("__pixelsPlaced", (event: CustomEvent<number>) => {
|
||||||
|
if (typeof event.detail === "number") {
|
||||||
|
this.storage.setItem(this.storageKey, event.detail);
|
||||||
|
this.emitter.emit(0, event.detail);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(storage: Storage) {
|
||||||
|
if (!this.instance) {
|
||||||
|
this.instance = new PixelPlaced(storage);
|
||||||
|
}
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(fn: (count: number) => void) {
|
||||||
|
this.emitter.on(0, fn);
|
||||||
|
}
|
||||||
|
off(fn: (count: number) => void) {
|
||||||
|
this.emitter.off(0, fn);
|
||||||
|
}
|
||||||
|
get count() {
|
||||||
|
return this.storage.getItem(this.storageKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user