528 lines
15 KiB
JavaScript
528 lines
15 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;
|
|
|
|
const imgurSearch = document.getElementById('imgur-search');
|
|
const imgurSearchBtn = document.getElementById('imgur-search-btn');
|
|
const imgurSearchDetails = document.getElementById('imgur-search-details');
|
|
|
|
imgurSearchBtn.onclick = async () => {
|
|
imgurSearch.classList.remove('ok');
|
|
imgurSearch.classList.remove('err');
|
|
imgurSearchDetails.innerHTML = '<br>';
|
|
|
|
const e = await doJSONRequest('POST', '/imgur/search', {},
|
|
{ name: imgurSearch.value });
|
|
|
|
if (e.success) {
|
|
imgurSearch.classList.add('ok');
|
|
} else {
|
|
imgurSearch.classList.add('err');
|
|
|
|
if(e.error) {
|
|
imgurSearchDetails.innerHTML = `
|
|
<strong>Error:</strong> ${e.error}<br>`;
|
|
}
|
|
}
|
|
};
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|