Add ALT text to images

Correct static methods
Tidy up code
This commit is contained in:
Michał 2023-03-11 22:14:03 +00:00
parent e192554a0b
commit 3ee287d6e3
23 changed files with 427 additions and 430 deletions

View file

@ -5,7 +5,7 @@
| |_| | | | | | |_| | |__| __/ (_| \__ \
\___/|_| |_|_|\__, |_____\___|\__, |___/
|___/ |___/
Created by Fluffy Bean - Version 23.03.10
Created by Fluffy Bean - Version 23.03.11
"""
# Import system modules
@ -46,7 +46,7 @@ def create_app(test_config=None):
"""
Create and configure the main app
"""
app = Flask(__name__,instance_path=os.path.join(USER_DIR, 'instance'))
app = Flask(__name__, instance_path=os.path.join(USER_DIR, 'instance'))
assets = Environment()
cache = Cache(config={'CACHE_TYPE': 'SimpleCache', 'CACHE_DEFAULT_TIMEOUT': 300})
compress = Compress()
@ -71,13 +71,11 @@ def create_app(test_config=None):
except OSError:
pass
# Load theme
theme_manager.CompileTheme('default', app.root_path)
# Bundle JS files
js = Bundle('js/*.js', output='gen/packed.js')
assets.register('js_all', js)
@app.errorhandler(403)
@app.errorhandler(404)
@ -89,7 +87,6 @@ def create_app(test_config=None):
msg = err.description
return render_template('error.html', error=error, msg=msg), err.code
# Load login, registration and logout manager
from . import auth
app.register_blueprint(auth.blueprint)
@ -110,10 +107,8 @@ def create_app(test_config=None):
# Load APIs
from . import api
app.register_blueprint(api.blueprint)
logging.info('Gallery started successfully!')
logging.info('Gallery started successfully!')
assets.init_app(app)
cache.init_app(app)

View file

@ -19,7 +19,7 @@ from sqlalchemy.orm import sessionmaker
from gallery.auth import login_required
from . import db # Import db to create a session
from . import db
from . import metadata as mt
@ -39,15 +39,15 @@ def uploads(file):
# Get args
width = request.args.get('w', default=0, type=int) # Width of image
height = request.args.get('h', default=0, type=int) # Height of image
filtered = request.args.get('f', default=False, type=bool) # Whether to apply filters
blur = request.args.get('b', default=False, type=bool) # Whether to force blur
filtered = request.args.get('f', default=False, type=bool) # Whether to apply filters
blur = request.args.get('b', default=False, type=bool) # Whether to force blur
# if no args are passed, return the raw file
if width == 0 and height == 0 and not filtered:
if not os.path.exists(os.path.join(current_app.config['UPLOAD_FOLDER'],
secure_filename(file))):
abort(404)
return send_from_directory(current_app.config['UPLOAD_FOLDER'], file ,as_attachment=True)
return send_from_directory(current_app.config['UPLOAD_FOLDER'], file, as_attachment=True)
# Of either width or height is 0, set it to the other value to keep aspect ratio
if width > 0 and height == 0:
@ -60,7 +60,7 @@ def uploads(file):
# Open image and set extension
try:
img = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'],file))
img = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], file))
except FileNotFoundError:
logging.error('File not found: %s, possibly broken upload', file)
abort(404)
@ -79,7 +79,7 @@ def uploads(file):
# If has NSFW tag, blur image, etc.
if filtered:
#img = img.filter(ImageFilter.GaussianBlur(20))
# img = img.filter(ImageFilter.GaussianBlur(20))
pass
# If forced to blur, blur image
@ -122,7 +122,7 @@ def upload():
img_name = "GWAGWA_"+str(uuid4())
img_path = os.path.join(current_app.config['UPLOAD_FOLDER'], img_name+'.'+img_ext)
if not img_ext in current_app.config['ALLOWED_EXTENSIONS'].keys():
if img_ext not in current_app.config['ALLOWED_EXTENSIONS'].keys():
logging.info('File extension not allowed: %s', img_ext)
abort(403)
@ -142,14 +142,14 @@ def upload():
# Save to database
try:
query = db.Posts(author_id = g.user.id,
created_at = dt.utcnow(),
file_name = img_name+'.'+img_ext,
file_type = img_ext,
image_exif = img_exif,
image_colours = img_colors,
post_description = form_description,
post_alt = form_alt)
query = db.Posts(author_id=g.user.id,
created_at=dt.utcnow(),
file_name=img_name+'.'+img_ext,
file_type=img_ext,
image_exif=img_exif,
image_colours=img_colors,
post_description=form_description,
post_alt=form_alt)
db_session.add(query)
db_session.commit()
@ -159,6 +159,7 @@ def upload():
return 'Gwa Gwa'
@blueprint.route('/delete/<int:image_id>', methods=['POST'])
@login_required
def delete_image(image_id):
@ -194,7 +195,7 @@ def delete_image(image_id):
abort(500)
logging.info('Removed image (%s) %s', image_id, img.file_name)
flash(['Image was all in Le Head!', 1])
flash(['Image was all in Le Head!', 0])
return 'Gwa Gwa'
@ -268,7 +269,7 @@ def logfile():
"""
Gets the log file and returns it as a JSON object
"""
filename = logging.getLoggerClass().root.handlers[0].baseFilename
filename = 'only.log'
log_dict = {}
i = 0

View file

@ -1,5 +1,5 @@
"""
OnlyLegs - Authentification
OnlyLegs - Authentication
User registration, login and logout and locking access to pages behind a login
"""
import re
@ -29,7 +29,7 @@ def login_required(view):
@functools.wraps(view)
def wrapped_view(**kwargs):
if g.user is None or session.get('uuid') is None:
logging.error('Authentification failed')
logging.error('Authentication failed')
session.clear()
return redirect(url_for('gallery.index'))
@ -75,7 +75,6 @@ def register():
email_regex = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
username_regex = re.compile(r'\b[A-Za-z0-9._%+-]+\b')
if not username or not username_regex.match(username):
error.append('Username is invalid!')
@ -95,7 +94,6 @@ def register():
if error:
return jsonify(error)
try:
register_user = db.Users(username=username,
email=email,
@ -124,7 +122,6 @@ def login():
user = db_session.query(db.Users).filter_by(username=username).first()
error = []
if user is None:
logging.error('User %s does not exist. Login attempt from %s',
username, request.remote_addr)
@ -137,18 +134,17 @@ def login():
if error:
abort(403)
try:
session.clear()
session['user_id'] = user.id
session['uuid'] = str(uuid.uuid4())
session_query = db.Sessions(user_id=user.id,
session_uuid=session.get('uuid'),
ip_address=request.remote_addr,
user_agent=request.user_agent.string,
active=True,
created_at=dt.utcnow())
session_uuid=session.get('uuid'),
ip_address=request.remote_addr,
user_agent=request.user_agent.string,
active=True,
created_at=dt.utcnow())
db_session.add(session_query)
db_session.commit()

View file

@ -1,6 +1,5 @@
"""
OnlyLegs - Database
Database models and functions for SQLAlchemy
OnlyLegs - Database models and functions for SQLAlchemy
"""
import os
import platformdirs
@ -11,12 +10,12 @@ from sqlalchemy.orm import declarative_base, relationship, backref, mapped_colum
path_to_db = os.path.join(platformdirs.user_config_dir('onlylegs'), 'gallery.sqlite')
engine = create_engine(f'sqlite:///{path_to_db}', echo=False)
# engine = create_engine(f'postgresql://username:password@host:port/database_name', echo=False)
# engine = create_engine(f'mysql://username:password@host:port/database_name', echo=False)
# engine = create_engine('postgresql://username:password@host:port/database_name', echo=False)
# engine = create_engine('mysql://username:password@host:port/database_name', echo=False)
base = declarative_base()
class Users (base): # pylint: disable=too-few-public-methods, C0103
class Users (base): # pylint: disable=too-few-public-methods, C0103
"""
User table
Joins with post, groups, session and log
@ -35,7 +34,7 @@ class Users (base): # pylint: disable=too-few-public-methods, C0103
log = relationship('Logs', backref='users')
class Posts (base): # pylint: disable=too-few-public-methods, C0103
class Posts (base): # pylint: disable=too-few-public-methods, C0103
"""
Post table
Joins with group_junction
@ -58,7 +57,7 @@ class Posts (base): # pylint: disable=too-few-public-methods, C0103
junction = relationship('GroupJunction', backref='posts')
class Groups (base): # pylint: disable=too-few-public-methods, C0103
class Groups (base): # pylint: disable=too-few-public-methods, C0103
"""
Group table
Joins with group_junction
@ -74,7 +73,7 @@ class Groups (base): # pylint: disable=too-few-public-methods, C0103
junction = relationship('GroupJunction', backref='groups')
class GroupJunction (base): # pylint: disable=too-few-public-methods, C0103
class GroupJunction (base): # pylint: disable=too-few-public-methods, C0103
"""
Junction table for posts and groups
Joins with posts and groups
@ -87,7 +86,7 @@ class GroupJunction (base): # pylint: disable=too-few-public-methods, C0103
post_id = Column(Integer, ForeignKey('posts.id'))
class Sessions (base): # pylint: disable=too-few-public-methods, C0103
class Sessions (base): # pylint: disable=too-few-public-methods, C0103
"""
Session table
Joins with user
@ -103,7 +102,7 @@ class Sessions (base): # pylint: disable=too-few-public-methods, C0103
created_at = Column(DateTime, nullable=False)
class Logs (base): # pylint: disable=too-few-public-methods, C0103
class Logs (base): # pylint: disable=too-few-public-methods, C0103
"""
Log table
Joins with user
@ -118,7 +117,7 @@ class Logs (base): # pylint: disable=too-few-public-methods, C0103
created_at = Column(DateTime, nullable=False)
class Bans (base): # pylint: disable=too-few-public-methods, C0103
class Bans (base): # pylint: disable=too-few-public-methods, C0103
"""
Bans table
"""

View file

@ -1,6 +1,6 @@
"""
Onlylegs - API endpoints
Used intermally by the frontend and possibly by other applications
Onlylegs - Image Groups
Why groups? Because I don't like calling these albums, sounds more limiting that it actually is
"""
import logging
import json
@ -31,8 +31,10 @@ def groups():
thumbnail = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group.id).order_by(db.GroupJunction.date_added.desc()).first()
if thumbnail is not None:
thumbnail_filename = db_session.query(db.Posts.file_name).filter(db.Posts.id == thumbnail[0]).first()
group.thumbnail = thumbnail_filename[0]
group.thumbnail = db_session.query(db.Posts.file_name,
db.Posts.post_alt,
db.Posts.image_colours,
db.Posts.id).filter(db.Posts.id == thumbnail[0]).first()
else:
group.thumbnail = None
@ -47,6 +49,8 @@ def group(group_id):
if group is None:
abort(404, 'Group not found')
group.author_username = db_session.query(db.Users.username).filter(db.Users.id == group.author_id).first()[0]
group_images = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group_id).order_by(db.GroupJunction.date_added.desc()).all()
@ -75,15 +79,12 @@ def group_post(group_id, image_id):
group = db_session.query(db.Groups).filter(db.Groups.id == group[0]).first()
img.groups.append(group)
next = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group_id).filter(db.GroupJunction.post_id > image_id).order_by(db.GroupJunction.date_added.asc()).first()
prev = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group_id).filter(db.GroupJunction.post_id < image_id).order_by(db.GroupJunction.date_added.desc()).first()
next_url = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group_id).filter(db.GroupJunction.post_id > image_id).order_by(db.GroupJunction.date_added.asc()).first()
prev_url = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group_id).filter(db.GroupJunction.post_id < image_id).order_by(db.GroupJunction.date_added.desc()).first()
if next is not None:
next = url_for('group.group_post', group_id=group_id, image_id=next[0])
if prev is not None:
prev = url_for('group.group_post', group_id=group_id, image_id=prev[0])
if next_url is not None:
next_url = url_for('group.group_post', group_id=group_id, image_id=next_url[0])
if prev_url is not None:
prev_url = url_for('group.group_post', group_id=group_id, image_id=prev_url[0])
return render_template('image.html',
image=img,
next_url=next,
prev_url=prev)
return render_template('image.html', image=img, next_url=next_url, prev_url=prev_url)

View file

@ -1,5 +1,5 @@
"""
OnlyLegs - Metatada Parser
OnlyLegs - Metadata Parser
Parse metadata from images if available
otherwise get some basic information from the file
"""
@ -11,6 +11,7 @@ from PIL.ExifTags import TAGS
from .helpers import *
from .mapping import *
class Metadata:
"""
Metadata parser
@ -53,7 +54,8 @@ class Metadata:
return None
return self.format_data(self.encoded)
def format_data(self, encoded_exif): # pylint: disable=R0912 # For now, this is fine
@staticmethod
def format_data(encoded_exif):
"""
Formats the data into a dictionary
"""
@ -66,15 +68,15 @@ class Metadata:
# Thanks chatGPT xP
for key, value in encoded_exif.items():
for mapping_name, mapping in EXIF_MAPPING:
if key in mapping:
if len(mapping[key]) == 2:
exif[mapping_name][mapping[key][0]] = {
for mapping_name, mapping_val in EXIF_MAPPING:
if key in mapping_val:
if len(mapping_val[key]) == 2:
exif[mapping_name][mapping_val[key][0]] = {
'raw': value,
'formatted': getattr(helpers, mapping[key][1])(value),
'formatted': getattr(helpers, mapping_val[key][1])(value),
}
else:
exif[mapping_name][mapping[key][0]] = {
exif[mapping_name][mapping_val[key][0]] = {
'raw': value,
}

View file

@ -4,6 +4,7 @@ Metadata formatting helpers
"""
from datetime import datetime
def human_size(value):
"""
Formats the size of a file in a human readable format
@ -276,7 +277,10 @@ def lens_specification(value):
"""
Maps the value of the lens specification to a human readable format
"""
return str(value[0] / value[1]) + 'mm - ' + str(value[2] / value[3]) + 'mm'
try:
return str(value[0] / value[1]) + 'mm - ' + str(value[2] / value[3]) + 'mm'
except Exception as err:
return None
def compression_type(value):

View file

@ -61,4 +61,9 @@ FILE_MAPPING = {
'RatingPercent': ['Rating Percent', 'rating_percent'],
}
EXIF_MAPPING = [('Photographer', PHOTOGRAHER_MAPPING),('Camera', CAMERA_MAPPING),('Software', SOFTWARE_MAPPING),('File', FILE_MAPPING)]
EXIF_MAPPING = [
('Photographer', PHOTOGRAHER_MAPPING),
('Camera', CAMERA_MAPPING),
('Software', SOFTWARE_MAPPING),
('File', FILE_MAPPING)
]

View file

@ -20,15 +20,17 @@ db_session = db_session()
@blueprint.route('/')
def index():
"""
Home page of the website, shows the feed of latest images
Home page of the website, shows the feed of the latest images
"""
images = db_session.query(db.Posts.file_name,
db.Posts.post_alt,
db.Posts.image_colours,
db.Posts.created_at,
db.Posts.id).order_by(db.Posts.id.desc()).all()
return render_template('index.html', images=images)
@blueprint.route('/image/<int:image_id>')
def image(image_id):
"""
@ -47,15 +49,16 @@ def image(image_id):
group = db_session.query(db.Groups).filter(db.Groups.id == group[0]).first()
img.groups.append(group)
next = db_session.query(db.Posts.id).filter(db.Posts.id > image_id).order_by(db.Posts.id.asc()).first()
prev = db_session.query(db.Posts.id).filter(db.Posts.id < image_id).order_by(db.Posts.id.desc()).first()
next_url = db_session.query(db.Posts.id).filter(db.Posts.id > image_id).order_by(db.Posts.id.asc()).first()
prev_url = db_session.query(db.Posts.id).filter(db.Posts.id < image_id).order_by(db.Posts.id.desc()).first()
if next is not None:
next = url_for('gallery.image', image_id=next[0])
if prev is not None:
prev = url_for('gallery.image', image_id=prev[0])
if next_url is not None:
next_url = url_for('gallery.image', image_id=next_url[0])
if prev_url is not None:
prev_url = url_for('gallery.image', image_id=prev_url[0])
return render_template('image.html', image=img, next_url=next_url, prev_url=prev_url)
return render_template('image.html', image=img, next_url=next, prev_url=prev)
@blueprint.route('/profile')
def profile():
@ -64,9 +67,10 @@ def profile():
"""
return render_template('profile.html', user_id='gwa gwa')
@blueprint.route('/profile/<int:user_id>')
def profile_id(user_id):
"""
Shows user ofa given id, displays their uploads and other info
"""
return render_template('profile.html', user_id=user_id)
return render_template('profile.html', user_id=user_id)

View file

@ -17,6 +17,7 @@ def general():
"""
return render_template('settings/general.html')
@blueprint.route('/server')
@login_required
def server():
@ -25,6 +26,7 @@ def server():
"""
return render_template('settings/server.html')
@blueprint.route('/account')
@login_required
def account():
@ -33,6 +35,7 @@ def account():
"""
return render_template('settings/account.html')
@blueprint.route('/logs')
@login_required
def logs():

View file

@ -8,8 +8,10 @@ import platformdirs
import logging
import yaml
USER_DIR = platformdirs.user_config_dir('onlylegs')
class SetupApp:
"""
Setup the application on first run
@ -36,7 +38,8 @@ class SetupApp:
print("You can find the config files at:", USER_DIR)
sys.exit()
def make_dir(self):
@staticmethod
def make_dir():
"""
Create the user directory
"""
@ -49,7 +52,8 @@ class SetupApp:
print("Error creating user directory:", err)
sys.exit(1)
def make_env(self):
@staticmethod
def make_env():
"""
Create the .env file with default values
"""
@ -67,7 +71,8 @@ class SetupApp:
print("Generated default .env file, please edit!")
def make_yaml(self):
@staticmethod
def make_yaml():
"""
Create the YAML config file with default values
"""
@ -107,17 +112,18 @@ class SetupApp:
print("Generated default YAML config, please edit!")
def logging_config(self):
LOGS_PATH = os.path.join(platformdirs.user_config_dir('onlylegs'), 'logs')
@staticmethod
def logging_config():
logs_path = os.path.join(platformdirs.user_config_dir('onlylegs'), 'logs')
if not os.path.isdir(LOGS_PATH):
os.mkdir(LOGS_PATH)
print("Created logs directory at:", LOGS_PATH)
if not os.path.isdir(logs_path):
os.mkdir(logs_path)
print("Created logs directory at:", logs_path)
logging.getLogger('werkzeug').disabled = True
logging.basicConfig(
filename=os.path.join(LOGS_PATH, 'only.log'),
filename=os.path.join(logs_path, 'only.log'),
level=logging.INFO,
datefmt='%Y-%m-%d %H:%M:%S',
format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s',
encoding='utf-8')
encoding='utf-8')

121
gallery/static/js/login.js Normal file
View file

@ -0,0 +1,121 @@
// Function to show login
function showLogin() {
popUpShow(
'idk what to put here, just login please',
'Need an account? <span class="pop-up__link" onclick="showRegister()">Register!</span>',
'<button class="pop-up__btn pop-up__btn-primary-fill" form="loginForm" type="submit">Login</button>',
'<form id="loginForm" onsubmit="return login(event)">\
<input class="pop-up__input" type="text" placeholder="Namey" id="username"/>\
<input class="pop-up__input" type="password" placeholder="Passywassy" id="password"/>\
</form>'
);
};
// Function to login
function login(event) {
// AJAX takes control of subby form :3
event.preventDefault();
let formUsername = document.querySelector("#username").value;
let formPassword = document.querySelector("#password").value;
if (formUsername === "" || formPassword === "") {
addNotification("Please fill in all fields!!!!", 3);
return;
}
// Make form
var formData = new FormData();
formData.append("username", formUsername);
formData.append("password", formPassword);
$.ajax({
url: '/auth/login',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
location.reload();
},
error: function (response) {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here... Wrong information', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
}
}
});
}
// Function to show register
function showRegister() {
popUpShow(
'Who are you?',
'Already have an account? <span class="pop-up__link" onclick="showLogin()">Login!</span>',
'<button class="pop-up__btn pop-up__btn-primary-fill" form="registerForm" type="submit">Register</button>',
'<form id="registerForm" onsubmit="return register(event)">\
<input class="pop-up__input" type="text" placeholder="Namey" id="username"/>\
<input class="pop-up__input" type="text" placeholder="E mail!" id="email"/>\
<input class="pop-up__input" type="password" placeholder="Passywassy" id="password"/>\
<input class="pop-up__input" type="password" placeholder="Passywassy again!" id="password-repeat"/>\
</form>'
);
};
// Function to register
function register(obj) {
// AJAX takes control of subby form
event.preventDefault();
let formUsername = document.querySelector("#username").value;
let formEmail = document.querySelector("#email").value;
let formPassword = document.querySelector("#password").value;
let formPasswordRepeat = document.querySelector("#password-repeat").value;
if (formUsername === "" || formEmail === "" || formPassword === "" || formPasswordRepeat === "") {
addNotification("Please fill in all fields!!!!", 3);
return;
}
// Make form
var formData = new FormData();
formData.append("username", formUsername);
formData.append("email", formEmail);
formData.append("password", formPassword);
formData.append("password-repeat", formPasswordRepeat);
$.ajax({
url: '/auth/register',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
if (response === "gwa gwa") {
addNotification('Registered successfully! Now please login to continue', 1);
showLogin();
} else {
for (var i = 0; i < response.length; i++) {
addNotification(response[i], 2);
}
}
},
error: function (response) {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here...', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
}
}
});
}

