diff --git a/onlylegs/__init__.py b/onlylegs/__init__.py deleted file mode 100644 index e15e3e1..0000000 --- a/onlylegs/__init__.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -Onlylegs Gallery -This is the main app file, it loads all the other files and sets up the app -""" -import os -import logging - -from flask_assets import Bundle -from flask_migrate import init as migrate_init - -from flask import Flask, render_template, abort -from werkzeug.exceptions import HTTPException -from werkzeug.security import generate_password_hash - -from onlylegs.extensions import db, migrate, login_manager, assets, compress, cache -from onlylegs.config import INSTANCE_DIR, MIGRATIONS_DIR -from onlylegs.models import User -from onlylegs.views import ( - index as view_index, - image as view_image, - group as view_group, - settings as view_settings, - profile as view_profile, -) -from onlylegs.api import media as api_media, group as api_group, account as api_account -from onlylegs import auth as view_auth -from onlylegs import gwagwa - - -def create_app(): # pylint: disable=R0914 - """ - Create and configure the main app - """ - app = Flask(__name__, instance_path=INSTANCE_DIR) - app.config.from_pyfile("config.py") - - # DATABASE - db.init_app(app) - migrate.init_app(app, db, directory=MIGRATIONS_DIR) - - # If database file doesn't exist, create it - if not os.path.exists(os.path.join(INSTANCE_DIR, "gallery.sqlite3")): - print("Creating database") - with app.app_context(): - db.create_all() - - register_user = User( - username=app.config["ADMIN_CONF"]["username"], - email=app.config["ADMIN_CONF"]["email"], - password=generate_password_hash("changeme!", method="sha256"), - ) - db.session.add(register_user) - db.session.commit() - - print( - """ -#################################################### -# DEFAULY ADMIN USER GENERATED WITH GIVEN USERNAME # -# THE DEFAULT PASSWORD "changeme!" HAS BEEN USED, # -# PLEASE UPDATE IT IN THE SETTINGS! # -#################################################### - """ - ) - - # Check if migrations directory exists, if not create it - with app.app_context(): - if not os.path.exists(MIGRATIONS_DIR): - print("Creating migrations directory") - migrate_init(directory=MIGRATIONS_DIR) - - # LOGIN MANAGER - # can also set session_protection to "strong" - # this would protect against session hijacking - login_manager.init_app(app) - login_manager.login_view = "onlylegs.index" - - @login_manager.user_loader - def load_user(user_id): # skipcq: PTC-W0065 - return User.query.filter_by(alt_id=user_id).first() - - @login_manager.unauthorized_handler - def unauthorized(): # skipcq: PTC-W0065 - error = 401 - msg = "You are not authorized to view this page!!!!" - return render_template("error.html", error=error, msg=msg), error - - # ERROR HANDLERS - @app.errorhandler(Exception) - def error_page(err): # skipcq: PTC-W0065 - """ - Error handlers, if the error is not a HTTP error, return 500 - """ - if not isinstance(err, HTTPException): - abort(500) - return ( - render_template("error.html", error=err.code, msg=err.description), - err.code, - ) - - # ASSETS - assets.init_app(app) - - scripts = Bundle( - "js/*.js", output="gen/js.js", depends="js/*.js" - ) # filter jsmin is broken :c - styles = Bundle( - "sass/style.sass", - filters="libsass, cssmin", - output="gen/styles.css", - depends="sass/**/*.sass", - ) - - assets.register("scripts", scripts) - assets.register("styles", styles) - - # BLUEPRINTS - app.register_blueprint(view_auth.blueprint) - app.register_blueprint(view_index.blueprint) - app.register_blueprint(view_image.blueprint) - app.register_blueprint(view_group.blueprint) - app.register_blueprint(view_profile.blueprint) - app.register_blueprint(view_settings.blueprint) - - # APIS - app.register_blueprint(api_media.blueprint) - app.register_blueprint(api_group.blueprint) - app.register_blueprint(api_account.blueprint) - - # CACHE AND COMPRESS - cache.init_app(app) - compress.init_app(app) - - # Yupee! We got there :3 - print("Done!") - logging.info("Gallery started successfully!") - return app diff --git a/onlylegs/api.py b/onlylegs/api.py new file mode 100644 index 0000000..fffeea5 --- /dev/null +++ b/onlylegs/api.py @@ -0,0 +1,182 @@ +""" +Onlylegs - API endpoints +""" +import os +import pathlib +import re +import logging +from uuid import uuid4 + +from flask import ( + Blueprint, + abort, + send_from_directory, + jsonify, + request, + current_app, +) +from flask_login import login_required, current_user + +from colorthief import ColorThief + +from onlylegs.extensions import db +from onlylegs.models import Users, Pictures +from onlylegs.utils.metadata import yoink +from onlylegs.utils.generate_image import generate_thumbnail + + +blueprint = Blueprint("api", __name__, url_prefix="/api") + + +@blueprint.route("/account/picture/", methods=["POST"]) +@login_required +def account_picture(user_id): + """ + Returns the profile of a user + """ + user = db.get_or_404(Users, user_id) + file = request.files.get("file", None) + + # If no image is uploaded, return 404 error + if not file: + return jsonify({"error": "No file uploaded"}), 400 + if user.id != current_user.id: + return jsonify({"error": "You are not allowed to do this, go away"}), 403 + + # Get file extension, generate random name and set file path + img_ext = pathlib.Path(file.filename).suffix.replace(".", "").lower() + img_name = str(user.id) + img_path = os.path.join(current_app.config["PFP_FOLDER"], img_name + "." + img_ext) + + # Check if file extension is allowed + if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys(): + logging.info("File extension not allowed: %s", img_ext) + return jsonify({"error": "File extension not allowed"}), 403 + + if user.picture: + # Delete cached files and old image + os.remove(os.path.join(current_app.config["PFP_FOLDER"], user.picture)) + cache_name = user.picture.rsplit(".")[0] + for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob( + cache_name + "*" + ): + os.remove(cache_file) + + # Save file + try: + file.save(img_path) + except OSError as err: + logging.info("Error saving file %s because of %s", img_path, err) + return jsonify({"error": "Error saving file"}), 500 + + img_colors = ColorThief(img_path).get_color() + + # Save to database + user.colour = img_colors + user.picture = str(img_name + "." + img_ext) + db.session.commit() + + return jsonify({"message": "File uploaded"}), 200 + + +@blueprint.route("/account/username/", methods=["POST"]) +@login_required +def account_username(user_id): + """ + Returns the profile of a user + """ + user = db.get_or_404(Users, user_id) + new_name = request.form["name"] + + username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b") + + # Validate the form + if not new_name or not username_regex.match(new_name): + return jsonify({"error": "Username is invalid"}), 400 + if user.id != current_user.id: + return jsonify({"error": "You are not allowed to do this, go away"}), 403 + + # Save to database + user.username = new_name + db.session.commit() + + return jsonify({"message": "Username changed"}), 200 + + +@blueprint.route("/media/", methods=["GET"]) +def media(path): + """ + Returns image from media folder + r for resolution, thumb for thumbnail etc + e for extension, jpg, png etc + """ + res = request.args.get("r", "").strip() + ext = request.args.get("e", "").strip() + + # if no args are passed, return the raw file + if not res and not ext: + if not os.path.exists(os.path.join(current_app.config["MEDIA_FOLDER"], path)): + abort(404) + return send_from_directory(current_app.config["MEDIA_FOLDER"], path) + + # Generate thumbnail, if None is returned a server error occured + thumb = generate_thumbnail(path, res, ext) + if not thumb: + abort(500) + + response = send_from_directory(os.path.dirname(thumb), os.path.basename(thumb)) + response.headers["Cache-Control"] = "public, max-age=31536000" + response.headers["Expires"] = "31536000" + + return response + + +@blueprint.route("/media/upload", methods=["POST"]) +@login_required +def upload(): + """ + Uploads an image to the server and saves it to the database + """ + form_file = request.files.get("file", None) + form = request.form + + if not form_file: + return jsonify({"message": "No file"}), 400 + + # Get file extension, generate random name and set file path + img_ext = pathlib.Path(form_file.filename).suffix.replace(".", "").lower() + img_name = "GWAGWA_" + str(uuid4()) + img_path = os.path.join( + current_app.config["UPLOAD_FOLDER"], img_name + "." + img_ext + ) + + # Check if file extension is allowed + if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys(): + logging.info("File extension not allowed: %s", img_ext) + return jsonify({"message": "File extension not allowed"}), 403 + + # Save file + try: + form_file.save(img_path) + except OSError as err: + logging.info("Error saving file %s because of %s", img_path, err) + return jsonify({"message": "Error saving file"}), 500 + + img_exif = yoink(img_path) # Get EXIF data + img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette + + # Save to database + query = Pictures( + author_id=current_user.id, + filename=img_name + "." + img_ext, + mimetype=img_ext, + exif=img_exif, + colours=img_colors, + description=form["description"], + alt=form["alt"], + ) + + db.session.add(query) + db.session.commit() + + return jsonify({"message": "File uploaded"}), 200 diff --git a/onlylegs/api/__init__.py b/onlylegs/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/onlylegs/api/account.py b/onlylegs/api/account.py deleted file mode 100644 index fb54c25..0000000 --- a/onlylegs/api/account.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Onlylegs - API endpoints -""" -import os -import pathlib -import re -import logging - -from flask import Blueprint, jsonify, request, current_app -from flask_login import login_required, current_user - -from colorthief import ColorThief - -from onlylegs.extensions import db -from onlylegs.models import User - - -blueprint = Blueprint("account_api", __name__, url_prefix="/api/account") - - -@blueprint.route("/picture/", methods=["POST"]) -@login_required -def account_picture(user_id): - """ - Returns the profile of a user - """ - user = db.get_or_404(User, user_id) - file = request.files["file"] - - # If no image is uploaded, return 404 error - if not file: - return jsonify({"error": "No file uploaded"}), 400 - if user.id != current_user.id: - return jsonify({"error": "You are not allowed to do this, go away"}), 403 - - # Get file extension, generate random name and set file path - img_ext = pathlib.Path(file.filename).suffix.replace(".", "").lower() - img_name = str(user.id) - img_path = os.path.join(current_app.config["PFP_FOLDER"], img_name + "." + img_ext) - - # Check if file extension is allowed - if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys(): - logging.info("File extension not allowed: %s", img_ext) - return jsonify({"error": "File extension not allowed"}), 403 - - if user.picture: - # Delete cached files and old image - os.remove(os.path.join(current_app.config["PFP_FOLDER"], user.picture)) - cache_name = user.picture.rsplit(".")[0] - for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob( - cache_name + "*" - ): - os.remove(cache_file) - - # Save file - try: - file.save(img_path) - except OSError as err: - logging.info("Error saving file %s because of %s", img_path, err) - return jsonify({"error": "Error saving file"}), 500 - - img_colors = ColorThief(img_path).get_color() - - # Save to database - user.colour = img_colors - user.picture = str(img_name + "." + img_ext) - db.session.commit() - - return jsonify({"message": "File uploaded"}), 200 - - -@blueprint.route("/username/", methods=["POST"]) -@login_required -def account_username(user_id): - """ - Returns the profile of a user - """ - user = db.get_or_404(User, user_id) - new_name = request.form["name"] - - username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b") - - # Validate the form - if not new_name or not username_regex.match(new_name): - return jsonify({"error": "Username is invalid"}), 400 - if user.id != current_user.id: - return jsonify({"error": "You are not allowed to do this, go away"}), 403 - - # Save to database - user.username = new_name - db.session.commit() - - return jsonify({"message": "Username changed"}), 200 diff --git a/onlylegs/api/group.py b/onlylegs/api/group.py deleted file mode 100644 index 9be39a5..0000000 --- a/onlylegs/api/group.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Onlylegs - API endpoints -""" -from flask import Blueprint, flash, jsonify, request -from flask_login import login_required, current_user - -from onlylegs.extensions import db -from onlylegs.models import Post, Group, GroupJunction - - -blueprint = Blueprint("group_api", __name__, url_prefix="/api/group") - - -@blueprint.route("/create", methods=["POST"]) -@login_required -def create_group(): - """ - Creates a group - """ - new_group = Group( - name=request.form["name"], - description=request.form["description"], - author_id=current_user.id, - ) - - db.session.add(new_group) - db.session.commit() - - return jsonify({"message": "Group created", "id": new_group.id}) - - -@blueprint.route("/modify", methods=["POST"]) -@login_required -def modify_group(): - """ - Changes the images in a group - """ - group_id = request.form["group"] - image_id = request.form["image"] - action = request.form["action"] - - group = db.get_or_404(Group, group_id) - db.get_or_404(Post, image_id) # Check if image exists - - if group.author_id != current_user.id: - return jsonify({"message": "You are not the owner of this group"}), 403 - - if ( - action == "add" - and not GroupJunction.query.filter_by( - group_id=group_id, post_id=image_id - ).first() - ): - db.session.add(GroupJunction(group_id=group_id, post_id=image_id)) - elif request.form["action"] == "remove": - GroupJunction.query.filter_by(group_id=group_id, post_id=image_id).delete() - - db.session.commit() - return jsonify({"message": "Group modified"}) - - -@blueprint.route("/delete", methods=["POST"]) -def delete_group(): - """ - Deletes a group - """ - group_id = request.form["group"] - group = db.get_or_404(Group, group_id) - - if group.author_id != current_user.id: - return jsonify({"message": "You are not the owner of this group"}), 403 - - GroupJunction.query.filter_by(group_id=group_id).delete() - db.session.delete(group) - db.session.commit() - - flash(["Group yeeted!", "1"]) - return jsonify({"message": "Group deleted"}) diff --git a/onlylegs/api/media.py b/onlylegs/api/media.py deleted file mode 100644 index 2ce774b..0000000 --- a/onlylegs/api/media.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -Onlylegs - API endpoints -Media upload and retrieval -""" -from uuid import uuid4 -import os -import pathlib -import logging - -from flask import ( - Blueprint, - flash, - abort, - send_from_directory, - jsonify, - request, - current_app, -) -from flask_login import login_required, current_user - -from colorthief import ColorThief - -from onlylegs.extensions import db -from onlylegs.models import Post, GroupJunction -from onlylegs.utils import metadata as mt -from onlylegs.utils.generate_image import generate_thumbnail - - -blueprint = Blueprint("media_api", __name__, url_prefix="/api/media") - - -@blueprint.route("/", methods=["GET"]) -def media(path): - """ - Returns a file from the uploads folder - r for resolution, thumb for thumbnail etc - e for extension, jpg, png etc - """ - res = request.args.get("r", default=None, type=str) - ext = request.args.get("e", default=None, type=str) - # path = secure_filename(path) - - # if no args are passed, return the raw file - if not res and not ext: - if not os.path.exists(os.path.join(current_app.config["MEDIA_FOLDER"], path)): - abort(404) - return send_from_directory(current_app.config["MEDIA_FOLDER"], path) - - thumb = generate_thumbnail(path, res, ext) - if not thumb: - abort(500) - - return send_from_directory(os.path.dirname(thumb), os.path.basename(thumb)) - - -@blueprint.route("/upload", methods=["POST"]) -@login_required -def upload(): - """ - Uploads an image to the server and saves it to the database - """ - form_file = request.files["file"] - form = request.form - - if not form_file: - return jsonify({"message": "No file"}), 400 - - # Get file extension, generate random name and set file path - img_ext = pathlib.Path(form_file.filename).suffix.replace(".", "").lower() - img_name = "GWAGWA_" + str(uuid4()) - img_path = os.path.join( - current_app.config["UPLOAD_FOLDER"], img_name + "." + img_ext - ) - - # Check if file extension is allowed - if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys(): - logging.info("File extension not allowed: %s", img_ext) - return jsonify({"message": "File extension not allowed"}), 403 - - # Save file - try: - form_file.save(img_path) - except OSError as err: - logging.info("Error saving file %s because of %s", img_path, err) - return jsonify({"message": "Error saving file"}), 500 - - img_exif = mt.Metadata(img_path).yoink() # Get EXIF data - img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette - - # Save to database - query = Post( - author_id=current_user.id, - filename=img_name + "." + img_ext, - mimetype=img_ext, - exif=img_exif, - colours=img_colors, - description=form["description"], - alt=form["alt"], - ) - - db.session.add(query) - db.session.commit() - - return jsonify({"message": "File uploaded"}), 200 - - -@blueprint.route("/delete/", methods=["POST"]) -@login_required -def delete_image(image_id): - """ - Deletes an image from the server and database - """ - post = db.get_or_404(Post, image_id) - - # Check if image exists and if user is allowed to delete it (author) - if post.author_id != current_user.id: - logging.info("User %s tried to delete image %s", current_user.id, image_id) - return ( - jsonify({"message": "You are not allowed to delete this image, heck off"}), - 403, - ) - - # Delete file - try: - os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], post.filename)) - except FileNotFoundError: - logging.warning( - "File not found: %s, already deleted or never existed", post.filename - ) - - # Delete cached files - cache_name = post.filename.rsplit(".")[0] - for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob( - cache_name + "*" - ): - os.remove(cache_file) - - GroupJunction.query.filter_by(post_id=image_id).delete() - db.session.delete(post) - db.session.commit() - - logging.info("Removed image (%s) %s", image_id, post.filename) - flash(["Image was all in Le Head!", "1"]) - return jsonify({"message": "Image deleted"}), 200 diff --git a/onlylegs/app.py b/onlylegs/app.py new file mode 100644 index 0000000..0dd4349 --- /dev/null +++ b/onlylegs/app.py @@ -0,0 +1,138 @@ +""" +Onlylegs Gallery +This is the main app file, it loads all the other files and sets up the app +""" +import os +import logging + +from flask_assets import Bundle +from flask_migrate import init as migrate_init + +from flask import Flask, render_template, abort +from werkzeug.exceptions import HTTPException +from werkzeug.security import generate_password_hash + +from onlylegs.extensions import db, migrate, login_manager, assets, compress, cache +from onlylegs.config import INSTANCE_DIR, MIGRATIONS_DIR +from onlylegs.models import Users +from onlylegs.views import ( + index as view_index, + image as view_image, + group as view_group, + settings as view_settings, + profile as view_profile, +) +from onlylegs import api +from onlylegs import auth as view_auth +from onlylegs import filters + +app = Flask(__name__, instance_path=INSTANCE_DIR) +app.config.from_pyfile("config.py") + +# DATABASE +db.init_app(app) +migrate.init_app(app, db, directory=MIGRATIONS_DIR) + +# If database file doesn't exist, create it +if not os.path.exists(os.path.join(INSTANCE_DIR, "gallery.sqlite3")): + print("Creating database") + with app.app_context(): + db.create_all() + + register_user = Users( + username=app.config["ADMIN_CONF"]["username"], + email=app.config["ADMIN_CONF"]["email"], + password=generate_password_hash("changeme!", method="sha256"), + ) + db.session.add(register_user) + db.session.commit() + + print( + """ +#################################################### +# DEFAULT ADMIN USER GENERATED WITH GIVEN USERNAME # +# THE DEFAULT PASSWORD "changeme!" HAS BEEN USED, # +# PLEASE UPDATE IT IN THE SETTINGS! # +#################################################### + """ + ) + +# Check if migrations directory exists, if not create it +with app.app_context(): + if not os.path.exists(MIGRATIONS_DIR): + print("Creating migrations directory") + migrate_init(directory=MIGRATIONS_DIR) + +# LOGIN MANAGER +# can also set session_protection to "strong" +# this would protect against session hijacking +login_manager.init_app(app) +login_manager.login_view = "onlylegs.index" + + +@login_manager.user_loader +def load_user(user_id): + return Users.query.filter_by(alt_id=user_id).first() + + +@login_manager.unauthorized_handler +def unauthorized(): + error = 401 + msg = "You are not authorized to view this page!!!!" + return render_template("error.html", error=error, msg=msg), error + + +# ERROR HANDLERS +@app.errorhandler(Exception) +def error_page(err): + """ + Error handlers, if the error is not a HTTP error, return 500 + """ + if not isinstance(err, HTTPException): + abort(500) + return ( + render_template("error.html", error=err.code, msg=err.description), + err.code, + ) + + +# ASSETS +assets.init_app(app) + +scripts = Bundle( + "js/*.js", output="gen/js.js", depends="js/*.js" +) # filter jsmin is broken :c +styles = Bundle( + "sass/style.sass", + filters="libsass, cssmin", + output="gen/styles.css", + depends="sass/**/*.sass", +) + +assets.register("scripts", scripts) +assets.register("styles", styles) + +# BLUEPRINTS +app.register_blueprint(view_auth.blueprint) +app.register_blueprint(view_index.blueprint) +app.register_blueprint(view_image.blueprint) +app.register_blueprint(view_group.blueprint) +app.register_blueprint(view_profile.blueprint) +app.register_blueprint(view_settings.blueprint) + +app.register_blueprint(api.blueprint) + +# FILTERS +app.register_blueprint(filters.blueprint) + +# CACHE AND COMPRESS +cache.init_app(app) +compress.init_app(app) + +# Yupee! We got there :3 +print("Done!") +logging.info("Gallery started successfully!") + + +if __name__ == "__main__": + app.run() diff --git a/onlylegs/auth.py b/onlylegs/auth.py index 6b5884f..c3d0698 100644 --- a/onlylegs/auth.py +++ b/onlylegs/auth.py @@ -11,7 +11,7 @@ from werkzeug.security import check_password_hash, generate_password_hash from flask_login import login_user, logout_user, login_required from onlylegs.extensions import db -from onlylegs.models import User +from onlylegs.models import Users blueprint = Blueprint("auth", __name__, url_prefix="/auth") @@ -28,7 +28,7 @@ def login(): password = request.form["password"].strip() remember = bool(request.form["remember-me"]) - user = User.query.filter_by(username=username).first() + user = Users.query.filter_by(username=username).first() if not user or not check_password_hash(user.password, password): logging.error("Login attempt from %s", request.remote_addr) @@ -77,7 +77,7 @@ def register(): elif password_repeat != password: error.append("Passwords do not match!") - user_exists = User.query.filter_by(username=username).first() + user_exists = Users.query.filter_by(username=username).first() if user_exists: error.append("User already exists!") @@ -86,7 +86,7 @@ def register(): print(error) return jsonify(error), 400 - register_user = User( + register_user = Users( username=username, email=email, password=generate_password_hash(password, method="sha256"), diff --git a/onlylegs/config.py b/onlylegs/config.py index 58e7a80..06cd271 100644 --- a/onlylegs/config.py +++ b/onlylegs/config.py @@ -3,6 +3,7 @@ Gallery configuration file """ import os import platformdirs +import importlib.metadata from dotenv import load_dotenv from yaml import safe_load @@ -41,3 +42,6 @@ MEDIA_FOLDER = os.path.join(user_dir, "media") # Database INSTANCE_DIR = instance_dir MIGRATIONS_DIR = os.path.join(INSTANCE_DIR, "migrations") + +# App +APP_VERSION = importlib.metadata.version("OnlyLegs") diff --git a/onlylegs/extensions.py b/onlylegs/extensions.py index 0b27ca4..9e3eb84 100644 --- a/onlylegs/extensions.py +++ b/onlylegs/extensions.py @@ -13,4 +13,4 @@ migrate = Migrate() login_manager = LoginManager() assets = Environment() compress = Compress() -cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300}) +cache = Cache(config={"CACHE_TYPE": "simple", "CACHE_DEFAULT_TIMEOUT": 300}) diff --git a/onlylegs/filters.py b/onlylegs/filters.py new file mode 100644 index 0000000..765003b --- /dev/null +++ b/onlylegs/filters.py @@ -0,0 +1,20 @@ +""" +OnlyLegs filters +Custom Jinja2 filters +""" +from flask import Blueprint +from onlylegs.utils import colour as colour_utils + + +blueprint = Blueprint("filters", __name__) + + +@blueprint.app_template_filter() +def colour_contrast(colour): + """ + Pass in the colour of the background and will return + a css variable based on the contrast of text required to be readable + "color: var(--fg-white);" or "color: var(--fg-black);" + """ + colour_obj = colour_utils.Colour(colour) + return "rgb(var(--fg-black));" if colour_obj.is_light() else "rgb(var(--fg-white));" diff --git a/onlylegs/gwagwa.py b/onlylegs/gwagwa.py deleted file mode 100644 index 6fb58f6..0000000 --- a/onlylegs/gwagwa.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -Gwa Gwa! -""" -print("Gwa Gwa!") diff --git a/onlylegs/langs/gb.json b/onlylegs/langs/gb.json deleted file mode 100644 index d3f0895..0000000 --- a/onlylegs/langs/gb.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "IMAGES_UPLOADED": "%s images uploaded!", - "DONT USE THIS": "variable:format(data), jinja2 doesnt use the same method as Django does, odd" -} \ No newline at end of file diff --git a/onlylegs/models.py b/onlylegs/models.py index af3eb6f..6970a9b 100644 --- a/onlylegs/models.py +++ b/onlylegs/models.py @@ -6,18 +6,18 @@ from flask_login import UserMixin from onlylegs.extensions import db -class GroupJunction(db.Model): # pylint: disable=too-few-public-methods, C0103 +class AlbumJunction(db.Model): # pylint: disable=too-few-public-methods, C0103 """ - Junction table for posts and groups - Joins with posts and groups + Junction table for picturess and albums + Joins with picturess and albums """ - __tablename__ = "group_junction" + __tablename__ = "album_junction" id = db.Column(db.Integer, primary_key=True) - group_id = db.Column(db.Integer, db.ForeignKey("group.id")) - post_id = db.Column(db.Integer, db.ForeignKey("post.id")) + album_id = db.Column(db.Integer, db.ForeignKey("albums.id")) + picture_id = db.Column(db.Integer, db.ForeignKey("pictures.id")) date_added = db.Column( db.DateTime, @@ -26,16 +26,15 @@ class GroupJunction(db.Model): # pylint: disable=too-few-public-methods, C0103 ) -class Post(db.Model): # pylint: disable=too-few-public-methods, C0103 +class Pictures(db.Model): # pylint: disable=too-few-public-methods, C0103 """ - Post table + Pictures table """ - __tablename__ = "post" + __tablename__ = "pictures" id = db.Column(db.Integer, primary_key=True) - - author_id = db.Column(db.Integer, db.ForeignKey("user.id")) + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) filename = db.Column(db.String, unique=True, nullable=False) mimetype = db.Column(db.String, nullable=False) @@ -51,37 +50,37 @@ class Post(db.Model): # pylint: disable=too-few-public-methods, C0103 server_default=db.func.now(), # pylint: disable=E1102 ) - junction = db.relationship("GroupJunction", backref="posts") + album_fk = db.relationship("AlbumJunction", backref="pictures") -class Group(db.Model): # pylint: disable=too-few-public-methods, C0103 +class Albums(db.Model): # pylint: disable=too-few-public-methods, C0103 """ - Group table + albums table """ - __tablename__ = "group" + __tablename__ = "albums" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, nullable=False) description = db.Column(db.String, nullable=False) - author_id = db.Column(db.Integer, db.ForeignKey("user.id")) + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) created_at = db.Column( db.DateTime, nullable=False, server_default=db.func.now(), # pylint: disable=E1102 ) - junction = db.relationship("GroupJunction", backref="groups") + album_fk = db.relationship("AlbumJunction", backref="albums") -class User(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C0103 +class Users(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C0103 """ - User table + Users table """ - __tablename__ = "user" + __tablename__ = "users" # Gallery used information id = db.Column(db.Integer, primary_key=True) @@ -100,8 +99,8 @@ class User(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C010 server_default=db.func.now(), # pylint: disable=E1102 ) - posts = db.relationship("Post", backref="author") - groups = db.relationship("Group", backref="author") + pictures_fk = db.relationship("Pictures", backref="author") + albums_fk = db.relationship("Albums", backref="author") def get_id(self): return str(self.alt_id) diff --git a/onlylegs/static/banner.png b/onlylegs/static/banner.png index 179ed20..67aacfc 100644 Binary files a/onlylegs/static/banner.png and b/onlylegs/static/banner.png differ diff --git a/onlylegs/static/fonts/Rubik.ttf b/onlylegs/static/fonts/Rubik.ttf deleted file mode 100644 index 547f069..0000000 Binary files a/onlylegs/static/fonts/Rubik.ttf and /dev/null differ diff --git a/onlylegs/static/fonts/font.css b/onlylegs/static/fonts/font.css deleted file mode 100644 index bb8f61b..0000000 --- a/onlylegs/static/fonts/font.css +++ /dev/null @@ -1,7 +0,0 @@ -@font-face { - font-family: 'Rubik'; - src: url('./Rubik.ttf') format('truetype'); - font-style: normal; - font-display: block; - font-weight: 300 900; -} \ No newline at end of file diff --git a/onlylegs/static/js/clipboard.js b/onlylegs/static/js/clipboard.js new file mode 100644 index 0000000..0882f56 --- /dev/null +++ b/onlylegs/static/js/clipboard.js @@ -0,0 +1,8 @@ +function copyToClipboard(data) { + try { + navigator.clipboard.writeText(data) + addNotification("Copied to clipboard!", 4); + } catch (err) { + addNotification("Oh noes, something when wrong D:", 2); + } +} \ No newline at end of file diff --git a/onlylegs/static/js/fade.js b/onlylegs/static/js/fade.js new file mode 100644 index 0000000..56749ba --- /dev/null +++ b/onlylegs/static/js/fade.js @@ -0,0 +1,11 @@ +// fade in images +function imgFade(obj, time = 200) { + setTimeout(() => { + obj.style.animation = `imgFadeIn ${time}ms`; + + setTimeout(() => { + obj.style.opacity = null; + obj.style.animation = null; + }, time); + }, 1); +} diff --git a/onlylegs/static/js/grid.js b/onlylegs/static/js/grid.js new file mode 100644 index 0000000..54aae09 --- /dev/null +++ b/onlylegs/static/js/grid.js @@ -0,0 +1,88 @@ +function makeGrid() { + // Get the container and images + const container = document.querySelector('.gallery-grid'); + const images = document.querySelectorAll('.gallery-item'); + + // Set the gap between images + const gap = 0.6 * 16; + const maxWidth = container.clientWidth - gap; + const maxHeight = 13 * 16; + + + if (window.innerWidth < 800) { + for (let i = 0; i < images.length; i++) { + images[i].style.height = images[i].offsetWidth + 'px'; + + images[i].style.width = null; + images[i].style.left = null; + images[i].style.top = null; + } + + container.style.height = null; + return; + } + + + // Calculate the width and height of each image + let calculated = {}; + for (let i = 0; i < images.length; i++) { + const image = images[i].querySelector('img'); + const width = image.naturalWidth; + const height = image.naturalHeight; + + let ratio = width / height; + const newWidth = maxHeight * ratio; + + if (newWidth > maxWidth) { + newWidth = maxWidth / 3 - gap; // 3 images per row max + ratio = newWidth / height; + } + + calculated[i] = {"width": newWidth, + "height": maxHeight, + "ratio": ratio}; + } + + // Next images position + let nextTop = gap; + let nextLeft = gap; + + for (let i = 0; i < images.length; i++) { + let currentRow = []; + let currectLength = 0; + + // While the row is not full add images to it + while (currectLength < maxWidth && i !== images.length) { + currentRow.push(i); + currectLength += calculated[i]["width"]; + i++; + } + + // Go back one image since the last one pushed the row over the limit + i--; + + // Calculate the amount of space required to fill the row + const currentRowDiff = (currectLength - maxWidth); + + // Cycle through the images in the row and adjust their width and left position + for (let j = 0; j < currentRow.length; j++) { + const image = images[currentRow[j]]; + const data = calculated[currentRow[j]]; + // Shrink compared to the % of the row it takes up + const shrink = currentRowDiff * (data["width"] / currectLength); + + image.style.width = data["width"] - shrink - gap + 'px'; + image.style.height = data["height"] + 'px'; + image.style.left = nextLeft + 'px'; + image.style.top = nextTop + 'px'; + + nextLeft += data["width"] - shrink; + } + + // Reset for the next row + nextTop += maxHeight + gap; + nextLeft = gap; + } + + container.style.height = nextTop + 'px'; +} \ No newline at end of file diff --git a/onlylegs/static/js/groupPage.js b/onlylegs/static/js/groupPage.js new file mode 100644 index 0000000..193dccc --- /dev/null +++ b/onlylegs/static/js/groupPage.js @@ -0,0 +1,155 @@ +function groupDeletePopup() { + let title = 'Yeet!'; + let subtitle = + 'Are you surrrre? This action is irreversible ' + + 'and very final. This wont delete the images, ' + + 'but it will remove them from this group.' + let body = null; + + let deleteBtn = document.createElement('button'); + deleteBtn.classList.add('btn-block'); + deleteBtn.classList.add('critical'); + deleteBtn.innerHTML = 'Dewww eeeet!'; + deleteBtn.onclick = groupDeleteConfirm; + + popupShow(title, subtitle, body, [popupCancelButton, deleteBtn]); +} + +function groupDeleteConfirm(event) { + // AJAX takes control of subby form :3 + event.preventDefault(); + + fetch('/group/' + group_data['id'], { + method: 'DELETE', + }).then(response => { + if (response.ok) { + window.location.href = '/group/'; + } else { + addNotification('Server exploded, returned:' + response.status, 2); + } + }).catch(error => { + addNotification('Error yeeting group!' + error, 2); + }); +} + +function groupEditPopup() { + let title = 'Nothing stays the same'; + let subtitle = 'Add, remove, or change, the power is in your hands...' + + let formSubmitButton = document.createElement('button'); + formSubmitButton.setAttribute('form', 'groupEditForm'); + formSubmitButton.setAttribute('type', 'submit'); + formSubmitButton.classList.add('btn-block'); + formSubmitButton.classList.add('primary'); + formSubmitButton.innerHTML = 'Saveeee'; + + // Create form + let body = document.createElement('form'); + body.setAttribute('onsubmit', 'return groupEditConfirm(event);'); + body.id = 'groupEditForm'; + + let formImageId = document.createElement('input'); + formImageId.setAttribute('type', 'text'); + formImageId.setAttribute('placeholder', 'Image ID'); + formImageId.setAttribute('required', ''); + formImageId.classList.add('input-block'); + formImageId.id = 'groupFormImageId'; + + let formAction = document.createElement('input'); + formAction.setAttribute('type', 'text'); + formAction.setAttribute('value', 'add'); + formAction.setAttribute('placeholder', '[add, remove]'); + formAction.setAttribute('required', ''); + formAction.classList.add('input-block'); + formAction.id = 'groupFormAction'; + + body.appendChild(formImageId); + body.appendChild(formAction); + + popupShow(title, subtitle, body, [popupCancelButton, formSubmitButton]); +} + +function groupEditConfirm(event) { + // AJAX takes control of subby form :3 + event.preventDefault(); + + let imageId = document.querySelector("#groupFormImageId").value; + let action = document.querySelector("#groupFormAction").value; + let formData = new FormData(); + formData.append("imageId", imageId); + formData.append("action", action); + + fetch('/group/' + group_data['id'], { + method: 'PUT', + body: formData + }).then(response => { + if (response.ok) { + window.location.reload(); + } else { + addNotification('Server exploded, returned:' + response.status, 2); + } + }).catch(error => { + addNotification('Error!!!!! Panic!!!!' + error, 2); + }); +} + +function groupCreatePopup() { + let title = 'New stuff!'; + let subtitle = + 'Image groups are a simple way to ' + + '"group" images together, are you ready?' + + let formSubmitButton = document.createElement('button'); + formSubmitButton.setAttribute('form', 'groupCreateForm'); + formSubmitButton.setAttribute('type', 'submit'); + formSubmitButton.classList.add('btn-block'); + formSubmitButton.classList.add('primary'); + formSubmitButton.innerHTML = 'Huzzah!'; + + // Create form + let body = document.createElement('form'); + body.setAttribute('onsubmit', 'return groupCreateConfirm(event);'); + body.id = 'groupCreateForm'; + + let formName = document.createElement('input'); + formName.setAttribute('type', 'text'); + formName.setAttribute('placeholder', 'Group namey'); + formName.setAttribute('required', ''); + formName.classList.add('input-block'); + formName.id = 'groupFormName'; + + let formDescription = document.createElement('input'); + formDescription.setAttribute('type', 'text'); + formDescription.setAttribute('placeholder', 'What it about????'); + formDescription.classList.add('input-block'); + formDescription.id = 'groupFormDescription'; + + body.appendChild(formName); + body.appendChild(formDescription); + + popupShow(title, subtitle, body, [popupCancelButton, formSubmitButton]); +} + +function groupCreateConfirm(event) { + // AJAX takes control of subby form :3 + event.preventDefault(); + + let name = document.querySelector("#groupFormName").value; + let description = document.querySelector("#groupFormDescription").value; + let formData = new FormData(); + formData.append("name", name); + formData.append("description", description); + + fetch('/group/', { + method: 'POST', + body: formData + }).then(response => { + if (response.ok) { + window.location.reload(); + } else { + addNotification('Server exploded, returned:' + response.status, 2); + } + }).catch(error => { + addNotification('Error summoning group!' + error, 2); + }); +} \ No newline at end of file diff --git a/onlylegs/static/js/imagePage.js b/onlylegs/static/js/imagePage.js new file mode 100644 index 0000000..b98d46d --- /dev/null +++ b/onlylegs/static/js/imagePage.js @@ -0,0 +1,101 @@ +function imageFullscreen() { + let info = document.querySelector('.info-container'); + let image = document.querySelector('.image-container'); + + if (info.classList.contains('collapsed')) { + info.classList.remove('collapsed'); + image.classList.remove('collapsed'); + document.cookie = "image-info=0" + } else { + info.classList.add('collapsed'); + image.classList.add('collapsed'); + document.cookie = "image-info=1" + } +} +function imageDeletePopup() { + let title = 'DESTRUCTION!!!!!!'; + let subtitle = + 'Do you want to delete this image along with ' + + 'all of its data??? This action is irreversible!'; + let body = null; + + let deleteBtn = document.createElement('button'); + deleteBtn.classList.add('btn-block'); + deleteBtn.classList.add('critical'); + deleteBtn.innerHTML = 'Dewww eeeet!'; + deleteBtn.onclick = imageDeleteConfirm; + + popupShow(title, subtitle, body, [popupCancelButton, deleteBtn]); +} +function imageDeleteConfirm() { + popupDismiss(); + + fetch('/image/' + image_data["id"], { + method: 'DELETE', + }).then(response => { + if (response.ok) { + window.location.href = '/'; + } else { + addNotification('Image *clings*', 2); + } + }); +} + +function imageEditPopup() { + let title = 'Edit image!'; + let subtitle = 'Enter funny stuff here!'; + + let formSubmitButton = document.createElement('button'); + formSubmitButton.setAttribute('form', 'imageEditForm'); + formSubmitButton.setAttribute('type', 'submit'); + formSubmitButton.classList.add('btn-block'); + formSubmitButton.classList.add('primary'); + formSubmitButton.innerHTML = 'Saveeee'; + + // Create form + let body = document.createElement('form'); + body.setAttribute('onsubmit', 'return imageEditConfirm(event);'); + body.id = 'imageEditForm'; + + let formAlt = document.createElement('input'); + formAlt.setAttribute('type', 'text'); + formAlt.setAttribute('value', image_data["alt"]); + formAlt.setAttribute('placeholder', 'Image Alt'); + formAlt.classList.add('input-block'); + formAlt.id = 'imageFormAlt'; + + let formDescription = document.createElement('input'); + formDescription.setAttribute('type', 'text'); + formDescription.setAttribute('value', image_data["description"]); + formDescription.setAttribute('placeholder', 'Image Description'); + formDescription.classList.add('input-block'); + formDescription.id = 'imageFormDescription'; + + body.appendChild(formAlt); + body.appendChild(formDescription); + + popupShow(title, subtitle, body, [popupCancelButton, formSubmitButton]); +} + +function imageEditConfirm(event) { + // Yoink subby form + event.preventDefault(); + + let alt = document.querySelector('#imageFormAlt').value; + let description = document.querySelector('#imageFormDescription').value; + let form = new FormData(); + form.append('alt', alt); + form.append('description', description); + + fetch('/image/' + image_data["id"], { + method: 'PUT', + body: form, + }).then(response => { + if (response.ok) { + popupDismiss(); + window.location.reload(); + } else { + addNotification('Image *clings*', 2); + } + }); +} diff --git a/onlylegs/static/js/index.js b/onlylegs/static/js/index.js deleted file mode 100644 index d9d0aa5..0000000 --- a/onlylegs/static/js/index.js +++ /dev/null @@ -1,93 +0,0 @@ -// fade in images -function imgFade(obj, time = 250) { - obj.style.transition = `opacity ${time}ms`; - obj.style.opacity = 1; -} -// Lazy load images when they are in view -function loadOnView() { - const lazyLoad = document.querySelectorAll('#lazy-load'); - const webpSupport = checkWebpSupport(); - - for (let i = 0; i < lazyLoad.length; i++) { - const image = lazyLoad[i]; - if (image.getBoundingClientRect().top < window.innerHeight && image.getBoundingClientRect().bottom > 0) { - if (!image.src && webpSupport) { - image.src = `${image.getAttribute('data-src')}&e=webp`; - } else if (!image.src) { - image.src = image.getAttribute('data-src'); - } - } - } -} - -window.onload = function () { - loadOnView(); - - const times = document.querySelectorAll('.time'); - for (let i = 0; i < times.length; i++) { - // Remove milliseconds - const raw = times[i].innerHTML.split('.')[0]; - - // Parse YYYY-MM-DD HH:MM:SS to Date object - const time = raw.split(' ')[1]; - const date = raw.split(' ')[0].split('-'); - - // Format to YYYY/MM/DD HH:MM:SS and convert to UTC Date object - const dateTime = new Date(`${date[0]}/${date[1]}/${date[2]} ${time} UTC`); - - // Convert to local time - times[i].innerHTML = `${dateTime.toLocaleDateString()} ${dateTime.toLocaleTimeString()}`; - } - - // Top Of Page button - const topOfPage = document.querySelector('.top-of-page'); - if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) { - topOfPage.classList.add('show'); - } else { - topOfPage.classList.remove('show'); - } - topOfPage.onclick = function () { - document.body.scrollTop = 0; - document.documentElement.scrollTop = 0; - } - - // Info button - const infoButton = document.querySelector('.info-button'); - if (infoButton) { - if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) { - infoButton.classList.remove('show'); - } else { - infoButton.classList.add('show'); - } - infoButton.onclick = function () { - popUpShow('OnlyLegs', - 'v0.1.2 ' + - 'using Phosphoricons and Flask.' + - '
Made by Fluffy and others with ❤️'); - } - } -}; -window.onscroll = function () { - loadOnView(); - - // Top Of Page button - const topOfPage = document.querySelector('.top-of-page'); - if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) { - topOfPage.classList.add('show'); - } else { - topOfPage.classList.remove('show'); - } - - // Info button - const infoButton = document.querySelector('.info-button'); - if (infoButton) { - if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) { - infoButton.classList.remove('show'); - } else { - infoButton.classList.add('show'); - } - } -}; -window.onresize = function () { - loadOnView(); -}; diff --git a/onlylegs/static/js/login.js b/onlylegs/static/js/login.js index 1e2287a..ed95512 100644 --- a/onlylegs/static/js/login.js +++ b/onlylegs/static/js/login.js @@ -5,7 +5,7 @@ function showLogin() { cancelBtn.classList.add('btn-block'); cancelBtn.classList.add('transparent'); cancelBtn.innerHTML = 'nuuuuuuuu'; - cancelBtn.onclick = popupDissmiss; + cancelBtn.onclick = popupDismiss; loginBtn = document.createElement('button'); loginBtn.classList.add('btn-block'); @@ -50,7 +50,7 @@ function showLogin() { loginForm.appendChild(passwordInput); loginForm.appendChild(rememberMeSpan); - popUpShow( + popupShow( 'Login!', 'Need an account? Register!', loginForm, @@ -103,7 +103,7 @@ function showRegister() { cancelBtn.classList.add('btn-block'); cancelBtn.classList.add('transparent'); cancelBtn.innerHTML = 'nuuuuuuuu'; - cancelBtn.onclick = popupDissmiss; + cancelBtn.onclick = popupDismiss; registerBtn = document.createElement('button'); registerBtn.classList.add('btn-block'); @@ -146,7 +146,7 @@ function showRegister() { registerForm.appendChild(passwordInput); registerForm.appendChild(passwordInputRepeat); - popUpShow( + popupShow( 'Who are you?', 'Already have an account? Login!', registerForm, diff --git a/onlylegs/static/js/popup.js b/onlylegs/static/js/popup.js index b0b19ac..0ef8cc4 100644 --- a/onlylegs/static/js/popup.js +++ b/onlylegs/static/js/popup.js @@ -1,4 +1,4 @@ -function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null) { +function popupShow(titleText, subtitleText, bodyContent=null, userActions=null) { // Get popup elements const popupSelector = document.querySelector('.pop-up'); const headerSelector = document.querySelector('.pop-up-header'); @@ -9,38 +9,47 @@ function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null) actionsSelector.innerHTML = ''; // Set popup header and subtitle - const titleElement = document.createElement('h2'); - titleElement.innerHTML = titleText; - headerSelector.appendChild(titleElement); + let titleElement = document.createElement('h2'); + titleElement.innerHTML = titleText; + headerSelector.appendChild(titleElement); - const subtitleElement = document.createElement('p'); - subtitleElement.innerHTML = subtitleText; - headerSelector.appendChild(subtitleElement); + let subtitleElement = document.createElement('p'); + subtitleElement.innerHTML = subtitleText; + headerSelector.appendChild(subtitleElement); - if (bodyContent) { - headerSelector.appendChild(bodyContent); - } + if (bodyContent) { headerSelector.appendChild(bodyContent) } // Set buttons that will be displayed if (userActions) { - // for each user action, add the element - for (let i = 0; i < userActions.length; i++) { - actionsSelector.appendChild(userActions[i]); - } + userActions.forEach((action) => { + actionsSelector.appendChild(action); + }); } else { - actionsSelector.innerHTML = ''; + let closeButton = document.createElement('button'); + closeButton.classList.add('btn-block'); + closeButton.classList.add('transparent'); + closeButton.innerHTML = 'Yeet!'; + closeButton.onclick = popupDismiss; + actionsSelector.appendChild(closeButton); } // Stop scrolling and show popup document.querySelector("html").style.overflow = "hidden"; popupSelector.style.display = 'block'; - setTimeout(() => { popupSelector.classList.add('active') }, 5); // 2ms delay to allow for css transition >:C + + // 5ms delay to allow for css transition >:C + setTimeout(() => { popupSelector.classList.add('active') }, 5); } -function popupDissmiss() { +function popupDismiss() { const popupSelector = document.querySelector('.pop-up'); - document.querySelector("html").style.overflow = "auto"; popupSelector.classList.remove('active'); setTimeout(() => { popupSelector.style.display = 'none'; }, 200); } + +const popupCancelButton = document.createElement('button'); + popupCancelButton.classList.add('btn-block'); + popupCancelButton.classList.add('transparent'); + popupCancelButton.innerHTML = 'nuuuuuuuu'; + popupCancelButton.onclick = popupDismiss; diff --git a/onlylegs/static/js/square.js b/onlylegs/static/js/square.js new file mode 100644 index 0000000..ff9bfb4 --- /dev/null +++ b/onlylegs/static/js/square.js @@ -0,0 +1,6 @@ +function keepSquare() { + let square = document.getElementsByClassName('square') + for (let i = 0; i < square.length; i++) { + square[i].style.height = square[i].offsetWidth + 'px'; + } +} \ No newline at end of file diff --git a/onlylegs/static/js/webp.js b/onlylegs/static/js/webp.js deleted file mode 100644 index 93a4ade..0000000 --- a/onlylegs/static/js/webp.js +++ /dev/null @@ -1,10 +0,0 @@ -function checkWebpSupport() { - let webpSupport = false; - try { - webpSupport = document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0; - } catch (e) { - webpSupport = false; - } - - return webpSupport; -} diff --git a/onlylegs/static/logo-black.svg b/onlylegs/static/logo-black.svg deleted file mode 100644 index 559ad4d..0000000 --- a/onlylegs/static/logo-black.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - diff --git a/onlylegs/static/logo-white.svg b/onlylegs/static/logo-white.svg deleted file mode 100644 index a50b3f3..0000000 --- a/onlylegs/static/logo-white.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - diff --git a/onlylegs/static/manifest.json b/onlylegs/static/manifest.json deleted file mode 100644 index ab9009a..0000000 --- a/onlylegs/static/manifest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "OnlyLegs", - "short_name": "OnlyLegs", - "start_url": "/", - "display": "standalone", - "background_color": "#151515", - "theme_color": "#151515", - "description": "A gallery built for fast and simple image management!", - "icons": [ - { - "src": "icon.png", - "sizes": "621x621", - "type": "image/png" - } - ], - "splash_pages": null - } - \ No newline at end of file diff --git a/onlylegs/static/sass/animations.sass b/onlylegs/static/sass/animations.sass index 9fc623e..d85be1b 100644 --- a/onlylegs/static/sass/animations.sass +++ b/onlylegs/static/sass/animations.sass @@ -8,4 +8,12 @@ 0% left: -100% 100% - left: 100% \ No newline at end of file + left: 100% + +@keyframes imgFadeIn + 0% + opacity: 0 + // filter: blur(0.5rem) + 100% + opacity: 1 + // filter: blur(0) diff --git a/onlylegs/static/sass/components/banner.sass b/onlylegs/static/sass/components/banner.sass index 483c071..037e38d 100644 --- a/onlylegs/static/sass/components/banner.sass +++ b/onlylegs/static/sass/components/banner.sass @@ -20,26 +20,10 @@ background-color: RGB($fg-black) color: RGB($fg-white) - &::after - content: '' - - width: $rad - height: calc(#{$rad} * 2) - - position: absolute - bottom: calc(#{$rad} * -2) - left: 0 - - background-color: RGB($bg-bright) - border-radius: $rad 0 0 0 - box-shadow: 0 calc(#{$rad} * -1) 0 0 RGB($bg-100) - .banner height: 30rem max-height: 69vh - background-color: RGB($bg-300) - img position: absolute inset: 0 @@ -122,10 +106,10 @@ background-color: RGB($primary) border-radius: $rad + overflow: hidden .banner-small height: 3.5rem - background-color: RGB($bg-100) .banner-content padding: 0 0.5rem diff --git a/onlylegs/static/sass/components/buttons/block.sass b/onlylegs/static/sass/components/buttons/block.sass index 3891edd..f16f96e 100644 --- a/onlylegs/static/sass/components/buttons/block.sass +++ b/onlylegs/static/sass/components/buttons/block.sass @@ -59,6 +59,10 @@ &.black @include btn-block($black) + &.disabled, &:disabled + color: RGB($fg-dim) + cursor: unset + .input-checkbox padding: 0 display: flex diff --git a/onlylegs/static/sass/components/buttons/pill.sass b/onlylegs/static/sass/components/buttons/pill.sass index e9723f3..3767c01 100644 --- a/onlylegs/static/sass/components/buttons/pill.sass +++ b/onlylegs/static/sass/components/buttons/pill.sass @@ -68,6 +68,10 @@ color: RGB($primary) + &.disabled, &:disabled + color: RGB($fg-dim) + cursor: unset + .pill__critical color: RGB($critical) diff --git a/onlylegs/static/sass/components/gallery.sass b/onlylegs/static/sass/components/gallery.sass index 9b980ce..87d63ca 100644 --- a/onlylegs/static/sass/components/gallery.sass +++ b/onlylegs/static/sass/components/gallery.sass @@ -24,8 +24,6 @@ margin: 0.35rem padding: 0 - height: auto - position: relative border-radius: $rad-inner @@ -44,8 +42,7 @@ height: auto position: absolute - left: 0 - bottom: 0 + inset: 0 display: flex flex-direction: column @@ -88,19 +85,6 @@ object-position: center background-color: RGB($bg-bright) - filter: blur(0.5rem) - opacity: 0 - - transition: all 0.2s cubic-bezier(.79, .14, .15, .86) - - &.loaded - filter: blur(0) - opacity: 1 - - &:after - content: "" - display: block - padding-bottom: 100% &:hover box-shadow: 0 0.2rem 0.4rem 0.1rem RGBA($bg-100, 0.6) @@ -112,8 +96,6 @@ margin: 0.35rem padding: 0 - height: auto - position: relative border-radius: $rad-inner @@ -178,8 +160,7 @@ height: 100% position: absolute - top: 0 - left: 0 + inset: 0 object-fit: cover object-position: center @@ -187,14 +168,8 @@ background-color: RGB($bg-bright) border-radius: $rad-inner box-shadow: 0 0 0.4rem 0.25rem RGBA($bg-100, 0.1) - filter: blur(0.5rem) - opacity: 0 - transition: all 0.2s cubic-bezier(.79, .14, .15, .86) - - &.loaded - filter: blur(0) - opacity: 1 + transition: transform 0.2s cubic-bezier(.79, .14, .15, .86) &.size-1 .data-1 @@ -219,11 +194,6 @@ transform: scale(0.6) rotate(-1deg) translate(-15%, -23%) z-index: +1 - &:after - content: "" - display: block - padding-bottom: 100% - &:hover .images &.size-1 @@ -252,3 +222,7 @@ @media (max-width: 800px) .gallery-grid grid-template-columns: auto auto auto + + .gallery-item + margin: 0.35rem + position: relative \ No newline at end of file diff --git a/onlylegs/static/sass/components/image-view.sass b/onlylegs/static/sass/components/image-view.sass new file mode 100644 index 0000000..1439670 --- /dev/null +++ b/onlylegs/static/sass/components/image-view.sass @@ -0,0 +1,259 @@ +.info-container + padding: 0.5rem 0 0 0.5rem + width: 27rem + position: absolute + top: 0 + left: 0 + bottom: 0 + background-image: linear-gradient(90deg, $bg-transparent, transparent) + overflow-y: auto + transition: left 0.3s cubic-bezier(0.76, 0, 0.17, 1) + z-index: 2 + -ms-overflow-style: none + scrollbar-width: none + &::-webkit-scrollbar + display: none + + &.collapsed + left: -27rem +@media (max-width: 1100px) + .info-container + padding: 0 0.5rem 0 0.5rem + width: 100% + position: relative + background: none + // While probably not the best way of doing this + // Not bothered to fight with CSS today + &.collapsed + left: 0 + +details + margin-bottom: 0.5rem + padding: 0.5rem + display: flex + flex-direction: column + background-color: RGB($bg-300) + color: RGB($fg-white) + border-radius: $rad + overflow: hidden + + summary + height: 1.5rem + display: flex + flex-direction: row + justify-content: flex-start + align-items: center + position: relative + color: RGB($primary) + + > i + margin-right: 0 + font-size: 1.25rem + + &.collapse-indicator + transition: transform 0.15s cubic-bezier(.79, .14, .15, .86) + + h2 + margin: 0 0.5rem + font-size: 1.1rem + font-weight: 500 + + &[open] + summary + margin-bottom: 0.5rem + + > i.collapse-indicator + transform: rotate(90deg) + + p + margin: 0 + padding: 0 + + font-size: 1rem + font-weight: 400 + + text-overflow: ellipsis + overflow: hidden + + .link + margin: 0 + padding: 0 + + color: RGB($primary) + + cursor: pointer + text-decoration: none + + &:hover + text-decoration: underline + + .pfp + width: 1.1rem + height: 1.1rem + + border-radius: $rad-inner + + object-fit: cover + + table + margin: 0 + padding: 0 + + width: 100% + + overflow-x: hidden + border-collapse: collapse + + tr + white-space: nowrap + + td + padding-bottom: 0.5rem + + max-width: 0 + + font-size: 1rem + font-weight: 400 + + vertical-align: top + + > * + vertical-align: top + + td:first-child + padding-right: 0.5rem + + width: 50% + + overflow: hidden + text-overflow: ellipsis + white-space: nowrap + td:last-child + width: 50% + + white-space: normal + word-break: break-word + + tr:last-of-type td + padding-bottom: 0 + +.img-colours + width: 100% + + display: flex + gap: 0.5rem + + button + margin: 0 + padding: 0 + + width: 1.6rem + height: 1.6rem + + display: flex + justify-content: center + align-items: center + + border-radius: $rad-inner + border: none + + i + font-size: 1rem + opacity: 0 + transition: opacity 0.15s ease-in-out + + &:hover i + opacity: 1 + +.img-groups + width: 100% + display: flex + flex-wrap: wrap + gap: 0.5rem + +.image-container + padding: 0.5rem + position: absolute + top: 0 + left: 27rem + right: 0 + bottom: 0 + z-index: 2 + transition: left 0.3s cubic-bezier(0.76, 0, 0.17, 1), padding 0.3s cubic-bezier(0.76, 0, 0.17, 1) + + picture + margin: auto + width: 100% + height: 100% + display: flex + overflow: hidden + + img + margin: auto + padding: 0 + width: auto + height: auto + max-width: 100% + max-height: 100% + object-fit: contain + object-position: center + border-radius: $rad + + &.collapsed + padding: 0 + left: 0 + + picture img + border-radius: 0 +@media (max-width: 1100px) + .image-container + position: relative + left: 0 + + picture + margin: 0 auto + max-height: 69vh + + img + max-height: 69vh + + &.collapsed + padding: 0.5rem + left: 0 + + picture img + border-radius: $rad + +.background + position: absolute + inset: 0 + background-color: RGB($bg-300) + background-image: linear-gradient(to right, RGB($bg-400) 15%, RGB($bg-200) 35%, RGB($bg-400) 50%) + background-size: 1000px 640px + animation: imgLoading 1.8s linear infinite forwards + user-select: none + overflow: hidden + z-index: 1 + + img + position: absolute + inset: 0 + width: 100% + height: 100% + background-color: RGB($fg-white) + filter: blur(3rem) saturate(1.2) brightness(0.7) + transform: scale(1.1) + object-fit: cover + object-position: center center + + &::after + content: '' + position: absolute + inset: 0 + width: 100% + height: 100% + z-index: +1 + +@media (max-width: 1100px) + #fullscreenImage + display: none \ No newline at end of file diff --git a/onlylegs/static/sass/components/image-view/background.sass b/onlylegs/static/sass/components/image-view/background.sass deleted file mode 100644 index e7478b5..0000000 --- a/onlylegs/static/sass/components/image-view/background.sass +++ /dev/null @@ -1,42 +0,0 @@ -.background - width: 100% - height: 100vh - - position: fixed - top: 0 - left: 0 - - background-color: RGB($bg-300) - background-image: linear-gradient(to right, RGB($bg-400) 15%, RGB($bg-200) 35%, RGB($bg-400) 50%) - background-size: 1000px 640px - animation: imgLoading 1.8s linear infinite forwards - - user-select: none - overflow: hidden - z-index: 1 - - img - position: absolute - top: 0 - left: 0 - - width: 100% - height: 100% - - background-color: RGB($fg-white) - - filter: blur(1rem) saturate(1.2) - transform: scale(1.1) - - object-fit: cover - object-position: center center - - span - position: absolute - top: 0 - left: 0 - - width: 100% - height: 100% - - z-index: +1 \ No newline at end of file diff --git a/onlylegs/static/sass/components/image-view/image.sass b/onlylegs/static/sass/components/image-view/image.sass deleted file mode 100644 index 99fd1ac..0000000 --- a/onlylegs/static/sass/components/image-view/image.sass +++ /dev/null @@ -1,28 +0,0 @@ -.image-container - margin: auto - - width: 100% - height: 100% - - display: flex - overflow: hidden - - img - margin: auto - padding: 0 - - width: auto - height: auto - max-width: 100% - max-height: 100% - - object-fit: contain - object-position: center - -@media (max-width: 1100px) - .image-container - margin: 0 auto - max-height: 69vh - - img - max-height: 69vh \ No newline at end of file diff --git a/onlylegs/static/sass/components/image-view/info-tab.sass b/onlylegs/static/sass/components/image-view/info-tab.sass deleted file mode 100644 index 1b42c1e..0000000 --- a/onlylegs/static/sass/components/image-view/info-tab.sass +++ /dev/null @@ -1,215 +0,0 @@ -.info-container - padding: 0.5rem 0 0.5rem 0.5rem - - width: 27rem - height: 100vh - - position: absolute - top: 0 - left: 0 - - display: flex - flex-direction: column - gap: 0.5rem - - background-image: linear-gradient(90deg, $bg-transparent, transparent) - - overflow-y: auto - z-index: +4 - transition: left 0.3s cubic-bezier(0.76, 0, 0.17, 1) - -ms-overflow-style: none - scrollbar-width: none - &::-webkit-scrollbar - display: none - - &.collapsed - left: -27rem - -.info-tab - width: 100% - - display: flex - flex-direction: column - - position: relative - - background-color: RGB($bg-300) - border-radius: $rad - - transition: max-height 0.3s cubic-bezier(.79, .14, .15, .86) - - &.collapsed - height: 2.5rem - - .collapse-indicator - transform: rotate(90deg) - - .info-header - border-radius: $rad - - .info-table - display: none - -.collapse-indicator - margin: 0 - padding: 0 - - position: absolute - top: 0.6rem - right: 0.6rem - - background-color: transparent - color: RGB($primary) - border: none - - z-index: +2 - - transition: transform 0.15s cubic-bezier(.79, .14, .15, .86) - cursor: pointer - - > i - font-size: 1.1rem - color: RGB($primary) - -.info-header - padding: 0.5rem - - width: 100% - height: 2.5rem - - display: flex - justify-content: start - align-items: center - gap: 0.5rem - - background-color: RGB($bg-200) - border-radius: $rad $rad 0 0 - - > i - font-size: 1.25rem - color: RGB($primary) - - h2 - margin: 0 - - font-size: 1.1rem - font-weight: 500 - - color: RGB($primary) - - text-overflow: ellipsis - overflow: hidden - -.info-table - margin: 0 - padding: 0.5rem - - display: flex - flex-direction: column - gap: 1rem - - color: RGB($fg-white) - - overflow-x: hidden - - p - margin: 0 - padding: 0 - - font-size: 1rem - font-weight: 400 - - text-overflow: ellipsis - overflow: hidden - - .link - margin: 0 - padding: 0 - - color: RGB($primary) - - cursor: pointer - text-decoration: none - - &:hover - text-decoration: underline - - table - margin: 0 - padding: 0 - - width: 100% - - overflow-x: hidden - border-collapse: collapse - - tr - white-space: nowrap - - td - padding-bottom: 0.5rem - - max-width: 0 - - font-size: 1rem - font-weight: 400 - - vertical-align: top - - td:first-child - padding-right: 0.5rem - - width: 50% - - overflow: hidden - text-overflow: ellipsis - white-space: nowrap - td:last-child - width: 50% - - white-space: normal - word-break: break-word - - tr:last-of-type td - padding-bottom: 0 - -.img-colours - width: 100% - - display: flex - gap: 0.5rem - - span - margin: 0 - padding: 0 - - width: 1.5rem - height: 1.5rem - - display: flex - justify-content: center - align-items: center - - border-radius: $rad-inner - // border: 1px solid RGB($white) - -.img-groups - width: 100% - display: flex - flex-wrap: wrap - gap: 0.5rem - -@media (max-width: 1100px) - .info-container - padding: 0.5rem - - width: 100% - height: 100% - - position: relative - - &.collapsed - left: unset - - .info-container - background: transparent diff --git a/onlylegs/static/sass/components/image-view/view.sass b/onlylegs/static/sass/components/image-view/view.sass deleted file mode 100644 index 8103d88..0000000 --- a/onlylegs/static/sass/components/image-view/view.sass +++ /dev/null @@ -1,59 +0,0 @@ -@import 'background' -@import 'info-tab' -@import 'image' - - -.image-grid - padding: 0 - - width: 100% - height: 100vh - - position: relative - - display: flex - flex-direction: column - z-index: 3 - - .image-block - margin: 0 0 0 27rem - padding: 0.5rem - - width: calc(100% - 27rem) - height: 100vh - - position: relative - - display: flex - flex-direction: column - gap: 0 - - z-index: 3 - transition: margin 0.3s cubic-bezier(0.76, 0, 0.17, 1), width 0.3s cubic-bezier(0.76, 0, 0.17, 1) - - .pill-row - margin-top: 0.5rem - - &.collapsed - .image-block - margin: 0 - width: 100% - -@media (max-width: 1100px) - .image-grid - height: auto - - .image-block - margin: 0 - padding: 0.5rem 0.5rem 0 0.5rem - - width: 100% - height: auto - - transition: margin 0s, width 0s - - .pill-row - #fullscreenImage - display: none - - diff --git a/onlylegs/static/sass/components/navigation.sass b/onlylegs/static/sass/components/navigation.sass index c58805e..760a7d3 100644 --- a/onlylegs/static/sass/components/navigation.sass +++ b/onlylegs/static/sass/components/navigation.sass @@ -1,36 +1,20 @@ -.navigation - margin: 0 - padding: 0 - +nav width: 3.5rem height: 100% height: 100dvh display: flex flex-direction: column - justify-content: space-between position: fixed top: 0 left: 0 - background-color: RGB($bg-100) - color: RGB($fg-white) + background-color: transparent + color: inherit z-index: 69 - .logo - margin: 0 - padding: 0 - - width: 3.5rem - height: 3.5rem - min-height: 3.5rem - - display: flex - flex-direction: row - align-items: center - .navigation-spacer height: 100% @@ -50,6 +34,7 @@ align-items: center background-color: transparent + color: inherit border: none text-decoration: none @@ -58,7 +43,7 @@ padding: 0.5rem font-size: 1.3rem border-radius: $rad-inner - color: RGB($fg-white) + color: inherit > .nav-pfp padding: 0.4rem @@ -72,68 +57,29 @@ object-fit: cover border-radius: $rad-inner - .tool-tip - padding: 0.4rem 0.7rem - - display: block - - position: absolute - top: 50% - left: 3rem - transform: translateY(-50%) - - font-size: 0.9rem - font-weight: 500 - - background-color: RGB($bg-100) - color: RGB($fg-white) - opacity: 0 - border-radius: $rad-inner - - transition: opacity 0.2s cubic-bezier(.76,0,.17,1), left 0.2s cubic-bezier(.76,0,.17,1) - - pointer-events: none - - > i - display: block - - position: absolute - top: 50% - left: -0.45rem - transform: translateY(-50%) - - font-size: 0.75rem - - color: RGB($bg-100) - &:hover > i, .nav-pfp background: RGBA($fg-white, 0.1) - span - opacity: 1 - left: 3.9rem - &.selected - > i - color: RGB($primary) + color: RGB($primary) &::before content: '' display: block position: absolute - top: 3px - left: 0 + top: 0.5rem + left: 0.2rem width: 3px - height: calc(100% - 6px) + height: calc(100% - 1rem) - background-color: RGB($primary) + background-color: currentColor border-radius: $rad-inner @media (max-width: $breakpoint) - .navigation + nav width: 100vw height: 3.5rem @@ -145,6 +91,8 @@ bottom: 0 left: 0 + background-color: RGB($background) + > span display: none diff --git a/onlylegs/static/sass/components/notification.sass b/onlylegs/static/sass/components/notification.sass index 3a468a2..feea589 100644 --- a/onlylegs/static/sass/components/notification.sass +++ b/onlylegs/static/sass/components/notification.sass @@ -22,23 +22,22 @@ margin: 0 padding: 0 - width: 450px + width: 24rem height: auto position: fixed - top: 0.3rem + bottom: 0.3rem right: 0.3rem display: flex - flex-direction: column + flex-direction: column-reverse z-index: 621 .sniffle__notification - margin: 0 0 0.3rem 0 + margin-top: 0.3rem padding: 0 - width: 450px height: auto max-height: 100px @@ -56,7 +55,7 @@ box-sizing: border-box overflow: hidden - transition: all 0.25s ease-in-out, opacity 0.2s ease-in-out, transform 0.2s cubic-bezier(.68,-0.55,.27,1.55) + transition: opacity 0.2s ease-in-out, transform 0.2s cubic-bezier(.68,-0.55,.27,1.55) &::after content: "" @@ -89,10 +88,8 @@ &.hide margin: 0 max-height: 0 - opacity: 0 transform: translateX(100%) - transition: all 0.4s ease-in-out, max-height 0.2s ease-in-out .sniffle__notification-icon @@ -130,6 +127,7 @@ @media (max-width: $breakpoint) .notifications + bottom: 3.8rem width: calc(100vw - 0.6rem) height: auto @@ -138,7 +136,7 @@ &.hide opacity: 0 - transform: translateY(-5rem) + transform: translateY(1rem) .sniffle__notification-time width: 100% diff --git a/onlylegs/static/sass/components/settings.sass b/onlylegs/static/sass/components/settings.sass index cce4ac3..8587a76 100644 --- a/onlylegs/static/sass/components/settings.sass +++ b/onlylegs/static/sass/components/settings.sass @@ -26,4 +26,4 @@ padding: 0 font-size: 1.25rem - font-weight: 700 \ No newline at end of file + font-weight: 700 diff --git a/onlylegs/static/sass/components/tags.sass b/onlylegs/static/sass/components/tags.sass index 4901829..6da6e67 100644 --- a/onlylegs/static/sass/components/tags.sass +++ b/onlylegs/static/sass/components/tags.sass @@ -1,11 +1,11 @@ .tag-icon margin: 0 - padding: 0.25rem 0.5rem + padding: 0.3rem 0.5rem display: flex - align-items: center + align-items: flex-end justify-content: center - gap: 0.25rem + gap: 0.3rem font-size: 0.9rem font-weight: 500 @@ -19,9 +19,8 @@ cursor: pointer transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out - svg - width: 1.15rem - height: 1.15rem + i + font-size: 1.15rem &:hover - background-color: RGBA($primary, 0.3) + background-color: RGBA($primary, 0.2) diff --git a/onlylegs/static/sass/style.sass b/onlylegs/static/sass/style.sass index 097346c..daf7094 100644 --- a/onlylegs/static/sass/style.sass +++ b/onlylegs/static/sass/style.sass @@ -18,15 +18,13 @@ @import "components/buttons/pill" @import "components/buttons/block" -@import "components/image-view/view" +@import "components/image-view" @import "components/settings" -// Reset * box-sizing: border-box - font-family: $font - scrollbar-color: RGB($primary) transparent + font-family: $font ::-webkit-scrollbar width: 0.5rem @@ -37,66 +35,49 @@ ::-webkit-scrollbar-thumb:hover background: RGB($fg-white) -html, body +html margin: 0 padding: 0 - - min-height: 100vh - max-width: 100vw - - background-color: RGB($fg-white) - scroll-behavior: smooth - overflow-x: hidden -.wrapper +body margin: 0 padding: 0 0 0 3.5rem + max-width: 100% min-height: 100vh + display: grid + grid-template-rows: auto 1fr auto + + background-color: RGB($background) + color: RGB($foreground) + + overflow-x: hidden +@media (max-width: $breakpoint) + body + padding: 0 0 3.5rem 0 + +main display: flex flex-direction: column - - background-color: RGB($bg-bright) - color: RGB($bg-100) - -.big-text - height: 20rem - - display: flex - flex-direction: column - justify-content: center - align-items: center - - color: RGB($bg-100) - - h1 - margin: 0 2rem - - font-size: 4rem - font-weight: 900 - text-align: center - - p - margin: 0 2rem - - max-width: 40rem - font-size: 1rem - font-weight: 400 - text-align: center + position: relative + background: RGBA($white, 1) + color: RGB($fg-black) + border-top-left-radius: $rad + overflow: hidden +@media (max-width: $breakpoint) + main + border-top-left-radius: 0 .error-page - width: 100% - height: 100vh + min-height: 100% display: flex flex-direction: column justify-content: center align-items: center - background-color: RGB($bg-bright) - h1 margin: 0 2rem @@ -113,23 +94,8 @@ html, body font-size: 1.25rem font-weight: 400 text-align: center - - color: $fg-black - - @media (max-width: $breakpoint) - .wrapper - padding: 0 0 3.5rem 0 - - .big-text - height: calc(75vh - 3.5rem) - - h1 - font-size: 3.5rem - .error-page - height: calc(100vh - 3.5rem) - h1 font-size: 4.5rem diff --git a/onlylegs/static/sass/variables.sass b/onlylegs/static/sass/variables.sass index bc1bd9f..e3fcd83 100644 --- a/onlylegs/static/sass/variables.sass +++ b/onlylegs/static/sass/variables.sass @@ -37,6 +37,11 @@ $font: 'Rubik', sans-serif $breakpoint: 800px +// New variables, Slowly moving over to them because I suck at planning ahead and coding reeee +$background: var(--bg-100) +$foreground: var(--fg-white) + + \:root --bg-dim: 16, 16, 16 --bg-bright: 232, 227, 227 @@ -66,7 +71,7 @@ $breakpoint: 800px --success: var(--green) --info: var(--blue) - --rad: 8px + --rad: 0.5rem --rad-inner: calc(var(--rad) / 2) --animation-smooth: cubic-bezier(0.76, 0, 0.17, 1) diff --git a/onlylegs/templates/base.html b/onlylegs/templates/base.html new file mode 100644 index 0000000..b580c7f --- /dev/null +++ b/onlylegs/templates/base.html @@ -0,0 +1,195 @@ + + + + {{ config.WEBSITE_CONF.name }} + + + + + + + + + + + + + + + + + + + + + + + + + + {% assets "scripts" %} {% endassets %} + {% assets "styles" %} {% endassets %} + {% block head %}{% endblock %} + + +
+ + +
+ +
+
+
+
+
+ + + +
{% block header %}{% endblock %}
+ +
+ {% if current_user.is_authenticated %} +
+ +
+ +

