diff --git a/README.md b/README.md index 20f5d0e..ac0254c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - + diff --git a/gallery/__init__.py b/gallery/__init__.py index 80f403b..1793a69 100644 --- a/gallery/__init__.py +++ b/gallery/__init__.py @@ -25,50 +25,52 @@ from sqlalchemy.orm import sessionmaker from gallery import db -USER_DIR = platformdirs.user_config_dir('onlylegs') +USER_DIR = platformdirs.user_config_dir("onlylegs") db_session = sessionmaker(bind=db.engine) db_session = db_session() login_manager = LoginManager() assets = Environment() -cache = Cache(config={'CACHE_TYPE': 'SimpleCache', 'CACHE_DEFAULT_TIMEOUT': 300}) +cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300}) compress = Compress() -def create_app(test_config=None): +def create_app(test_config=None): # pylint: disable=R0914 """ 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")) # Get environment variables - load_dotenv(os.path.join(USER_DIR, '.env')) + load_dotenv(os.path.join(USER_DIR, ".env")) print("Loaded environment variables") # Get config file - with open(os.path.join(USER_DIR, 'conf.yml'), encoding='utf-8', mode='r') as file: + with open(os.path.join(USER_DIR, "conf.yml"), encoding="utf-8", mode="r") as file: conf = safe_load(file) print("Loaded config") # App configuration app.config.from_mapping( - SECRET_KEY=os.environ.get('FLASK_SECRET'), - DATABASE=os.path.join(app.instance_path, 'gallery.sqlite3'), - UPLOAD_FOLDER=os.path.join(USER_DIR, 'uploads'), - ALLOWED_EXTENSIONS=conf['upload']['allowed-extensions'], - MAX_CONTENT_LENGTH=1024 * 1024 * conf['upload']['max-size'], - WEBSITE=conf['website'], + SECRET_KEY=os.environ.get("FLASK_SECRET"), + DATABASE=os.path.join(app.instance_path, "gallery.sqlite3"), + UPLOAD_FOLDER=os.path.join(USER_DIR, "uploads"), + ALLOWED_EXTENSIONS=conf["upload"]["allowed-extensions"], + MAX_CONTENT_LENGTH=1024 * 1024 * conf["upload"]["max-size"], + ADMIN_CONF=conf["admin"], + UPLOAD_CONF=conf["upload"], + WEBSITE_CONF=conf["website"], ) if test_config is None: - app.config.from_pyfile('config.py', silent=True) + app.config.from_pyfile("config.py", silent=True) else: app.config.from_mapping(test_config) login_manager.init_app(app) - login_manager.login_view = 'gallery.index' - login_manager.session_protection = 'strong' + login_manager.login_view = "gallery.index" + login_manager.session_protection = "strong" @login_manager.user_loader def load_user(user_id): @@ -77,50 +79,54 @@ def create_app(test_config=None): @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 + msg = "You are not authorized to view this page!!!!" + return render_template("error.html", error=error, msg=msg), error - js_pre = Bundle( - 'js/pre/*.js', - output='gen/pre_packed.js', - depends='js/pre/*.js' + lib = Bundle( + "lib/*.js", filters="jsmin", output="gen/lib.js", depends="lib/*.js" ) - js_post = Bundle( - 'js/post/*.js', - output='gen/post_packed.js', - depends='js/post/*.js' + scripts = Bundle( + "js/*.js", filters="jsmin", output="gen/index.js", depends="js/*.js" ) styles = Bundle( - 'sass/*.sass', - filters='libsass', - output='gen/styles.css', - depends='sass/**/*.sass' + "sass/*.sass", filters="libsass, cssmin", output="gen/styles.css", depends='sass/**/*.sass' ) - assets.register('js_pre', js_pre) - assets.register('js_post', js_post) - assets.register('styles', styles) + assets.register("lib", lib) + assets.register("js", scripts) + assets.register("styles", styles) # Error handlers, if the error is not a HTTP error, return 500 @app.errorhandler(Exception) def error_page(err): # noqa if not isinstance(err, HTTPException): abort(500) - return render_template('error.html', error=err.code, msg=err.description), err.code + return ( + render_template("error.html", error=err.code, msg=err.description), + err.code, + ) # Load login, registration and logout manager from gallery import auth + app.register_blueprint(auth.blueprint) - # Load the different routes - from gallery.views import api, groups, routing, settings + # Load the API + from gallery import api + app.register_blueprint(api.blueprint) - app.register_blueprint(groups.blueprint) - app.register_blueprint(routing.blueprint) + + # Load the different views + from gallery.views import index, image, group, settings, profile + + app.register_blueprint(index.blueprint) + app.register_blueprint(image.blueprint) + app.register_blueprint(group.blueprint) + app.register_blueprint(profile.blueprint) app.register_blueprint(settings.blueprint) # Log to file that the app has started - logging.info('Gallery started successfully!') + logging.info("Gallery started successfully!") # Initialize extensions and return app assets.init_app(app) diff --git a/gallery/views/api.py b/gallery/api.py similarity index 55% rename from gallery/views/api.py rename to gallery/api.py index 7e386dc..7515d41 100644 --- a/gallery/views/api.py +++ b/gallery/api.py @@ -21,26 +21,28 @@ from gallery.utils import metadata as mt from gallery.utils.generate_image import generate_thumbnail -blueprint = Blueprint('api', __name__, url_prefix='/api') +blueprint = Blueprint("api", __name__, url_prefix="/api") db_session = sessionmaker(bind=db.engine) db_session = db_session() -@blueprint.route('/file/', methods=['GET']) +@blueprint.route("/file/", methods=["GET"]) def file(file_name): """ Returns a file from the uploads folder r for resolution, 400x400 or thumb for thumbnail """ - res = request.args.get('r', default=None, type=str) # Type of file (thumb, etc) - ext = request.args.get('e', default=None, type=str) # File extension + res = request.args.get("r", default=None, type=str) # Type of file (thumb, etc) + ext = request.args.get("e", default=None, type=str) # File extension file_name = secure_filename(file_name) # Sanitize file name # 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['UPLOAD_FOLDER'], file_name)): + if not os.path.exists( + os.path.join(current_app.config["UPLOAD_FOLDER"], file_name) + ): abort(404) - return send_from_directory(current_app.config['UPLOAD_FOLDER'], file_name) + return send_from_directory(current_app.config["UPLOAD_FOLDER"], file_name) thumb = generate_thumbnail(file_name, res, ext) if not thumb: @@ -49,13 +51,13 @@ def file(file_name): return send_from_directory(os.path.dirname(thumb), os.path.basename(thumb)) -@blueprint.route('/upload', methods=['POST']) +@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_file = request.files["file"] form = request.form # If no image is uploaded, return 404 error @@ -63,41 +65,45 @@ def upload(): return abort(404) # Get file extension, generate random name and set file path - img_ext = pathlib.Path(form_file.filename).suffix.replace('.', '').lower() + 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) + 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) + if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys(): + logging.info("File extension not allowed: %s", img_ext) abort(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) + logging.info("Error saving file %s because of %s", img_path, err) abort(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 = db.Posts(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']) + query = db.Posts( + 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 'Gwa Gwa' # Return something so the browser doesn't show an error + return "Gwa Gwa" # Return something so the browser doesn't show an error -@blueprint.route('/delete/', methods=['POST']) +@blueprint.route("/delete/", methods=["POST"]) @login_required def delete_image(image_id): """ @@ -113,14 +119,16 @@ def delete_image(image_id): # Delete file try: - os.remove(os.path.join(current_app.config['UPLOAD_FOLDER'], img.filename)) + os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], img.filename)) except FileNotFoundError: - logging.warning('File not found: %s, already deleted or never existed', img.filename) + logging.warning( + "File not found: %s, already deleted or never existed", img.filename + ) # Delete cached files - cache_path = os.path.join(platformdirs.user_config_dir('onlylegs'), 'cache') - cache_name = img.filename.rsplit('.')[0] - for cache_file in pathlib.Path(cache_path).glob(cache_name + '*'): + cache_path = os.path.join(platformdirs.user_config_dir("onlylegs"), "cache") + cache_name = img.filename.rsplit(".")[0] + for cache_file in pathlib.Path(cache_path).glob(cache_name + "*"): os.remove(cache_file) # Delete from database @@ -134,36 +142,38 @@ def delete_image(image_id): # Commit all changes db_session.commit() - logging.info('Removed image (%s) %s', image_id, img.filename) - flash(['Image was all in Le Head!', '1']) - return 'Gwa Gwa' + logging.info("Removed image (%s) %s", image_id, img.filename) + flash(["Image was all in Le Head!", "1"]) + return "Gwa Gwa" -@blueprint.route('/group/create', methods=['POST']) +@blueprint.route("/group/create", methods=["POST"]) @login_required def create_group(): """ Creates a group """ - new_group = db.Groups(name=request.form['name'], - description=request.form['description'], - author_id=current_user.id) + new_group = db.Groups( + name=request.form["name"], + description=request.form["description"], + author_id=current_user.id, + ) db_session.add(new_group) db_session.commit() - return ':3' + return ":3" -@blueprint.route('/group/modify', methods=['POST']) +@blueprint.route("/group/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_id = request.form["group"] + image_id = request.form["image"] + action = request.form["action"] group = db_session.query(db.Groups).filter_by(id=group_id).first() @@ -172,28 +182,31 @@ def modify_group(): elif group.author_id != current_user.id: abort(403) - if action == 'add': - if not (db_session.query(db.GroupJunction) - .filter_by(group_id=group_id, post_id=image_id) - .first()): - db_session.add(db.GroupJunction(group_id=group_id, - post_id=image_id)) - elif request.form['action'] == 'remove': - (db_session.query(db.GroupJunction) - .filter_by(group_id=group_id, post_id=image_id) - .delete()) + if action == "add": + if not ( + db_session.query(db.GroupJunction) + .filter_by(group_id=group_id, post_id=image_id) + .first() + ): + db_session.add(db.GroupJunction(group_id=group_id, post_id=image_id)) + elif request.form["action"] == "remove": + ( + db_session.query(db.GroupJunction) + .filter_by(group_id=group_id, post_id=image_id) + .delete() + ) db_session.commit() - return ':3' + return ":3" -@blueprint.route('/group/delete', methods=['POST']) +@blueprint.route("/group/delete", methods=["POST"]) def delete_group(): """ Deletes a group """ - group_id = request.form['group'] + group_id = request.form["group"] group = db_session.query(db.Groups).filter_by(id=group_id).first() @@ -206,5 +219,5 @@ def delete_group(): db_session.query(db.GroupJunction).filter_by(group_id=group_id).delete() db_session.commit() - flash(['Group yeeted!', '1']) - return ':3' + flash(["Group yeeted!", "1"]) + return ":3" diff --git a/gallery/auth.py b/gallery/auth.py index 36e1bc7..163c3c4 100644 --- a/gallery/auth.py +++ b/gallery/auth.py @@ -14,39 +14,39 @@ from sqlalchemy.orm import sessionmaker from gallery import db -blueprint = Blueprint('auth', __name__, url_prefix='/auth') +blueprint = Blueprint("auth", __name__, url_prefix="/auth") db_session = sessionmaker(bind=db.engine) db_session = db_session() -@blueprint.route('/login', methods=['POST']) +@blueprint.route("/login", methods=["POST"]) def login(): """ Log in a registered user by adding the user id to the session """ error = [] - username = request.form['username'].strip() - password = request.form['password'].strip() - remember = bool(request.form['remember-me']) + username = request.form["username"].strip() + password = request.form["password"].strip() + remember = bool(request.form["remember-me"]) user = db_session.query(db.Users).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) - error.append('Username or Password is incorrect!') + logging.error("Login attempt from %s", request.remote_addr) + error.append("Username or Password is incorrect!") if error: abort(403) login_user(user, remember=remember) - logging.info('User %s logged in from %s', username, request.remote_addr) - flash(['Logged in successfully!', '4']) - return 'ok', 200 + logging.info("User %s logged in from %s", username, request.remote_addr) + flash(["Logged in successfully!", "4"]) + return "ok", 200 -@blueprint.route('/register', methods=['POST']) +@blueprint.route("/register", methods=["POST"]) def register(): """ Register a new user @@ -54,55 +54,58 @@ def register(): error = [] # Thanks Fennec for reminding me to strip out the whitespace lol - username = request.form['username'].strip() - email = request.form['email'].strip() - password = request.form['password'].strip() - password_repeat = request.form['password-repeat'].strip() + username = request.form["username"].strip() + email = request.form["email"].strip() + password = request.form["password"].strip() + password_repeat = request.form["password-repeat"].strip() - 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') + 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") # Validate the form if not username or not username_regex.match(username): - error.append('Username is invalid!') + error.append("Username is invalid!") if not email or not email_regex.match(email): - error.append('Email is invalid!') + error.append("Email is invalid!") if not password: - error.append('Password is empty!') + error.append("Password is empty!") elif len(password) < 8: - error.append('Password is too short! Longer than 8 characters pls') + error.append("Password is too short! Longer than 8 characters pls") if not password_repeat: - error.append('Enter password again!') + error.append("Enter password again!") elif password_repeat != password: - error.append('Passwords do not match!') + error.append("Passwords do not match!") user_exists = db_session.query(db.Users).filter_by(username=username).first() if user_exists: - error.append('User already exists!') + error.append("User already exists!") # If there are errors, return them if error: print(error) return jsonify(error), 400 - register_user = db.Users(username=username, email=email, - password=generate_password_hash(password, method='sha256')) + register_user = db.Users( + username=username, + email=email, + password=generate_password_hash(password, method="sha256"), + ) db_session.add(register_user) db_session.commit() - logging.info('User %s registered', username) - return 'ok', 200 + logging.info("User %s registered", username) + return "ok", 200 -@blueprint.route('/logout') +@blueprint.route("/logout") @login_required def logout(): """ Clear the current session, including the stored user id """ logout_user() - flash(['Goodbye!!!', '4']) - return redirect(url_for('gallery.index')) + flash(["Goodbye!!!", "4"]) + return redirect(url_for("gallery.index")) diff --git a/gallery/db.py b/gallery/db.py index d916bd7..a6b2638 100644 --- a/gallery/db.py +++ b/gallery/db.py @@ -5,27 +5,36 @@ from uuid import uuid4 import os import platformdirs -from sqlalchemy import (create_engine, Column, Integer, String, - DateTime, ForeignKey, PickleType, func) +from sqlalchemy import ( + create_engine, + Column, + Integer, + String, + DateTime, + ForeignKey, + PickleType, + func, +) from sqlalchemy.orm import declarative_base, relationship from flask_login import UserMixin -USER_DIR = platformdirs.user_config_dir('onlylegs') -DB_PATH = os.path.join(USER_DIR, 'instance', 'gallery.sqlite3') +USER_DIR = platformdirs.user_config_dir("onlylegs") +DB_PATH = os.path.join(USER_DIR, "instance", "gallery.sqlite3") # In the future, I want to add support for other databases -engine = create_engine(f'sqlite:///{DB_PATH}', echo=False) +engine = create_engine(f"sqlite:///{DB_PATH}", echo=False) base = declarative_base() -class Users (base, UserMixin): # pylint: disable=too-few-public-methods, C0103 +class Users(base, UserMixin): # pylint: disable=too-few-public-methods, C0103 """ User table Joins with post, groups, session and log """ - __tablename__ = 'users' + + __tablename__ = "users" # Gallery used information id = Column(Integer, primary_key=True) @@ -34,26 +43,31 @@ class Users (base, UserMixin): # pylint: disable=too-few-public-methods, C0103 username = Column(String, unique=True, nullable=False) email = Column(String, unique=True, nullable=False) password = Column(String, nullable=False) - joined_at = Column(DateTime, nullable=False, server_default=func.now()) # pylint: disable=E1102 + joined_at = Column( + DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102 + ) - posts = relationship('Posts', backref='users') - groups = relationship('Groups', backref='users') - log = relationship('Logs', backref='users') + posts = relationship("Posts", backref="users") + groups = relationship("Groups", backref="users") + log = relationship("Logs", backref="users") def get_id(self): return str(self.alt_id) -class Posts (base): # pylint: disable=too-few-public-methods, C0103 +class Posts(base): # pylint: disable=too-few-public-methods, C0103 """ Post table Joins with group_junction """ - __tablename__ = 'posts' + + __tablename__ = "posts" id = Column(Integer, primary_key=True) - author_id = Column(Integer, ForeignKey('users.id')) - created_at = Column(DateTime, nullable=False, server_default=func.now()) # pylint: disable=E1102 + author_id = Column(Integer, ForeignKey("users.id")) + created_at = Column( + DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102 + ) filename = Column(String, unique=True, nullable=False) mimetype = Column(String, nullable=False) exif = Column(PickleType, nullable=False) @@ -61,66 +75,79 @@ class Posts (base): # pylint: disable=too-few-public-methods, C0103 description = Column(String, nullable=False) alt = Column(String, nullable=False) - 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 Joins with group_junction """ - __tablename__ = 'groups' + + __tablename__ = "groups" id = Column(Integer, primary_key=True) name = Column(String, nullable=False) description = Column(String, nullable=False) - author_id = Column(Integer, ForeignKey('users.id')) - created_at = Column(DateTime, nullable=False, server_default=func.now()) # pylint: disable=E1102 + author_id = Column(Integer, ForeignKey("users.id")) + created_at = Column( + DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102 + ) - 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 Joins with posts and groups """ - __tablename__ = 'group_junction' + + __tablename__ = "group_junction" id = Column(Integer, primary_key=True) - date_added = Column(DateTime, nullable=False, server_default=func.now()) # pylint: disable=E1102 - group_id = Column(Integer, ForeignKey('groups.id')) - post_id = Column(Integer, ForeignKey('posts.id')) + date_added = Column( + DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102 + ) + group_id = Column(Integer, ForeignKey("groups.id")) + post_id = Column(Integer, ForeignKey("posts.id")) -class Logs (base): # pylint: disable=too-few-public-methods, C0103 +class Logs(base): # pylint: disable=too-few-public-methods, C0103 """ Log table Joins with user """ - __tablename__ = 'logs' + + __tablename__ = "logs" id = Column(Integer, primary_key=True) - user_id = Column(Integer, ForeignKey('users.id')) + user_id = Column(Integer, ForeignKey("users.id")) ip_address = Column(String, nullable=False) code = Column(Integer, nullable=False) note = Column(String, nullable=False) - created_at = Column(DateTime, nullable=False, server_default=func.now()) # pylint: disable=E1102 + created_at = Column( + DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102 + ) -class Bans (base): # pylint: disable=too-few-public-methods, C0103 +class Bans(base): # pylint: disable=too-few-public-methods, C0103 """ Bans table """ - __tablename__ = 'bans' + + __tablename__ = "bans" id = Column(Integer, primary_key=True) ip_address = Column(String, nullable=False) code = Column(Integer, nullable=False) note = Column(String, nullable=False) - banned_at = Column(DateTime, nullable=False, server_default=func.now()) # pylint: disable=E1102 + banned_at = Column( + DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102 + ) # check if database file exists, if not create it if not os.path.isfile(DB_PATH): base.metadata.create_all(engine) - print('Database created') + print("Database created") diff --git a/gallery/langs/gb.json b/gallery/langs/gb.json new file mode 100644 index 0000000..d3f0895 --- /dev/null +++ b/gallery/langs/gb.json @@ -0,0 +1,4 @@ +{ + "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/gallery/static/fonts/Manrope[wght].ttf b/gallery/static/fonts/Manrope[wght].ttf deleted file mode 100644 index 21c45b9..0000000 Binary files a/gallery/static/fonts/Manrope[wght].ttf and /dev/null differ diff --git a/gallery/static/fonts/Manrope[wght].woff2 b/gallery/static/fonts/Manrope[wght].woff2 deleted file mode 100644 index 4d677aa..0000000 Binary files a/gallery/static/fonts/Manrope[wght].woff2 and /dev/null differ diff --git a/gallery/static/fonts/Rubik.ttf b/gallery/static/fonts/Rubik.ttf new file mode 100644 index 0000000..547f069 Binary files /dev/null and b/gallery/static/fonts/Rubik.ttf differ diff --git a/gallery/static/js/pre/main.js b/gallery/static/js/index.js similarity index 85% rename from gallery/static/js/pre/main.js rename to gallery/static/js/index.js index 2c75b9e..5d55656 100644 --- a/gallery/static/js/pre/main.js +++ b/gallery/static/js/index.js @@ -1,11 +1,3 @@ -let webpSupport = false; -try { - new Image().src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA='; - webpSupport = true; -} catch (e) { - webpSupport = false; -} - // fade in images function imgFade(obj, time = 250) { obj.style.transition = `opacity ${time}ms`; @@ -14,6 +6,7 @@ function imgFade(obj, time = 250) { // 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++) { let image = lazyLoad[i]; @@ -71,11 +64,10 @@ window.onload = function () { infoButton.classList.add('show'); } infoButton.onclick = function () { - popUpShow('OnlyLegs on Flask', - 'Using Phosphoricons and ' + - 'Manrope
' + - 'Made by Fluffy and others with ❤️
' + - 'V23.04.05'); + popUpShow('OnlyLegs', + 'V23.04.08 ' + + 'using Phosphoricons and Flask.' + + '
Made by Fluffy and others with ❤️'); } } }; diff --git a/gallery/static/js/post/login.js b/gallery/static/js/login.js similarity index 100% rename from gallery/static/js/post/login.js rename to gallery/static/js/login.js diff --git a/gallery/static/js/post/uploadTab.js b/gallery/static/js/uploadTab.js similarity index 100% rename from gallery/static/js/post/uploadTab.js rename to gallery/static/js/uploadTab.js diff --git a/gallery/static/js/pre/notifications.js b/gallery/static/lib/notifications.js similarity index 95% rename from gallery/static/js/pre/notifications.js rename to gallery/static/lib/notifications.js index 6b4f9d7..1cda690 100644 --- a/gallery/static/js/pre/notifications.js +++ b/gallery/static/lib/notifications.js @@ -24,6 +24,7 @@ function addNotification(notificationText, notificationLevel) { let iconElement = document.createElement('span'); iconElement.classList.add('sniffle__notification-icon'); notification.appendChild(iconElement); + // Set the icon based on the notification level, not pretty but it works :3 if (notificationLevel === 1) { notification.classList.add('success'); @@ -45,11 +46,6 @@ function addNotification(notificationText, notificationLevel) { description.innerHTML = notificationText; notification.appendChild(description); - // Create span to show time remaining - let timer = document.createElement('span'); - timer.classList.add('sniffle__notification-time'); - notification.appendChild(timer); - // Append notification to container notificationContainer.appendChild(notification); setTimeout(function() { notification.classList.add('show'); }, 5); @@ -65,5 +61,3 @@ function addNotification(notificationText, notificationLevel) { } }, 5000); } - -// uwu diff --git a/gallery/static/js/pre/popup.js b/gallery/static/lib/popup.js similarity index 100% rename from gallery/static/js/pre/popup.js rename to gallery/static/lib/popup.js diff --git a/gallery/static/lib/webp.js b/gallery/static/lib/webp.js new file mode 100644 index 0000000..c39c4ff --- /dev/null +++ b/gallery/static/lib/webp.js @@ -0,0 +1,10 @@ +function checkWebpSupport() { + var webpSupport = false; + try { + webpSupport = document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0; + } catch (e) { + webpSupport = false; + } + + return webpSupport; +} diff --git a/gallery/static/manifest.json b/gallery/static/manifest.json new file mode 100644 index 0000000..ab9009a --- /dev/null +++ b/gallery/static/manifest.json @@ -0,0 +1,18 @@ +{ + "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/gallery/static/sass/animations.sass b/gallery/static/sass/animations.sass index 364f9d4..9fc623e 100644 --- a/gallery/static/sass/animations.sass +++ b/gallery/static/sass/animations.sass @@ -4,20 +4,6 @@ 100% background-position: 468px 0 -@keyframes notificationTimeout - 0% - left: -100% - height: 3px - 90% - left: 0% - height: 3px - 95% - left: 0% - height: 0 - 100% - left: 0% - height: 0 - @keyframes uploadingLoop 0% left: -100% diff --git a/gallery/static/sass/components/banner.sass b/gallery/static/sass/components/banner.sass index 4ab9c9e..e9d713f 100644 --- a/gallery/static/sass/components/banner.sass +++ b/gallery/static/sass/components/banner.sass @@ -4,6 +4,22 @@ position: relative color: RGB($fg-white) + .link + padding: 0.1rem 0.3rem + + text-decoration: none + font-weight: 500 + + background-color: RGB($fg-white) + color: RGB($fg-black) + border-radius: $rad-inner + + cursor: pointer + + &:hover + background-color: RGB($fg-black) + color: RGB($fg-white) + &::after content: '' @@ -73,23 +89,20 @@ .banner-header grid-area: header - white-space: nowrap - text-overflow: ellipsis - overflow: hidden text-align: left font-size: 6.9rem - font-weight: 800 + font-weight: 700 color: RGB($primary) .banner-info grid-area: info font-size: 1rem - font-weight: 600 + font-weight: 400 .banner-subtitle grid-area: subtitle font-size: 1rem - font-weight: 600 + font-weight: 400 .pill-row margin-top: auto @@ -130,14 +143,14 @@ text-overflow: ellipsis overflow: hidden text-align: left - font-weight: 800 + font-weight: 700 font-size: 1.5rem color: RGB($primary) .banner-info font-size: 0.9rem - font-weight: 600 + font-weight: 400 .pill-row margin-left: auto @@ -161,19 +174,21 @@ flex-direction: column justify-content: center align-items: center - gap: 0.25rem + gap: 1rem .banner-header - font-size: 3rem text-align: center + font-size: 2.5rem - .banner-info, - .banner-subtitle + .banner-info font-size: 1.1rem text-align: center + .banner-subtitle + display: none + .pill-row - margin-top: 1rem + margin-top: 0rem .banner-small .banner-content diff --git a/gallery/static/sass/components/buttons/block.sass b/gallery/static/sass/components/buttons/block.sass index 15be087..2b767ee 100644 --- a/gallery/static/sass/components/buttons/block.sass +++ b/gallery/static/sass/components/buttons/block.sass @@ -9,10 +9,10 @@ outline: 2px solid RGBA($color, 0.3) .btn-block - padding: 0.5rem 1rem + padding: 0.4rem 0.7rem width: auto - min-height: 2.5rem + min-height: 2.3rem display: flex justify-content: center @@ -22,7 +22,7 @@ position: relative font-size: 1rem - font-weight: 600 + font-weight: 400 text-align: center background-color: transparent @@ -68,16 +68,16 @@ label font-size: 1rem - font-weight: 600 + font-weight: 400 text-align: left color: RGB($fg-white) .input-block - padding: 0.5rem 1rem + padding: 0.4rem 0.7rem width: auto - min-height: 2.5rem + min-height: 2.3rem display: flex justify-content: flex-start @@ -86,7 +86,7 @@ position: relative font-size: 1rem - font-weight: 600 + font-weight: 400 text-align: left background-color: RGBA($white, 0.1) @@ -116,7 +116,7 @@ padding: 1rem 1.25rem width: 100% - min-height: 2.5rem + min-height: 2.3rem display: flex flex-direction: column @@ -127,7 +127,7 @@ position: relative font-size: 1rem - font-weight: 600 + font-weight: 400 text-align: center background-color: RGBA($white, 0.1) diff --git a/gallery/static/sass/components/buttons/pill.sass b/gallery/static/sass/components/buttons/pill.sass index e98fa1a..d0b7ee6 100644 --- a/gallery/static/sass/components/buttons/pill.sass +++ b/gallery/static/sass/components/buttons/pill.sass @@ -16,9 +16,28 @@ display: flex - background-color: RGB($bg-100) + background-color: RGB($bg-200) border-radius: $rad +.pill-text + margin: 0 + padding: 0.5rem + + width: auto + height: 2.5rem + + display: flex + justify-content: center + align-items: center + + position: relative + + text-align: center + font-size: 1rem + font-weight: 400 + + color: RGB($fg-white) + .pill-item margin: 0 padding: 0.5rem @@ -45,11 +64,6 @@ color: RGB($primary) - .tool-tip - opacity: 1 - top: -2.3rem - transform: translateX(calc(-50% + 1.25rem )) - .pill__critical color: RGB($critical) @@ -72,47 +86,6 @@ &:hover color: RGB($fg-white) -.tool-tip - margin: 0 - padding: 0.35rem 0.7rem - - width: auto - - display: block - - position: absolute - top: -1.7rem - left: 0 - transform: translateX(calc(-50% + 1.25rem )) - - font-size: 0.9rem - font-weight: 700 - - background-color: #000000 - color: RGB($fg-white) - opacity: 0 - border-radius: $rad-inner - - transition: opacity 0.2s cubic-bezier(.76,0,.17,1), top 0.2s cubic-bezier(.76,0,.17,1) - - pointer-events: none - - svg - margin: 0 - font-size: 1rem - - width: 0.75rem - height: 0.75rem - - display: block - - position: absolute - left: 50% - bottom: -0.46rem - transform: translateX(-50%) - - color: #000000 - @media (max-width: $breakpoint) .tool-tip display: none \ No newline at end of file diff --git a/gallery/static/sass/components/elements/notification.sass b/gallery/static/sass/components/elements/notification.sass index 76de391..078ae97 100644 --- a/gallery/static/sass/components/elements/notification.sass +++ b/gallery/static/sass/components/elements/notification.sass @@ -1,7 +1,21 @@ +@keyframes notificationTimeout + 0% + left: -100% + height: 3px + 90% + left: 0% + height: 3px + 95% + left: 0% + height: 0 + 100% + left: 0% + height: 0 + @mixin notification($color) color: RGB($color) - .sniffle__notification-time + &::after background-color: RGB($color) .notifications @@ -44,6 +58,21 @@ transition: all 0.25s ease-in-out, opacity 0.2s ease-in-out, transform 0.2s cubic-bezier(.68,-0.55,.27,1.55) + &::after + content: "" + + width: 450px + height: 3px + + position: absolute + bottom: 0px + left: 0px + + background-color: RGB($fg-white) + + z-index: +2 + animation: notificationTimeout 5.1s linear + &.success @include notification($success) &.warning @@ -100,21 +129,6 @@ line-height: 1 text-align: left -.sniffle__notification-time - margin: 0 - padding: 0 - - width: 450px - height: 3px - - position: absolute - bottom: 0px - left: 0px - - background-color: RGB($fg-white) - - animation: notificationTimeout 5.1s linear - @media (max-width: $breakpoint) .notifications width: calc(100vw - 0.6rem) diff --git a/gallery/static/sass/components/elements/pop-up.sass b/gallery/static/sass/components/elements/pop-up.sass index 880d4f7..8af0dc8 100644 --- a/gallery/static/sass/components/elements/pop-up.sass +++ b/gallery/static/sass/components/elements/pop-up.sass @@ -70,8 +70,8 @@ top: 0 font-size: 1.5rem - font-weight: 800 - text-align: center + font-weight: 700 + text-align: left color: RGB($fg-white) @@ -81,8 +81,8 @@ width: 100% font-size: 1rem - font-weight: 500 - text-align: center + font-weight: 400 + text-align: left color: RGB($fg-white) @@ -96,7 +96,6 @@ a, .link font-size: 1rem font-weight: 500 - text-align: center line-height: 1 color: RGB($primary) diff --git a/gallery/static/sass/components/elements/upload-panel.sass b/gallery/static/sass/components/elements/upload-panel.sass index 05e9824..19325bb 100644 --- a/gallery/static/sass/components/elements/upload-panel.sass +++ b/gallery/static/sass/components/elements/upload-panel.sass @@ -57,9 +57,9 @@ position: absolute bottom: 0 - left: -400px + left: -25rem - width: 400px + width: 25rem height: 100% display: flex @@ -213,7 +213,7 @@ height: 95% left: 0 - bottom: calc(-100vh + 3.5rem) + bottom: -100vh border-radius: $rad $rad 0 0 diff --git a/gallery/static/sass/components/gallery.sass b/gallery/static/sass/components/gallery.sass index 63a832e..69ee691 100644 --- a/gallery/static/sass/components/gallery.sass +++ b/gallery/static/sass/components/gallery.sass @@ -1,12 +1,12 @@ .gallery-grid margin: 0 - padding: 0.5rem + padding: 0.65rem width: 100% display: grid grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) - gap: 0.5rem + gap: 0.65rem .gallery-item margin: 0 @@ -17,9 +17,11 @@ position: relative border-radius: $rad-inner + box-shadow: 0 0.15rem 0.4rem 0.1rem RGBA($bg-100, 0.4) box-sizing: border-box overflow: hidden + transition: box-shadow 0.2s cubic-bezier(.79, .14, .15, .86) .image-filter margin: 0 @@ -37,11 +39,11 @@ flex-direction: column justify-content: flex-end - background-image: linear-gradient(to top, rgba($bg-100, 0.5), transparent) + background-image: linear-gradient(to top, rgba($bg-100, 0.69), transparent) opacity: 0 // hide z-index: +4 - transition: background-image 0.3s cubic-bezier(.79, .14, .15, .86), opacity 0.3s cubic-bezier(.79, .14, .15, .86) + transition: opacity 0.2s cubic-bezier(.79, .14, .15, .86) .image-title, .image-subtitle @@ -57,11 +59,11 @@ .image-title font-size: 0.9rem - font-weight: 800 + font-weight: 700 .image-subtitle font-size: 0.8rem - font-weight: 600 + font-weight: 400 img width: 100% @@ -72,7 +74,6 @@ object-fit: cover object-position: center - transform: scale(1.05) background-color: RGB($bg-bright) filter: blur(0.5rem) @@ -90,12 +91,10 @@ padding-bottom: 100% &:hover - .image-filter - background-image: linear-gradient(to top, rgba($bg-100, 0.69), transparent) - opacity: 1 + box-shadow: 0 0.2rem 0.4rem 0.1rem RGBA($bg-100, 0.6) - img - transform: scale(1) + .image-filter + opacity: 1 .group-item margin: 0 @@ -144,11 +143,11 @@ .image-title font-size: 0.9rem - font-weight: 800 + font-weight: 700 .image-subtitle font-size: 0.8rem - font-weight: 600 + font-weight: 400 .images margin: 0 diff --git a/gallery/static/sass/components/image-view/fullscreen.sass b/gallery/static/sass/components/image-view/fullscreen.sass index 3c61771..f8e0fbc 100644 --- a/gallery/static/sass/components/image-view/fullscreen.sass +++ b/gallery/static/sass/components/image-view/fullscreen.sass @@ -1,6 +1,6 @@ .image-fullscreen margin: 0 - padding: 0 0 0 3.5rem + padding: 0 width: 100% height: 100% @@ -14,7 +14,7 @@ opacity: 0 // hide background-color: $bg-transparent - z-index: 21 + z-index: 100 box-sizing: border-box diff --git a/gallery/static/sass/components/image-view/info-tab.sass b/gallery/static/sass/components/image-view/info-tab.sass index 86fffca..47c9d44 100644 --- a/gallery/static/sass/components/image-view/info-tab.sass +++ b/gallery/static/sass/components/image-view/info-tab.sass @@ -1,6 +1,10 @@ .info-container - width: 100% - height: 100% + width: 25rem + height: 100vh + + position: absolute + top: 0 + left: 0 display: flex flex-direction: column @@ -9,6 +13,11 @@ background-color: RGB($bg-200) overflow-y: auto + z-index: +4 + transition: left 0.3s cubic-bezier(0.76, 0, 0.17, 1) + + &.collapsed + left: -25rem .info-tab width: 100% @@ -34,6 +43,9 @@ opacity: 0 .collapse-indicator + margin: 0 + padding: 0 + width: 1.25rem height: 1.25rem @@ -41,12 +53,14 @@ 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) - user-select: none + cursor: pointer .info-header margin: 0 @@ -80,7 +94,7 @@ padding: 0 font-size: 1.1rem - font-weight: 600 + font-weight: 500 color: RGB($primary) @@ -104,7 +118,7 @@ padding: 0 font-size: 1rem - font-weight: 500 + font-weight: 400 text-overflow: ellipsis overflow: hidden @@ -157,7 +171,7 @@ white-space: nowrap font-size: 1rem - font-weight: 500 + font-weight: 400 td:last-child padding: 0 @@ -169,7 +183,7 @@ white-space: nowrap font-size: 1rem - font-weight: 500 + font-weight: 400 td.empty-table opacity: 0.3 @@ -205,4 +219,14 @@ @media (max-width: 1100px) .info-container + width: 100% + height: 100% + + position: relative + + display: flex + flex-direction: column gap: 0.5rem + + &.collapsed + left: unset diff --git a/gallery/static/sass/components/image-view/view.sass b/gallery/static/sass/components/image-view/view.sass index 6636976..22e47fe 100644 --- a/gallery/static/sass/components/image-view/view.sass +++ b/gallery/static/sass/components/image-view/view.sass @@ -7,48 +7,68 @@ .image-grid padding: 0 - position: relative - - display: grid - grid-template-areas: 'info image' 'info tools' - grid-template-columns: 25rem 1fr - grid-template-rows: 1fr auto - gap: 0 - + width: 100% height: 100vh + position: relative + + display: flex + flex-direction: column + gap: 0.5rem z-index: 3 -#image-info - grid-area: info -#image-tools - grid-area: tools - padding: 0 0 0.5rem 0 -#image-container - grid-area: image + .image-block + margin: 0 0 0 25rem + padding: 0 + + width: calc(100% - 25rem) + 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-bottom: 0.5rem + + &.collapsed + .image-block + margin: 0 + width: 100% @media (max-width: 1100px) .image-grid padding: 0.5rem - - display: flex - flex-direction: column - gap: 0.5rem - height: auto - .image-container - margin: 0 auto - padding: 0 + .image-block + margin: 0 + width: 100% + height: auto - max-height: 69vh + gap: 0.5rem - img - max-height: 69vh + transition: margin 0s, width 0s - #image-tools - padding: 0 + .image-container + margin: 0 auto + padding: 0 + max-height: 69vh + + img + max-height: 69vh + + .pill-row + margin-bottom: 0 + + #fullscreenImage + display: none .info-container background: transparent @@ -59,7 +79,4 @@ .info-tab.collapsed .info-header border-radius: $rad -@media (max-width: $breakpoint) - .image-fullscreen - padding: 0 0 3.5rem 0 \ No newline at end of file diff --git a/gallery/static/sass/components/navigation.sass b/gallery/static/sass/components/navigation.sass index 718c351..5235e34 100644 --- a/gallery/static/sass/components/navigation.sass +++ b/gallery/static/sass/components/navigation.sass @@ -68,7 +68,7 @@ .tool-tip margin: 0 - padding: 0.35rem 0.7rem + padding: 0.4rem 0.7rem display: block @@ -78,9 +78,9 @@ transform: translateY(-50%) font-size: 0.9rem - font-weight: 700 + font-weight: 500 - background-color: #000000 + background-color: RGB($bg-100) color: RGB($fg-white) opacity: 0 border-radius: $rad-inner @@ -103,7 +103,7 @@ left: -0.45rem transform: translateY(-50%) - color: #000000 + color: RGB($bg-100) &:hover > svg diff --git a/gallery/static/sass/style.sass b/gallery/static/sass/style.sass index 4b12d56..0d5fc4f 100644 --- a/gallery/static/sass/style.sass +++ b/gallery/static/sass/style.sass @@ -25,6 +25,8 @@ box-sizing: border-box font-family: $font + scrollbar-color: RGB($primary) transparent + ::-webkit-scrollbar width: 0.5rem ::-webkit-scrollbar-track diff --git a/gallery/static/sass/variables.sass b/gallery/static/sass/variables.sass index d77effa..321a03d 100644 --- a/gallery/static/sass/variables.sass +++ b/gallery/static/sass/variables.sass @@ -33,7 +33,7 @@ $rad-inner: var(--rad-inner) $animation-smooth: var(--animation-smooth) $animation-bounce: var(--animation-bounce) -$font: 'Manrope', sans-serif +$font: 'Rubik', sans-serif $breakpoint: 800px @@ -77,8 +77,10 @@ $breakpoint: 800px // I have no clue if its webassets or libsass thats doing this shit // But one of them is trying to "correct" the path, and 404-ing the // font, so enjoy this path fuckery + @font-face - font-family: 'Manrope' - src: url('../../../../static/fonts/Manrope[wght].woff2') format('woff2') + font-family: 'Rubik' + src: url('../../../../static/fonts/Rubik.ttf') format('truetype') font-style: normal font-display: swap + font-weight: 300 900 diff --git a/gallery/templates/groups/group.html b/gallery/templates/group.html similarity index 90% rename from gallery/templates/groups/group.html rename to gallery/templates/group.html index 11638ab..caa5313 100644 --- a/gallery/templates/groups/group.html +++ b/gallery/templates/group.html @@ -184,12 +184,23 @@ color: {{ text_colour }} !important; } + .banner-content .link { + background-color: {{ text_colour }} !important; + color: rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}) !important; + } + .banner-content .link:hover { + background-color: rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}) !important; + color: {{ text_colour }} !important; + } + .banner-filter { - background: linear-gradient(90deg, rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}), rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 0.3)) !important; + background: linear-gradient(90deg, rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}), + rgba({{ images.0.colours.1.0 }}, {{ images.0.colours.1.1 }}, {{ images.0.colours.1.2 }}, 0.3)) !important; } @media (max-width: 800px) { .banner-filter { - background: linear-gradient(180deg, rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 0.8), rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 0.5)) !important; + background: linear-gradient(180deg, rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 1), + rgba({{ images.0.colours.1.0 }}, {{ images.0.colours.1.1 }}, {{ images.0.colours.1.2 }}, 0.5)) !important; } } @@ -209,12 +220,12 @@ {% block content %} {% if images %}