View file

@ -40,252 +40,6 @@ function loadOnView() {
}
}
}
// Function to upload images
function uploadFile() {
// AJAX takes control of subby form
event.preventDefault();
const jobList = document.querySelector(".upload-jobs");
// Check for empty upload
if ($("#file").val() === "") {
addNotification("Please select a file to upload", 2);
} else {
// Make form
let formData = new FormData();
formData.append("file", $("#file").prop("files")[0]);
formData.append("alt", $("#alt").val());
formData.append("description", $("#description").val());
formData.append("tags", $("#tags").val());
formData.append("submit", $("#submit").val());
// Upload the information
$.ajax({
url: '/api/upload',
type: 'post',
data: formData,
contentType: false,
processData: false,
beforeSend: function () {
jobContainer = document.createElement("div");
jobContainer.classList.add("job");
jobStatus = document.createElement("span");
jobStatus.classList.add("job__status");
jobStatus.innerHTML = "Uploading...";
jobProgress = document.createElement("span");
jobProgress.classList.add("progress");
jobImg = document.createElement("img");
jobImg.src = URL.createObjectURL($("#file").prop("files")[0]);
jobImgFilter = document.createElement("span");
jobImgFilter.classList.add("img-filter");
jobContainer.appendChild(jobStatus);
jobContainer.appendChild(jobProgress);
jobContainer.appendChild(jobImg);
jobContainer.appendChild(jobImgFilter);
jobList.appendChild(jobContainer);
},
success: function (response) {
jobContainer.classList.add("success");
jobStatus.innerHTML = "Uploaded!";
if (!document.querySelector(".upload-panel").classList.contains("open")) {
addNotification("Image uploaded successfully", 1);
}
},
error: function (response) {
jobContainer.classList.add("critical");
switch (response.status) {
case 500:
jobStatus.innerHTML = "Server exploded, F's in chat";
break;
case 400:
case 404:
jobStatus.innerHTML = "Error uploading. Blame yourself";
break;
case 403:
jobStatus.innerHTML = "None but devils play past here...";
break;
case 413:
jobStatus.innerHTML = "File too large!!!!!!";
break;
default:
jobStatus.innerHTML = "Error uploading file, blame someone";
break;
}
if (!document.querySelector(".upload-panel").classList.contains("open")) {
addNotification("Error uploading file", 2);
}
},
});
// Empty values
$("#file").val("");
$("#alt").val("");
$("#description").val("");
$("#tags").val("");
}
};
// open upload tab
function openUploadTab() {
// Stop scrolling
document.querySelector("html").style.overflow = "hidden";
document.querySelector(".content").tabIndex = "-1";
// Open upload tab
const uploadTab = document.querySelector(".upload-panel");
uploadTab.style.display = "block";
setTimeout(function () {
uploadTab.classList.add("open");
}, 10);
}
// close upload tab
function closeUploadTab() {
// un-Stop scrolling
document.querySelector("html").style.overflow = "auto";
document.querySelector(".content").tabIndex = "";
// Close upload tab
const uploadTab = document.querySelector(".upload-panel");
uploadTab.classList.remove("open");
setTimeout(function () {
uploadTab.style.display = "none";
}, 250);
}
// toggle upload tab
function toggleUploadTab() {
if (document.querySelector(".upload-panel").classList.contains("open")) {
closeUploadTab();
} else {
openUploadTab();
}
}
// Function to show login
function showLogin() {
popUpShow(
'idk what to put here, just login please',
'Need an account? <span class="pop-up__link" onclick="showRegister()">Register!</span>',
'<button class="pop-up__btn pop-up__btn-primary-fill" form="loginForm" type="submit">Login</button>',
'<form id="loginForm" onsubmit="return login(event)">\
<input class="pop-up__input" type="text" placeholder="Namey" id="username"/>\
<input class="pop-up__input" type="password" placeholder="Passywassy" id="password"/>\
</form>'
);
};
// Function to login
function login(event) {
// AJAX takes control of subby form :3
event.preventDefault();
let formUsername = document.querySelector("#username").value;
let formPassword = document.querySelector("#password").value;
if (formUsername === "" || formPassword === "") {
addNotification("Please fill in all fields!!!!", 3);
return;
}
// Make form
var formData = new FormData();
formData.append("username", formUsername);
formData.append("password", formPassword);
$.ajax({
url: '/auth/login',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
location.reload();
},
error: function (response) {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here... Wrong information', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
}
}
});
}
// Function to show register
function showRegister() {
popUpShow(
'Who are you?',
'Already have an account? <span class="pop-up__link" onclick="showLogin()">Login!</span>',
'<button class="pop-up__btn pop-up__btn-primary-fill" form="registerForm" type="submit">Register</button>',
'<form id="registerForm" onsubmit="return register(event)">\
<input class="pop-up__input" type="text" placeholder="Namey" id="username"/>\
<input class="pop-up__input" type="text" placeholder="E mail!" id="email"/>\
<input class="pop-up__input" type="password" placeholder="Passywassy" id="password"/>\
<input class="pop-up__input" type="password" placeholder="Passywassy again!" id="password-repeat"/>\
</form>'
);
};
// Function to register
function register(obj) {
// AJAX takes control of subby form
event.preventDefault();
let formUsername = document.querySelector("#username").value;
let formEmail = document.querySelector("#email").value;
let formPassword = document.querySelector("#password").value;
let formPasswordRepeat = document.querySelector("#password-repeat").value;
if (formUsername === "" || formEmail === "" || formPassword === "" || formPasswordRepeat === "") {
addNotification("Please fill in all fields!!!!", 3);
return;
}
// Make form
var formData = new FormData();
formData.append("username", formUsername);
formData.append("email", formEmail);
formData.append("password", formPassword);
formData.append("password-repeat", formPasswordRepeat);
$.ajax({
url: '/auth/register',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
if (response === "gwa gwa") {
addNotification('Registered successfully! Now please login to continue', 1);
showLogin();
} else {
for (var i = 0; i < response.length; i++) {
addNotification(response[i], 2);
}
}
},
error: function (response) {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here...', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
}
}
});
}
window.onload = function () {
loadOnView();

View file

@ -0,0 +1,128 @@
// Function to upload images
function uploadFile() {
// AJAX takes control of subby form
event.preventDefault();
const jobList = document.querySelector(".upload-jobs");
// Check for empty upload
if ($("#file").val() === "") {
addNotification("Please select a file to upload", 2);
} else {
// Make form
let formData = new FormData();
formData.append("file", $("#file").prop("files")[0]);
formData.append("alt", $("#alt").val());
formData.append("description", $("#description").val());
formData.append("tags", $("#tags").val());
formData.append("submit", $("#submit").val());
// Upload the information
$.ajax({
url: '/api/upload',
type: 'post',
data: formData,
contentType: false,
processData: false,
beforeSend: function () {
jobContainer = document.createElement("div");
jobContainer.classList.add("job");
jobStatus = document.createElement("span");
jobStatus.classList.add("job__status");
jobStatus.innerHTML = "Uploading...";
jobProgress = document.createElement("span");
jobProgress.classList.add("progress");
jobImg = document.createElement("img");
jobImg.src = URL.createObjectURL($("#file").prop("files")[0]);
jobImgFilter = document.createElement("span");
jobImgFilter.classList.add("img-filter");
jobContainer.appendChild(jobStatus);
jobContainer.appendChild(jobProgress);
jobContainer.appendChild(jobImg);
jobContainer.appendChild(jobImgFilter);
jobList.appendChild(jobContainer);
},
success: function (response) {
jobContainer.classList.add("success");
jobStatus.innerHTML = "Uploaded!";
if (!document.querySelector(".upload-panel").classList.contains("open")) {
addNotification("Image uploaded successfully", 1);
}
},
error: function (response) {
jobContainer.classList.add("critical");
switch (response.status) {
case 500:
jobStatus.innerHTML = "Server exploded, F's in chat";
break;
case 400:
case 404:
jobStatus.innerHTML = "Error uploading. Blame yourself";
break;
case 403:
jobStatus.innerHTML = "None but devils play past here...";
break;
case 413:
jobStatus.innerHTML = "File too large!!!!!!";
break;
default:
jobStatus.innerHTML = "Error uploading file, blame someone";
break;
}
if (!document.querySelector(".upload-panel").classList.contains("open")) {
addNotification("Error uploading file", 2);
}
},
});
// Empty values
$("#file").val("");
$("#alt").val("");
$("#description").val("");
$("#tags").val("");
}
};
// open upload tab
function openUploadTab() {
// Stop scrolling
document.querySelector("html").style.overflow = "hidden";
document.querySelector(".content").tabIndex = "-1";
// Open upload tab
const uploadTab = document.querySelector(".upload-panel");
uploadTab.style.display = "block";
setTimeout(function () {
uploadTab.classList.add("open");
}, 10);
}
// close upload tab
function closeUploadTab() {
// un-Stop scrolling
document.querySelector("html").style.overflow = "auto";
document.querySelector(".content").tabIndex = "";
// Close upload tab
const uploadTab = document.querySelector(".upload-panel");
uploadTab.classList.remove("open");
setTimeout(function () {
uploadTab.style.display = "none";
}, 250);
}
// toggle upload tab
function toggleUploadTab() {
if (document.querySelector(".upload-panel").classList.contains("open")) {
closeUploadTab();
} else {
openUploadTab();
}
}

View file

@ -17,7 +17,7 @@
<div class="banner-content">
<p><span id="contrast-check" data-color="rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }})">{{ group.description }}</span></p>
<h1><span id="contrast-check" data-color="rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }})">{{ group.name }}</span></h1>
<p><span id="contrast-check" data-color="rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }})">{{ images|length }} Images</span></p>
<p><span id="contrast-check" data-color="rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }})">By {{ group.author_username }} - {{ images|length }} Images</span></p>
</div>
{% else %}
<img src="{{ url_for('static', filename='images/bg.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
@ -25,7 +25,7 @@
<div class="banner-content">
<p>{{ group.description }}</p>
<h1>{{ group.name }}</h1>
<p>{{ images|length }} Images</p>
<p>By {{ group.author_username }} - {{ images|length }} Images</p>
</div>
{% endif %}
</div>
@ -41,11 +41,11 @@
<div class="gallery-grid">
{% for image in images %}
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('group.group_post', group_id=group.id, image_id=image.id) }}" style="background-color: rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }})">
<span class="image-filter" style="background: linear-gradient(to top, rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }}), transparent)">
<p></p>
<h2><span id="contrast-check" data-color="rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }})"><span class="time">{{ image.created_at }}</span></span></h2>
</span>
<img data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
<div class="image-filter">
<p class="image-subtitle"></p>
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
</div>
<img alt="{{ image.post_alt }}" data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
</a>
{% endfor %}
</div>

