Clean up JS for Groups and Images page

Clean up group.py and image.py
This commit is contained in:
Michał 2023-09-24 15:42:26 +01:00
parent bdecdaff7c
commit 54a98a8591
13 changed files with 470 additions and 474 deletions

View file

@ -9,7 +9,6 @@ from uuid import uuid4
from flask import ( from flask import (
Blueprint, Blueprint,
flash,
abort, abort,
send_from_directory, send_from_directory,
jsonify, jsonify,
@ -21,7 +20,7 @@ from flask_login import login_required, current_user
from colorthief import ColorThief from colorthief import ColorThief
from onlylegs.extensions import db from onlylegs.extensions import db
from onlylegs.models import Users, Pictures, Albums, AlbumJunction from onlylegs.models import Users, Pictures
from onlylegs.utils.metadata import yoink from onlylegs.utils.metadata import yoink
from onlylegs.utils.generate_image import generate_thumbnail from onlylegs.utils.generate_image import generate_thumbnail
@ -111,8 +110,8 @@ def media(path):
r for resolution, thumb for thumbnail etc r for resolution, thumb for thumbnail etc
e for extension, jpg, png etc e for extension, jpg, png etc
""" """
res = request.args.get("r", default=None).strip() res = request.args.get("r", "").strip()
ext = request.args.get("e", default=None).strip() ext = request.args.get("e", "").strip()
# if no args are passed, return the raw file # if no args are passed, return the raw file
if not res and not ext: if not res and not ext:
@ -181,113 +180,3 @@ def upload():
db.session.commit() db.session.commit()
return jsonify({"message": "File uploaded"}), 200 return jsonify({"message": "File uploaded"}), 200
@blueprint.route("/media/delete/<int:image_id>", methods=["POST"])
@login_required
def delete_image(image_id):
"""
Deletes an image from the server and database
"""
post = db.get_or_404(Pictures, image_id)
# Check if image exists and if user is allowed to delete it (author)
if post.author_id != current_user.id:
logging.info("User %s tried to delete image %s", current_user.id, image_id)
return (
jsonify({"message": "You are not allowed to delete this image, heck off"}),
403,
)
# Delete file
try:
os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], post.filename))
except FileNotFoundError:
logging.warning(
"File not found: %s, already deleted or never existed", post.filename
)
# Delete cached files
cache_name = post.filename.rsplit(".")[0]
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(
cache_name + "*"
):
os.remove(cache_file)
AlbumJunction.query.filter_by(picture_id=image_id).delete()
db.session.delete(post)
db.session.commit()
logging.info("Removed image (%s) %s", image_id, post.filename)
flash(["Image was all in Le Head!", "1"])
return jsonify({"message": "Image deleted"}), 200
@blueprint.route("/group/create", methods=["POST"])
@login_required
def create_group():
"""
Creates a group
"""
group_name = request.form.get("name", "").strip()
group_description = request.form.get("description", "").strip()
group_author = current_user.id
new_group = Albums(
name=group_name,
description=group_description,
author_id=group_author,
)
db.session.add(new_group)
db.session.commit()
return jsonify({"message": "Group created", "id": new_group.id})
@blueprint.route("/group/modify", methods=["POST"])
@login_required
def modify_group():
"""
Changes the images in a group
"""
group_id = request.form.get("group", "").strip()
image_id = request.form.get("image", "").strip()
action = request.form.get("action", "").strip()
group = db.get_or_404(Albums, group_id)
db.get_or_404(Pictures, image_id) # Check if image exists
if group.author_id != current_user.id:
return jsonify({"message": "You are not the owner of this group"}), 403
junction_exist = AlbumJunction.query.filter_by(
album_id=group_id, picture_id=image_id
).first()
if action == "add" and not junction_exist:
db.session.add(AlbumJunction(album_id=group_id, picture_id=image_id))
elif request.form["action"] == "remove":
AlbumJunction.query.filter_by(album_id=group_id, picture_id=image_id).delete()
db.session.commit()
return jsonify({"message": "Group modified"})
@blueprint.route("/group/delete", methods=["POST"])
def delete_group():
"""
Deletes a group
"""
group_id = request.form.get("group", "").strip()
group = db.get_or_404(Albums, group_id)
if group.author_id != current_user.id:
return jsonify({"message": "You are not the owner of this group"}), 403
AlbumJunction.query.filter_by(album_id=group_id).delete()
db.session.delete(group)
db.session.commit()
flash(["Group yeeted!", "1"])
return jsonify({"message": "Group deleted"})

View file