Upload stuffs

+

May the world see your stuff 👀

+
+ + + + + + +
+
+
+
+ {% endif %} + + {% block content %}{% endblock %} +
+ + + + {% block script %}{% endblock %} + + \ No newline at end of file diff --git a/onlylegs/templates/error.html b/onlylegs/templates/error.html index 9d76c79..7e9c20c 100644 --- a/onlylegs/templates/error.html +++ b/onlylegs/templates/error.html @@ -1,7 +1,7 @@ -{% extends 'layout.html' %} +{% extends 'base.html' %} {% block content %} - +

{{error}}

{{msg}}

- +
{% endblock %} diff --git a/onlylegs/templates/group.html b/onlylegs/templates/group.html index bebe8ec..67cfb1f 100644 --- a/onlylegs/templates/group.html +++ b/onlylegs/templates/group.html @@ -1,227 +1,76 @@ -{% extends 'layout.html' %} +{% extends 'base.html' %} +{% from 'macros/image.html' import gallery_item %} {% block nav_groups %}selected{% endblock %} + {% block head %} {% if images %} - + + + + {% endif %} {% endblock %} -{% block content %} + +{% block header %} {% if images %} {% endif %} +{% endblock %} +{% block content %} {% if images %} {% else %}
diff --git a/onlylegs/templates/image.html b/onlylegs/templates/image.html index 5cf33d9..2a76cf0 100644 --- a/onlylegs/templates/image.html +++ b/onlylegs/templates/image.html @@ -1,215 +1,157 @@ -{% extends 'layout.html' %} +{% extends 'base.html' %} {% block page_index %} {% if return_page %}?page={{ return_page }}{% endif %}{% endblock %} + {% block head %} - - + + + + {% endblock %} -{% block content %} -
- {{ image.alt }} - -
- -
-
-
- {{ image.alt }} -
+{% block header %} + {% endblock %} -{% block script %} - -{% endblock %} \ No newline at end of file +
+ + + + {{ image.alt }} + +
+ +
+
+ +