View file

@ -23,17 +23,23 @@
{% if groups %}
<div class="gallery-grid">
{% for group in groups %}
<a id="group-{{ group.id }}" class="gallery-item" href="{{ url_for('group.group', group_id=group.id) }}">
<span class="image-filter">
<p></p>
<h2>{{ group.name }}</h2>
</span>
{% if group.thumbnail %}
<img data-src="{{ group.thumbnail }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
{% else %}
{% if group.thumbnail %}
<a id="group-{{ group.id }}" class="gallery-item" href="{{ url_for('group.group', group_id=group.id) }}" style="background-color: rgb({{ group.thumbnail.image_colours.0.0 }}, {{ group.thumbnail.image_colours.0.1 }}, {{ group.thumbnail.image_colours.0.2 }})">
<div class="image-filter">
<p class="image-subtitle"></p>
<p class="image-title">{{ group.name }}</p>
</div>
<img data-src="{{ group.thumbnail.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
</a>
{% else %}
<a id="group-{{ group.id }}" class="gallery-item" href="{{ url_for('group.group', group_id=group.id) }}">
<div class="image-filter">
<p class="image-subtitle"></p>
<p class="image-title">{{ group.name }}</p>
</div>
<img src="{{ url_for('static', filename='images/error.png') }}" onload="imgFade(this)" style="opacity:0;"/>
{% endif %}
</a>
</a>
{% endif %}
{% endfor %}
</div>
{% else %}