@ -0,0 +1,155 @@
function groupDeletePopup() {
let title = 'Yeet!';
let subtitle =
'Are you surrrre? This action is irreversible ' +
'and very final. This wont delete the images, ' +
'but it will remove them from this group.'
let body = null;
let deleteBtn = document.createElement('button');
deleteBtn.classList.add('btn-block');
deleteBtn.classList.add('critical');
deleteBtn.innerHTML = 'Dewww eeeet!';
deleteBtn.onclick = groupDeleteConfirm;
popupShow(title, subtitle, body, [popupCancelButton, deleteBtn]);
}
function groupDeleteConfirm(event) {
// AJAX takes control of subby form :3
event.preventDefault();
fetch('/group/' + group_data['id'], {
method: 'DELETE',
}).then(response => {
if (response.ok) {
window.location.href = '/group/';
} else {
addNotification('Server exploded, returned:' + response.status, 2);
}
}).catch(error => {
addNotification('Error yeeting group!' + error, 2);
});
}
function groupEditPopup() {
let title = 'Nothing stays the same';
let subtitle = 'Add, remove, or change, the power is in your hands...'
let formSubmitButton = document.createElement('button');
formSubmitButton.setAttribute('form', 'groupEditForm');
formSubmitButton.setAttribute('type', 'submit');
formSubmitButton.classList.add('btn-block');
formSubmitButton.classList.add('primary');
formSubmitButton.innerHTML = 'Saveeee';
// Create form
let body = document.createElement('form');
body.setAttribute('onsubmit', 'return groupEditConfirm(event);');
body.id = 'groupEditForm';
let formImageId = document.createElement('input');
formImageId.setAttribute('type', 'text');
formImageId.setAttribute('placeholder', 'Image ID');
formImageId.setAttribute('required', '');
formImageId.classList.add('input-block');
formImageId.id = 'groupFormImageId';
let formAction = document.createElement('input');
formAction.setAttribute('type', 'text');
formAction.setAttribute('value', 'add');
formAction.setAttribute('placeholder', '[add, remove]');
formAction.setAttribute('required', '');
formAction.classList.add('input-block');
formAction.id = 'groupFormAction';
body.appendChild(formImageId);
body.appendChild(formAction);
popupShow(title, subtitle, body, [popupCancelButton, formSubmitButton]);
}
function groupEditConfirm(event) {
// AJAX takes control of subby form :3
event.preventDefault();
let imageId = document.querySelector("#groupFormImageId").value;
let action = document.querySelector("#groupFormAction").value;
let formData = new FormData();
formData.append("imageId", imageId);
formData.append("action", action);
fetch('/group/' + group_data['id'], {
method: 'PUT',
body: formData
}).then(response => {
if (response.ok) {
window.location.reload();
} else {
addNotification('Server exploded, returned:' + response.status, 2);
}
}).catch(error => {
addNotification('Error!!!!! Panic!!!!' + error, 2);
});
}
function groupCreatePopup() {
let title = 'New stuff!';
let subtitle =
'Image groups are a simple way to ' +
'"group" images together, are you ready?'
let formSubmitButton = document.createElement('button');
formSubmitButton.setAttribute('form', 'groupCreateForm');
formSubmitButton.setAttribute('type', 'submit');
formSubmitButton.classList.add('btn-block');
formSubmitButton.classList.add('primary');
formSubmitButton.innerHTML = 'Huzzah!';
// Create form
let body = document.createElement('form');
body.setAttribute('onsubmit', 'return groupCreateConfirm(event);');
body.id = 'groupCreateForm';
let formName = document.createElement('input');
formName.setAttribute('type', 'text');
formName.setAttribute('placeholder', 'Group namey');
formName.setAttribute('required', '');
formName.classList.add('input-block');
formName.id = 'groupFormName';
let formDescription = document.createElement('input');
formDescription.setAttribute('type', 'text');
formDescription.setAttribute('placeholder', 'What it about????');
formDescription.classList.add('input-block');
formDescription.id = 'groupFormDescription';
body.appendChild(formName);
body.appendChild(formDescription);
popupShow(title, subtitle, body, [popupCancelButton, formSubmitButton]);
}
function groupCreateConfirm(event) {
// AJAX takes control of subby form :3
event.preventDefault();
let name = document.querySelector("#groupFormName").value;
let description = document.querySelector("#groupFormDescription").value;
let formData = new FormData();
formData.append("name", name);
formData.append("description", description);
fetch('/group/', {
method: 'POST',
body: formData
}).then(response => {
if (response.ok) {
window.location.reload();
} else {
addNotification('Server exploded, returned:' + response.status, 2);
}
}).catch(error => {
addNotification('Error summoning group!' + error, 2);
});
}

View file

