Roastmonday/scripts/pictureViewer.js

738 lines
22 KiB
JavaScript

document.addEventListener("DOMContentLoaded", function(event) {
formatDates();
getPostTexts();
getUserDetails();
handleAvatarHover();
initFileUpload();
setupUploadPostPreview();
});
function formatDates() {
let timestampElements = document.querySelectorAll('.postdate[ts]');
timestampElements.forEach(function(el) {
let d = new Date(el.attributes['ts'].value * 1000);
el.innerText = d.toLocaleString();
});
}
function getPostTexts() {
let boxes = document.querySelectorAll('.post-box');
boxes.forEach(function(el) {
let id = el.querySelector('.post-meta-box').querySelector('span').innerText.replace('#','');
get('get_posttext.php?id='+id).then(function(response) {
let info;
try {
info = JSON.parse(response);
} catch (error) {
showError(`Unknown response from server: ${response}`);
return;
}
let postTextBox = el.querySelector('.post-text-box');
let loader = postTextBox.querySelector('.loader');
if (loader !== null) {
postTextBox.removeChild(loader);
}
if (info.success) {
postTextBox.innerHTML = info.text;
} else {
if (info.error == 404) {
console.log('Post ', id, ' not found');
} else {
console.log('Error fetching post ', id, 'Error code: ', info.error);
}
}
}, function(error) {
console.log('Error fetching post ', id, error);
});
});
}
function getUserDetails() {
let boxes = document.querySelectorAll('.user-box');
boxes.forEach(function(el) {
let userBox = el.querySelector('.username');
if (userBox == null) {
userBox = el.querySelector('span');
}
let id = userBox.innerText;
get('get_userdetails.php?id='+id).then(function(response) {
let info;
try {
info = JSON.parse(response);
} catch (error) {
showError(`Unknown response from server: ${response}`);
return;
}
let loader = userBox.querySelector('.loader-small');
if (loader !== null) {
userBox.removeChild(loader);
}
if (info.success) {
if (info.name != null) {
el.innerHTML = '<span>' + info.name + '</span><span class="username">' + id + '</span>';
}
let presenceIndicator = document.createElement('div');
presenceIndicator.classList.add('presence-indicator');
switch (info.presence) {
case 0:
presenceIndicator.style.borderColor = "red";
presenceIndicator.style.setProperty('border-color', 'var(--red)');
break;
case 1:
presenceIndicator.style.borderColor = "green";
presenceIndicator.style.setProperty('border-color', 'var(--green)');
break;
}
el.appendChild(presenceIndicator);
} else {
if (info.error == 404) {
console.log('User ', id, ' not found');
} else {
console.log('Error fetching user ', id, 'Error code: ', info.error);
}
}
}, function(error) {
console.log('Error fetching user ', id, error);
});
});
}
function handleAvatarHover() {
let avatars = document.querySelectorAll('img.avatar');
avatars.forEach(function(el) {
el.addEventListener('mouseenter', function() {
// Blur non hovered
let nonHovered = document.querySelectorAll('img.avatar:not(:hover)');
nonHovered.forEach(function(img) {
img.classList.add('blur');
//img.classList.remove('unblur');
//img.style.filter = "blur(5px)";
});
});
el.addEventListener('mouseleave', function() {
// Unblur
let avtrs = document.querySelectorAll('img.avatar');
avtrs.forEach(function(img) {
img.classList.remove('blur');
//img.classList.add('unblur');
//img.style.filter = "blur(0px)";
});
});
});
}
function showLoginForm(authUrl) {
const authDiv = document.getElementById("authorizeDiv");
authDiv.classList.remove("hidden");
authDiv.querySelector("a").href = authUrl;
}
function initUploadForm(formId) {
document.querySelectorAll('.upload-form').forEach(function(el) {
el.style.display = "none";
});
const form = document.getElementById(formId);
form.style.display = form.offsetHeight == 0 ? "inherit" : "none";
form.reset();
removeImage();
document.getElementById('uploadProgress').style.display = "none";
}
function showAvatarResetDate(e) {
const val = e.valueAsNumber || parseInt(e.value) || 3;
let d = new Date();
d.setDate(d.getDate() + val);
e.parentElement.querySelector('span').innerText = d.toLocaleDateString();
}
function checkLoginStatus() {
return new Promise(function(resolve, reject) {
let req = new XMLHttpRequest();
req.open('GET', 'set_temp_avatar.php?status');
req.onload = function() {
// This is called even on 404 etc
// so check the status
console.log("onload", req);
if (req.status == 200) {
// Resolve the promise with the response text
//resolve(req.response);
let result = req.response;
if (result.length < 1) {
var error = new Error("No response");
error.name = "NoResponse";
reject(error);
}
try {
result = JSON.parse(result);
} catch (err) {
reject(err);
return;
}
resolve(result);
} else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
console.error("Network error");
reject(Error('Network Error'));
};
// Make the request
req.send();
});
}
function transitionEndEventName () {
var i,
undefined,
el = document.createElement('div'),
transitions = {
'transition':'transitionend',
'OTransition':'otransitionend', // oTransitionEnd in very old Opera
'MozTransition':'transitionend',
'WebkitTransition':'webkitTransitionEnd'
};
for (i in transitions) {
if (transitions.hasOwnProperty(i) && el.style[i] !== undefined) {
return transitions[i];
}
}
//TODO: throw 'TransitionEnd event is not supported in this browser';
}
function initFileUpload() {
document.getElementById('showUploadForm').addEventListener('click', e => {
initUploadForm('manuallyAddAvatarForm');
});
document.getElementById('addTempAvatar').addEventListener('click', e => {
document.getElementById('loadingLoginStatus').classList.remove('hidden');
checkLoginStatus().then(result => {
document.getElementById('loadingLoginStatus').classList.add('hidden');
console.log("Authentication status: ", result, !!result.authenticated);
if(result.error) {
console.error(`Error checking authentication status: ${result.error.message}`);
return;
}
if (!!result.authenticated) {
initUploadForm('uploadTempAvatar');
document.getElementById('uploadTempAvatar').querySelector('textarea').maxlength = result.maxPostLength;
} else {
showLoginForm(result.authUrl);
}
}, error => {
console.error("Error checking authentication status", error);
});
});
document.getElementById('cbShouldPostAvatar').addEventListener('click', e => {
document.getElementById('uploadTempAvatar').querySelector('.posttext').style.display = e.target.checked ? 'inherit' : 'none';
});
document.querySelectorAll('.posttext').forEach(el => {
el.addEventListener('focus', e => {
if (e.target.selectionStart == e.target.selectionEnd && !document.firstTextboxFocus) {
document.firstTextboxFocus = true;
setTimeout(function() {
console.log("Selecting start", e, e.target);
e.target.selectionStart = 0;
e.target.selectionEnd = 0;
});
}
});
});
const durationInput = document.querySelector('#uploadTempAvatar input[name=avatarDuration]');
showAvatarResetDate(durationInput);
durationInput.addEventListener('change', e => {
showAvatarResetDate(e.target);
});
var isAdvancedUpload = function() {
var div = document.createElement('div');
return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && 'FormData' in window && 'FileReader' in window;
}();
if (isAdvancedUpload) {
window.droppedFile = false;
const dragOverEvents = ['dragover', 'dragenter'];
const dragEndEvents = ['dragleave', 'dragend', 'drop'];
const events = dragOverEvents.concat(dragEndEvents).concat(['drag', 'dragstart']);
const zone = document.querySelectorAll('.file-upload').forEach(function(form) {
form.classList.add('advanced-upload');
const fileSelect = form.querySelector('input[type=file]');
events.forEach(event => {
form.addEventListener(event, e => {
// preventing the unwanted behaviours
e.preventDefault();
e.stopPropagation();
});
});
dragOverEvents.forEach(event => {
form.addEventListener(event, e => {
form.classList.add('is-dragover');
});
});
dragEndEvents.forEach(event => {
form.addEventListener(event, e => {
form.classList.remove('is-dragover');
});
});
form.addEventListener('drop', e => {
imageAdded(e.dataTransfer.files, e);
});
fileSelect.addEventListener('change', e => {
imageAdded(e.target.files, e);
});
});
}
console.log(document.querySelectorAll('.removeImage'));
document.querySelectorAll('.removeImage').forEach(function(el) {
el.style.zIndex = 1000;
el.addEventListener('click', e => {
removeImage();
});
});
}
function removeImage(e) {
console.log(e);
window.droppedFile = null;
//document.getElementById('removeImage').style.display = "none";
document.querySelectorAll('.removeImage').forEach(function(el) {
el.style.display = "none";
});
document.querySelectorAll('.avatarPreview').forEach(function(el) {
window.URL.revokeObjectURL(el.src);
el.style.display = "none";
el.src = "";
});
}
function handleManualAvatarUpload(e) {
startUpload(e).then(result => {
notify("Avatar added successfully", {type: "success"});
showNewlyAddedPicture(result);
e.style.display = "none";
}, error => {
notify(`Error saving avatar: ${error.message}`, {type: "error"});
e.style.display = "none";
});
return false;
}
function handleTempAvatarUpload(e) {
startUpload(e).then(result => {
if (result.warn) {
notify(result.warn, {type: "warn"});
} else {
notify("Your temporary theme avatar has been saved succesfully", {type: "success"});
}
e.style.display = "none";
}, error => {
console.error(error);
notify(`Error saving avatar: ${error.message}`, {type: "error"});
});
return false;
}
function startUpload(e) {
console.log(window.droppedFile, e);
const progress = e.querySelector('.uploadProgress');
return new Promise(function(resolve, reject) {
postForm(e, (position, total) => {
progress.max = total;
progress.value = position;
progress.style.display = "inherit";
}).then(result => {
if (result.length < 1) {
reject({'message': 'Received no response from server :( '});
return;
}
try {
result = JSON.parse(result);
} catch (err) {
reject({'message': `Unknown response from server: ${result}`});
return;
}
console.log("Done", result);
if (result.error) {
reject(result.error);
} else {
resolve(result);
}
}, error => {
console.error(error);
let serverError;
if (typeof(error) == "object") {
serverError = error;
} else {
try {
serverError = JSON.parse(error);
} catch (err) {
serverError = {message: "Unknown error"};
}
}
reject(serverError);
});
});
}
function showNewlyAddedPicture(postDetails) {
// TODO: Consolidate together with getUserDetails()
// TODO: Check if user already has a picture for this theme
document.querySelector('.avatar-grid').appendChild(createPostBox(info));
}
function createPostBox(info) {
/*
* info.succes: true (hopefully)
* info.error: Error object, containing "code" and "message"
* info.src: Avatar image
* info.presence: -1: gray, 0: red, 1: green
* info.user: @username. Might be null or empty string
* info.realname: Realname. Might be null or empty string
* info.posttext: HTML or pure text content of the post
* info.postid: ID of the post
* info.timestamp: timestamp of the post
*/
console.log(info);
const grid = document.querySelector('.avatar-grid');
let postBox = null;
// If there is already oen in the grid, clone that
if (grid.querySelectorAll(".avatar-box").length) {
postBox = grid.children[grid.children.length - 1].cloneNode(true);
} else {
console.error("No images in grid yet. Not fully implemented. TODO");
}
// New image
postBox.querySelector('.avatar').src = info.img;
// User info
const userDetails = postBox.querySelector('.user-box');
if (info.realname != null) {
userDetails.innerHTML = '<span>' + info.realname + '</span><span class="username">' + info.user + '</span>';
} else {
userDetails.innerHTML = '<span>' + info.user + '</span>';
}
/*
console.log(userDetails.outerHTML);
let userBox = userDetails.querySelector('.username');
if (userBox == null) {
userBox = userDetails.querySelector('span');
}
console.log(userBox.outerHTML);
userBox.innerText = info.user;
console.log(userBox.outerHTML);
if (info.realname != null) {
userBox.innerHTML = '<span>' + info.realname + '</span><span class="username">' + info.user + '</span>';
}*/
let presenceIndicator = userDetails.querySelector('.presence-indicator');
if (presenceIndicator == null) {
presenceIndicator = document.createElement('div');
presenceIndicator.classList.add('presence-indicator');
userDetails.appendChild(presenceIndicator);
}
switch (info.presence) {
case -1:
presenceIndicator.style.borderColor = "gray";
presenceIndicator.style.setProperty('border-color', 'var(--gray-dark)');
break;
case 0:
presenceIndicator.style.borderColor = "red";
presenceIndicator.style.setProperty('border-color', 'var(--red)');
break;
case 1:
presenceIndicator.style.borderColor = "green";
presenceIndicator.style.setProperty('border-color', 'var(--green)');
break;
}
// Post info
const postTextBox = postBox.querySelector('.post-text-box');
postTextBox.innerHTML = info.posttext;
const postMetaBox = postBox.querySelector('.post-meta-box');
const postLink = postMetaBox.querySelector('a');
postLink.href = "https://posts.pnut.io/" + info.postid;
postLink.innerText = '#' + info.postid;
let d = new Date(info.timestamp * 1000);
postMetaBox.querySelector('.postdate').innerText = d.toLocaleString();
return postBox;
}
function postForm(form, progressCallback) {
// Return a new promise.
console.log('Form: ', form);
return new Promise(function(resolve, reject) {
//const promise = new Promise();
if (form.id == 'manuallyAddAvatarForm') {
const idInput = form.querySelector('[name=postID]');
const pID = getPostID(idInput);
if (pID == -1) {
reject("Invalid post id");
} else {
idInput.value = pID;
}
}
var ajaxData = new FormData(form);
if (window.droppedFile) {
ajaxData.append(form.querySelector('input[type=file]').getAttribute('name'), window.droppedFile);
}
const id = getQueries().id;
if (!id) {
reject("Unknown theme id");
} else {
ajaxData.append('theme', id);
ajaxData.append('submit', 'submit');
console.log(ajaxData);
// Do the usual XHR stuff
let req = new XMLHttpRequest();
req.open(form.method, form.action);
req.onload = function() {
// This is called even on 404 etc
// so check the status
console.log("onload", req);
if (req.status == 200) {
// Resolve the promise with the response text
resolve(req.response);
}
else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
console.error("Network error");
reject(Error('Network Error'));
};
if (progressCallback) {
var eventSource = req.upload || req;
eventSource.addEventListener("progress", function(e) {
// normalize position attributes across XMLHttpRequest versions and browsers
var position = e.position || e.loaded;
var total = e.totalSize || e.total;
progressCallback(position, total);
});
}
// Make the request
req.send(ajaxData);
}
});
}
function imageAdded(arr, evt) {
if (!arr.length) {
return;
}
console.log("Dropped image", arr, evt);
window.droppedFile = arr[0];
const imgSize = window.droppedFile.size;
console.log("Size in Bytes", imgSize);
const maxSize = parseInt(document.querySelector('input[name=MAX_FILE_SIZE]').value);
let errorSpan = document.getElementById('imgTooLarge');
if (imgSize > maxSize) {
//<span style="display: block;">Image too large</span>
if (errorSpan == null) {
errorSpan = document.createElement('span');
errorSpan.classList.add('imgTooLarge');
errorSpan.innerText = `Image is too large. It is ${(imgSize/1024/1024).toFixed(2)}MiB, but should be <= ${(maxSize/1024/1024).toFixed(2)}MiB`;
errorSpan.style.display = 'inherit';
errorSpan.id = 'imgTooLarge';
document.querySelector('#uploadTempAvatar table tr td:nth-child(2)').appendChild(errorSpan);
} else {
errorSpan.style.display = 'inherit';
}
} else if (errorSpan != null) {
errorSpan.style.display = 'none';
}
getOrientation(function(e){
console.log("Orientation", e);
let prv = evt.target.parentElement.querySelector('.avatarPreview');
switch (e) {
case 1:
prv.style.transform = "rotate(0)";
break;
case 3:
prv.style.transform = "rotate(180deg)";
break;
case 6:
prv.style.transform = "rotate(90deg)";
break;
case 8:
prv.style.transform = "rotate(270deg)";
break;
default:
prv.style.transform = "rotate(0)";
break;
}
if (prv.src) {
window.URL.revokeObjectURL(prv.src);
}
prv.style.display = "inherit";
prv.src = window.URL.createObjectURL(droppedFile);
evt.target.parentElement.querySelector('.removeImage').style.display = "inherit";
evt.target.parentElement.querySelector('.removeImage').style.zIndex = 1000;zIndex = 1000;
});
}
function getOrientation(callback) {
var reader = new FileReader();
reader.onload = function(e) {
var view = new DataView(e.target.result);
const magic = view.getUint16(0, false);
if (magic != 0xFFD8) {
console.warn("Unknown magic bytes: ", magic.toString(16))
return callback(-2);
}
var length = view.byteLength, offset = 2;
console.debug("Length: ", length);
while (offset < length) {
const firstBytesTest = view.getUint16(offset+2, false);
if (firstBytesTest <= 8) {
console.warn("firstBytesTest <=8", firstBytesTest);
return callback(-1);
}
var marker = view.getUint16(offset, false);
offset += 2;
if (marker == 0xFFE1) {
console.debug("Found marker");
if (view.getUint32(offset += 2, false) != 0x45786966) {
console.debug("Invalid sequence following marker: ", view.getUint32(offset += 2, false).toString(16));
return callback(-1);
}
var little = view.getUint16(offset += 6, false) == 0x4949;
offset += view.getUint32(offset + 4, little);
var tags = view.getUint16(offset, little);
offset += 2;
for (var i = 0; i < tags; i++) {
if (view.getUint16(offset + (i * 12), little) == 0x0112) {
console.debug("Found rotation tag");
return callback(view.getUint16(offset + (i * 12) + 8, little));
}
}
} else if ((marker & 0xFF00) != 0xFF00) {
break;
} else {
offset += view.getUint16(offset, false);
}
}
return callback(-1);
};
reader.readAsArrayBuffer(window.droppedFile);
}
function setupUploadPostPreview() {
const postIdInput = document.querySelector('input[name=postID]');
window.lastPostIDKey = 0;
postIdInput.onkeyup = function() {
window.lastPostIDKey = Date.now();
setTimeout(function() {
const now = Date.now();
const diff = now - window.lastPostIDKey;
console.log(diff);
if (diff < 500) {
return;
}
const pID = getPostID(postIdInput);
get(`get_posttext.php?id=${pID}&avatarWidth=40`).then(function(response) {
try {
response = JSON.parse(response);
} catch (error) {
showError(`Unknown response from server: ${response}`);
return;
}
if (response.error) {
showError(`Error loading post #${pID}: ${response.error}`);
return;
}
const tbl = document.querySelector('.upload-form').querySelector('table');
let i = tbl.rows[1].cells.length - 1;
// Remove existing post boxes
while (tbl.rows[1].cells[i].querySelector('.post-box')) {
tbl.rows[1].removeChild(tbl.rows[1].cells[i]);
i = tbl.rows[1].cells.length - 1;
}
const cell = tbl.rows[1].insertCell();
const postBox = createPostBox(response);
const ab = postBox.querySelector('.avatar');
ab.style.width = '20px';
postBox.removeChild(ab.parentElement);
const ub = postBox.querySelector('.post-box').querySelector('.user-box');
ub.insertBefore(ab, ub.firstChild);
ab.className = "";
ab.style.borderRadius = "3px";
cell.style.verticalAlign = 'top';
cell.appendChild(postBox);
// Post box overflows horizontically (long links, etc). Remove set width and let it goooo
if (postBox.scrollWidth > postBox.clientWidth) {
postBox.style.width = "initial";
}
});
}, 500);
};
}
function getPostID(input) {
const parsed = parseInt(input.value);
if (isNaN(parsed)) {
matches = input.value.match(/(?:https?:\/\/)?((posts.*pnut.*)|.*pnut.*posts)\/(\d+)/);
if (!matches || Number.isInteger(matches[matches.length-1])) {
return -1;
}
return parseInt(matches[matches.length-1]);
}
return parsed;
}
function showError(message, params = {}) {
params.type = 'error';
notify(message, params);
}
function notify(message, params) {
defaultOptions = {
class: 'notif',
position: 'top|right',
duration: 5,
type: 'info'
};
const mergedOptions = {
...defaultOptions,
...params,
};
mergedOptions.duration *= 1000;
switch (mergedOptions.type) {
case 'success':
console.log(message);
toast.success(message, mergedOptions);
break;
case 'warn':
console.warn(message);
toast.warn(message, mergedOptions);
break;
case 'error':
console.error(message);
toast.alert(message, mergedOptions);
break;
default:
console.log(message);
toast.message(message, mergedOptions);
break;
}
}