View file

@ -4,18 +4,19 @@
{% block content %}
<div class="background">
<img src="/api/uploads/{{ image.file_name }}?w=1000&h=1000" onload="imgFade(this)" style="opacity:0;"/>
<img src="/api/uploads/{{ image.file_name }}?w=1920&h=1080" alt="{{ image.post_alt }}" onload="imgFade(this)" style="opacity:0;"/>
<span style="background-image: linear-gradient(to top, rgba({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }}, 1), transparent);"></span>
</div>
<div class="image-fullscreen">
<img src="" onload="imgFade(this);" style="opacity:0;" />
<img src="" alt="{{ image.post_alt }}" onload="imgFade(this);" style="opacity:0;" />
</div>
<div class="image-grid">
<div class="image-container" id="image-container">
<img
src="/api/uploads/{{ image.file_name }}?w=1000&h=1000"
src="/api/uploads/{{ image.file_name }}?w=1920&h=1080"
alt="{{ image.post_alt }}"
onload="imgFade(this)" style="opacity:0;"
onerror="this.src='/static/images/error.png'"
{% if "File" in image.image_exif %}
@ -119,6 +120,11 @@
</svg>
</div>
<div class="info-table">
<div class="img-colours">
{% for col in image.image_colours %}
<span style="background-color: rgb({{col.0}}, {{col.1}}, {{col.2}})"></span>
{% endfor %}
</div>
<table>
<tr>
<td>Image ID</td>
@ -133,11 +139,6 @@
<td><span class="time">{{ image.created_at }}</span></td>
</tr>
</table>
<div class="img-colours">
{% for col in image.image_colours %}
<span style="background-color: rgb({{col.0}}, {{col.1}}, {{col.2}})"></span>
{% endfor %}
</div>
<div class="img-groups">
{% for group in image.groups %}
<a href="/group/{{ group.id }}" class="tag-icon">
@ -251,7 +252,7 @@
'DESTRUCTION!!!!!!',
'This will delete the image and all of its data!!! This action is irreversible!!!!! Are you sure you want to do this?????',
'<button class="pop-up__btn pop-up__btn-critical-fill" onclick="deleteImage()">Dewww eeeet!</button>',
'<img src="/api/uploads/{{ image.file_name }}?w=1000&h=1000" />'
'<img src="/api/uploads/{{ image.file_name }}?w=1920&h=1080" />'
);
});
function deleteImage() {

View file

@ -18,11 +18,11 @@
<div class="gallery-grid">
{% for image in images %}
<a id="image-{{ image.id }}" class="gallery-item" href="/image/{{ image.id }}" style="background-color: rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }})">
<span class="image-filter" style="background: linear-gradient(to top, rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }}), transparent)">
<p></p>
<h2><span id="contrast-check" data-color="rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }})"><span class="time">{{ image.created_at }}</span></span></h2>
</span>
<img data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
<div class="image-filter">
<p class="image-subtitle"></p>
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
</div>
<img alt="{{ image.post_alt }}" data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
</a>
{% endfor %}
</div>

View file

@ -8,7 +8,7 @@ from datetime import datetime
import sass
class CompileTheme():
class CompileTheme:
"""
Compiles the theme into the static folder
"""
@ -33,7 +33,8 @@ class CompileTheme():
now = datetime.now()
print(f"{now.hour}:{now.minute}:{now.second} - Done!\n")
def load_sass(self, source_path, css_dest):
@staticmethod
def load_sass(source_path, css_dest):
"""
Compile the sass (or scss) file into css and save it to the static folder
"""
@ -54,7 +55,8 @@ class CompileTheme():
print("Compiled successfully!")
def load_fonts(self, source_path, font_dest):
@staticmethod
def load_fonts(source_path, font_dest):
"""
Copy the fonts folder to the static folder
"""

View file

@ -44,35 +44,33 @@
transform: scale(1.05) // scale up
transition: all 0.3s cubic-bezier(.79, .14, .15, .86)
> h2
margin: 0
padding: 0
.image-title
margin: 0
font-size: 1rem
font-weight: 600
font-size: 1rem
font-weight: 600
color: $primary
color: $primary
text-overflow: ellipsis
overflow: hidden
text-overflow: ellipsis
overflow: hidden
opacity: 0 // hide
transition: all 0.2s ease-in-out
opacity: 0 // hide
transition: all 0.2s ease-in-out
> p
margin: 0
padding: 0
.image-subtitle
margin: 0
font-size: 0.8rem
font-weight: 500
font-size: 0.8rem
font-weight: 500
color: $white
color: $white
text-overflow: ellipsis
overflow: hidden
text-overflow: ellipsis
overflow: hidden
opacity: 0 // hide
transition: all 0.2s ease-in-out
opacity: 0 // hide
transition: all 0.2s ease-in-out
img
width: 100%
@ -95,13 +93,14 @@
padding-bottom: 100%
&:hover
span
.image-filter
bottom: 0
opacity: 1
transform: scale(1)
> h2, > p
opacity: 1
.image-title,
.image-subtitle
opacity: 1
img
transform: scale(1)

View file

@ -173,14 +173,14 @@
margin: 0
padding: 0
width: 100%
height: 2rem
width: 1.5rem
height: 1.5rem
display: flex
justify-content: center
align-items: center
border-radius: $rad-inner
border-radius: 50%
// border: 1px solid $white
.img-groups

View file

@ -48,7 +48,7 @@ html, body
color: $black
.big-text
height: 60vh
height: 20rem
display: flex
flex-direction: column
@ -102,38 +102,8 @@ html, body
color: $white
footer
margin: 0
padding: 0.25rem 1rem
width: 100%
display: flex
justify-content: center
align-items: center
gap: 1rem
background-color: $white
p
margin: 0
padding: 0
font-size: 0.75rem
font-weight: 400
text-align: center
color: $black
a
margin: 0
padding: 0
font-size: 0.75rem
font-weight: 400
text-align: center
color: $primary
#contrast-check
transition: color 0.15s ease-in-out
@media (max-width: $breakpoint)

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "onlylegs"
version = "23.03.10"
version = "23.03.11"
description = "Gallery built for fast and simple image management"
authors = ["Fluffy-Bean <michal-gdula@protonmail.com>"]
license = "MIT"