@ -0,0 +1,101 @@
function imageFullscreen() {
let info = document.querySelector('.info-container');
let image = document.querySelector('.image-container');
if (info.classList.contains('collapsed')) {
info.classList.remove('collapsed');
image.classList.remove('collapsed');
document.cookie = "image-info=0"
} else {
info.classList.add('collapsed');
image.classList.add('collapsed');
document.cookie = "image-info=1"
}
}
function imageDeletePopup() {
let title = 'DESTRUCTION!!!!!!';
let subtitle =
'Do you want to delete this image along with ' +
'all of its data??? This action is irreversible!';
let body = null;
let deleteBtn = document.createElement('button');
deleteBtn.classList.add('btn-block');
deleteBtn.classList.add('critical');
deleteBtn.innerHTML = 'Dewww eeeet!';
deleteBtn.onclick = imageDeleteConfirm;
popupShow(title, subtitle, body, [popupCancelButton, deleteBtn]);
}
function imageDeleteConfirm() {
popupDismiss();
fetch('/image/' + image_data["id"], {
method: 'DELETE',
}).then(response => {
if (response.ok) {
window.location.href = '/';
} else {
addNotification('Image *clings*', 2);
}
});
}
function imageEditPopup() {
let title = 'Edit image!';
let subtitle = 'Enter funny stuff here!';
let formSubmitButton = document.createElement('button');
formSubmitButton.setAttribute('form', 'imageEditForm');
formSubmitButton.setAttribute('type', 'submit');
formSubmitButton.classList.add('btn-block');
formSubmitButton.classList.add('primary');
formSubmitButton.innerHTML = 'Saveeee';
// Create form
let body = document.createElement('form');
body.setAttribute('onsubmit', 'return imageEditConfirm(event);');
body.id = 'imageEditForm';
let formAlt = document.createElement('input');
formAlt.setAttribute('type', 'text');
formAlt.setAttribute('value', image_data["alt"]);
formAlt.setAttribute('placeholder', 'Image Alt');
formAlt.classList.add('input-block');
formAlt.id = 'imageFormAlt';
let formDescription = document.createElement('input');
formDescription.setAttribute('type', 'text');
formDescription.setAttribute('value', image_data["description"]);
formDescription.setAttribute('placeholder', 'Image Description');
formDescription.classList.add('input-block');
formDescription.id = 'imageFormDescription';
body.appendChild(formAlt);
body.appendChild(formDescription);
popupShow(title, subtitle, body, [popupCancelButton, formSubmitButton]);
}
function imageEditConfirm(event) {
// Yoink subby form
event.preventDefault();
let alt = document.querySelector('#imageFormAlt').value;
let description = document.querySelector('#imageFormDescription').value;
let form = new FormData();
form.append('alt', alt);
form.append('description', description);
fetch('/image/' + image_data["id"], {
method: 'PUT',
body: form,
}).then(response => {
if (response.ok) {
popupDismiss();
window.location.reload();
} else {
addNotification('Image *clings*', 2);
}
});
}

View file

