pixel-battle/static/scripts/main.js

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();
});