This repository has been archived on 2021-10-31. You can view files and clone it, but cannot push or open issues or pull requests.
SA3/hw7/Claudio_Maggioni/public/scripts/app.js

504 lines
14 KiB
JavaScript

// vim: set ts=2 sw=2 et tw=80:
class App {
static get BRUSHES() {
return {
'PenBrush': new PenBrush(),
'DiscBrush': new DiscBrush(),
'StarBrush': new StarBrush(),
};
}
render_timestamp = 0;
render_last = null;
render_timeout = null;
socketSessionId = null;
registerFavoriteEvents(_id, dom) {
const imgurForm = dom.querySelector('form.favorites');
const replaceToggle = dom.querySelector('input[name=replace]');
const albumInput = imgurForm.querySelector('input[name=album]');
const oldNameInput = imgurForm.querySelector('input[name=old_name]');
const replacePanel = imgurForm.querySelector('.replace');
const detailsPanel = imgurForm.querySelector('.details');
dom.querySelector('button.imgur').onclick = () => {
imgurForm.classList.toggle('hidden');
};
replaceToggle.onclick = () => {
if (replaceToggle.checked) {
replacePanel.classList.remove('hidden');
albumInput.setAttribute('disabled', 'disabled');
albumInput.removeAttribute('required');
oldNameInput.setAttribute('required', 'required');
} else {
replacePanel.classList.add('hidden');
albumInput.removeAttribute('disabled');
albumInput.setAttribute('required', 'required');
oldNameInput.removeAttribute('required');
}
};
imgurForm.onsubmit = e => {
imgurForm.classList.remove('ok');
imgurForm.classList.remove('err');
detailsPanel.innerHTML = '';
e.preventDefault();
const data = new FormData(imgurForm);
const obj = {
dataURL: data.get('dataURL'),
name: dom.querySelector('form.data input[name=name]').value,
replace: data.get('replace'),
oldName: data.get('old_name'),
album: data.get('album'),
tags: data.get('tags'),
favorites: data.get('favorites')
};
doJSONRequest('POST', '/imgur/ordeal', {}, obj)
.then(e => {
if (e.ordealSuccess === true) {
imgurForm.classList.add('ok');
if (e.views !== undefined) {
detailsPanel.innerHTML +=
`<strong>Views:</strong> ${e.views}<br>`;
}
if (e.votes) {
detailsPanel.innerHTML +=
`<strong>Upvotes:</strong> ${e.votes.ups}<br>
<strong>Downvotes:</strong> ${e.votes.downs}<br>`;
}
} else {
imgurForm.classList.add('err');
if (e.error) {
detailsPanel.innerHTML += `
<strong>Error:</strong> ${e.error}<br>`;
}
}
}).catch(e => {
console.error(e);
imgurForm.classList.add('err');
});
};
const form = dom.querySelector('form.data');
const delay = (f, ...args) => {
const execute = (f, ...args) => {
console.log('don\'t delay, solve Carzaniga today', ...args);
this.render_timestamp = new Date().getTime();
this.render_timeout = null;
this.render_last = null;
f(...args);
};
if (new Date().getTime() - this.render_timestamp < 100000) {
console.log('delaying', ...args);
this.render_last = () => execute(f, ...args);
if (this.render_timeout === null) {
this.render_timeout = setTimeout(() => this.render_last(), 100);
}
return;
}
execute(f, ...args);
}
form.querySelector('input[name=name]').onkeyup =
() => delay(submit, 'data-update', false);
const submit = (action, update = true) => {
let p;
const formData = new FormData(form);
const name = formData.get('name');
const dataURL = formData.get('dataURL');
const query = '?socketid=' + this.socketSessionId;
switch (action) {
case 'data-update':
p = doJSONRequest('PUT', '/favorites/' + _id + query, {},
{ name, dataURL });
break;
case 'data-delete':
p = doJSONRequest('DELETE', '/favorites/' + _id + query);
break;
case 'data-bookmark':
p = doJSONRequest('PUT', `/favorites/${_id}/bookmarked` + query,
{}, { bookmarked: true });
break;
case 'data-del-bookmark':
p = doJSONRequest('PUT', `/favorites/${_id}/bookmarked` + query,
{}, { bookmarked: false });
break;
default:
console.warn(action, ' is not a valid action');
return;
}
p.catch(console.error);
if (action == 'data-delete' && update) {
p.then(_ => dom.remove());
} else if (update) {
p.then(e => this.renderFavorite(e, dom));
}
if (!update) {
// updating via javascript to not disrupt cursor in name when
// replacing html
dom.querySelector('h3').innerText = name;
}
};
form.onsubmit = e => {
e.preventDefault();
const formData = new FormData(form);
const name = formData.get('name');
const dataURL = formData.get('dataURL');
const action = e.explicitOriginalTarget.getAttributeNames()
.filter(e => e.startsWith('data-'))[0];
submit(action);
};
}
renderFavorite(data, div) {
data.bookmarked = data.bookmarked === true || data.bookmarked === 'true';
dust.render('favourite_partial',
data,
(err, out) => {
if (err) {
console.error(err);
} else {
let dom;
if (div === 'top' || !div) {
dom = document.createElement('div');
dom.id = 'favorite_' + data._id;
dom.innerHTML = out;
if (div === 'top') {
this.favourites.prepend(dom);
} else {
this.favourites.appendChild(dom);
}
} else {
div.innerHTML = out;
dom = div;
}
dom.className = data.bookmarked ? 'bookmarked' : '';
this.registerFavoriteEvents(data._id, dom);
}
});
}
getFavorites(nameFilter, onlyBookmarked = false) {
let url = '/favorites/search?ajax=true';
if (nameFilter) {
url += '&name=' + encodeURIComponent(nameFilter);
}
if (onlyBookmarked) {
url += '&bookmarked=true';
}
doFetchRequest('GET', url, { 'Accept': 'text/html' })
.then(res => res.text())
.then(html => {
this.favourites.innerHTML = html;
this.favourites.childNodes.forEach(e => {
const id = e.id.substring('favorite_'.length);
this.registerFavoriteEvents(id, e);
});
})
.catch(console.error);
}
initSocket() {
const socket = io();
socket.on('connect', () => {
console.log('Socket connected');
});
socket.on('sessionId', (msg) => {
console.log('Socket session id is', msg.id);
this.socketSessionId = msg.id;
});
socket.on('disconnect', (reason) => {
console.log('Socket disconnected');
});
socket.on('reconnect', (attemptNumber) => {
console.log('Socket reconnected');
});
socket.on('favorite.created', (msg) => {
if (msg.socketid != this.socketSessionId) {
console.log(msg);
this.renderFavorite(msg, 'top');
}
});
socket.on('favorite.updated', (msg) => {
if (msg.socketid != this.socketSessionId) {
this.renderFavorite(msg, document
.getElementById('favorite_' + msg._id));
}
console.log(msg);
});
socket.on('favorite.deleted', (msg) => {
if (msg.socketid != this.socketSessionId) {
document.getElementById('favorite_' + msg._id).remove();
}
});
}
constructor(conf) {
if (!(typeof conf === 'object' && conf)) {
throw new Error('Argument conf different from specification');
}
this.canvas = document.getElementById(conf.canvas);
if (!this.canvas || this.canvas.tagName !== 'CANVAS') {
throw new Error(`canvas is not a canvas`);
}
this.ctx = this.canvas.getContext('2d');
this.favourites = document.querySelector('div#favourites');
this.initSocket();
this.getFavorites(false);
const nameFilter = document.getElementById('name-filter');
const bookmarkFilter = document.getElementById('bookmarked-filter');
const onSearch = () => {
const name = nameFilter.value;
const bookmark = bookmarkFilter.checked;
this.getFavorites(name, bookmark);
}
nameFilter.onkeyup = onSearch;
bookmarkFilter.onclick = onSearch;
if (typeof conf.buttons === 'object' && conf.buttons) {
this.buttons = {}
for (const b of ['clear', 'undo', 'camera'])
this.buttons[b] = document.getElementById(conf.buttons[b]);
if (this.buttons.clear) {
this.buttons.clear.addEventListener('click', () => {
this.erase();
this.background = null;
history.clear();
});
}
if (this.buttons.undo) {
this.buttons.undo.addEventListener('click', () => {
history.pop();
this.redrawAll();
});
}
if (this.buttons.camera) {
this.buttons.camera.addEventListener('click', () => {
const base64 = this.canvas.toDataURL();
const data = {
dataURL: base64,
name: 'My New Masterpiece',
bookmarked: false
};
doJSONRequest('POST', '/favorites?socketid=' + this.socketSessionId,
{}, data)
.then(data => this.renderFavorite(data, 'top'))
.catch(console.error);
});
}
const player = document.createElement('video');
player.addEventListener('loadeddata', () => {
player.play();
setTimeout(() => {
const imgCanvas = document.createElement('canvas');
imgCanvas.width = this.canvas.width;
imgCanvas.height = this.canvas.height;
const img = document.createElement('img');
imgCanvas.getContext('2d').drawImage(player, 0, 0, imgCanvas.width,
imgCanvas.height);
console.log(imgCanvas.toDataURL());
img.src = imgCanvas.toDataURL();
img.addEventListener('load', () => {
this.background = img;
this.redrawAll();
});
player.srcObject.getVideoTracks().forEach(track => track.stop());
}, 100);
});
const button = document.createElement('button');
button.type = 'button';
button.innerHTML = 'Photo';
button.addEventListener('click', () => {
navigator.mediaDevices.getUserMedia({ video: true })
.then((stream) => {
player.srcObject = stream;
});
});
document.getElementById('left-toolbar').appendChild(button);
}
this.ctx.lineWidth = 1;
this.strokeStyle = this.constructor.defaultStrokeStyle;
this.brush = 'PenBrush';
const brushToolbar = document.querySelector('#brush-toolbar');
if (brushToolbar) {
for (const name in App.BRUSHES) {
const b = document.createElement('button');
b.innerText = name;
b.addEventListener('click', () => this.brush = name);
brushToolbar.appendChild(b);
}
const label = document.createElement('label');
label.setAttribute('for', 'color');
const color = document.createElement('input');
color.type = 'color';
color.name = 'color';
color.value = this.constructor.defaultStrokeStyle;
color.addEventListener('change', () => this.strokeStyle = color.value);
brushToolbar.appendChild(label);
brushToolbar.appendChild(color);
}
const toMouse = (e, func) => {
if (e && e.touches && e.touches[0]) {
return func.bind(this)({
offsetX: e.touches[0].pageX - this.canvas.offsetLeft,
offsetY: e.touches[0].pageY - this.canvas.offsetTop
});
}
};
this.canvas.addEventListener('touchstart', e => toMouse(e, this.startPath));
this.canvas.addEventListener('mousedown', this.startPath.bind(this));
this.canvas.addEventListener('touchmove', e => toMouse(e, this.draw));
this.canvas.addEventListener('mousemove', this.draw.bind(this));
this.canvas.addEventListener('touchcancel', e => toMouse(e, this.endPath));
this.canvas.addEventListener('mouseup', this.endPath.bind(this));
this.canvas.addEventListener('mouseout', this.endPath.bind(this));
}
static get defaultStrokeStyle() {
return 'black';
}
get strokeStyle() {
if (this.ctx.strokeStyle == '#000000') {
return 'black';
}
return this.ctx.strokeStyle;
}
set strokeStyle(style) {
if (typeof style !== 'string') {
throw new Error('style is not a string');
}
this.ctx.strokeStyle = style;
}
get brush() {
return this._brush.name;
}
set brush(brushName) {
this._brush = App.BRUSHES[brushName];
}
erase() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
startPath(e, record = true) {
this.ctx.beginPath();
this.ctx.moveTo(e.offsetX, e.offsetY);
if (record) {
history.initializeNewPath();
history.push(new Stroke(this.brush, this.strokeStyle,
e.offsetX, e.offsetY));
}
if (e instanceof MouseEvent) {
this.mousedown = true;
}
}
draw(e, beginNew = true, record = true) {
if (this.mousedown || !(e instanceof MouseEvent)) {
this._brush.draw(this.ctx, this.strokeStyle, e.offsetX, e.offsetY);
if (record) {
history.push(new Stroke(this.brush, this.strokeStyle,
e.offsetX, e.offsetY));
}
if (beginNew) {
this.ctx.beginPath();
this.ctx.moveTo(e.offsetX, e.offsetY);
} else if (e instanceof MouseEvent) {
this.mousedown = false;
}
}
}
endPath(e, record = true) {
this.draw(e, false, record);
}
drawPath(path) {
const last = path.length - 1;
const lastBrush = this.brush;
const lastStyle = this.strokeStyle;
for (let i = 0; i <= last; i++) {
this.brush = path[i].brushName;
this.strokeStyle = path[i].strokeStyle;
switch(i) {
case 0: this.startPath(path[i], false); break;
case last: this.endPath(path[i], false); break;
default: this.draw(path[i], true, false);
}
}
this.brush = lastBrush;
this.strokeStyle = lastStyle;
}
redrawAll() {
this.erase();
if (this.background) {
this.ctx.drawImage(this.background, 0, 0, this.canvas.width,
this.canvas.height);
}
for (const path of history.paths) {
this.drawPath(path);
}
}
}