@ -5,7 +5,7 @@ function showLogin() {
cancelBtn.classList.add('btn-block'); cancelBtn.classList.add('btn-block');
cancelBtn.classList.add('transparent'); cancelBtn.classList.add('transparent');
cancelBtn.innerHTML = 'nuuuuuuuu'; cancelBtn.innerHTML = 'nuuuuuuuu';
cancelBtn.onclick = popupDissmiss; cancelBtn.onclick = popupDismiss;
loginBtn = document.createElement('button'); loginBtn = document.createElement('button');
loginBtn.classList.add('btn-block'); loginBtn.classList.add('btn-block');
@ -50,7 +50,7 @@ function showLogin() {
loginForm.appendChild(passwordInput); loginForm.appendChild(passwordInput);
loginForm.appendChild(rememberMeSpan); loginForm.appendChild(rememberMeSpan);
popUpShow( popupShow(
'Login!', 'Login!',
'Need an account? <span class="link" onclick="showRegister()">Register!</span>', 'Need an account? <span class="link" onclick="showRegister()">Register!</span>',
loginForm, loginForm,
@ -103,7 +103,7 @@ function showRegister() {
cancelBtn.classList.add('btn-block'); cancelBtn.classList.add('btn-block');
cancelBtn.classList.add('transparent'); cancelBtn.classList.add('transparent');
cancelBtn.innerHTML = 'nuuuuuuuu'; cancelBtn.innerHTML = 'nuuuuuuuu';
cancelBtn.onclick = popupDissmiss; cancelBtn.onclick = popupDismiss;
registerBtn = document.createElement('button'); registerBtn = document.createElement('button');
registerBtn.classList.add('btn-block'); registerBtn.classList.add('btn-block');
@ -146,7 +146,7 @@ function showRegister() {
registerForm.appendChild(passwordInput); registerForm.appendChild(passwordInput);
registerForm.appendChild(passwordInputRepeat); registerForm.appendChild(passwordInputRepeat);
popUpShow( popupShow(
'Who are you?', 'Who are you?',
'Already have an account? <span class="link" onclick="showLogin()">Login!</span>', 'Already have an account? <span class="link" onclick="showLogin()">Login!</span>',
registerForm, registerForm,

View file

@ -1,4 +1,4 @@
function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null) { function popupShow(titleText, subtitleText, bodyContent=null, userActions=null) {
// Get popup elements // Get popup elements
const popupSelector = document.querySelector('.pop-up'); const popupSelector = document.querySelector('.pop-up');
const headerSelector = document.querySelector('.pop-up-header'); const headerSelector = document.querySelector('.pop-up-header');
@ -9,38 +9,47 @@ function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null)
actionsSelector.innerHTML = ''; actionsSelector.innerHTML = '';
// Set popup header and subtitle // Set popup header and subtitle
const titleElement = document.createElement('h2'); let titleElement = document.createElement('h2');
titleElement.innerHTML = titleText; titleElement.innerHTML = titleText;
headerSelector.appendChild(titleElement); headerSelector.appendChild(titleElement);
const subtitleElement = document.createElement('p'); let subtitleElement = document.createElement('p');
subtitleElement.innerHTML = subtitleText; subtitleElement.innerHTML = subtitleText;
headerSelector.appendChild(subtitleElement); headerSelector.appendChild(subtitleElement);
if (bodyContent) { if (bodyContent) { headerSelector.appendChild(bodyContent) }
headerSelector.appendChild(bodyContent);
}
// Set buttons that will be displayed // Set buttons that will be displayed
if (userActions) { if (userActions) {
// for each user action, add the element userActions.forEach((action) => {
for (let i = 0; i < userActions.length; i++) { actionsSelector.appendChild(action);
actionsSelector.appendChild(userActions[i]); });
}
} else { } else {
actionsSelector.innerHTML = '<button class="btn-block transparent" onclick="popupDissmiss()">Close</button>'; let closeButton = document.createElement('button');
closeButton.classList.add('btn-block');
closeButton.classList.add('transparent');
closeButton.innerHTML = 'Yeet!';
closeButton.onclick = popupDismiss;
actionsSelector.appendChild(closeButton);
} }
// Stop scrolling and show popup // Stop scrolling and show popup
document.querySelector("html").style.overflow = "hidden"; document.querySelector("html").style.overflow = "hidden";
popupSelector.style.display = 'block'; popupSelector.style.display = 'block';
setTimeout(() => { popupSelector.classList.add('active') }, 5); // 2ms delay to allow for css transition >:C
// 5ms delay to allow for css transition >:C
setTimeout(() => { popupSelector.classList.add('active') }, 5);
} }
function popupDissmiss() { function popupDismiss() {
const popupSelector = document.querySelector('.pop-up'); const popupSelector = document.querySelector('.pop-up');
document.querySelector("html").style.overflow = "auto"; document.querySelector("html").style.overflow = "auto";
popupSelector.classList.remove('active'); popupSelector.classList.remove('active');
setTimeout(() => { popupSelector.style.display = 'none'; }, 200); setTimeout(() => { popupSelector.style.display = 'none'; }, 200);
} }
const popupCancelButton = document.createElement('button');
popupCancelButton.classList.add('btn-block');
popupCancelButton.classList.add('transparent');
popupCancelButton.innerHTML = 'nuuuuuuuu';
popupCancelButton.onclick = popupDismiss;

View file

@ -36,7 +36,7 @@
<button class="top-of-page" aria-label="Jump to top of page"><i class="ph ph-arrow-up"></i></button> <button class="top-of-page" aria-label="Jump to top of page"><i class="ph ph-arrow-up"></i></button>
<div class="pop-up"> <div class="pop-up">
<span class="pop-up__click-off" onclick="popupDissmiss()"></span> <span class="pop-up__click-off" onclick="popupDismiss()"></span>
<div class="pop-up-wrapper"> <div class="pop-up-wrapper">
<div class="pop-up-header"></div> <div class="pop-up-header"></div>
<div class="pop-up-controlls"></div> <div class="pop-up-controlls"></div>
@ -160,7 +160,7 @@
infoButton.classList.add('show'); infoButton.classList.add('show');
} }
infoButton.onclick = () => { infoButton.onclick = () => {
popUpShow('OnlyLegs', popupShow('OnlyLegs',
'<a href="https://github.com/Fluffy-Bean/onlylegs">v{{ config['APP_VERSION'] }}</a> ' + '<a href="https://github.com/Fluffy-Bean/onlylegs">v{{ config['APP_VERSION'] }}</a> ' +
'using <a href="https://phosphoricons.com/">Phosphoricons</a> and Flask.' + 'using <a href="https://phosphoricons.com/">Phosphoricons</a> and Flask.' +
'<br>Made by Fluffy and others with ❤️'); '<br>Made by Fluffy and others with ❤️');

View file

@ -11,163 +11,11 @@
{% endif %} {% endif %}
<script type="text/javascript"> <script type="text/javascript">
{% if current_user.id == group.author.id %} group_data = {
function groupDelete() { 'id': {{ group.id }},
cancelBtn = document.createElement('button'); 'name': "{{ group.name }}",
cancelBtn.classList.add('btn-block'); 'description': "{{ group.description }}",
cancelBtn.classList.add('transparent'); }
cancelBtn.innerHTML = 'AAAAAAAAAA';
cancelBtn.onclick = popupDissmiss;
deleteBtn = document.createElement('button');
deleteBtn.classList.add('btn-block');
deleteBtn.classList.add('critical');
deleteBtn.innerHTML = 'No ragrats!';
deleteBtn.onclick = deleteConfirm;
popUpShow('Yeet!',
'Are you surrrre? This action is irreversible and very final.' +
' This wont delete the images, but it will remove them from this group.',
null,
[cancelBtn, deleteBtn]);
}
function deleteConfirm(event) {
// AJAX takes control of subby form :3
event.preventDefault();
let formID = {{ group.id }};
if (!formID) {
addNotification("Dont tamper with the JavaScript pls!", 3);
return;
}
// Make form
const formData = new FormData();
formData.append("group", formID);
fetch('{{ url_for('api.delete_group') }}', {
method: 'POST',
body: formData
}).then(response => {
if (response.status === 200) {
// Redirect to groups page
window.location.href = '{{ url_for('group.groups') }}';
} else {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here... Bad information', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
}
}
}).catch(error => {
addNotification('Error yeeting group!', 2);
});
}
function groupEdit() {
// Create elements
cancelBtn = document.createElement('button');
cancelBtn.classList.add('btn-block');
cancelBtn.classList.add('transparent');
cancelBtn.innerHTML = 'go baaaaack';
cancelBtn.onclick = popupDissmiss;
submitBtn = document.createElement('button');
submitBtn.classList.add('btn-block');
submitBtn.classList.add('primary');
submitBtn.innerHTML = 'Saveeee';
submitBtn.type = 'submit';
submitBtn.setAttribute('form', 'editForm');
// Create form
editForm = document.createElement('form');
editForm.id = 'editForm';
editForm.setAttribute('onsubmit', 'return edit(event);');
groupInput = document.createElement('input');
groupInput.classList.add('input-block');
groupInput.type = 'text';
groupInput.placeholder = 'Group ID';
groupInput.value = {{ group.id }};
groupInput.id = 'group';
imageInput = document.createElement('input');
imageInput.classList.add('input-block');
imageInput.type = 'text';
imageInput.placeholder = 'Image ID';
imageInput.id = 'image';
actionInput = document.createElement('input');
actionInput.classList.add('input-block');
actionInput.type = 'text';
actionInput.placeholder = 'add/remove';
actionInput.value = 'add';
actionInput.id = 'action';
editForm.appendChild(groupInput);
editForm.appendChild(imageInput);
editForm.appendChild(actionInput);
popUpShow(
'Nothing stays the same',
'Add, remove, or change, the power is in your hands...',
editForm,
[cancelBtn, submitBtn]
);
}
function edit(event) {
// AJAX takes control of subby form :3
event.preventDefault();
let formGroup = document.querySelector("#group").value;
let formImage = document.querySelector("#image").value;
let formAction = document.querySelector("#action").value;
if (!formGroup || !formImage || !formAction) {
addNotification("All values must be set!", 3);
return;
}
// Make form
const formData = new FormData();
formData.append("group", formGroup);
formData.append("image", formImage);
formData.append("action", formAction);
fetch('{{ url_for('api.modify_group') }}', {
method: 'POST',
body: formData
}).then(response => {
if (response.status === 200) {
addNotification('Group edited!!!', 1);
popupDissmiss();
} else {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here... Bad information', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
}
}
}).catch(error => {
addNotification('Error!!!!! Panic!!!!', 2);
});
}
{% endif %}
</script> </script>
<style> <style>
@ -234,8 +82,8 @@
</div> </div>
{% if current_user.id == group.author.id %} {% if current_user.id == group.author.id %}
<div> <div>
<button class="pill-item pill__critical" onclick="groupDelete()"><i class="ph ph-trash"></i></button> <button class="pill-item pill__critical" onclick="groupDeletePopup()"><i class="ph ph-trash"></i></button>
<button class="pill-item pill__critical" onclick="groupEdit()"><i class="ph ph-pencil-simple"></i></button> <button class="pill-item pill__critical" onclick="groupEditPopup()"><i class="ph ph-pencil-simple"></i></button>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -252,8 +100,8 @@
</div> </div>
{% if current_user.id == group.author.id %} {% if current_user.id == group.author.id %}
<div> <div>
<button class="pill-item pill__critical" onclick="groupDelete()"><i class="ph ph-trash"></i></button> <button class="pill-item pill__critical" onclick="groupDeletePopup()"><i class="ph ph-trash"></i></button>
<button class="pill-item pill__critical" onclick="groupEdit()"><i class="ph ph-pencil-simple"></i></button> <button class="pill-item pill__critical" onclick="groupEditPopup()"><i class="ph ph-pencil-simple"></i></button>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View file

@ -9,65 +9,11 @@
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<script type="text/javascript"> <script type="text/javascript">
function fullscreen() { const image_data = {
let info = document.querySelector('.info-container'); 'id': {{ image.id }},
let image = document.querySelector('.image-container'); 'description': '{{ image.description }}',
'alt': '{{ image.alt }}',
if (info.classList.contains('collapsed')) { };
info.classList.remove('collapsed');
image.classList.remove('collapsed');
document.cookie = "image-info=0"
} else {
info.classList.add('collapsed');
image.classList.add('collapsed');
document.cookie = "image-info=1"
}
}
{% if current_user.id == image.author.id %}
function imageDelete() {
cancelBtn = document.createElement('button');
cancelBtn.classList.add('btn-block');
cancelBtn.classList.add('transparent');
cancelBtn.innerHTML = 'nuuuuuuuu';
cancelBtn.onclick = popupDissmiss;
deleteBtn = document.createElement('button');
deleteBtn.classList.add('btn-block');
deleteBtn.classList.add('critical');
deleteBtn.innerHTML = 'Dewww eeeet!';
deleteBtn.onclick = deleteConfirm;
popUpShow('DESTRUCTION!!!!!!',
'Do you want to delete this image along with all of its data??? ' +
'This action is irreversible!',
null,
[cancelBtn, deleteBtn]);
}
function deleteConfirm() {
popupDissmiss();
fetch('{{ url_for('api.delete_image', image_id=image['id']) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'delete'
})
}).then(function(response) {
if (response.ok) {
window.location.href = '/';
} else {
addNotification(`Image *clings*`, 2);
}
});
}
function imageEdit() {
addNotification("Not an option, oops!", 3);
}
{% endif %}
</script> </script>
<style> <style>
@ -85,14 +31,14 @@
<div class="pill-row"> <div class="pill-row">
{% if next_url %}<div><a class="pill-item" href="{{ next_url }}"><i class="ph ph-arrow-left"></i></a></div>{% endif %} {% if next_url %}<div><a class="pill-item" href="{{ next_url }}"><i class="ph ph-arrow-left"></i></a></div>{% endif %}
<div> <div>
<button class="pill-item" onclick="fullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button> <button class="pill-item" onclick="imageFullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button>
<button class="pill-item" onclick="copyToClipboard(window.location.href)"><i class="ph ph-export"></i></button> <button class="pill-item" onclick="copyToClipboard(window.location.href)"><i class="ph ph-export"></i></button>
<a class="pill-item" href="{{ url_for('api.media', path='uploads/' + image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a> <a class="pill-item" href="{{ url_for('api.media', path='uploads/' + image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a>
</div> </div>
{% if current_user.id == image.author.id %} {% if current_user.id == image.author.id %}
<div> <div>
<button class="pill-item pill__critical" onclick="imageDelete()"><i class="ph ph-trash"></i></button> <button class="pill-item pill__critical" onclick="imageDeletePopup()"><i class="ph ph-trash"></i></button>
<button class="pill-item pill__critical" onclick="imageEdit()"><i class="ph ph-pencil-simple"></i></button> <button class="pill-item pill__critical" onclick="imageEditPopup()"><i class="ph ph-pencil-simple"></i></button>
</div> </div>
{% endif %} {% endif %}
{% if prev_url %}<div><a class="pill-item" href="{{ prev_url }}"><i class="ph ph-arrow-right"></i></a></div>{% endif %} {% if prev_url %}<div><a class="pill-item" href="{{ prev_url }}"><i class="ph ph-arrow-right"></i></a></div>{% endif %}

View file

@ -3,95 +3,6 @@
{% block head %} {% block head %}
{% if images %}<meta name="theme-color" content="rgb{{ images.0.colours.0 }}"/>{% endif %} {% if images %}<meta name="theme-color" content="rgb{{ images.0.colours.0 }}"/>{% endif %}
{% if current_user.is_authenticated %}
<script type="text/javascript">
function showCreate() {
// Create elements
cancelBtn = document.createElement('button');
cancelBtn.classList.add('btn-block');
cancelBtn.classList.add('transparent');
cancelBtn.innerHTML = 'nuuuuuuuu';
cancelBtn.onclick = popupDissmiss;
submitBtn = document.createElement('button');
submitBtn.classList.add('btn-block');
submitBtn.classList.add('primary');
submitBtn.innerHTML = 'Submit!!';
submitBtn.type = 'submit';
submitBtn.setAttribute('form', 'createForm');
// Create form
createForm = document.createElement('form');
createForm.id = 'createForm';
createForm.setAttribute('onsubmit', 'return create(event);');
titleInput = document.createElement('input');
titleInput.classList.add('input-block');
titleInput.type = 'text';
titleInput.placeholder = 'Group namey';
titleInput.id = 'name';
descriptionInput = document.createElement('input');
descriptionInput.classList.add('input-block');
descriptionInput.type = 'text';
descriptionInput.placeholder = 'What it about????';
descriptionInput.id = 'description';
createForm.appendChild(titleInput);
createForm.appendChild(descriptionInput);
popUpShow(
'New stuff!',
'Image groups are a simple way to "group" images together, are you ready?',
createForm,
[cancelBtn, submitBtn]
);
}
function create(event) {
// AJAX takes control of subby form :3
event.preventDefault();
let formName = document.querySelector("#name").value;
let formDescription = document.querySelector("#description").value;
if (!formName) {
addNotification("Group name must be set!", 3);
return;
}
// Make form
const formData = new FormData();
formData.append("name", formName);
formData.append("description", formDescription);
fetch('{{ url_for('api.create_group') }}', {
method: 'POST',
body: formData
}).then(response => {
if (response.status === 200) {
addNotification('Group created!', 1);
popupDissmiss();
} else {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here... Bad information', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
}
}
}).catch(error => {
addNotification('Error making group! :c', 2);
});
}
</script>
{% endif %}
{% endblock %} {% endblock %}
{% block header %} {% block header %}
@ -108,7 +19,7 @@
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<div class="pill-row"> <div class="pill-row">
<div> <div>
<button class="pill-item" onclick="showCreate()"><i class="ph ph-plus"></i></button> <button class="pill-item" onclick="groupCreatePopup()"><i class="ph ph-plus"></i></button>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View file

@ -12,7 +12,7 @@
<script type="text/javascript"> <script type="text/javascript">
function moreInfo() { function moreInfo() {
popUpShow('{{ user.username }}', popupShow('{{ user.username }}',
'<p>Joined: {{ user.joined_at }}</p><br>' + '<p>Joined: {{ user.joined_at }}</p><br>' +
'<p>Images: {{ images|length }}</p><br>' + '<p>Images: {{ images|length }}</p><br>' +
'<p>Groups: {{ groups|length }}</p>'); '<p>Groups: {{ groups|length }}</p>');

View file

@ -7,7 +7,7 @@ from werkzeug.utils import secure_filename
from onlylegs.config import MEDIA_FOLDER, CACHE_FOLDER from onlylegs.config import MEDIA_FOLDER, CACHE_FOLDER
def generate_thumbnail(file_path, resolution, ext=None): def generate_thumbnail(file_path, resolution, ext=""):
""" """
Image thumbnail generator Image thumbnail generator
Uses PIL to generate a thumbnail of the image and saves it to the cache directory Uses PIL to generate a thumbnail of the image and saves it to the cache directory
@ -25,25 +25,25 @@ def generate_thumbnail(file_path, resolution, ext=None):
if not ext: if not ext:
ext = file_ext.strip(".") ext = file_ext.strip(".")
# PIL doesnt like jpg so we convert it to jpeg ext = "jpeg" if ext.lower() == "jpg" else ext.lower()
if ext.lower() == "jpg":
ext = "jpeg"
# Set resolution based on preset resolutions # Set resolution based on preset resolutions
if resolution in ["prev", "preview"]: if resolution in ("prev", "preview"):
res_x, res_y = (1920, 1080) res_x, res_y = (1920, 1080)
elif resolution in ["thumb", "thumbnail"]: elif resolution in ("thumb", "thumbnail"):
res_x, res_y = (300, 300) res_x, res_y = (300, 300)
elif resolution in ["pfp", "profile"]: elif resolution in ("pfp", "profile"):
res_x, res_y = (150, 150) res_x, res_y = (150, 150)
elif resolution in ["icon", "favicon"]: elif resolution in ("icon", "favicon"):
res_x, res_y = (30, 30) res_x, res_y = (30, 30)
else: else:
return None return None
cache_file_name = "{}_{}x{}.{}".format(file_name, res_x, res_y, ext).lower()
# If image has been already generated, return it from the cache # If image has been already generated, return it from the cache
if os.path.exists(os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}")): if os.path.exists(os.path.join(CACHE_FOLDER, cache_file_name)):
return os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}") return os.path.join(CACHE_FOLDER, cache_file_name)
# Check if image exists in the uploads directory # Check if image exists in the uploads directory
if not os.path.exists(os.path.join(MEDIA_FOLDER, file_path)): if not os.path.exists(os.path.join(MEDIA_FOLDER, file_path)):
@ -61,7 +61,7 @@ def generate_thumbnail(file_path, resolution, ext=None):
# Save image to cache directory # Save image to cache directory
try: try:
image.save( image.save(
os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}"), os.path.join(CACHE_FOLDER, cache_file_name),
icc_profile=image_icc, icc_profile=image_icc,
) )
except OSError: except OSError:
@ -69,11 +69,11 @@ def generate_thumbnail(file_path, resolution, ext=None):
# so we convert to RGB and try again # so we convert to RGB and try again
image = image.convert("RGB") image = image.convert("RGB")
image.save( image.save(
os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}"), os.path.join(CACHE_FOLDER, cache_file_name),
icc_profile=image_icc, icc_profile=image_icc,
) )
# No need to keep the image in memory, learned the hard way # No need to keep the image in memory, learned the hard way
image.close() image.close()
return os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}") return os.path.join(CACHE_FOLDER, cache_file_name)

