mirror of
https://github.com/Derpy-Leggies/OnlyLegs.git
synced 2024-12-28 02:16:07 +00:00
commit
63b725a5b7
|
@ -4,42 +4,42 @@ This is the main app file, it loads all the other files and sets up the app
|
|||
"""
|
||||
import os
|
||||
import logging
|
||||
import platformdirs
|
||||
|
||||
from flask_assets import Bundle
|
||||
|
||||
from flask_migrate import init as migrate_init
|
||||
from flask_migrate import upgrade as migrate_upgrade
|
||||
from flask_migrate import migrate as migrate_migrate
|
||||
|
||||
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.views import index, image, group, settings, profile
|
||||
from onlylegs import api
|
||||
from onlylegs import auth
|
||||
from onlylegs.config import INSTANCE_DIR, MIGRATIONS_DIR
|
||||
from onlylegs.models import User
|
||||
|
||||
|
||||
INSTACE_DIR = os.path.join(platformdirs.user_config_dir("onlylegs"), "instance")
|
||||
MIGRATIONS_DIR = os.path.join(INSTACE_DIR, "migrations")
|
||||
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=INSTACE_DIR)
|
||||
app = Flask(__name__, instance_path=INSTANCE_DIR)
|
||||
app.config.from_pyfile("config.py")
|
||||
|
||||
# DATABASE
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
migrate.init_app(app, db, directory=MIGRATIONS_DIR)
|
||||
|
||||
# If database file doesn't exist, create it
|
||||
if not os.path.exists(os.path.join(INSTACE_DIR, "gallery.sqlite3")):
|
||||
if not os.path.exists(os.path.join(INSTANCE_DIR, "gallery.sqlite3")):
|
||||
print("Creating database")
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
@ -68,12 +68,6 @@ def create_app(): # pylint: disable=R0914
|
|||
print("Creating migrations directory")
|
||||
migrate_init(directory=MIGRATIONS_DIR)
|
||||
|
||||
# Check if migrations are up to date
|
||||
with app.app_context():
|
||||
print("Checking for schema changes...")
|
||||
migrate_migrate(directory=MIGRATIONS_DIR)
|
||||
migrate_upgrade(directory=MIGRATIONS_DIR)
|
||||
|
||||
# LOGIN MANAGER
|
||||
# can also set session_protection to "strong"
|
||||
# this would protect against session hijacking
|
||||
|
@ -106,9 +100,11 @@ def create_app(): # pylint: disable=R0914
|
|||
# ASSETS
|
||||
assets.init_app(app)
|
||||
|
||||
scripts = Bundle("js/*.js", output="gen/js.js", depends="js/*.js") # filter jsmin is broken :c
|
||||
scripts = Bundle(
|
||||
"js/*.js", output="gen/js.js", depends="js/*.js"
|
||||
) # filter jsmin is broken :c
|
||||
styles = Bundle(
|
||||
"sass/*.sass",
|
||||
"sass/style.sass",
|
||||
filters="libsass, cssmin",
|
||||
output="gen/styles.css",
|
||||
depends="sass/**/*.sass",
|
||||
|
@ -118,13 +114,17 @@ def create_app(): # pylint: disable=R0914
|
|||
assets.register("styles", styles)
|
||||
|
||||
# BLUEPRINTS
|
||||
app.register_blueprint(auth.blueprint)
|
||||
app.register_blueprint(api.blueprint)
|
||||
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)
|
||||
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)
|
||||
|
|
205
onlylegs/api.py
205
onlylegs/api.py
|
@ -1,205 +0,0 @@
|
|||
"""
|
||||
Onlylegs - API endpoints
|
||||
"""
|
||||
from uuid import uuid4
|
||||
import os
|
||||
import pathlib
|
||||
import logging
|
||||
import platformdirs
|
||||
|
||||
from flask import Blueprint, send_from_directory, abort, flash, request, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from colorthief import ColorThief
|
||||
|
||||
from onlylegs.extensions import db
|
||||
from onlylegs.models import Post, Group, GroupJunction
|
||||
from onlylegs.utils import metadata as mt
|
||||
from onlylegs.utils.generate_image import generate_thumbnail
|
||||
|
||||
|
||||
blueprint = Blueprint("api", __name__, url_prefix="/api")
|
||||
|
||||
|
||||
@blueprint.route("/file/<file_name>", 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
|
||||
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)
|
||||
):
|
||||
abort(404)
|
||||
return send_from_directory(current_app.config["UPLOAD_FOLDER"], file_name)
|
||||
|
||||
thumb = generate_thumbnail(file_name, res, ext)
|
||||
if not thumb:
|
||||
abort(404)
|
||||
|
||||
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 no image is uploaded, return 404 error
|
||||
if not form_file:
|
||||
return abort(404)
|
||||
|
||||
# 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)
|
||||
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)
|
||||
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 = 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 "Gwa Gwa" # Return something so the browser doesn't show an error
|
||||
|
||||
|
||||
@blueprint.route("/delete/<int:image_id>", methods=["POST"])
|
||||
@login_required
|
||||
def delete_image(image_id):
|
||||
"""
|
||||
Deletes an image from the server and database
|
||||
"""
|
||||
post = Post.query.filter_by(id=image_id).first()
|
||||
|
||||
# Check if image exists and if user is allowed to delete it (author)
|
||||
if post is None:
|
||||
abort(404)
|
||||
if post.author_id != current_user.id:
|
||||
abort(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_path = os.path.join(platformdirs.user_config_dir("onlylegs"), "cache")
|
||||
cache_name = post.filename.rsplit(".")[0]
|
||||
for cache_file in pathlib.Path(cache_path).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 "Gwa Gwa"
|
||||
|
||||
|
||||
@blueprint.route("/group/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 ":3"
|
||||
|
||||
|
||||
@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 = 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:
|
||||
abort(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 ":3"
|
||||
|
||||
|
||||
@blueprint.route("/group/delete", methods=["POST"])
|
||||
def delete_group():
|
||||
"""
|
||||
Deletes a group
|
||||
"""
|
||||
group_id = request.form["group"]
|
||||
group = Group.query.filter_by(id=group_id).first()
|
||||
|
||||
if group is None:
|
||||
abort(404)
|
||||
elif group.author_id != current_user.id:
|
||||
abort(403)
|
||||
|
||||
GroupJunction.query.filter_by(group_id=group_id).delete()
|
||||
db.session.delete(group)
|
||||
db.session.commit()
|
||||
|
||||
flash(["Group yeeted!", "1"])
|
||||
return ":3"
|
0
onlylegs/api/__init__.py
Normal file
0
onlylegs/api/__init__.py
Normal file
93
onlylegs/api/account.py
Normal file
93
onlylegs/api/account.py
Normal file
|
@ -0,0 +1,93 @@
|
|||
"""
|
||||
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/<int:user_id>", 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/<int:user_id>", 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
|
78
onlylegs/api/group.py
Normal file
78
onlylegs/api/group.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
"""
|
||||
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"})
|
144
onlylegs/api/media.py
Normal file
144
onlylegs/api/media.py
Normal file
|
@ -0,0 +1,144 @@
|
|||
"""
|
||||
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("/<path:path>", 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/<int:image_id>", methods=["POST"])
|
||||
@login_required
|
||||
def delete_image(image_id):
|
||||
"""
|
||||
Deletes an image from the server and database
|
||||
"""
|
||||
post = db.get_or_404(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
|
|
@ -12,11 +12,11 @@ user_dir = platformdirs.user_config_dir("onlylegs")
|
|||
instance_dir = os.path.join(user_dir, "instance")
|
||||
|
||||
# Load environment variables
|
||||
print("Loading environment variables...")
|
||||
# print("Loading environment variables...")
|
||||
load_dotenv(os.path.join(user_dir, ".env"))
|
||||
|
||||
# Load config from user dir
|
||||
print("Loading config...")
|
||||
# print("Loading config...")
|
||||
with open(os.path.join(user_dir, "conf.yml"), encoding="utf-8", mode="r") as file:
|
||||
conf = safe_load(file)
|
||||
|
||||
|
@ -24,13 +24,20 @@ with open(os.path.join(user_dir, "conf.yml"), encoding="utf-8", mode="r") as fil
|
|||
# Flask config
|
||||
SECRET_KEY = os.environ.get("FLASK_SECRET")
|
||||
SQLALCHEMY_DATABASE_URI = "sqlite:///gallery.sqlite3"
|
||||
|
||||
# Upload config
|
||||
MAX_CONTENT_LENGTH = 1024 * 1024 * conf["upload"]["max-size"]
|
||||
UPLOAD_FOLDER = os.path.join(user_dir, "uploads")
|
||||
ALLOWED_EXTENSIONS = conf["upload"]["allowed-extensions"]
|
||||
|
||||
# Pass YAML config to app
|
||||
ADMIN_CONF = conf["admin"]
|
||||
UPLOAD_CONF = conf["upload"]
|
||||
WEBSITE_CONF = conf["website"]
|
||||
|
||||
# Directories
|
||||
UPLOAD_FOLDER = os.path.join(user_dir, "media", "uploads")
|
||||
CACHE_FOLDER = os.path.join(user_dir, "media", "cache")
|
||||
PFP_FOLDER = os.path.join(user_dir, "media", "pfp")
|
||||
MEDIA_FOLDER = os.path.join(user_dir, "media")
|
||||
|
||||
# Database
|
||||
INSTANCE_DIR = instance_dir
|
||||
MIGRATIONS_DIR = os.path.join(INSTANCE_DIR, "migrations")
|
||||
|
|
4
onlylegs/gwagwa.py
Normal file
4
onlylegs/gwagwa.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
Gwa Gwa!
|
||||
"""
|
||||
print("Gwa Gwa!")
|
|
@ -87,9 +87,11 @@ class User(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C010
|
|||
id = db.Column(db.Integer, primary_key=True)
|
||||
alt_id = db.Column(db.String, unique=True, nullable=False, default=str(uuid4()))
|
||||
|
||||
profile_picture = db.Column(db.String, nullable=True, default=None)
|
||||
username = db.Column(db.String, unique=True, nullable=False)
|
||||
picture = db.Column(db.String, default=None)
|
||||
colour = db.Column(db.PickleType, default=None)
|
||||
banner = db.Column(db.String, default=None)
|
||||
|
||||
username = db.Column(db.String, unique=True, nullable=False)
|
||||
email = db.Column(db.String, unique=True, nullable=False)
|
||||
password = db.Column(db.String, nullable=False)
|
||||
joined_at = db.Column(
|
||||
|
|
BIN
onlylegs/static/banner.png
Normal file
BIN
onlylegs/static/banner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 MiB |
|
@ -61,7 +61,7 @@ window.onload = function () {
|
|||
}
|
||||
infoButton.onclick = function () {
|
||||
popUpShow('OnlyLegs',
|
||||
'<a href="https://github.com/Fluffy-Bean/onlylegs">v0.1.0</a> ' +
|
||||
'<a href="https://github.com/Fluffy-Bean/onlylegs">v0.1.2</a> ' +
|
||||
'using <a href="https://phosphoricons.com/">Phosphoricons</a> and Flask.' +
|
||||
'<br>Made by Fluffy and others with ❤️');
|
||||
}
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
function addNotification(notificationText, notificationLevel) {
|
||||
const notificationContainer = document.querySelector('.notifications');
|
||||
|
||||
// Set the different icons for the different notification levels
|
||||
const successIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"></path></svg>';
|
||||
const criticalIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z"></path></svg>';
|
||||
const warningIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path></svg>';
|
||||
const infoIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path></svg>';
|
||||
|
||||
// Create notification element
|
||||
const notification = document.createElement('div');
|
||||
notification.classList.add('sniffle__notification');
|
||||
|
@ -28,16 +22,16 @@ function addNotification(notificationText, notificationLevel) {
|
|||
// Set the icon based on the notification level, not pretty but it works :3
|
||||
if (notificationLevel === 1) {
|
||||
notification.classList.add('success');
|
||||
iconElement.innerHTML = successIcon;
|
||||
iconElement.innerHTML = '<i class="ph ph-check-circle"></i>';
|
||||
} else if (notificationLevel === 2) {
|
||||
notification.classList.add('critical');
|
||||
iconElement.innerHTML = criticalIcon;
|
||||
iconElement.innerHTML = '<i class="ph ph-warning"></i>';
|
||||
} else if (notificationLevel === 3) {
|
||||
notification.classList.add('warning');
|
||||
iconElement.innerHTML = warningIcon;
|
||||
iconElement.innerHTML = '<i class="ph ph-siren"></i>';
|
||||
} else {
|
||||
notification.classList.add('info');
|
||||
iconElement.innerHTML = infoIcon;
|
||||
iconElement.innerHTML = '<i class="ph ph-info"></i>';
|
||||
}
|
||||
|
||||
// Create text element and append to notification
|
||||
|
|
|
@ -276,12 +276,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
// });
|
||||
|
||||
|
||||
fetch('/api/upload', {
|
||||
fetch('/api/media/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
// .then(response => response.json())
|
||||
.then(data => { addNotification("Image uploaded successfully", 1); })
|
||||
.then(data => {
|
||||
addNotification("Image uploaded successfully", 1);
|
||||
})
|
||||
.catch(error => {
|
||||
switch (response.status) {
|
||||
case 500:
|
||||
|
@ -303,7 +305,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
clearUpload();
|
||||
|
||||
// Reset drop
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
|
||||
.banner
|
||||
height: 30rem
|
||||
max-height: 69vh
|
||||
|
||||
background-color: RGB($bg-300)
|
||||
|
||||
img
|
||||
|
@ -72,9 +74,8 @@
|
|||
bottom: 0
|
||||
|
||||
display: grid
|
||||
grid-template-columns: 1fr auto
|
||||
grid-template-rows: 1fr auto auto
|
||||
grid-template-areas: 'info info' 'header header' 'subtitle options'
|
||||
grid-template-columns: auto 1fr auto
|
||||
grid-template-areas: 'info info info' 'image header header' 'subtitle subtitle options'
|
||||
|
||||
z-index: +2
|
||||
|
||||
|
@ -109,6 +110,19 @@
|
|||
margin-top: auto
|
||||
grid-area: options
|
||||
|
||||
.banner-picture
|
||||
grid-area: image
|
||||
|
||||
margin: auto 1rem auto 0
|
||||
|
||||
position: relative
|
||||
|
||||
width: 6.9rem
|
||||
height: 6.9rem
|
||||
|
||||
background-color: RGB($primary)
|
||||
border-radius: $rad
|
||||
|
||||
.banner-small
|
||||
height: 3.5rem
|
||||
background-color: RGB($bg-100)
|
||||
|
@ -165,8 +179,12 @@
|
|||
display: none
|
||||
|
||||
.banner
|
||||
min-height: 17rem
|
||||
min-height: 15rem
|
||||
height: auto
|
||||
max-height: 30vh
|
||||
|
||||
.banner-filter
|
||||
background: linear-gradient(to bottom, RGB($bg-100), transparent)
|
||||
|
||||
.banner-content
|
||||
padding: 0.5rem
|
||||
|
@ -178,7 +196,7 @@
|
|||
align-items: center
|
||||
|
||||
.banner-header
|
||||
margin: 1rem 0
|
||||
margin: 0.7rem 0
|
||||
text-align: center
|
||||
font-size: 2.5rem
|
||||
|
||||
|
@ -192,6 +210,12 @@
|
|||
.pill-row
|
||||
margin-top: 0rem
|
||||
|
||||
.banner-picture
|
||||
margin: 0 auto
|
||||
width: 4rem
|
||||
height: 4rem
|
||||
display: flex
|
||||
|
||||
.banner-small
|
||||
.banner-content
|
||||
.banner-info
|
||||
|
|
|
@ -149,6 +149,9 @@
|
|||
opacity: 0
|
||||
cursor: pointer
|
||||
|
||||
i
|
||||
font-size: 1.2rem
|
||||
|
||||
.status
|
||||
width: 100%
|
||||
white-space: nowrap
|
||||
|
|
|
@ -23,15 +23,13 @@
|
|||
cursor: pointer
|
||||
transition: all 0.2s cubic-bezier(.86, 0, .07, 1)
|
||||
|
||||
i
|
||||
margin: 0.5rem
|
||||
font-size: 1.25rem
|
||||
|
||||
&:hover
|
||||
color: RGB($info)
|
||||
|
||||
svg
|
||||
margin: 0.5rem
|
||||
|
||||
width: 1.25rem
|
||||
height: 1.25rem
|
||||
|
||||
&.show
|
||||
right: 0.75rem
|
||||
opacity: 1
|
||||
|
|
|
@ -54,13 +54,14 @@
|
|||
|
||||
position: relative
|
||||
|
||||
text-decoration: none
|
||||
|
||||
border: none
|
||||
background-color: transparent
|
||||
color: RGB($fg-white)
|
||||
|
||||
svg
|
||||
width: 1.25rem
|
||||
height: 1.25rem
|
||||
i
|
||||
font-size: 1.25rem
|
||||
|
||||
&:hover
|
||||
cursor: pointer
|
||||
|
@ -74,7 +75,7 @@
|
|||
background: RGB($critical)
|
||||
color: RGB($fg-white)
|
||||
|
||||
svg
|
||||
i
|
||||
color: RGB($critical)
|
||||
|
||||
&:hover
|
||||
|
|
|
@ -23,15 +23,13 @@
|
|||
cursor: pointer
|
||||
transition: all 0.2s cubic-bezier(.86, 0, .07, 1)
|
||||
|
||||
i
|
||||
margin: 0.5rem
|
||||
font-size: 1.25rem
|
||||
|
||||
&:hover
|
||||
color: RGB($primary)
|
||||
|
||||
svg
|
||||
margin: 0.5rem
|
||||
|
||||
width: 1.25rem
|
||||
height: 1.25rem
|
||||
|
||||
&.show
|
||||
right: 0.75rem
|
||||
opacity: 1
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
.gallery-header
|
||||
margin: 0.5rem
|
||||
padding: 0
|
||||
|
||||
width: 100%
|
||||
|
||||
display: flex
|
||||
flex-direction: row
|
||||
justify-content: flex-start
|
||||
|
||||
font-size: 2rem
|
||||
font-weight: 700
|
||||
|
||||
.gallery-grid
|
||||
margin: 0
|
||||
padding: 0.35rem
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
.image-container
|
||||
margin: auto
|
||||
padding: 0.5rem
|
||||
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
@ -19,3 +18,11 @@
|
|||
|
||||
object-fit: contain
|
||||
object-position: center
|
||||
|
||||
@media (max-width: 1100px)
|
||||
.image-container
|
||||
margin: 0 auto
|
||||
max-height: 69vh
|
||||
|
||||
img
|
||||
max-height: 69vh
|
|
@ -1,4 +1,6 @@
|
|||
.info-container
|
||||
padding: 0.5rem 0 0.5rem 0.5rem
|
||||
|
||||
width: 27rem
|
||||
height: 100vh
|
||||
|
||||
|
@ -8,13 +10,17 @@
|
|||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 0
|
||||
gap: 0.5rem
|
||||
|
||||
background-color: RGB($bg-200)
|
||||
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
|
||||
|
@ -27,7 +33,7 @@
|
|||
|
||||
position: relative
|
||||
|
||||
background-color: RGB($bg-200)
|
||||
background-color: RGB($bg-300)
|
||||
border-radius: $rad
|
||||
|
||||
transition: max-height 0.3s cubic-bezier(.79, .14, .15, .86)
|
||||
|
@ -38,17 +44,16 @@
|
|||
.collapse-indicator
|
||||
transform: rotate(90deg)
|
||||
|
||||
.info-header
|
||||
border-radius: $rad
|
||||
|
||||
.info-table
|
||||
height: 0
|
||||
padding: 0
|
||||
display: none
|
||||
|
||||
.collapse-indicator
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
width: 1.25rem
|
||||
height: 1.25rem
|
||||
|
||||
position: absolute
|
||||
top: 0.6rem
|
||||
right: 0.6rem
|
||||
|
@ -62,8 +67,11 @@
|
|||
transition: transform 0.15s cubic-bezier(.79, .14, .15, .86)
|
||||
cursor: pointer
|
||||
|
||||
> i
|
||||
font-size: 1.1rem
|
||||
color: RGB($primary)
|
||||
|
||||
.info-header
|
||||
margin: 0
|
||||
padding: 0.5rem
|
||||
|
||||
width: 100%
|
||||
|
@ -74,24 +82,15 @@
|
|||
align-items: center
|
||||
gap: 0.5rem
|
||||
|
||||
position: sticky
|
||||
top: 0
|
||||
z-index: +1
|
||||
|
||||
background-color: RGB($bg-200)
|
||||
border-radius: $rad $rad 0 0
|
||||
|
||||
svg
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
width: 1.25rem
|
||||
height: 1.25rem
|
||||
|
||||
fill: RGB($primary)
|
||||
> i
|
||||
font-size: 1.25rem
|
||||
color: RGB($primary)
|
||||
|
||||
h2
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
font-size: 1.1rem
|
||||
font-weight: 500
|
||||
|
@ -202,14 +201,15 @@
|
|||
|
||||
@media (max-width: 1100px)
|
||||
.info-container
|
||||
padding: 0.5rem
|
||||
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
position: relative
|
||||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 0.5rem
|
||||
|
||||
&.collapsed
|
||||
left: unset
|
||||
|
||||
.info-container
|
||||
background: transparent
|
||||
|
|
|
@ -13,12 +13,11 @@
|
|||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 0.5rem
|
||||
z-index: 3
|
||||
|
||||
.image-block
|
||||
margin: 0 0 0 27rem
|
||||
padding: 0
|
||||
padding: 0.5rem
|
||||
|
||||
width: calc(100% - 27rem)
|
||||
height: 100vh
|
||||
|
@ -33,49 +32,28 @@
|
|||
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
|
||||
margin-top: 0.5rem
|
||||
|
||||
&.collapsed
|
||||
.image-block
|
||||
margin: 0
|
||||
width: 100%
|
||||
|
||||
|
||||
@media (max-width: 1100px)
|
||||
.image-grid
|
||||
padding: 0.5rem
|
||||
height: auto
|
||||
|
||||
.image-block
|
||||
margin: 0
|
||||
padding: 0.5rem 0.5rem 0 0.5rem
|
||||
|
||||
width: 100%
|
||||
height: auto
|
||||
|
||||
gap: 0.5rem
|
||||
|
||||
transition: margin 0s, width 0s
|
||||
|
||||
.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
|
||||
|
||||
.info-header
|
||||
border-radius: $rad $rad 0 0
|
||||
|
||||
.info-tab.collapsed .info-header
|
||||
border-radius: $rad
|
||||
|
||||
|
||||
|
|
|
@ -54,20 +54,25 @@
|
|||
|
||||
text-decoration: none
|
||||
|
||||
> svg
|
||||
margin: 0
|
||||
> i
|
||||
padding: 0.5rem
|
||||
|
||||
width: 2.5rem
|
||||
height: 2.5rem
|
||||
|
||||
font-size: 1.3rem
|
||||
border-radius: $rad-inner
|
||||
color: RGB($fg-white)
|
||||
|
||||
transition: color 0.2s ease-out, transform 0.2s ease-out
|
||||
> .nav-pfp
|
||||
padding: 0.4rem
|
||||
width: 2.3rem
|
||||
height: 2.3rem
|
||||
border-radius: $rad-inner
|
||||
|
||||
img
|
||||
width: 100%
|
||||
height: 100%
|
||||
object-fit: cover
|
||||
border-radius: $rad-inner
|
||||
|
||||
.tool-tip
|
||||
margin: 0
|
||||
padding: 0.4rem 0.7rem
|
||||
|
||||
display: block
|
||||
|
@ -89,13 +94,7 @@
|
|||
|
||||
pointer-events: none
|
||||
|
||||
> svg
|
||||
margin: 0
|
||||
font-size: 1rem
|
||||
|
||||
width: 0.75rem
|
||||
height: 0.75rem
|
||||
|
||||
> i
|
||||
display: block
|
||||
|
||||
position: absolute
|
||||
|
@ -103,10 +102,12 @@
|
|||
left: -0.45rem
|
||||
transform: translateY(-50%)
|
||||
|
||||
font-size: 0.75rem
|
||||
|
||||
color: RGB($bg-100)
|
||||
|
||||
&:hover
|
||||
> svg
|
||||
> i, .nav-pfp
|
||||
background: RGBA($fg-white, 0.1)
|
||||
|
||||
span
|
||||
|
@ -114,7 +115,7 @@
|
|||
left: 3.9rem
|
||||
|
||||
&.selected
|
||||
> svg
|
||||
> i
|
||||
color: RGB($primary)
|
||||
|
||||
&::before
|
||||
|
|
|
@ -108,9 +108,8 @@
|
|||
|
||||
background-color: RGB($bg-200)
|
||||
|
||||
svg
|
||||
width: 1.25rem
|
||||
height: 1.25rem
|
||||
i
|
||||
font-size: 1.25rem
|
||||
|
||||
.sniffle__notification-text
|
||||
margin: 0
|
||||
|
|
|
@ -138,7 +138,7 @@
|
|||
@media (max-width: $breakpoint)
|
||||
.pop-up
|
||||
.pop-up-wrapper
|
||||
width: calc(100% - 0.75rem)
|
||||
max-width: calc(100% - 0.75rem)
|
||||
max-height: 95vh
|
||||
|
||||
.pop-up-content
|
||||
|
|
29
onlylegs/static/sass/components/settings.sass
Normal file
29
onlylegs/static/sass/components/settings.sass
Normal file
|
@ -0,0 +1,29 @@
|
|||
.settings-content
|
||||
margin: 0.5rem
|
||||
padding: 1rem
|
||||
|
||||
position: relative
|
||||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: center
|
||||
gap: 1rem
|
||||
|
||||
background-color: RGB($bg-400)
|
||||
color: RGB($fg-white)
|
||||
border: 2px solid RGB($bg-200)
|
||||
border-radius: $rad-inner
|
||||
|
||||
h2
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
font-size: 1.5rem
|
||||
font-weight: 700
|
||||
|
||||
h3
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
font-size: 1.25rem
|
||||
font-weight: 700
|
|
@ -86,7 +86,7 @@
|
|||
|
||||
&::after
|
||||
content: ''
|
||||
width: 8rem
|
||||
width: 6rem
|
||||
height: 3px
|
||||
|
||||
position: absolute
|
||||
|
@ -97,6 +97,12 @@
|
|||
background-color: RGB($bg-400)
|
||||
border-radius: $rad-inner
|
||||
|
||||
transition: width 0.25s $animation-bounce
|
||||
|
||||
&.dragging #dragIndicator::after
|
||||
width: 9rem
|
||||
background-color: RGB($primary)
|
||||
|
||||
.upload-jobs
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
@import "components/buttons/block"
|
||||
|
||||
@import "components/image-view/view"
|
||||
@import "components/settings"
|
||||
|
||||
// Reset
|
||||
*
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
const formData = new FormData();
|
||||
formData.append("group", formID);
|
||||
|
||||
fetch('{{ url_for('api.delete_group') }}', {
|
||||
fetch('{{ url_for('group_api.delete_group') }}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => {
|
||||
|
@ -147,7 +147,7 @@
|
|||
formData.append("image", formImage);
|
||||
formData.append("action", formAction);
|
||||
|
||||
fetch('{{ url_for('api.modify_group') }}', {
|
||||
fetch('{{ url_for('group_api.modify_group') }}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => {
|
||||
|
@ -209,8 +209,7 @@
|
|||
.navigation {
|
||||
background-color: rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}) !important;
|
||||
}
|
||||
.navigation-item > svg {
|
||||
fill: {{ text_colour }} !important;
|
||||
.navigation-item > i {
|
||||
color: {{ text_colour }} !important;
|
||||
}
|
||||
.navigation-item.selected::before {
|
||||
|
@ -222,7 +221,7 @@
|
|||
{% block content %}
|
||||
{% if images %}
|
||||
<div class="banner">
|
||||
<img src="{{ url_for('api.file', file_name=images.0.filename ) }}?r=prev" onload="imgFade(this)" style="opacity:0;" alt="{% if images.0.alt %}{{ images.0.alt }}{% else %}Group Banner{% endif %}"/>
|
||||
<img src="{{ url_for('media_api.media', path='uploads/' + images.0.filename ) }}?r=prev" onload="imgFade(this)" style="opacity:0;" alt="{% if images.0.alt %}{{ images.0.alt }}{% else %}Group Banner{% endif %}"/>
|
||||
<span class="banner-filter"></span>
|
||||
<div class="banner-content">
|
||||
<p class="banner-info"><a href="{{ url_for('profile.profile', id=group.author.id) }}" class="link">By {{ group.author.username }}</a></p>
|
||||
|
@ -230,18 +229,12 @@
|
|||
<p class="banner-subtitle">{{ images|length }} Images · {{ group.description }}</p>
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<button class="pill-item" onclick="groupShare()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,112v96a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V112A16,16,0,0,1,56,96H80a8,8,0,0,1,0,16H56v96H200V112H176a8,8,0,0,1,0-16h24A16,16,0,0,1,216,112ZM93.66,69.66,120,43.31V136a8,8,0,0,0,16,0V43.31l26.34,26.35a8,8,0,0,0,11.32-11.32l-40-40a8,8,0,0,0-11.32,0l-40,40A8,8,0,0,0,93.66,69.66Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item" onclick="groupShare()"><i class="ph ph-export"></i></button>
|
||||
</div>
|
||||
{% if current_user.id == group.author.id %}
|
||||
<div>
|
||||
<button class="pill-item pill__critical" onclick="groupDelete()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item pill__critical" onclick="groupEdit()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item pill__critical" onclick="groupDelete()"><i class="ph ph-trash"></i></button>
|
||||
<button class="pill-item pill__critical" onclick="groupEdit()"><i class="ph ph-pencil-simple"></i></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -254,18 +247,12 @@
|
|||
<p class="banner-info">By {{ group.author.username }}</p>
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<button class="pill-item" onclick="groupShare()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,112v96a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V112A16,16,0,0,1,56,96H80a8,8,0,0,1,0,16H56v96H200V112H176a8,8,0,0,1,0-16h24A16,16,0,0,1,216,112ZM93.66,69.66,120,43.31V136a8,8,0,0,0,16,0V43.31l26.34,26.35a8,8,0,0,0,11.32-11.32l-40-40a8,8,0,0,0-11.32,0l-40,40A8,8,0,0,0,93.66,69.66Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item" onclick="groupShare()"><i class="ph ph-export"></i></button>
|
||||
</div>
|
||||
{% if current_user.id == group.author.id %}
|
||||
<div>
|
||||
<button class="pill-item pill__critical" onclick="groupDelete()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item pill__critical" onclick="groupEdit()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item pill__critical" onclick="groupDelete()"><i class="ph ph-trash"></i></button>
|
||||
<button class="pill-item pill__critical" onclick="groupEdit()"><i class="ph ph-pencil-simple"></i></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -278,10 +265,9 @@
|
|||
{% for image in images %}
|
||||
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('group.group_post', group_id=group.id, image_id=image.id) }}" style="background-color: rgb({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }})">
|
||||
<div class="image-filter">
|
||||
<p class="image-subtitle"></p>
|
||||
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
|
||||
</div>
|
||||
<img alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
<img alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% block page_index %}
|
||||
{% if return_page %}?page={{ return_page }}{% endif %}{% endblock %}
|
||||
{% block head %}
|
||||
<meta property="og:image" content="{{ url_for('api.file', file_name=image.filename) }}"/>
|
||||
<meta property="og:image" content="{{ url_for('media_api.media', path='uploads/' + image.filename) }}"/>
|
||||
<meta name="theme-color" content="rgb({{ image.colours.0.0 }}{{ image.colours.0.1 }}{{ image.colours.0.2 }})"/>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
@ -50,7 +50,7 @@
|
|||
function deleteConfirm() {
|
||||
popupDissmiss();
|
||||
|
||||
fetch('{{ url_for('api.delete_image', image_id=image['id']) }}', {
|
||||
fetch('{{ url_for('media_api.delete_image', image_id=image['id']) }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
@ -82,7 +82,7 @@
|
|||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="background">
|
||||
<img src="{{ url_for('api.file', file_name=image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/>
|
||||
<img src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
|
@ -90,7 +90,7 @@
|
|||
<div class="image-block">
|
||||
<div class="image-container">
|
||||
<img
|
||||
src="{{ url_for('api.file', file_name=image.filename) }}?r=prev"
|
||||
src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=prev"
|
||||
alt="{{ image.alt }}"
|
||||
onload="imgFade(this)"
|
||||
style="opacity: 0;"
|
||||
|
@ -103,54 +103,28 @@
|
|||
</div>
|
||||
|
||||
<div class="pill-row">
|
||||
{% if next_url %}
|
||||
<div>
|
||||
<a class="pill-item" href="{{ next_url }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if next_url %}<div><a class="pill-item" href="{{ next_url }}"><i class="ph ph-arrow-left"></i></a></div>{% endif %}
|
||||
<div>
|
||||
<button class="pill-item" onclick="fullscreen()" id="fullscreenImage">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item" onclick="imageShare()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,112v96a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V112A16,16,0,0,1,56,96H80a8,8,0,0,1,0,16H56v96H200V112H176a8,8,0,0,1,0-16h24A16,16,0,0,1,216,112ZM93.66,69.66,120,43.31V136a8,8,0,0,0,16,0V43.31l26.34,26.35a8,8,0,0,0,11.32-11.32l-40-40a8,8,0,0,0-11.32,0l-40,40A8,8,0,0,0,93.66,69.66Z"></path></svg>
|
||||
</button>
|
||||
<a class="pill-item" href="/api/file/{{ image.filename }}" download onclick="addNotification('Download started!', 4)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Zm-42.34-61.66a8,8,0,0,1,0,11.32l-24,24a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L120,164.69V120a8,8,0,0,1,16,0v44.69l10.34-10.35A8,8,0,0,1,157.66,154.34Z"></path></svg>
|
||||
</a>
|
||||
<button class="pill-item" onclick="fullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button>
|
||||
<button class="pill-item" onclick="imageShare()"><i class="ph ph-export"></i></button>
|
||||
<a class="pill-item" href="{{ url_for('media_api.media', path='uploads/' + image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a>
|
||||
</div>
|
||||
{% if current_user.id == image.author.id %}
|
||||
<div>
|
||||
<button class="pill-item pill__critical" onclick="imageDelete()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item pill__critical" onclick="imageEdit()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if prev_url %}
|
||||
<div>
|
||||
<a class="pill-item" href="{{ prev_url }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"></path></svg>
|
||||
</a>
|
||||
<button class="pill-item pill__critical" onclick="imageDelete()"><i class="ph ph-trash"></i></button>
|
||||
<button class="pill-item pill__critical" onclick="imageEdit()"><i class="ph ph-pencil-simple"></i></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if prev_url %}<div><a class="pill-item" href="{{ prev_url }}"><i class="ph ph-arrow-right"></i></a></div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-container">
|
||||
<div class="info-tab">
|
||||
<div class="info-header">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path></svg>
|
||||
<i class="ph ph-info"></i>
|
||||
<h2>Info</h2>
|
||||
<button class="collapse-indicator">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor">
|
||||
<path d="M7.071 5.314l4.95-4.95a1 1 0 1 1 1.414 1.414L7.778 7.435a1 1 0 0 1-1.414 0L.707 1.778A1 1 0 1 1 2.121.364l4.95 4.95z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="collapse-indicator"><i class="ph ph-caret-down"></i></button>
|
||||
</div>
|
||||
<div class="info-table">
|
||||
<table>
|
||||
|
@ -178,7 +152,7 @@
|
|||
<div class="img-groups">
|
||||
{% for group in image.groups %}
|
||||
<a href="{{ url_for('group.group', group_id=group.id) }}" class="tag-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,72H131.31L104,44.69A15.86,15.86,0,0,0,92.69,40H40A16,16,0,0,0,24,56V200.62A15.4,15.4,0,0,0,39.38,216H216.89A15.13,15.13,0,0,0,232,200.89V88A16,16,0,0,0,216,72ZM40,56H92.69l16,16H40ZM216,200H40V88H216Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M223.68,66.15,135.68,18a15.88,15.88,0,0,0-15.36,0l-88,48.17a16,16,0,0,0-8.32,14v95.64a16,16,0,0,0,8.32,14l88,48.17a15.88,15.88,0,0,0,15.36,0l88-48.17a16,16,0,0,0,8.32-14V80.18A16,16,0,0,0,223.68,66.15ZM128,32l80.34,44-29.77,16.3-80.35-44ZM128,120,47.66,76l33.9-18.56,80.34,44ZM40,90l80,43.78v85.79L40,175.82Zm176,85.78h0l-80,43.79V133.82l32-17.51V152a8,8,0,0,0,16,0V107.55L216,90v85.77Z"></path></svg>
|
||||
{{ group['name'] }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
@ -190,26 +164,17 @@
|
|||
<div class="info-tab">
|
||||
<div class="info-header">
|
||||
{% if tag == 'Photographer' %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M160,40a32,32,0,1,0-32,32A32,32,0,0,0,160,40ZM128,56a16,16,0,1,1,16-16A16,16,0,0,1,128,56Zm90.34,78.05L173.17,82.83a32,32,0,0,0-24-10.83H106.83a32,32,0,0,0-24,10.83L37.66,134.05a20,20,0,0,0,28.13,28.43l16.3-13.08L65.55,212.28A20,20,0,0,0,102,228.8l26-44.87,26,44.87a20,20,0,0,0,36.41-16.52L173.91,149.4l16.3,13.08a20,20,0,0,0,28.13-28.43Zm-11.51,16.77a4,4,0,0,1-5.66,0c-.21-.2-.42-.4-.65-.58L165,121.76A8,8,0,0,0,152.26,130L175.14,217a7.72,7.72,0,0,0,.48,1.35,4,4,0,1,1-7.25,3.38,6.25,6.25,0,0,0-.33-.63L134.92,164a8,8,0,0,0-13.84,0L88,221.05a6.25,6.25,0,0,0-.33.63,4,4,0,0,1-2.26,2.07,4,4,0,0,1-5-5.45,7.72,7.72,0,0,0,.48-1.35L103.74,130A8,8,0,0,0,91,121.76L55.48,150.24c-.23.18-.44.38-.65.58a4,4,0,1,1-5.66-5.65c.12-.12.23-.24.34-.37L94.83,93.41a16,16,0,0,1,12-5.41h42.34a16,16,0,0,1,12,5.41l45.32,51.39c.11.13.22.25.34.37A4,4,0,0,1,206.83,150.82Z"></path></svg>
|
||||
<h2>Photographer</h2>
|
||||
<i class="ph ph-person"></i><h2>Photographer</h2>
|
||||
{% elif tag == 'Camera' %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M208,56H180.28L166.65,35.56A8,8,0,0,0,160,32H96a8,8,0,0,0-6.65,3.56L75.71,56H48A24,24,0,0,0,24,80V192a24,24,0,0,0,24,24H208a24,24,0,0,0,24-24V80A24,24,0,0,0,208,56Zm8,136a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V80a8,8,0,0,1,8-8H80a8,8,0,0,0,6.66-3.56L100.28,48h55.43l13.63,20.44A8,8,0,0,0,176,72h32a8,8,0,0,1,8,8ZM128,88a44,44,0,1,0,44,44A44.05,44.05,0,0,0,128,88Zm0,72a28,28,0,1,1,28-28A28,28,0,0,1,128,160Z"></path></svg>
|
||||
<h2>Camera</h2>
|
||||
<i class="ph ph-camera"></i><h2>Camera</h2>
|
||||
{% elif tag == 'Software' %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M24,96v72a8,8,0,0,0,8,8h80a8,8,0,0,1,0,16H96v16h16a8,8,0,0,1,0,16H64a8,8,0,0,1,0-16H80V192H32A24,24,0,0,1,8,168V96A24,24,0,0,1,32,72h80a8,8,0,0,1,0,16H32A8,8,0,0,0,24,96ZM208,64H176a8,8,0,0,0,0,16h32a8,8,0,0,0,0-16Zm0,32H176a8,8,0,0,0,0,16h32a8,8,0,0,0,0-16Zm40-48V208a16,16,0,0,1-16,16H152a16,16,0,0,1-16-16V48a16,16,0,0,1,16-16h80A16,16,0,0,1,248,48ZM232,208V48H152V208h80Zm-40-40a12,12,0,1,0,12,12A12,12,0,0,0,192,168Z"></path></svg>
|
||||
<h2>Software</h2>
|
||||
<i class="ph ph-desktop-tower"></i><h2>Software</h2>
|
||||
{% elif tag == 'File' %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M110.66,147.56a8,8,0,0,0-13.32,0L76.49,178.85l-9.76-15.18a8,8,0,0,0-13.46,0l-36,56A8,8,0,0,0,24,232H152a8,8,0,0,0,6.66-12.44ZM38.65,216,60,182.79l9.63,15a8,8,0,0,0,13.39.11l21-31.47L137.05,216Zm175-133.66-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40v96a8,8,0,0,0,16,0V40h88V88a8,8,0,0,0,8,8h48V216h-8a8,8,0,0,0,0,16h8a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160Z"></path></svg>
|
||||
<h2>File</h2>
|
||||
<i class="ph ph-file-image"></i><h2>File</h2>
|
||||
{% else %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M110.66,147.56a8,8,0,0,0-13.32,0L76.49,178.85l-9.76-15.18a8,8,0,0,0-13.46,0l-36,56A8,8,0,0,0,24,232H152a8,8,0,0,0,6.66-12.44ZM38.65,216,60,182.79l9.63,15a8,8,0,0,0,13.39.11l21-31.47L137.05,216Zm175-133.66-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40v96a8,8,0,0,0,16,0V40h88V88a8,8,0,0,0,8,8h48V216h-8a8,8,0,0,0,0,16h8a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160Z"></path></svg>
|
||||
<h2>{{ tag }}</h2>
|
||||
<i class="ph ph-file-image"></i><h2>{{ tag }}</h2>
|
||||
{% endif %}
|
||||
<button class="collapse-indicator">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor">
|
||||
<path d="M7.071 5.314l4.95-4.95a1 1 0 1 1 1.414 1.414L7.778 7.435a1 1 0 0 1-1.414 0L.707 1.778A1 1 0 1 1 2.121.364l4.95 4.95z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="collapse-indicator"><i class="ph ph-caret-down"></i></button>
|
||||
</div>
|
||||
<div class="info-table">
|
||||
<table>
|
||||
|
|
|
@ -15,29 +15,13 @@
|
|||
{% if pages > 1 %}
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
{% if pages > 4 %}
|
||||
<a class="pill-item" href="{{ url_for('gallery.index') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M232,128a8,8,0,0,1-8,8H91.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L91.31,120H224A8,8,0,0,1,232,128ZM40,32a8,8,0,0,0-8,8V216a8,8,0,0,0,16,0V40A8,8,0,0,0,40,32Z"></path></svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="pill-item" href="{% if (page - 1) > 1 %} {{ url_for('gallery.index', page=page-1) }} {% else %} {{ url_for('gallery.index') }} {% endif %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z"></path></svg>
|
||||
</a>
|
||||
{% if pages > 4 %}<a class="pill-item" href="{{ url_for('gallery.index') }}"><i class="ph ph-arrow-line-left"></i></a>{% endif %}
|
||||
<a class="pill-item" href="{% if (page - 1) > 1 %} {{ url_for('gallery.index', page=page-1) }} {% else %} {{ url_for('gallery.index') }} {% endif %}"><i class="ph ph-arrow-left"></i></a>
|
||||
</div>
|
||||
|
||||
<span class="pill-text">
|
||||
{{ page }} / {{ pages }}
|
||||
</span>
|
||||
|
||||
<span class="pill-text">{{ page }} / {{ pages }}</span>
|
||||
<div>
|
||||
<a class="pill-item" href="{% if (page + 1) < pages %} {{ url_for('gallery.index', page=page+1) }} {% else %} {{ url_for('gallery.index', page=pages) }} {% endif %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"></path></svg>
|
||||
</a>
|
||||
{% if pages > 4 %}
|
||||
<a class="pill-item" href="{{ url_for('gallery.index', page=pages) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M189.66,122.34a8,8,0,0,1,0,11.32l-72,72a8,8,0,0,1-11.32-11.32L164.69,136H32a8,8,0,0,1,0-16H164.69L106.34,61.66a8,8,0,0,1,11.32-11.32ZM216,32a8,8,0,0,0-8,8V216a8,8,0,0,0,16,0V40A8,8,0,0,0,216,32Z"></path></svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="pill-item" href="{% if (page + 1) < pages %} {{ url_for('gallery.index', page=page+1) }} {% else %} {{ url_for('gallery.index', page=pages) }} {% endif %}"><i class="ph ph-arrow-right"></i></a>
|
||||
{% if pages > 4 %}<a class="pill-item" href="{{ url_for('gallery.index', page=pages) }}"><i class="ph ph-arrow-line-right"></i></a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -49,10 +33,9 @@
|
|||
{% for image in images %}
|
||||
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('image.image', image_id=image.id) }}" style="background-color: rgb({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }})">
|
||||
<div class="image-filter">
|
||||
<p class="image-subtitle"></p>
|
||||
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
|
||||
</div>
|
||||
<img fetchpriority="low" alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
<img fetchpriority="low" alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
<link rel="manifest" href="static/manifest.json"/>
|
||||
|
||||
<!-- phosphor icons!!! -->
|
||||
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
||||
|
||||
<link
|
||||
href="{{url_for('static', filename='logo-black.svg')}}"
|
||||
rel="icon"
|
||||
|
@ -48,14 +51,8 @@
|
|||
<body>
|
||||
<div class="notifications"></div>
|
||||
|
||||
<button class="top-of-page" aria-label="Jump to top of page">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M184,216a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,216Zm45.66-101.66-96-96a8,8,0,0,0-11.32,0l-96,96A8,8,0,0,0,32,128H72v24a8,8,0,0,0,8,8h96a8,8,0,0,0,8-8V128h40a8,8,0,0,0,5.66-13.66ZM176,176H80a8,8,0,0,0,0,16h96a8,8,0,0,0,0-16Z"></path></svg>
|
||||
</button>
|
||||
{% if request.path == "/" %}
|
||||
<button class="info-button" aria-label="Show info on gallery">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M140,180a12,12,0,1,1-12-12A12,12,0,0,1,140,180ZM128,72c-22.06,0-40,16.15-40,36v4a8,8,0,0,0,16,0v-4c0-11,10.77-20,24-20s24,9,24,20-10.77,20-24,20a8,8,0,0,0-8,8v8a8,8,0,0,0,16,0v-.72c18.24-3.35,32-17.9,32-35.28C168,88.15,150.06,72,128,72Zm104,56A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"></path></svg>
|
||||
</button>
|
||||
{% endif %}
|
||||
<button class="top-of-page" aria-label="Jump to top of page"><i class="ph ph-arrow-up"></i></button>
|
||||
{% if request.path == "/" %}<button class="info-button" aria-label="Show info on gallery"><i class="ph ph-question"></i></button>{% endif %}
|
||||
|
||||
<div class="pop-up">
|
||||
<span class="pop-up__click-off" onclick="popupDissmiss()"></span>
|
||||
|
@ -71,28 +68,19 @@
|
|||
<!--<img src="{{url_for('static', filename='icon.png')}}" alt="Logo" class="logo" onload="this.style.opacity=1;" style="opacity:0">-->
|
||||
|
||||
<a href="{{ url_for('gallery.index') }}{% block page_index %}{% endblock %}" class="navigation-item {% block nav_home %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M208,32H80A16,16,0,0,0,64,48V64H48A16,16,0,0,0,32,80V208a16,16,0,0,0,16,16H176a16,16,0,0,0,16-16V192h16a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM80,48H208v69.38l-16.7-16.7a16,16,0,0,0-22.62,0L93.37,176H80Zm96,160H48V80H64v96a16,16,0,0,0,16,16h96ZM104,88a16,16,0,1,1,16,16A16,16,0,0,1,104,88Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Home
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
<i class="ph-fill ph-images-square"></i>
|
||||
<span class="tool-tip">Home<i class="ph-fill ph-caret-left"></i></span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('group.groups') }}" class="navigation-item {% block nav_groups %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M245,110.64A16,16,0,0,0,232,104H216V88a16,16,0,0,0-16-16H130.67L102.94,51.2a16.14,16.14,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V208h0a8,8,0,0,0,8,8H211.1a8,8,0,0,0,7.59-5.47l28.49-85.47A16.05,16.05,0,0,0,245,110.64ZM93.34,64l27.73,20.8a16.12,16.12,0,0,0,9.6,3.2H200v16H146.43a16,16,0,0,0-8.88,2.69l-20,13.31H69.42a15.94,15.94,0,0,0-14.86,10.06L40,166.46V64Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Groups
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
<i class="ph-fill ph-package"></i>
|
||||
<span class="tool-tip">Groups<i class="ph-fill ph-caret-left"></i></span>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<button class="navigation-item {% block nav_upload %}{% endblock %}" onclick="toggleUploadTab()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M74.34,77.66a8,8,0,0,1,0-11.32l48-48a8,8,0,0,1,11.32,0l48,48a8,8,0,0,1-11.32,11.32L136,43.31V128a8,8,0,0,1-16,0V43.31L85.66,77.66A8,8,0,0,1,74.34,77.66ZM240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16h68a4,4,0,0,1,4,4v3.46c0,13.45,11,24.79,24.46,24.54A24,24,0,0,0,152,128v-4a4,4,0,0,1,4-4h68A16,16,0,0,1,240,136Zm-40,32a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Upload
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
<i class="ph-fill ph-upload"></i>
|
||||
<span class="tool-tip">Upload<i class="ph-fill ph-caret-left"></i></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
|
@ -100,27 +88,29 @@
|
|||
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('profile.profile') }}" class="navigation-item {% block nav_profile %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M231.73,221.94A8,8,0,0,1,224,232H160A8,8,0,0,1,152.27,222a40,40,0,0,1,17.11-23.33,32,32,0,1,1,45.24,0A40,40,0,0,1,231.73,221.94ZM216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16h80a8,8,0,0,0,0-16H40V64H93.33l27.74,20.8a16.12,16.12,0,0,0,9.6,3.2H216v32a8,8,0,0,0,16,0V88A16,16,0,0,0,216,72Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Profile
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
{% if current_user.picture %}
|
||||
<span class="nav-pfp">
|
||||
<img
|
||||
src="{{ url_for('media_api.media', path='pfp/' + current_user.picture) }}?r=icon"
|
||||
alt="Profile picture"
|
||||
onload="imgFade(this)"
|
||||
style="opacity:0;"
|
||||
/>
|
||||
</span>
|
||||
{% else %}
|
||||
<i class="ph-fill ph-folder-simple-user"></i>
|
||||
{% endif %}
|
||||
<span class="tool-tip">Profile<i class="ph-fill ph-caret-left"></i></span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('settings.general') }}" class="navigation-item {% block nav_settings %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,130.16q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.6,107.6,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.29,107.29,0,0,0-26.25-10.86,8,8,0,0,0-7.06,1.48L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.6,107.6,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06ZM128,168a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Settings
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
<i class="ph-fill ph-gear-fine"></i>
|
||||
<span class="tool-tip">Settings<i class="ph-fill ph-caret-left"></i></span>
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="navigation-item {% block nav_login %}{% endblock %}" onclick="showLogin()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M141.66,133.66l-40,40A8,8,0,0,1,88,168V136H24a8,8,0,0,1,0-16H88V88a8,8,0,0,1,13.66-5.66l40,40A8,8,0,0,1,141.66,133.66ZM192,32H136a8,8,0,0,0,0,16h56V208H136a8,8,0,0,0,0,16h56a16,16,0,0,0,16-16V48A16,16,0,0,0,192,32Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Login
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
<i class="ph-fill ph-sign-in"></i>
|
||||
<span class="tool-tip">Login<i class="ph-fill ph-caret-left"></i></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -134,7 +124,7 @@
|
|||
<p>May the world see your stuff 👀</p>
|
||||
<form id="uploadForm">
|
||||
<button class="fileDrop-block" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H80a8,8,0,0,1,0,16H32v64H224V136H176a8,8,0,0,1,0-16h48A16,16,0,0,1,240,136ZM85.66,77.66,120,43.31V128a8,8,0,0,0,16,0V43.31l34.34,34.35a8,8,0,0,0,11.32-11.32l-48-48a8,8,0,0,0-11.32,0l-48,48A8,8,0,0,0,85.66,77.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<i class="ph ph-upload"></i>
|
||||
<span class="status">Choose or Drop file</span>
|
||||
<input type="file" id="file" tab-index="-1"/>
|
||||
</button>
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
formData.append("name", formName);
|
||||
formData.append("description", formDescription);
|
||||
|
||||
fetch('{{ url_for('api.create_group') }}', {
|
||||
fetch('{{ url_for('group_api.create_group') }}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => {
|
||||
|
@ -108,9 +108,7 @@
|
|||
{% if current_user.is_authenticated %}
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<button class="pill-item" onclick="showCreate()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item" onclick="showCreate()"><i class="ph ph-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -128,7 +126,7 @@
|
|||
<div class="images size-{{ group.images|length }}">
|
||||
{% if group.images|length > 0 %}
|
||||
{% for image in group.images %}
|
||||
<img data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load" class="data-{{ loop.index }}" {% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}/>
|
||||
<img data-src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load" class="data-{{ loop.index }}" {% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}/>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='error.png') }}" class="loaded" alt="Error thumbnail"/>
|
||||
|
|
|
@ -1,29 +1,86 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block head %}
|
||||
{% if user.picture %}
|
||||
<meta property="og:image" content="{{ url_for('media_api.media', path='pfp/' + user.picture) }}"/>
|
||||
{% endif %}
|
||||
{% if user.colour %}
|
||||
<meta name="theme-color" content="rgb({{ user.colour.0 }}, {{ user.colour.1 }}, {{ user.colour.2 }})"/>
|
||||
{% endif %}
|
||||
|
||||
{% block nav_profile %}selected{% endblock %}
|
||||
<script type="text/javascript">
|
||||
function moreInfo() {
|
||||
popUpShow('{{ user.username }}',
|
||||
'<p>Joined: {{ user.joined_at }}</p><br>' +
|
||||
'<p>Images: {{ images|length }}</p><br>' +
|
||||
'<p>Groups: {{ groups|length }}</p>');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.banner-picture {
|
||||
background-color: rgb({{ user.colour.0 }}, {{ user.colour.1 }}, {{ user.colour.2 }}) !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block nav_profile %}{% if user.id == current_user.id %}selected{% endif %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="banner-small">
|
||||
<div class="banner">
|
||||
{% if user.banner %}
|
||||
<img src="{{ url_for('static', filename='icon.png') }}" alt="Profile Banner" onload="imgFade(this)" style="opacity:0;"/>
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='banner.png') }}" alt="Profile Banner" onload="imgFade(this)" style="opacity:0;"/>
|
||||
{% endif %}
|
||||
<span class="banner-filter"></span>
|
||||
<div class="banner-content">
|
||||
{% if user.picture %}
|
||||
<img
|
||||
class="banner-picture"
|
||||
src="{{ url_for('media_api.media', path='pfp/' + user.picture) }}?r=pfp"
|
||||
alt="Profile picture"
|
||||
onload="imgFade(this)"
|
||||
style="opacity:0;"
|
||||
/>
|
||||
{% else %}
|
||||
<img
|
||||
class="banner-picture"
|
||||
src="{{ url_for('static', filename='icon.png') }}"
|
||||
alt="Profile picture"
|
||||
onload="imgFade(this)"
|
||||
style="opacity:0;"
|
||||
/>
|
||||
{% endif %}
|
||||
<h1 class="banner-header">{{ user.username }}</h1>
|
||||
<p class="banner-info">Member since <span class="time">{{ user.joined_at }}</span></p>
|
||||
<p class="banner-subtitle">{{ images|length }} Images · {{ groups|length }} Groups</p>
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<button class="pill-item" onclick="profileShare()"><i class="ph ph-export"></i></button>
|
||||
<button class="pill-item" onclick="moreInfo()"><i class="ph ph-info"></i></button>
|
||||
</div>
|
||||
{% if user.id == current_user.id %}
|
||||
<div>
|
||||
<a href="{{ url_for('settings.general') }}#profileSettings" class="pill-item pill__critical"><i class="ph ph-user-circle-gear"></i></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if images %}
|
||||
<h1 class="gallery-header">Images</h1>
|
||||
<div class="gallery-grid">
|
||||
{% for image in images %}
|
||||
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('image.image', image_id=image.id) }}" style="background-color: rgb({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }})">
|
||||
<div class="image-filter">
|
||||
<p class="image-subtitle"></p>
|
||||
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
|
||||
</div>
|
||||
<img fetchpriority="low" alt="{{ image.alt }}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
<img fetchpriority="low" alt="{{ image.alt }}" data-src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="big-text">
|
||||
<h1>*crickets chirping*</h1>
|
||||
<p>There are no images here yet, upload some!</p>
|
||||
<p>There are no images here yet, oopsie!</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
40
onlylegs/templates/settings.html
Normal file
40
onlylegs/templates/settings.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block nav_settings %}selected{% endblock %}
|
||||
{% block content %}
|
||||
<div class="banner-small">
|
||||
<div class="banner-content">
|
||||
<h1 class="banner-header">Settings</h1>
|
||||
<p class="banner-info">{% block banner_subtitle%}{% endblock %}</p>
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<a class="pill-item pill__critical" href="{{ url_for( 'auth.logout' ) }}"><i class="ph ph-sign-out"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-content" id="profileSettings">
|
||||
<h2>Profile Settings</h2>
|
||||
<form method="POST" action="{{ url_for('account_api.account_picture', user_id=current_user.id) }}" enctype="multipart/form-data">
|
||||
<h3>Profile Picture</h3>
|
||||
<input type="file" name="file" tab-index="-1"/>
|
||||
<input type="submit" value="Upload" class="btn-block">
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ url_for('account_api.account_username', user_id=current_user.id) }}" enctype="multipart/form-data">
|
||||
<h3>Username</h3>
|
||||
<input type="text" name="name" class="input-block" value="{{ current_user.username }}" />
|
||||
<input type="submit" value="Upload" class="btn-block"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="settings-content" id="profileSettings">
|
||||
<h2>Account Settings</h2>
|
||||
<form method="POST" action="" enctype="multipart/form-data">
|
||||
<h3>Email</h3>
|
||||
<input type="text" name="email" class="input-block" value="{{ current_user.email }}" />
|
||||
<input type="submit" value="Upload" class="btn-block"/>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,12 +0,0 @@
|
|||
{% extends 'settings/settings_layout.html' %}
|
||||
|
||||
{% block settings_account %}settings-nav__item-selected{% endblock %}
|
||||
{% block settings_content %}
|
||||
<h2>Account</h2>
|
||||
<p>Is session fresh?</p>
|
||||
{% if fresh %}
|
||||
<p>Yes</p>
|
||||
{% else %}
|
||||
<p>No</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,6 +0,0 @@
|
|||
{% extends 'settings/settings_layout.html' %}
|
||||
|
||||
{% block settings_general %}settings-nav__item-selected{% endblock %}
|
||||
{% block settings_content %}
|
||||
<h2>General</h2>
|
||||
{% endblock %}
|
|
@ -1,30 +0,0 @@
|
|||
{% extends 'settings/settings_layout.html' %}
|
||||
|
||||
{% block settings_logs %}settings-nav__item-selected{% endblock %}
|
||||
{% block settings_content %}
|
||||
<h2>Logs</h2>
|
||||
<div class="settings-list" id="logs">
|
||||
<div class="log" style="display:flex;flex-direction:row;gap:0.5rem;"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
const output = document.getElementById('logs');
|
||||
|
||||
setInterval(function() {
|
||||
$.ajax({
|
||||
url: '{{ url_for('api.logfile') }}',
|
||||
type: 'GET',
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
|
||||
// for each item in response, log to console
|
||||
response.forEach(function(item) {
|
||||
console.log(item);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 1000); // 10 seconds
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,6 +0,0 @@
|
|||
{% extends 'settings/settings_layout.html' %}
|
||||
|
||||
{% block settings_server %}settings-nav__item-selected{% endblock %}
|
||||
{% block settings_content %}
|
||||
<h2>Server</h2>
|
||||
{% endblock %}
|
|
@ -1,29 +0,0 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block nav_settings %}selected{% endblock %}
|
||||
{% block content %}
|
||||
<div class="banner-small">
|
||||
<div class="banner-content">
|
||||
<h1 class="banner-header">Settings</h1>
|
||||
<p class="banner-info">{% block banner_subtitle%}{% endblock %}</p>
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<a class="pill-item pill__critical" href="{{ url_for( 'auth.logout' ) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M112,216a8,8,0,0,1-8,8H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32h56a8,8,0,0,1,0,16H48V208h56A8,8,0,0,1,112,216Zm109.66-93.66-40-40a8,8,0,0,0-11.32,11.32L196.69,120H104a8,8,0,0,0,0,16h92.69l-26.35,26.34a8,8,0,0,0,11.32,11.32l40-40A8,8,0,0,0,221.66,122.34Z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-nav">
|
||||
<a href="{{ url_for('settings.general') }}" class="settings-nav__item {% block settings_general %}{% endblock %}">General</a>
|
||||
<a href="{{ url_for('settings.server') }}" class="settings-nav__item {% block settings_server %}{% endblock %}">Server</a>
|
||||
<a href="{{ url_for('settings.account') }}" class="settings-nav__item {% block settings_account %}{% endblock %}">Account</a>
|
||||
<a href="{{ url_for('settings.logs') }}" class="settings-nav__item {% block settings_logs %}{% endblock %}">Logs</a>
|
||||
</div>
|
||||
|
||||
<div class="settings-content">
|
||||
{% block settings_content %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,18 +1,13 @@
|
|||
"""
|
||||
Tools for generating images and thumbnails
|
||||
"""
|
||||
|
||||
import os
|
||||
import platformdirs
|
||||
from PIL import Image, ImageOps
|
||||
from werkzeug.utils import secure_filename
|
||||
from onlylegs.config import MEDIA_FOLDER, CACHE_FOLDER
|
||||
|
||||
|
||||
CACHE_PATH = os.path.join(platformdirs.user_config_dir("onlylegs"), "cache")
|
||||
UPLOAD_PATH = os.path.join(platformdirs.user_config_dir("onlylegs"), "uploads")
|
||||
|
||||
|
||||
def generate_thumbnail(file_name, resolution, ext=None):
|
||||
def generate_thumbnail(file_path, resolution, ext=None):
|
||||
"""
|
||||
Image thumbnail generator
|
||||
Uses PIL to generate a thumbnail of the image and saves it to the cache directory
|
||||
|
@ -21,10 +16,11 @@ def generate_thumbnail(file_name, resolution, ext=None):
|
|||
ext is the file extension of the image
|
||||
"""
|
||||
# Make image cache directory if it doesn't exist
|
||||
if not os.path.exists(CACHE_PATH):
|
||||
os.makedirs(CACHE_PATH)
|
||||
if not os.path.exists(CACHE_FOLDER):
|
||||
os.makedirs(CACHE_FOLDER)
|
||||
|
||||
# no sussy business
|
||||
file_name = os.path.basename(file_path)
|
||||
file_name, file_ext = secure_filename(file_name).rsplit(".")
|
||||
if not ext:
|
||||
ext = file_ext.strip(".")
|
||||
|
@ -38,21 +34,23 @@ def generate_thumbnail(file_name, resolution, ext=None):
|
|||
res_x, res_y = (1920, 1080)
|
||||
elif resolution in ["thumb", "thumbnail"]:
|
||||
res_x, res_y = (400, 400)
|
||||
elif resolution in ["pfp", "profile"]:
|
||||
res_x, res_y = (200, 200)
|
||||
elif resolution in ["icon", "favicon"]:
|
||||
res_x, res_y = (10, 10)
|
||||
res_x, res_y = (25, 25)
|
||||
else:
|
||||
return None
|
||||
|
||||
# If image has been already generated, return it from the cache
|
||||
if os.path.exists(os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}")):
|
||||
return os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}")
|
||||
if os.path.exists(os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}")):
|
||||
return os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}")
|
||||
|
||||
# Check if image exists in the uploads directory
|
||||
if not os.path.exists(os.path.join(UPLOAD_PATH, f"{file_name}.{file_ext}")):
|
||||
if not os.path.exists(os.path.join(MEDIA_FOLDER, file_path)):
|
||||
return None
|
||||
|
||||
# Open image and rotate it based on EXIF data and get ICC profile so colors are correct
|
||||
image = Image.open(os.path.join(UPLOAD_PATH, f"{file_name}.{file_ext}"))
|
||||
image = Image.open(os.path.join(MEDIA_FOLDER, file_path))
|
||||
image_icc = image.info.get("icc_profile")
|
||||
img_x, img_y = image.size
|
||||
|
||||
|
@ -63,7 +61,7 @@ def generate_thumbnail(file_name, resolution, ext=None):
|
|||
# Save image to cache directory
|
||||
try:
|
||||
image.save(
|
||||
os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}"),
|
||||
os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}"),
|
||||
icc_profile=image_icc,
|
||||
)
|
||||
except OSError:
|
||||
|
@ -71,11 +69,11 @@ def generate_thumbnail(file_name, resolution, ext=None):
|
|||
# so we convert to RGB and try again
|
||||
image = image.convert("RGB")
|
||||
image.save(
|
||||
os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}"),
|
||||
os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}"),
|
||||
icc_profile=image_icc,
|
||||
)
|
||||
|
||||
# No need to keep the image in memory, learned the hard way
|
||||
image.close()
|
||||
|
||||
return os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}")
|
||||
return os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}")
|
||||
|
|
|
@ -5,7 +5,8 @@ from flask import Blueprint, render_template, request
|
|||
from werkzeug.exceptions import abort
|
||||
from flask_login import current_user
|
||||
|
||||
from onlylegs.models import Post, User
|
||||
from onlylegs.models import Post, User, Group
|
||||
from onlylegs.extensions import db
|
||||
|
||||
|
||||
blueprint = Blueprint("profile", __name__, url_prefix="/profile")
|
||||
|
@ -26,11 +27,9 @@ def profile():
|
|||
abort(404, "You must be logged in to view your own profile!")
|
||||
|
||||
# Get the user's data
|
||||
user = User.query.filter(User.id == user_id).first()
|
||||
|
||||
if not user:
|
||||
abort(404, "User not found :c")
|
||||
user = db.get_or_404(User, user_id, description="User not found :<")
|
||||
|
||||
images = Post.query.filter(Post.author_id == user_id).all()
|
||||
groups = Group.query.filter(Group.author_id == user_id).all()
|
||||
|
||||
return render_template("profile.html", user=user, images=images)
|
||||
return render_template("profile.html", user=user, images=images, groups=groups)
|
||||
|
|
|
@ -4,6 +4,7 @@ OnlyLegs - Settings page
|
|||
from flask import Blueprint, render_template
|
||||
from flask_login import login_required
|
||||
|
||||
|
||||
blueprint = Blueprint("settings", __name__, url_prefix="/settings")
|
||||
|
||||
|
||||
|
@ -13,31 +14,4 @@ def general():
|
|||
"""
|
||||
General settings page
|
||||
"""
|
||||
return render_template("settings/general.html")
|
||||
|
||||
|
||||
@blueprint.route("/server")
|
||||
@login_required
|
||||
def server():
|
||||
"""
|
||||
Server settings page
|
||||
"""
|
||||
return render_template("settings/server.html")
|
||||
|
||||
|
||||
@blueprint.route("/account")
|
||||
@login_required
|
||||
def account():
|
||||
"""
|
||||
Account settings page
|
||||
"""
|
||||
return render_template("settings/account.html")
|
||||
|
||||
|
||||
@blueprint.route("/logs")
|
||||
@login_required
|
||||
def logs():
|
||||
"""
|
||||
Logs settings page
|
||||
"""
|
||||
return render_template("settings/logs.html")
|
||||
return render_template("settings.html")
|
||||
|
|
6
poetry.lock
generated
6
poetry.lock
generated
|
@ -959,14 +959,14 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "67.6.1"
|
||||
version = "67.7.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "setuptools-67.6.1-py3-none-any.whl", hash = "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"},
|
||||
{file = "setuptools-67.6.1.tar.gz", hash = "sha256:257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a"},
|
||||
{file = "setuptools-67.7.0-py3-none-any.whl", hash = "sha256:888be97fde8cc3afd60f7784e678fa29ee13c4e5362daa7104a93bba33646c50"},
|
||||
{file = "setuptools-67.7.0.tar.gz", hash = "sha256:b7e53a01c6c654d26d2999ee033d8c6125e5fa55f03b7b193f937ae7ac999f22"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
[tool.poetry]
|
||||
name = "onlylegs"
|
||||
version = "0.1.0"
|
||||
description = "Gallery built for fast and simple image management"
|
||||
authors = ["Fluffy-Bean <michal-gdula@protonmail.com>"]
|
||||
name = "OnlyLegs"
|
||||
version = "0.1.2"
|
||||
repository = "https://github.com/Fluffy-Bean/onlylegs"
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
description = "Gallery built for fast and simple image management"
|
||||
authors = ["Fluffy-Bean <michal-gdula@protonmail.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
|
|
6
run.py
6
run.py
|
@ -1,12 +1,13 @@
|
|||
"""
|
||||
Run script for OnlyLegs
|
||||
"""
|
||||
import importlib.metadata
|
||||
from setup.args import PORT, ADDRESS, WORKERS, DEBUG
|
||||
from setup.configuration import Configuration
|
||||
|
||||
|
||||
print(
|
||||
"""
|
||||
f"""
|
||||
:::::::: :::: ::: ::: ::: ::: ::: ::::::::: ::::::::: ::::::::
|
||||
:+: :+: :+:+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
|
||||
+:+ +:+ :+:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
|
||||
|
@ -15,12 +16,11 @@ print(
|
|||
#+# #+# #+# #+#+# #+# #+# #+# #+# #+# #+# #+# #+#
|
||||
######## ### #### ########## ### ########## ######### ######### ########
|
||||
|
||||
Created by Fluffy Bean - Version 0.1.0
|
||||
Created by Fluffy Bean - {importlib.metadata.version("OnlyLegs")}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
# Run pre-startup checks and load configuration
|
||||
Configuration()
|
||||
|
||||
|
||||
|
|
|
@ -45,7 +45,10 @@ class Configuration:
|
|||
"""
|
||||
os.makedirs(USER_DIR)
|
||||
os.makedirs(os.path.join(USER_DIR, "instance"))
|
||||
os.makedirs(os.path.join(USER_DIR, "uploads"))
|
||||
os.makedirs(os.path.join(USER_DIR, "media"))
|
||||
os.makedirs(os.path.join(USER_DIR, "media", "uploads"))
|
||||
os.makedirs(os.path.join(USER_DIR, "media", "cache"))
|
||||
os.makedirs(os.path.join(USER_DIR, "media", "pfp"))
|
||||
|
||||
print("Created user directory at:", USER_DIR)
|
||||
|
||||
|
|
Loading…
Reference in a new issue