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 # Import system modules
@ -46,7 +46,7 @@ def create_app(test_config=None):
""" """
Create and configure the main app 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() assets = Environment()
cache = Cache(config={'CACHE_TYPE': 'SimpleCache', 'CACHE_DEFAULT_TIMEOUT': 300}) cache = Cache(config={'CACHE_TYPE': 'SimpleCache', 'CACHE_DEFAULT_TIMEOUT': 300})
compress = Compress() compress = Compress()
@ -71,13 +71,11 @@ def create_app(test_config=None):
except OSError: except OSError:
pass pass
# Load theme # Load theme
theme_manager.CompileTheme('default', app.root_path) theme_manager.CompileTheme('default', app.root_path)
# Bundle JS files # Bundle JS files
js = Bundle('js/*.js', output='gen/packed.js') js = Bundle('js/*.js', output='gen/packed.js')
assets.register('js_all', js) assets.register('js_all', js)
@app.errorhandler(403) @app.errorhandler(403)
@app.errorhandler(404) @app.errorhandler(404)
@ -89,7 +87,6 @@ def create_app(test_config=None):
msg = err.description msg = err.description
return render_template('error.html', error=error, msg=msg), err.code return render_template('error.html', error=error, msg=msg), err.code
# Load login, registration and logout manager # Load login, registration and logout manager
from . import auth from . import auth
app.register_blueprint(auth.blueprint) app.register_blueprint(auth.blueprint)
@ -110,10 +107,8 @@ def create_app(test_config=None):
# Load APIs # Load APIs
from . import api from . import api
app.register_blueprint(api.blueprint) app.register_blueprint(api.blueprint)
logging.info('Gallery started successfully!')
logging.info('Gallery started successfully!')
assets.init_app(app) assets.init_app(app)
cache.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 gallery.auth import login_required
from . import db # Import db to create a session from . import db
from . import metadata as mt from . import metadata as mt
@ -39,15 +39,15 @@ def uploads(file):
# Get args # Get args
width = request.args.get('w', default=0, type=int) # Width of image width = request.args.get('w', default=0, type=int) # Width of image
height = request.args.get('h', default=0, type=int) # Height 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 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 blur = request.args.get('b', default=False, type=bool) # Whether to force blur
# if no args are passed, return the raw file # if no args are passed, return the raw file
if width == 0 and height == 0 and not filtered: if width == 0 and height == 0 and not filtered:
if not os.path.exists(os.path.join(current_app.config['UPLOAD_FOLDER'], if not os.path.exists(os.path.join(current_app.config['UPLOAD_FOLDER'],
secure_filename(file))): secure_filename(file))):
abort(404) 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 # Of either width or height is 0, set it to the other value to keep aspect ratio
if width > 0 and height == 0: if width > 0 and height == 0:
@ -60,7 +60,7 @@ def uploads(file):
# Open image and set extension # Open image and set extension
try: 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: except FileNotFoundError:
logging.error('File not found: %s, possibly broken upload', file) logging.error('File not found: %s, possibly broken upload', file)
abort(404) abort(404)
@ -79,7 +79,7 @@ def uploads(file):
# If has NSFW tag, blur image, etc. # If has NSFW tag, blur image, etc.
if filtered: if filtered:
#img = img.filter(ImageFilter.GaussianBlur(20)) # img = img.filter(ImageFilter.GaussianBlur(20))
pass pass
# If forced to blur, blur image # If forced to blur, blur image
@ -122,7 +122,7 @@ def upload():
img_name = "GWAGWA_"+str(uuid4()) img_name = "GWAGWA_"+str(uuid4())
img_path = os.path.join(current_app.config['UPLOAD_FOLDER'], img_name+'.'+img_ext) 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) logging.info('File extension not allowed: %s', img_ext)
abort(403) abort(403)
@ -142,14 +142,14 @@ def upload():
# Save to database # Save to database
try: try:
query = db.Posts(author_id = g.user.id, query = db.Posts(author_id=g.user.id,
created_at = dt.utcnow(), created_at=dt.utcnow(),
file_name = img_name+'.'+img_ext, file_name=img_name+'.'+img_ext,
file_type = img_ext, file_type=img_ext,
image_exif = img_exif, image_exif=img_exif,
image_colours = img_colors, image_colours=img_colors,
post_description = form_description, post_description=form_description,
post_alt = form_alt) post_alt=form_alt)
db_session.add(query) db_session.add(query)
db_session.commit() db_session.commit()
@ -159,6 +159,7 @@ def upload():
return 'Gwa Gwa' return 'Gwa Gwa'
@blueprint.route('/delete/<int:image_id>', methods=['POST']) @blueprint.route('/delete/<int:image_id>', methods=['POST'])
@login_required @login_required
def delete_image(image_id): def delete_image(image_id):
@ -194,7 +195,7 @@ def delete_image(image_id):
abort(500) abort(500)
logging.info('Removed image (%s) %s', image_id, img.file_name) 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' return 'Gwa Gwa'
@ -268,7 +269,7 @@ def logfile():
""" """
Gets the log file and returns it as a JSON object Gets the log file and returns it as a JSON object
""" """
filename = logging.getLoggerClass().root.handlers[0].baseFilename filename = 'only.log'
log_dict = {} log_dict = {}
i = 0 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 User registration, login and logout and locking access to pages behind a login
""" """
import re import re
@ -29,7 +29,7 @@ def login_required(view):
@functools.wraps(view) @functools.wraps(view)
def wrapped_view(**kwargs): def wrapped_view(**kwargs):
if g.user is None or session.get('uuid') is None: if g.user is None or session.get('uuid') is None:
logging.error('Authentification failed') logging.error('Authentication failed')
session.clear() session.clear()
return redirect(url_for('gallery.index')) 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') 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') username_regex = re.compile(r'\b[A-Za-z0-9._%+-]+\b')
if not username or not username_regex.match(username): if not username or not username_regex.match(username):
error.append('Username is invalid!') error.append('Username is invalid!')
@ -95,7 +94,6 @@ def register():
if error: if error:
return jsonify(error) return jsonify(error)
try: try:
register_user = db.Users(username=username, register_user = db.Users(username=username,
email=email, email=email,
@ -124,7 +122,6 @@ def login():
user = db_session.query(db.Users).filter_by(username=username).first() user = db_session.query(db.Users).filter_by(username=username).first()
error = [] error = []
if user is None: if user is None:
logging.error('User %s does not exist. Login attempt from %s', logging.error('User %s does not exist. Login attempt from %s',
username, request.remote_addr) username, request.remote_addr)
@ -137,18 +134,17 @@ def login():
if error: if error:
abort(403) abort(403)
try: try:
session.clear() session.clear()
session['user_id'] = user.id session['user_id'] = user.id
session['uuid'] = str(uuid.uuid4()) session['uuid'] = str(uuid.uuid4())
session_query = db.Sessions(user_id=user.id, session_query = db.Sessions(user_id=user.id,
session_uuid=session.get('uuid'), session_uuid=session.get('uuid'),
ip_address=request.remote_addr, ip_address=request.remote_addr,
user_agent=request.user_agent.string, user_agent=request.user_agent.string,
active=True, active=True,
created_at=dt.utcnow()) created_at=dt.utcnow())
db_session.add(session_query) db_session.add(session_query)
db_session.commit() db_session.commit()

View file

@ -1,6 +1,5 @@
""" """
OnlyLegs - Database OnlyLegs - Database models and functions for SQLAlchemy
Database models and functions for SQLAlchemy
""" """
import os import os
import platformdirs 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') 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'sqlite:///{path_to_db}', echo=False)
# engine = create_engine(f'postgresql://username:password@host:port/database_name', echo=False) # engine = create_engine('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('mysql://username:password@host:port/database_name', echo=False)
base = declarative_base() 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 User table
Joins with post, groups, session and log 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') 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 Post table
Joins with group_junction Joins with group_junction
@ -58,7 +57,7 @@ class Posts (base): # pylint: disable=too-few-public-methods, C0103
junction = relationship('GroupJunction', backref='posts') 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 Group table
Joins with group_junction Joins with group_junction
@ -74,7 +73,7 @@ class Groups (base): # pylint: disable=too-few-public-methods, C0103
junction = relationship('GroupJunction', backref='groups') 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 Junction table for posts and groups
Joins with 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')) 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 Session table
Joins with user Joins with user
@ -103,7 +102,7 @@ class Sessions (base): # pylint: disable=too-few-public-methods, C0103
created_at = Column(DateTime, nullable=False) 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 Log table
Joins with user Joins with user
@ -118,7 +117,7 @@ class Logs (base): # pylint: disable=too-few-public-methods, C0103
created_at = Column(DateTime, nullable=False) 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 Bans table
""" """

View file

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

View file

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

View file

@ -4,6 +4,7 @@ Metadata formatting helpers
""" """
from datetime import datetime from datetime import datetime
def human_size(value): def human_size(value):
""" """
Formats the size of a file in a human readable format 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 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): def compression_type(value):

View file

@ -61,4 +61,9 @@ FILE_MAPPING = {
'RatingPercent': ['Rating Percent', 'rating_percent'], '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('/') @blueprint.route('/')
def index(): 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, images = db_session.query(db.Posts.file_name,
db.Posts.post_alt,
db.Posts.image_colours, db.Posts.image_colours,
db.Posts.created_at, db.Posts.created_at,
db.Posts.id).order_by(db.Posts.id.desc()).all() db.Posts.id).order_by(db.Posts.id.desc()).all()
return render_template('index.html', images=images) return render_template('index.html', images=images)
@blueprint.route('/image/<int:image_id>') @blueprint.route('/image/<int:image_id>')
def image(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() group = db_session.query(db.Groups).filter(db.Groups.id == group[0]).first()
img.groups.append(group) img.groups.append(group)
next = db_session.query(db.Posts.id).filter(db.Posts.id > image_id).order_by(db.Posts.id.asc()).first() next_url = 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() 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: if next_url is not None:
next = url_for('gallery.image', image_id=next[0]) next_url = url_for('gallery.image', image_id=next_url[0])
if prev is not None: if prev_url is not None:
prev = url_for('gallery.image', image_id=prev[0]) 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') @blueprint.route('/profile')
def profile(): def profile():
@ -64,9 +67,10 @@ def profile():
""" """
return render_template('profile.html', user_id='gwa gwa') return render_template('profile.html', user_id='gwa gwa')
@blueprint.route('/profile/<int:user_id>') @blueprint.route('/profile/<int:user_id>')
def profile_id(user_id): def profile_id(user_id):
""" """
Shows user ofa given id, displays their uploads and other info 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') return render_template('settings/general.html')
@blueprint.route('/server') @blueprint.route('/server')
@login_required @login_required
def server(): def server():
@ -25,6 +26,7 @@ def server():
""" """
return render_template('settings/server.html') return render_template('settings/server.html')
@blueprint.route('/account') @blueprint.route('/account')
@login_required @login_required
def account(): def account():
@ -33,6 +35,7 @@ def account():
""" """
return render_template('settings/account.html') return render_template('settings/account.html')
@blueprint.route('/logs') @blueprint.route('/logs')
@login_required @login_required
def logs(): def logs():

View file

@ -8,8 +8,10 @@ import platformdirs
import logging import logging
import yaml import yaml
USER_DIR = platformdirs.user_config_dir('onlylegs') USER_DIR = platformdirs.user_config_dir('onlylegs')
class SetupApp: class SetupApp:
""" """
Setup the application on first run Setup the application on first run
@ -36,7 +38,8 @@ class SetupApp:
print("You can find the config files at:", USER_DIR) print("You can find the config files at:", USER_DIR)
sys.exit() sys.exit()
def make_dir(self): @staticmethod
def make_dir():
""" """
Create the user directory Create the user directory
""" """
@ -49,7 +52,8 @@ class SetupApp:
print("Error creating user directory:", err) print("Error creating user directory:", err)
sys.exit(1) sys.exit(1)
def make_env(self): @staticmethod
def make_env():
""" """
Create the .env file with default values Create the .env file with default values
""" """
@ -67,7 +71,8 @@ class SetupApp:
print("Generated default .env file, please edit!") print("Generated default .env file, please edit!")
def make_yaml(self): @staticmethod
def make_yaml():
""" """
Create the YAML config file with default values Create the YAML config file with default values
""" """
@ -107,17 +112,18 @@ class SetupApp:
print("Generated default YAML config, please edit!") print("Generated default YAML config, please edit!")
def logging_config(self): @staticmethod
LOGS_PATH = os.path.join(platformdirs.user_config_dir('onlylegs'), 'logs') def logging_config():
logs_path = os.path.join(platformdirs.user_config_dir('onlylegs'), 'logs')
if not os.path.isdir(LOGS_PATH): if not os.path.isdir(logs_path):
os.mkdir(LOGS_PATH) os.mkdir(logs_path)
print("Created logs directory at:", LOGS_PATH) print("Created logs directory at:", logs_path)
logging.getLogger('werkzeug').disabled = True logging.getLogger('werkzeug').disabled = True
logging.basicConfig( logging.basicConfig(
filename=os.path.join(LOGS_PATH, 'only.log'), filename=os.path.join(logs_path, 'only.log'),
level=logging.INFO, level=logging.INFO,
datefmt='%Y-%m-%d %H:%M:%S', datefmt='%Y-%m-%d %H:%M:%S',
format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)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 () { window.onload = function () {
loadOnView(); 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"> <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> <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> <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> </div>
{% else %} {% else %}
<img src="{{ url_for('static', filename='images/bg.svg') }}" onload="imgFade(this)" style="opacity:0;"/> <img src="{{ url_for('static', filename='images/bg.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
@ -25,7 +25,7 @@
<div class="banner-content"> <div class="banner-content">
<p>{{ group.description }}</p> <p>{{ group.description }}</p>
<h1>{{ group.name }}</h1> <h1>{{ group.name }}</h1>
<p>{{ images|length }} Images</p> <p>By {{ group.author_username }} - {{ images|length }} Images</p>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -41,11 +41,11 @@
<div class="gallery-grid"> <div class="gallery-grid">
{% for image in images %} {% 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 }})"> <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)"> <div class="image-filter">
<p></p> <p class="image-subtitle"></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> <p class="image-title"><span class="time">{{ image.created_at }}</span></p>
</span> </div>
<img data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/> <img alt="{{ image.post_alt }}" data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View file

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

View file

@ -4,18 +4,19 @@
{% block content %} {% block content %}
<div class="background"> <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> <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>
<div class="image-fullscreen"> <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>
<div class="image-grid"> <div class="image-grid">
<div class="image-container" id="image-container"> <div class="image-container" id="image-container">
<img <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;" onload="imgFade(this)" style="opacity:0;"
onerror="this.src='/static/images/error.png'" onerror="this.src='/static/images/error.png'"
{% if "File" in image.image_exif %} {% if "File" in image.image_exif %}
@ -119,6 +120,11 @@
</svg> </svg>
</div> </div>
<div class="info-table"> <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> <table>
<tr> <tr>
<td>Image ID</td> <td>Image ID</td>
@ -133,11 +139,6 @@
<td><span class="time">{{ image.created_at }}</span></td> <td><span class="time">{{ image.created_at }}</span></td>
</tr> </tr>
</table> </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"> <div class="img-groups">
{% for group in image.groups %} {% for group in image.groups %}
<a href="/group/{{ group.id }}" class="tag-icon"> <a href="/group/{{ group.id }}" class="tag-icon">
@ -251,7 +252,7 @@
'DESTRUCTION!!!!!!', 'DESTRUCTION!!!!!!',
'This will delete the image and all of its data!!! This action is irreversible!!!!! Are you sure you want to do this?????', '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>', '<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() { function deleteImage() {

View file

@ -18,11 +18,11 @@
<div class="gallery-grid"> <div class="gallery-grid">
{% for image in images %} {% 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 }})"> <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)"> <div class="image-filter">
<p></p> <p class="image-subtitle"></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> <p class="image-title"><span class="time">{{ image.created_at }}</span></p>
</span> </div>
<img data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/> <img alt="{{ image.post_alt }}" data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -8,7 +8,7 @@ from datetime import datetime
import sass import sass
class CompileTheme(): class CompileTheme:
""" """
Compiles the theme into the static folder Compiles the theme into the static folder
""" """
@ -33,7 +33,8 @@ class CompileTheme():
now = datetime.now() now = datetime.now()
print(f"{now.hour}:{now.minute}:{now.second} - Done!\n") 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 Compile the sass (or scss) file into css and save it to the static folder
""" """
@ -54,7 +55,8 @@ class CompileTheme():
print("Compiled successfully!") 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 Copy the fonts folder to the static folder
""" """

View file

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

View file

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

View file

@ -48,7 +48,7 @@ html, body
color: $black color: $black
.big-text .big-text
height: 60vh height: 20rem
display: flex display: flex
flex-direction: column flex-direction: column
@ -102,38 +102,8 @@ html, body
color: $white color: $white
footer #contrast-check
margin: 0 transition: color 0.15s ease-in-out
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
@media (max-width: $breakpoint) @media (max-width: $breakpoint)

View file

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