View file

@ -3,8 +3,8 @@ Onlylegs - Image Groups
Why groups? Because I don't like calling these albums Why groups? Because I don't like calling these albums
sounds more limiting that it actually is in this gallery sounds more limiting that it actually is in this gallery
""" """
from flask import Blueprint, render_template, url_for, request from flask import Blueprint, render_template, url_for, request, flash, jsonify
from flask_login import login_required, current_user
from onlylegs.models import Pictures, Users, AlbumJunction, Albums from onlylegs.models import Pictures, Users, AlbumJunction, Albums
from onlylegs.extensions import db from onlylegs.extensions import db
from onlylegs.utils import colour from onlylegs.utils import colour
@ -50,7 +50,29 @@ def groups():
return render_template("list.html", groups=groups) return render_template("list.html", groups=groups)
@blueprint.route("/<int:group_id>") @blueprint.route("/", methods=["POST"])
@login_required
def groups_post():
"""
Creates a group
"""
group_name = request.form.get("name", "").strip()
group_description = request.form.get("description", "").strip()
new_group = Albums(
name=group_name,
description=group_description,
author_id=current_user.id,
)
db.session.add(new_group)
db.session.commit()
flash(["Group created!", "1"])
return jsonify({"message": "Group created", "id": new_group.id})
@blueprint.route("/<int:group_id>", methods=["GET"])
def group(group_id): def group(group_id):
""" """
Group view, shows all images in a group Group view, shows all images in a group
@ -86,6 +108,54 @@ def group(group_id):
) )
@blueprint.route("/<int:group_id>", methods=["PUT"])
@login_required
def group_put(group_id):
"""
Changes the images in a group
"""
image_id = request.form.get("imageId", "").strip()
action = request.form.get("action", "").strip()
group_record = db.get_or_404(Albums, group_id)
db.get_or_404(Pictures, image_id) # Check if image exists
if group_record.author_id != current_user.id:
return jsonify({"message": "You are not the owner of this group"}), 403
junction_exist = AlbumJunction.query.filter_by(
album_id=group_id, picture_id=image_id
).first()
if action == "add" and not junction_exist:
db.session.add(AlbumJunction(album_id=group_id, picture_id=image_id))
elif request.form["action"] == "remove":
AlbumJunction.query.filter_by(album_id=group_id, picture_id=image_id).delete()
db.session.commit()
flash(["Group modified!", "1"])
return jsonify({"message": "Group modified"})
@blueprint.route("/<int:group_id>", methods=["DELETE"])
@login_required
def group_delete(group_id):
"""
Deletes a group
"""
group_record = db.get_or_404(Albums, group_id)
if group_record.author_id != current_user.id:
return jsonify({"message": "You are not the owner of this group"}), 403
AlbumJunction.query.filter_by(album_id=group_id).delete()
db.session.delete(group_record)
db.session.commit()
flash(["Group yeeted!", "1"])
return jsonify({"message": "Group deleted"})
@blueprint.route("/<int:group_id>/<int:image_id>") @blueprint.route("/<int:group_id>/<int:image_id>")
def group_post(group_id, image_id): def group_post(group_id, image_id):
""" """