Info

+ +
+ + + + + + + + + + + {% if image.description %} + + + + + {% endif %} +
Author + {% if image.author.picture %} + Profile Picture + {% endif %} + {{ image.author.username }} +
Upload date{{ image.created_at }}
Description{{ image.description }}
+
+ {% for col in image.colours %} + + {% endfor %} +
+ {% if image.groups %} +
+ {% for group in image.groups %} + {{ group['name'] }} + {% endfor %} +
+ {% endif %} +
+ {% for tag in image.exif %} +
+ + {% if tag == 'Photographer' %} +

Photographer

+ {% elif tag == 'Camera' %} +

Camera

+ {% elif tag == 'Software' %} +

Software

+ {% elif tag == 'File' %} +

File

+ {% else %} +

{{ tag }}

+ {% endif %} + + +
+ + {% for subtag in image.exif[tag] %} + + + {% if image.exif[tag][subtag]['formatted'] %} + {% if image.exif[tag][subtag]['type'] == 'date' %} + + {% else %} + + {% endif %} + {% elif image.exif[tag][subtag]['raw'] %} + + {% else %} + + {% endif %} + + {% endfor %} +
{{ subtag }}{{ image.exif[tag][subtag]['formatted'] }}{{ image.exif[tag][subtag]['formatted'] }}{{ image.exif[tag][subtag]['raw'] }}Oops, an error
+
+ {% endfor %} +
+{% endblock %} diff --git a/onlylegs/templates/index.html b/onlylegs/templates/index.html index 340313f..eead3a8 100644 --- a/onlylegs/templates/index.html +++ b/onlylegs/templates/index.html @@ -1,11 +1,17 @@ -{% extends 'layout.html' %} -{% block nav_home %}selected{% endblock %} -{% block content %} +{% extends 'base.html' %} +{% from 'macros/image.html' import gallery_item %} +{% block head %} + + + +{% endblock %} + +{% block header %} +{% endblock %} +{% block nav_home %}selected{% endblock %} +{% block content %} {% if images %} {% else %}
diff --git a/onlylegs/templates/layout.html b/onlylegs/templates/layout.html deleted file mode 100644 index e01adb2..0000000 --- a/onlylegs/templates/layout.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - {{ config.WEBSITE_CONF.name }} - - - - - - - - - - - - - - - - - - - - - - - - - - {% assets "scripts" %} - - {% endassets %} - - {% assets "styles" %} - - {% endassets %} - - {% block head %}{% endblock %} - - -
- - - {% if request.path == "/" %}{% endif %} - -
- -
-
-
-
-
- -
- - - - {% if current_user.is_authenticated %} -
- -
- -

Upload stuffs

-

May the world see your stuff 👀

-
- - - - - - -
-
-
-
- {% endif %} - -
- {% block content %} - {% endblock %} -
-
- - - - {% block script %}{% endblock %} - - \ No newline at end of file diff --git a/onlylegs/templates/list.html b/onlylegs/templates/list.html index af2edaa..21a7c9b 100644 --- a/onlylegs/templates/list.html +++ b/onlylegs/templates/list.html @@ -1,100 +1,11 @@ -{% extends 'layout.html' %} +{% extends 'base.html' %} {% block nav_groups %}selected{% endblock %} + {% block head %} - {% if images %} - - {% endif %} - - {% if current_user.is_authenticated %} - - {% endif %} + {% if images %}{% endif %} {% endblock %} -{% block content %} + +{% block header %} +{% endblock %} +{% block content %} {% if groups %}