222 строки
6.7 KiB
JavaScript
222 строки
6.7 KiB
JavaScript
const apiURL = new URL('http://localhost:8000');
|
|
const wsURL = new URL('ws://localhost:8000/ws/v1/pixels');
|
|
|
|
class ColorBar {
|
|
#colorPicker;
|
|
|
|
constructor() {
|
|
this.#colorPicker = document.querySelector('#color-picker');
|
|
}
|
|
|
|
get hex() {
|
|
return this.#colorPicker.value.substring(1, 7);
|
|
};
|
|
|
|
get decimal() {
|
|
return parseInt(this.hex, 16);
|
|
};
|
|
}
|
|
|
|
class BattleField {
|
|
WIDTH$U = 64;
|
|
HEIGHT$U = 32;
|
|
PIXEL_SIZE$PX = 96;
|
|
BORDER_SIZE$PX = 4;
|
|
BACKGROUND_COLOR$HEX = '#FFFFFFFF';
|
|
BORDER_COLOR$HEX = '#00000010';
|
|
|
|
#colorBar;
|
|
#main;
|
|
#canvas;
|
|
#context;
|
|
|
|
constructor() {
|
|
this.#colorBar = new ColorBar();
|
|
this.#main = document.querySelector('#main');
|
|
this.#canvas = document.querySelector('#battlefield');
|
|
this.#context = this.#canvas.getContext('2d');
|
|
this.#canvas.width = this.WIDTH$U * this.PIXEL_SIZE$PX;
|
|
this.#canvas.height = this.HEIGHT$U * this.PIXEL_SIZE$PX;
|
|
|
|
this.#main.onpointerdown = (event) => {
|
|
const movement = () => Math.sqrt(Math.pow(xd$px, 2) + Math.pow(yd$px, 2)) / window.innerWidth > .005;
|
|
|
|
let x0$pct = event.pageX * 100 / window.innerWidth;
|
|
let y0$pct = event.pageY * 100 / window.innerHeight;
|
|
let x0$px = event.pageX;
|
|
let y0$px = event.pageY;
|
|
let xd$px = 0;
|
|
let yd$px = 0;
|
|
|
|
this.#main.onpointermove = (event) => {
|
|
if (movement()) document.body.classList.add('grabbing');
|
|
this.x += event.pageX * 100 / window.innerWidth - x0$pct;
|
|
this.y += event.pageY * 100 / window.innerHeight - y0$pct;
|
|
x0$pct = event.pageX * 100 / window.innerWidth;
|
|
y0$pct = event.pageY * 100 / window.innerHeight;
|
|
xd$px += Math.abs(x0$px - event.pageX);
|
|
yd$px += Math.abs(y0$px - event.pageY);
|
|
};
|
|
|
|
this.#main.onpointerup = () => {
|
|
this.#main.onpointermove = null;
|
|
this.#main.onpointerup = null;
|
|
document.body.classList.remove('grabbing');
|
|
if (movement()) return;
|
|
const xhr = new XMLHttpRequest();
|
|
const url = new URL(apiURL);
|
|
url.pathname = '/api/v1/pixels';
|
|
xhr.open('PUT', url);
|
|
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
|
xhr.send(
|
|
JSON.stringify({
|
|
x: Math.floor((event.pageX - this.#canvas.offsetLeft) / this.scale / this.PIXEL_SIZE$PX + this.WIDTH$U / 2),
|
|
y: Math.floor((event.pageY - this.#canvas.offsetTop) / this.scale / this.PIXEL_SIZE$PX + this.HEIGHT$U / 2),
|
|
color: this.#colorBar.decimal,
|
|
})
|
|
);
|
|
};
|
|
};
|
|
|
|
document.onwheel = (event) => {
|
|
if (event.deltaY > 0) this.scale += .1;
|
|
if (event.deltaY < 0) this.scale -= .1;
|
|
};
|
|
|
|
for (let x = 0; x < this.WIDTH$U; x++) {
|
|
for (let y = 0; y < this.HEIGHT$U; y++) {
|
|
this.setPixel(
|
|
x,
|
|
y,
|
|
this.BACKGROUND_COLOR$HEX,
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
get x() {
|
|
return Number(this.#canvas.style.left.match(/^(.*)%$/)[1]);
|
|
};
|
|
|
|
set x(value) {
|
|
// const min$pct = -100 * this.scale;
|
|
// const max$pct = 100 * this.scale + 100;
|
|
// if (value < min$pct) {
|
|
// this.#canvas.style.left = `${min$pct}%`;
|
|
// return;
|
|
// }
|
|
// if (value > max$pct) {
|
|
// this.#canvas.style.left = `${max$pct}%`;
|
|
// return;
|
|
// }
|
|
this.#canvas.style.left = `${value}%`;
|
|
};
|
|
|
|
get y() {
|
|
return Number(this.#canvas.style.top.match(/^(.*)%$/)[1]);
|
|
};
|
|
|
|
set y(value) {
|
|
// const min$pct = -100 * this.scale;
|
|
// const max$pct = 100 * this.scale + 100;
|
|
// if (value < min$pct) {
|
|
// this.#canvas.style.top = `${min$pct}%`;
|
|
// return;
|
|
// }
|
|
// if (value > max$pct) {
|
|
// this.#canvas.style.top = `${max$pct}%`;
|
|
// return;
|
|
// }
|
|
this.#canvas.style.top = `${value}%`;
|
|
};
|
|
|
|
get scale() {
|
|
return Number(this.#canvas.style.scale);
|
|
};
|
|
|
|
set scale(value) {
|
|
if (value >= .2 && value <= 1) {
|
|
this.#canvas.style.scale = String(value);
|
|
|
|
// TODO(костыль)
|
|
// this.x = this.x;
|
|
// this.y = this.y;
|
|
}
|
|
};
|
|
|
|
fill() {
|
|
const xhr = new XMLHttpRequest();
|
|
const url = new URL(apiURL);
|
|
url.pathname = '/api/v1/pixels';
|
|
xhr.onload = () => {
|
|
if (xhr.status !== 200) return;
|
|
const response = JSON.parse(xhr.response);
|
|
for (const pixel of response.pixels) {
|
|
this.setPixel(
|
|
pixel.x,
|
|
pixel.y,
|
|
decimalToHex(pixel.color),
|
|
);
|
|
}
|
|
};
|
|
xhr.open('GET', url);
|
|
xhr.send();
|
|
};
|
|
|
|
setPixel(x, y, color) {
|
|
let leftPadding = x === 0 ? this.BORDER_SIZE$PX : 0;
|
|
let topPadding = y === 0 ? this.BORDER_SIZE$PX : 0;
|
|
this.#context.fillStyle = this.BACKGROUND_COLOR$HEX;
|
|
this.#context.fillRect(
|
|
x * this.PIXEL_SIZE$PX,
|
|
y * this.PIXEL_SIZE$PX,
|
|
this.PIXEL_SIZE$PX,
|
|
this.PIXEL_SIZE$PX,
|
|
);
|
|
this.#context.fillStyle = this.BORDER_COLOR$HEX;
|
|
this.#context.fillRect(
|
|
x * this.PIXEL_SIZE$PX,
|
|
y * this.PIXEL_SIZE$PX,
|
|
this.PIXEL_SIZE$PX,
|
|
this.PIXEL_SIZE$PX,
|
|
);
|
|
this.#context.fillStyle = color;
|
|
this.#context.fillRect(
|
|
x * this.PIXEL_SIZE$PX + leftPadding,
|
|
y * this.PIXEL_SIZE$PX + topPadding,
|
|
this.PIXEL_SIZE$PX - this.BORDER_SIZE$PX - leftPadding,
|
|
this.PIXEL_SIZE$PX - this.BORDER_SIZE$PX - topPadding,
|
|
);
|
|
};
|
|
}
|
|
|
|
const decimalToHex = (decimal) => `#${decimal.toString(16)}`;
|
|
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
const battlefield = new BattleField();
|
|
battlefield.fill();
|
|
|
|
const wsConnect = () => {
|
|
const ws = new WebSocket(wsURL);
|
|
|
|
ws.onmessage = (event) => {
|
|
const pixel = JSON.parse(event.data);
|
|
battlefield.setPixel(
|
|
pixel.x,
|
|
pixel.y,
|
|
decimalToHex(pixel.color),
|
|
);
|
|
};
|
|
|
|
ws.onclose = (event) => {
|
|
setTimeout(wsConnect, 5000);
|
|
}
|
|
|
|
ws.onerror = () => {
|
|
ws.close();
|
|
};
|
|
};
|
|
|
|
wsConnect();
|
|
});
|