View file

@ -1,8 +1,20 @@
""" """
Onlylegs - Image View Onlylegs - Image View
""" """
import os
import logging
import pathlib
from math import ceil from math import ceil
from flask import Blueprint, render_template, url_for, current_app, request from flask import (
Blueprint,
render_template,
url_for,
current_app,
request,
flash,
jsonify,
)
from flask_login import current_user
from onlylegs.models import Pictures, AlbumJunction, Albums from onlylegs.models import Pictures, AlbumJunction, Albums
from onlylegs.extensions import db from onlylegs.extensions import db
@ -10,7 +22,7 @@ from onlylegs.extensions import db
blueprint = Blueprint("image", __name__, url_prefix="/image") blueprint = Blueprint("image", __name__, url_prefix="/image")
@blueprint.route("/<int:image_id>") @blueprint.route("/<int:image_id>", methods=["GET"])
def image(image_id): def image(image_id):
""" """
Image view, shows the image and its metadata Image view, shows the image and its metadata
@ -50,10 +62,8 @@ def image(image_id):
) )
# If there is a next or previous image, get the url # If there is a next or previous image, get the url
if next_url: next_url = url_for("image.image", image_id=next_url[0]) if next_url else None
next_url = url_for("image.image", image_id=next_url[0]) prev_url = url_for("image.image", image_id=prev_url[0]) if prev_url else None
if prev_url:
prev_url = url_for("image.image", image_id=prev_url[0])
# Yoink all the images in the database # Yoink all the images in the database
total_images = ( total_images = (
@ -86,3 +96,60 @@ def image(image_id):
return_page=return_page, return_page=return_page,
close_tab=close_tab, close_tab=close_tab,
) )
@blueprint.route("/<int:image_id>", methods=["PUT"])
def image_put(image_id):
"""
Update the image metadata
"""
image_record = db.get_or_404(Pictures, image_id, description="Image not found :<")
image_record.description = request.form.get("description", image_record.description)
image_record.alt = request.form.get("alt", image_record.alt)
print(request.form.get("description"))
db.session.commit()
flash(["Image updated!", "1"])
return "OK", 200
@blueprint.route("/<int:image_id>", methods=["DELETE"])
def image_delete(image_id):
image_record = db.get_or_404(Pictures, image_id)
# Check if image exists and if user is allowed to delete it (author)
if image_record.author_id != current_user.id:
logging.info("User %s tried to delete image %s", current_user.id, image_id)
return (
jsonify({"message": "You are not allowed to delete this image, heck off"}),
403,
)
# Delete file
try:
os.remove(
os.path.join(current_app.config["UPLOAD_FOLDER"], image_record.filename)
)
except FileNotFoundError:
logging.warning(
"File not found: %s, already deleted or never existed",
image_record.filename,
)
# Delete cached files
cache_name = image_record.filename.rsplit(".")[0]
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(
cache_name + "*"
):
os.remove(cache_file)
AlbumJunction.query.filter_by(picture_id=image_id).delete()
db.session.delete(image_record)
db.session.commit()
logging.info("Removed image (%s) %s", image_id, image_record.filename)
flash(["Image was all in Le Head!", "1"])
return jsonify({"message": "Image deleted"}), 200