mirror of
https://github.com/Derpy-Leggies/OnlyLegs.git
synced 2025-01-29 01:28:24 +00:00
commit
a33fa2f690
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
github: Fluffy-Bean
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,7 +1,9 @@
|
||||||
gallery/static/theme
|
|
||||||
gallery/static/.webassets-cache
|
gallery/static/.webassets-cache
|
||||||
gallery/static/gen
|
gallery/static/gen
|
||||||
|
|
||||||
|
instance/
|
||||||
|
migrations/
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
@ -11,7 +13,6 @@ venv/
|
||||||
*.pyc
|
*.pyc
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
||||||
instance/
|
|
||||||
|
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.coverage
|
.coverage
|
||||||
|
|
|
@ -2,79 +2,87 @@
|
||||||
Onlylegs Gallery
|
Onlylegs Gallery
|
||||||
This is the main app file, it loads all the other files and sets up the app
|
This is the main app file, it loads all the other files and sets up the app
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Import system modules
|
|
||||||
import os
|
import os
|
||||||
import logging
|
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
|
||||||
|
|
||||||
# Flask
|
|
||||||
from flask_compress import Compress
|
|
||||||
from flask_caching import Cache
|
|
||||||
from flask_assets import Environment, Bundle
|
|
||||||
from flask_login import LoginManager
|
|
||||||
from flask import Flask, render_template, abort
|
from flask import Flask, render_template, abort
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
# Configuration
|
from gallery.extensions import db, migrate, login_manager, assets, compress, cache
|
||||||
import platformdirs
|
from gallery.views import index, image, group, settings, profile
|
||||||
from dotenv import load_dotenv
|
from gallery.models import User
|
||||||
from yaml import safe_load
|
from gallery import api
|
||||||
|
from gallery import auth
|
||||||
# Import database
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
from gallery import db
|
|
||||||
|
|
||||||
|
|
||||||
USER_DIR = platformdirs.user_config_dir("onlylegs")
|
INSTACE_DIR = os.path.join(platformdirs.user_config_dir("onlylegs"), "instance")
|
||||||
|
MIGRATIONS_DIR = os.path.join(INSTACE_DIR, "migrations")
|
||||||
|
|
||||||
|
|
||||||
db_session = sessionmaker(bind=db.engine)
|
def create_app(): # pylint: disable=R0914
|
||||||
db_session = db_session()
|
|
||||||
login_manager = LoginManager()
|
|
||||||
assets = Environment()
|
|
||||||
cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300})
|
|
||||||
compress = Compress()
|
|
||||||
|
|
||||||
|
|
||||||
def create_app(test_config=None): # pylint: disable=R0914
|
|
||||||
"""
|
"""
|
||||||
Create and configure the main app
|
Create and configure the main app
|
||||||
"""
|
"""
|
||||||
app = Flask(__name__, instance_path=os.path.join(USER_DIR, "instance"))
|
app = Flask(__name__, instance_path=INSTACE_DIR)
|
||||||
|
app.config.from_pyfile("config.py")
|
||||||
|
|
||||||
# Get environment variables
|
# DATABASE
|
||||||
load_dotenv(os.path.join(USER_DIR, ".env"))
|
db.init_app(app)
|
||||||
print("Loaded environment variables")
|
migrate.init_app(app, db)
|
||||||
|
|
||||||
# Get config file
|
# If database file doesn't exist, create it
|
||||||
with open(os.path.join(USER_DIR, "conf.yml"), encoding="utf-8", mode="r") as file:
|
if not os.path.exists(os.path.join(INSTACE_DIR, "gallery.sqlite3")):
|
||||||
conf = safe_load(file)
|
print("Creating database")
|
||||||
print("Loaded config")
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
# App configuration
|
register_user = User(
|
||||||
app.config.from_mapping(
|
username=app.config["ADMIN_CONF"]["username"],
|
||||||
SECRET_KEY=os.environ.get("FLASK_SECRET"),
|
email=app.config["ADMIN_CONF"]["email"],
|
||||||
DATABASE=os.path.join(app.instance_path, "gallery.sqlite3"),
|
password=generate_password_hash("changeme!", method="sha256"),
|
||||||
UPLOAD_FOLDER=os.path.join(USER_DIR, "uploads"),
|
)
|
||||||
ALLOWED_EXTENSIONS=conf["upload"]["allowed-extensions"],
|
db.session.add(register_user)
|
||||||
MAX_CONTENT_LENGTH=1024 * 1024 * conf["upload"]["max-size"],
|
db.session.commit()
|
||||||
ADMIN_CONF=conf["admin"],
|
|
||||||
UPLOAD_CONF=conf["upload"],
|
|
||||||
WEBSITE_CONF=conf["website"],
|
|
||||||
)
|
|
||||||
|
|
||||||
if test_config is None:
|
print(
|
||||||
app.config.from_pyfile("config.py", silent=True)
|
"""
|
||||||
else:
|
####################################################
|
||||||
app.config.from_mapping(test_config)
|
# DEFAULY ADMIN USER GENERATED WITH GIVEN USERNAME #
|
||||||
|
# THE DEFAULT PASSWORD "changeme!" HAS BEEN USED, #
|
||||||
|
# PLEASE UPDATE IT IN THE SETTINGS! #
|
||||||
|
####################################################
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if migrations directory exists, if not create it
|
||||||
|
with app.app_context():
|
||||||
|
if not os.path.exists(MIGRATIONS_DIR):
|
||||||
|
print("Creating migrations directory")
|
||||||
|
migrate_init(directory=MIGRATIONS_DIR)
|
||||||
|
|
||||||
|
# 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
|
||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
login_manager.login_view = "gallery.index"
|
login_manager.login_view = "gallery.index"
|
||||||
login_manager.session_protection = "strong"
|
|
||||||
|
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
return db_session.query(db.Users).filter_by(alt_id=user_id).first()
|
return User.query.filter_by(alt_id=user_id).first()
|
||||||
|
|
||||||
@login_manager.unauthorized_handler
|
@login_manager.unauthorized_handler
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
|
@ -82,23 +90,12 @@ def create_app(test_config=None): # pylint: disable=R0914
|
||||||
msg = "You are not authorized to view this page!!!!"
|
msg = "You are not authorized to view this page!!!!"
|
||||||
return render_template("error.html", error=error, msg=msg), error
|
return render_template("error.html", error=error, msg=msg), error
|
||||||
|
|
||||||
lib = Bundle(
|
# ERROR HANDLERS
|
||||||
"lib/*.js", filters="jsmin", output="gen/lib.js", depends="lib/*.js"
|
|
||||||
)
|
|
||||||
scripts = Bundle(
|
|
||||||
"js/*.js", filters="jsmin", output="gen/index.js", depends="js/*.js"
|
|
||||||
)
|
|
||||||
styles = Bundle(
|
|
||||||
"sass/*.sass", filters="libsass, cssmin", output="gen/styles.css", depends='sass/**/*.sass'
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
@app.errorhandler(Exception)
|
||||||
def error_page(err): # noqa
|
def error_page(err): # noqa
|
||||||
|
"""
|
||||||
|
Error handlers, if the error is not a HTTP error, return 500
|
||||||
|
"""
|
||||||
if not isinstance(err, HTTPException):
|
if not isinstance(err, HTTPException):
|
||||||
abort(500)
|
abort(500)
|
||||||
return (
|
return (
|
||||||
|
@ -106,30 +103,34 @@ def create_app(test_config=None): # pylint: disable=R0914
|
||||||
err.code,
|
err.code,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Load login, registration and logout manager
|
# ASSETS
|
||||||
from gallery import auth
|
assets.init_app(app)
|
||||||
|
|
||||||
|
scripts = Bundle("js/*.js", filters="jsmin", output="gen/js.js", depends="js/*.js")
|
||||||
|
styles = Bundle(
|
||||||
|
"sass/*.sass",
|
||||||
|
filters="libsass, cssmin",
|
||||||
|
output="gen/styles.css",
|
||||||
|
depends="sass/**/*.sass",
|
||||||
|
)
|
||||||
|
|
||||||
|
assets.register("scripts", scripts)
|
||||||
|
assets.register("styles", styles)
|
||||||
|
|
||||||
|
# BLUEPRINTS
|
||||||
app.register_blueprint(auth.blueprint)
|
app.register_blueprint(auth.blueprint)
|
||||||
|
|
||||||
# Load the API
|
|
||||||
from gallery import api
|
|
||||||
|
|
||||||
app.register_blueprint(api.blueprint)
|
app.register_blueprint(api.blueprint)
|
||||||
|
|
||||||
# Load the different views
|
|
||||||
from gallery.views import index, image, group, settings, profile
|
|
||||||
|
|
||||||
app.register_blueprint(index.blueprint)
|
app.register_blueprint(index.blueprint)
|
||||||
app.register_blueprint(image.blueprint)
|
app.register_blueprint(image.blueprint)
|
||||||
app.register_blueprint(group.blueprint)
|
app.register_blueprint(group.blueprint)
|
||||||
app.register_blueprint(profile.blueprint)
|
app.register_blueprint(profile.blueprint)
|
||||||
app.register_blueprint(settings.blueprint)
|
app.register_blueprint(settings.blueprint)
|
||||||
|
|
||||||
# Log to file that the app has started
|
# CACHE AND COMPRESS
|
||||||
logging.info("Gallery started successfully!")
|
|
||||||
|
|
||||||
# Initialize extensions and return app
|
|
||||||
assets.init_app(app)
|
|
||||||
cache.init_app(app)
|
cache.init_app(app)
|
||||||
compress.init_app(app)
|
compress.init_app(app)
|
||||||
|
|
||||||
|
# Yupee! We got there :3
|
||||||
|
print("Done!")
|
||||||
|
logging.info("Gallery started successfully!")
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -9,21 +9,17 @@ import platformdirs
|
||||||
|
|
||||||
from flask import Blueprint, send_from_directory, abort, flash, request, current_app
|
from flask import Blueprint, send_from_directory, abort, flash, request, current_app
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
from colorthief import ColorThief
|
from colorthief import ColorThief
|
||||||
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
from gallery.extensions import db
|
||||||
|
from gallery.models import Post, Group, GroupJunction
|
||||||
from gallery import db
|
|
||||||
from gallery.utils import metadata as mt
|
from gallery.utils import metadata as mt
|
||||||
from gallery.utils.generate_image import generate_thumbnail
|
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/<file_name>", methods=["GET"])
|
@blueprint.route("/file/<file_name>", methods=["GET"])
|
||||||
|
@ -87,7 +83,7 @@ def upload():
|
||||||
img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette
|
img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette
|
||||||
|
|
||||||
# Save to database
|
# Save to database
|
||||||
query = db.Posts(
|
query = Post(
|
||||||
author_id=current_user.id,
|
author_id=current_user.id,
|
||||||
filename=img_name + "." + img_ext,
|
filename=img_name + "." + img_ext,
|
||||||
mimetype=img_ext,
|
mimetype=img_ext,
|
||||||
|
@ -97,8 +93,8 @@ def upload():
|
||||||
alt=form["alt"],
|
alt=form["alt"],
|
||||||
)
|
)
|
||||||
|
|
||||||
db_session.add(query)
|
db.session.add(query)
|
||||||
db_session.commit()
|
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
|
||||||
|
|
||||||
|
@ -109,40 +105,33 @@ def delete_image(image_id):
|
||||||
"""
|
"""
|
||||||
Deletes an image from the server and database
|
Deletes an image from the server and database
|
||||||
"""
|
"""
|
||||||
img = db_session.query(db.Posts).filter_by(id=image_id).first()
|
post = Post.query.filter_by(id=image_id).first()
|
||||||
|
|
||||||
# Check if image exists and if user is allowed to delete it (author)
|
# Check if image exists and if user is allowed to delete it (author)
|
||||||
if img is None:
|
if post is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
if img.author_id != current_user.id:
|
if post.author_id != current_user.id:
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
# Delete file
|
# Delete file
|
||||||
try:
|
try:
|
||||||
os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], img.filename))
|
os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], post.filename))
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"File not found: %s, already deleted or never existed", img.filename
|
"File not found: %s, already deleted or never existed", post.filename
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete cached files
|
# Delete cached files
|
||||||
cache_path = os.path.join(platformdirs.user_config_dir("onlylegs"), "cache")
|
cache_path = os.path.join(platformdirs.user_config_dir("onlylegs"), "cache")
|
||||||
cache_name = img.filename.rsplit(".")[0]
|
cache_name = post.filename.rsplit(".")[0]
|
||||||
for cache_file in pathlib.Path(cache_path).glob(cache_name + "*"):
|
for cache_file in pathlib.Path(cache_path).glob(cache_name + "*"):
|
||||||
os.remove(cache_file)
|
os.remove(cache_file)
|
||||||
|
|
||||||
# Delete from database
|
GroupJunction.query.filter_by(post_id=image_id).delete()
|
||||||
db_session.query(db.Posts).filter_by(id=image_id).delete()
|
db.session.delete(post)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
# Remove all entries in junction table
|
logging.info("Removed image (%s) %s", image_id, post.filename)
|
||||||
groups = db_session.query(db.GroupJunction).filter_by(post_id=image_id).all()
|
|
||||||
for group in groups:
|
|
||||||
db_session.delete(group)
|
|
||||||
|
|
||||||
# Commit all changes
|
|
||||||
db_session.commit()
|
|
||||||
|
|
||||||
logging.info("Removed image (%s) %s", image_id, img.filename)
|
|
||||||
flash(["Image was all in Le Head!", "1"])
|
flash(["Image was all in Le Head!", "1"])
|
||||||
return "Gwa Gwa"
|
return "Gwa Gwa"
|
||||||
|
|
||||||
|
@ -153,14 +142,14 @@ def create_group():
|
||||||
"""
|
"""
|
||||||
Creates a group
|
Creates a group
|
||||||
"""
|
"""
|
||||||
new_group = db.Groups(
|
new_group = Group(
|
||||||
name=request.form["name"],
|
name=request.form["name"],
|
||||||
description=request.form["description"],
|
description=request.form["description"],
|
||||||
author_id=current_user.id,
|
author_id=current_user.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
db_session.add(new_group)
|
db.session.add(new_group)
|
||||||
db_session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return ":3"
|
return ":3"
|
||||||
|
|
||||||
|
@ -175,29 +164,23 @@ def modify_group():
|
||||||
image_id = request.form["image"]
|
image_id = request.form["image"]
|
||||||
action = request.form["action"]
|
action = request.form["action"]
|
||||||
|
|
||||||
group = db_session.query(db.Groups).filter_by(id=group_id).first()
|
group = db.get_or_404(Group, group_id)
|
||||||
|
db.get_or_404(Post, image_id) # Check if image exists
|
||||||
|
|
||||||
if group is None:
|
if group.author_id != current_user.id:
|
||||||
abort(404)
|
|
||||||
elif group.author_id != current_user.id:
|
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
if action == "add":
|
if (
|
||||||
if not (
|
action == "add"
|
||||||
db_session.query(db.GroupJunction)
|
and not GroupJunction.query.filter_by(
|
||||||
.filter_by(group_id=group_id, post_id=image_id)
|
group_id=group_id, post_id=image_id
|
||||||
.first()
|
).first()
|
||||||
):
|
):
|
||||||
db_session.add(db.GroupJunction(group_id=group_id, post_id=image_id))
|
db.session.add(GroupJunction(group_id=group_id, post_id=image_id))
|
||||||
elif request.form["action"] == "remove":
|
elif request.form["action"] == "remove":
|
||||||
(
|
GroupJunction.query.filter_by(group_id=group_id, post_id=image_id).delete()
|
||||||
db_session.query(db.GroupJunction)
|
|
||||||
.filter_by(group_id=group_id, post_id=image_id)
|
|
||||||
.delete()
|
|
||||||
)
|
|
||||||
|
|
||||||
db_session.commit()
|
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
return ":3"
|
return ":3"
|
||||||
|
|
||||||
|
|
||||||
|
@ -207,17 +190,16 @@ def delete_group():
|
||||||
Deletes a group
|
Deletes a group
|
||||||
"""
|
"""
|
||||||
group_id = request.form["group"]
|
group_id = request.form["group"]
|
||||||
|
group = Group.query.filter_by(id=group_id).first()
|
||||||
group = db_session.query(db.Groups).filter_by(id=group_id).first()
|
|
||||||
|
|
||||||
if group is None:
|
if group is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
elif group.author_id != current_user.id:
|
elif group.author_id != current_user.id:
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
db_session.query(db.Groups).filter_by(id=group_id).delete()
|
GroupJunction.query.filter_by(group_id=group_id).delete()
|
||||||
db_session.query(db.GroupJunction).filter_by(group_id=group_id).delete()
|
db.session.delete(group)
|
||||||
db_session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
flash(["Group yeeted!", "1"])
|
flash(["Group yeeted!", "1"])
|
||||||
return ":3"
|
return ":3"
|
||||||
|
|
|
@ -10,13 +10,11 @@ from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
from flask_login import login_user, logout_user, login_required
|
from flask_login import login_user, logout_user, login_required
|
||||||
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
from gallery.extensions import db
|
||||||
from gallery import db
|
from gallery.models import User
|
||||||
|
|
||||||
|
|
||||||
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"])
|
||||||
|
@ -30,7 +28,7 @@ def login():
|
||||||
password = request.form["password"].strip()
|
password = request.form["password"].strip()
|
||||||
remember = bool(request.form["remember-me"])
|
remember = bool(request.form["remember-me"])
|
||||||
|
|
||||||
user = db_session.query(db.Users).filter_by(username=username).first()
|
user = User.query.filter_by(username=username).first()
|
||||||
|
|
||||||
if not user or not check_password_hash(user.password, password):
|
if not user or not check_password_hash(user.password, password):
|
||||||
logging.error("Login attempt from %s", request.remote_addr)
|
logging.error("Login attempt from %s", request.remote_addr)
|
||||||
|
@ -79,7 +77,7 @@ def register():
|
||||||
elif password_repeat != password:
|
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()
|
user_exists = User.query.filter_by(username=username).first()
|
||||||
if user_exists:
|
if user_exists:
|
||||||
error.append("User already exists!")
|
error.append("User already exists!")
|
||||||
|
|
||||||
|
@ -88,13 +86,13 @@ def register():
|
||||||
print(error)
|
print(error)
|
||||||
return jsonify(error), 400
|
return jsonify(error), 400
|
||||||
|
|
||||||
register_user = db.Users(
|
register_user = User(
|
||||||
username=username,
|
username=username,
|
||||||
email=email,
|
email=email,
|
||||||
password=generate_password_hash(password, method="sha256"),
|
password=generate_password_hash(password, method="sha256"),
|
||||||
)
|
)
|
||||||
db_session.add(register_user)
|
db.session.add(register_user)
|
||||||
db_session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
logging.info("User %s registered", username)
|
logging.info("User %s registered", username)
|
||||||
return "ok", 200
|
return "ok", 200
|
||||||
|
|
36
gallery/config.py
Normal file
36
gallery/config.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
"""
|
||||||
|
Gallery configuration file
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import platformdirs
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from yaml import safe_load
|
||||||
|
|
||||||
|
|
||||||
|
# Set dirs
|
||||||
|
user_dir = platformdirs.user_config_dir("onlylegs")
|
||||||
|
instance_dir = os.path.join(user_dir, "instance")
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
print("Loading environment variables...")
|
||||||
|
load_dotenv(os.path.join(user_dir, ".env"))
|
||||||
|
|
||||||
|
# Load config from user dir
|
||||||
|
print("Loading config...")
|
||||||
|
with open(os.path.join(user_dir, "conf.yml"), encoding="utf-8", mode="r") as file:
|
||||||
|
conf = safe_load(file)
|
||||||
|
|
||||||
|
|
||||||
|
# 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"]
|
153
gallery/db.py
153
gallery/db.py
|
@ -1,153 +0,0 @@
|
||||||
"""
|
|
||||||
OnlyLegs - Database models and ions for SQLAlchemy
|
|
||||||
"""
|
|
||||||
from uuid import uuid4
|
|
||||||
import os
|
|
||||||
import platformdirs
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
|
|
||||||
# In the future, I want to add support for other databases
|
|
||||||
engine = create_engine(f"sqlite:///{DB_PATH}", echo=False)
|
|
||||||
base = declarative_base()
|
|
||||||
|
|
||||||
|
|
||||||
class Users(base, UserMixin): # pylint: disable=too-few-public-methods, C0103
|
|
||||||
"""
|
|
||||||
User table
|
|
||||||
Joins with post, groups, session and log
|
|
||||||
"""
|
|
||||||
|
|
||||||
__tablename__ = "users"
|
|
||||||
|
|
||||||
# Gallery used information
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
alt_id = Column(String, unique=True, nullable=False, default=str(uuid4()))
|
|
||||||
profile_picture = Column(String, nullable=True, default=None)
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
Post table
|
|
||||||
Joins with group_junction
|
|
||||||
"""
|
|
||||||
|
|
||||||
__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
|
|
||||||
)
|
|
||||||
filename = Column(String, unique=True, nullable=False)
|
|
||||||
mimetype = Column(String, nullable=False)
|
|
||||||
exif = Column(PickleType, nullable=False)
|
|
||||||
colours = Column(PickleType, nullable=False)
|
|
||||||
description = Column(String, nullable=False)
|
|
||||||
alt = Column(String, nullable=False)
|
|
||||||
|
|
||||||
junction = relationship("GroupJunction", backref="posts")
|
|
||||||
|
|
||||||
|
|
||||||
class Groups(base): # pylint: disable=too-few-public-methods, C0103
|
|
||||||
"""
|
|
||||||
Group table
|
|
||||||
Joins with group_junction
|
|
||||||
"""
|
|
||||||
|
|
||||||
__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
|
|
||||||
)
|
|
||||||
|
|
||||||
junction = relationship("GroupJunction", backref="groups")
|
|
||||||
|
|
||||||
|
|
||||||
class GroupJunction(base): # pylint: disable=too-few-public-methods, C0103
|
|
||||||
"""
|
|
||||||
Junction table for posts and groups
|
|
||||||
Joins with posts and groups
|
|
||||||
"""
|
|
||||||
|
|
||||||
__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"))
|
|
||||||
|
|
||||||
|
|
||||||
class Logs(base): # pylint: disable=too-few-public-methods, C0103
|
|
||||||
"""
|
|
||||||
Log table
|
|
||||||
Joins with user
|
|
||||||
"""
|
|
||||||
|
|
||||||
__tablename__ = "logs"
|
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Bans(base): # pylint: disable=too-few-public-methods, C0103
|
|
||||||
"""
|
|
||||||
Bans table
|
|
||||||
"""
|
|
||||||
|
|
||||||
__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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# check if database file exists, if not create it
|
|
||||||
if not os.path.isfile(DB_PATH):
|
|
||||||
base.metadata.create_all(engine)
|
|
||||||
print("Database created")
|
|
16
gallery/extensions.py
Normal file
16
gallery/extensions.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
Extensions used by the application
|
||||||
|
"""
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_migrate import Migrate
|
||||||
|
from flask_login import LoginManager
|
||||||
|
from flask_assets import Environment
|
||||||
|
from flask_compress import Compress
|
||||||
|
from flask_caching import Cache
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
migrate = Migrate()
|
||||||
|
login_manager = LoginManager()
|
||||||
|
assets = Environment()
|
||||||
|
compress = Compress()
|
||||||
|
cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300})
|
105
gallery/models.py
Normal file
105
gallery/models.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
"""
|
||||||
|
OnlyLegs - Database models and ions for SQLAlchemy
|
||||||
|
"""
|
||||||
|
from uuid import uuid4
|
||||||
|
from flask_login import UserMixin
|
||||||
|
from .extensions import db
|
||||||
|
|
||||||
|
|
||||||
|
class GroupJunction(db.Model): # pylint: disable=too-few-public-methods, C0103
|
||||||
|
"""
|
||||||
|
Junction table for posts and groups
|
||||||
|
Joins with posts and groups
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "group_junction"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
||||||
|
group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
|
||||||
|
post_id = db.Column(db.Integer, db.ForeignKey("post.id"))
|
||||||
|
|
||||||
|
date_added = db.Column(
|
||||||
|
db.DateTime,
|
||||||
|
nullable=False,
|
||||||
|
server_default=db.func.now(), # pylint: disable=E1102
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Post(db.Model): # pylint: disable=too-few-public-methods, C0103
|
||||||
|
"""
|
||||||
|
Post table
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "post"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
||||||
|
author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||||
|
|
||||||
|
filename = db.Column(db.String, unique=True, nullable=False)
|
||||||
|
mimetype = db.Column(db.String, nullable=False)
|
||||||
|
exif = db.Column(db.PickleType, nullable=False)
|
||||||
|
colours = db.Column(db.PickleType, nullable=False)
|
||||||
|
|
||||||
|
description = db.Column(db.String, nullable=False)
|
||||||
|
alt = db.Column(db.String, nullable=False)
|
||||||
|
|
||||||
|
created_at = db.Column(
|
||||||
|
db.DateTime,
|
||||||
|
nullable=False,
|
||||||
|
server_default=db.func.now(), # pylint: disable=E1102
|
||||||
|
)
|
||||||
|
|
||||||
|
junction = db.relationship("GroupJunction", backref="posts")
|
||||||
|
|
||||||
|
|
||||||
|
class Group(db.Model): # pylint: disable=too-few-public-methods, C0103
|
||||||
|
"""
|
||||||
|
Group table
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "group"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
||||||
|
name = db.Column(db.String, nullable=False)
|
||||||
|
description = db.Column(db.String, nullable=False)
|
||||||
|
|
||||||
|
author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||||
|
created_at = db.Column(
|
||||||
|
db.DateTime,
|
||||||
|
nullable=False,
|
||||||
|
server_default=db.func.now(), # pylint: disable=E1102
|
||||||
|
)
|
||||||
|
|
||||||
|
junction = db.relationship("GroupJunction", backref="groups")
|
||||||
|
|
||||||
|
|
||||||
|
class User(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C0103
|
||||||
|
"""
|
||||||
|
User table
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "user"
|
||||||
|
|
||||||
|
# Gallery used information
|
||||||
|
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)
|
||||||
|
|
||||||
|
email = db.Column(db.String, unique=True, nullable=False)
|
||||||
|
password = db.Column(db.String, nullable=False)
|
||||||
|
joined_at = db.Column(
|
||||||
|
db.DateTime,
|
||||||
|
nullable=False,
|
||||||
|
server_default=db.func.now(), # pylint: disable=E1102
|
||||||
|
)
|
||||||
|
|
||||||
|
posts = db.relationship("Post", backref="author")
|
||||||
|
groups = db.relationship("Group", backref="author")
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
return str(self.alt_id)
|
7
gallery/static/fonts/font.css
Normal file
7
gallery/static/fonts/font.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Rubik';
|
||||||
|
src: url('./Rubik.ttf') format('truetype');
|
||||||
|
font-style: normal;
|
||||||
|
font-display: block;
|
||||||
|
font-weight: 300 900;
|
||||||
|
}
|
|
@ -65,7 +65,7 @@ window.onload = function () {
|
||||||
}
|
}
|
||||||
infoButton.onclick = function () {
|
infoButton.onclick = function () {
|
||||||
popUpShow('OnlyLegs',
|
popUpShow('OnlyLegs',
|
||||||
'<a href="https://github.com/Fluffy-Bean/onlylegs">V23.04.08</a> ' +
|
'<a href="https://github.com/Fluffy-Bean/onlylegs">V23.04.10</a> ' +
|
||||||
'using <a href="https://phosphoricons.com/">Phosphoricons</a> and Flask.' +
|
'using <a href="https://phosphoricons.com/">Phosphoricons</a> and Flask.' +
|
||||||
'<br>Made by Fluffy and others with ❤️');
|
'<br>Made by Fluffy and others with ❤️');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
function addNotification(notificationText, notificationLevel) {
|
function addNotification(notificationText, notificationLevel) {
|
||||||
let notificationContainer = document.querySelector('.notifications');
|
const notificationContainer = document.querySelector('.notifications');
|
||||||
|
|
||||||
// Set the different icons for the different notification levels
|
// Set the different icons for the different notification levels
|
||||||
let 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 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>';
|
||||||
let 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 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>';
|
||||||
let 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 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>';
|
||||||
let 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>';
|
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
|
// Create notification element
|
||||||
let notification = document.createElement('div');
|
const notification = document.createElement('div');
|
||||||
notification.classList.add('sniffle__notification');
|
notification.classList.add('sniffle__notification');
|
||||||
notification.onclick = function() {
|
notification.onclick = function() {
|
||||||
if (notification) {
|
if (notification) {
|
||||||
|
@ -21,7 +21,7 @@ function addNotification(notificationText, notificationLevel) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create icon element and append to notification
|
// Create icon element and append to notification
|
||||||
let iconElement = document.createElement('span');
|
const iconElement = document.createElement('span');
|
||||||
iconElement.classList.add('sniffle__notification-icon');
|
iconElement.classList.add('sniffle__notification-icon');
|
||||||
notification.appendChild(iconElement);
|
notification.appendChild(iconElement);
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ function addNotification(notificationText, notificationLevel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create text element and append to notification
|
// Create text element and append to notification
|
||||||
let description = document.createElement('span');
|
const description = document.createElement('span');
|
||||||
description.classList.add('sniffle__notification-text');
|
description.classList.add('sniffle__notification-text');
|
||||||
description.innerHTML = notificationText;
|
description.innerHTML = notificationText;
|
||||||
notification.appendChild(description);
|
notification.appendChild(description);
|
|
@ -1,19 +1,19 @@
|
||||||
function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null) {
|
function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null) {
|
||||||
// Get popup elements
|
// Get popup elements
|
||||||
let popupSelector = document.querySelector('.pop-up');
|
const popupSelector = document.querySelector('.pop-up');
|
||||||
let headerSelector = document.querySelector('.pop-up-header');
|
const headerSelector = document.querySelector('.pop-up-header');
|
||||||
let actionsSelector = document.querySelector('.pop-up-controlls');
|
const actionsSelector = document.querySelector('.pop-up-controlls');
|
||||||
|
|
||||||
// Clear popup elements
|
// Clear popup elements
|
||||||
headerSelector.innerHTML = '';
|
headerSelector.innerHTML = '';
|
||||||
actionsSelector.innerHTML = '';
|
actionsSelector.innerHTML = '';
|
||||||
|
|
||||||
// Set popup header and subtitle
|
// Set popup header and subtitle
|
||||||
let titleElement = document.createElement('h2');
|
const titleElement = document.createElement('h2');
|
||||||
titleElement.innerHTML = titleText;
|
titleElement.innerHTML = titleText;
|
||||||
headerSelector.appendChild(titleElement);
|
headerSelector.appendChild(titleElement);
|
||||||
|
|
||||||
let subtitleElement = document.createElement('p');
|
const subtitleElement = document.createElement('p');
|
||||||
subtitleElement.innerHTML = subtitleText;
|
subtitleElement.innerHTML = subtitleText;
|
||||||
headerSelector.appendChild(subtitleElement);
|
headerSelector.appendChild(subtitleElement);
|
||||||
|
|
||||||
|
@ -25,8 +25,7 @@ function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null)
|
||||||
if (userActions) {
|
if (userActions) {
|
||||||
// for each user action, add the element
|
// for each user action, add the element
|
||||||
for (let i = 0; i < userActions.length; i++) {
|
for (let i = 0; i < userActions.length; i++) {
|
||||||
let action = userActions[i];
|
actionsSelector.appendChild(userActions[i]);
|
||||||
actionsSelector.appendChild(action);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
actionsSelector.innerHTML = '<button class="btn-block" onclick="popupDissmiss()">Close</button>';
|
actionsSelector.innerHTML = '<button class="btn-block" onclick="popupDissmiss()">Close</button>';
|
||||||
|
@ -39,7 +38,7 @@ function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null)
|
||||||
}
|
}
|
||||||
|
|
||||||
function popupDissmiss() {
|
function popupDissmiss() {
|
||||||
let popupSelector = document.querySelector('.pop-up');
|
const popupSelector = document.querySelector('.pop-up');
|
||||||
|
|
||||||
document.querySelector("html").style.overflow = "auto";
|
document.querySelector("html").style.overflow = "auto";
|
||||||
popupSelector.classList.remove('active');
|
popupSelector.classList.remove('active');
|
|
@ -1,5 +1,5 @@
|
||||||
function checkWebpSupport() {
|
function checkWebpSupport() {
|
||||||
var webpSupport = false;
|
let webpSupport = false;
|
||||||
try {
|
try {
|
||||||
webpSupport = document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0;
|
webpSupport = document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
|
@ -75,7 +75,6 @@
|
||||||
grid-template-columns: 1fr auto
|
grid-template-columns: 1fr auto
|
||||||
grid-template-rows: 1fr auto auto
|
grid-template-rows: 1fr auto auto
|
||||||
grid-template-areas: 'info info' 'header header' 'subtitle options'
|
grid-template-areas: 'info info' 'header header' 'subtitle options'
|
||||||
gap: 0.5rem
|
|
||||||
|
|
||||||
z-index: +2
|
z-index: +2
|
||||||
|
|
||||||
|
@ -89,6 +88,8 @@
|
||||||
.banner-header
|
.banner-header
|
||||||
grid-area: header
|
grid-area: header
|
||||||
|
|
||||||
|
margin: 0.5rem 0
|
||||||
|
|
||||||
text-align: left
|
text-align: left
|
||||||
font-size: 6.9rem
|
font-size: 6.9rem
|
||||||
font-weight: 700
|
font-weight: 700
|
||||||
|
@ -113,7 +114,7 @@
|
||||||
background-color: RGB($bg-100)
|
background-color: RGB($bg-100)
|
||||||
|
|
||||||
.banner-content
|
.banner-content
|
||||||
padding: 0.5rem
|
padding: 0 0.5rem
|
||||||
|
|
||||||
width: 100%
|
width: 100%
|
||||||
height: 100%
|
height: 100%
|
||||||
|
@ -124,7 +125,6 @@
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: row
|
flex-direction: row
|
||||||
justify-content: flex-start
|
justify-content: flex-start
|
||||||
gap: 1rem
|
|
||||||
|
|
||||||
z-index: +2
|
z-index: +2
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@
|
||||||
justify-self: flex-start
|
justify-self: flex-start
|
||||||
|
|
||||||
.banner-header
|
.banner-header
|
||||||
padding-bottom: 0.25rem
|
margin-right: 0.6rem
|
||||||
|
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
text-overflow: ellipsis
|
text-overflow: ellipsis
|
||||||
|
@ -149,6 +149,8 @@
|
||||||
color: RGB($primary)
|
color: RGB($primary)
|
||||||
|
|
||||||
.banner-info
|
.banner-info
|
||||||
|
margin-right: 0.6rem
|
||||||
|
|
||||||
font-size: 0.9rem
|
font-size: 0.9rem
|
||||||
font-weight: 400
|
font-weight: 400
|
||||||
|
|
||||||
|
@ -174,9 +176,9 @@
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
justify-content: center
|
justify-content: center
|
||||||
align-items: center
|
align-items: center
|
||||||
gap: 1rem
|
|
||||||
|
|
||||||
.banner-header
|
.banner-header
|
||||||
|
margin: 1rem 0
|
||||||
text-align: center
|
text-align: center
|
||||||
font-size: 2.5rem
|
font-size: 2.5rem
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
@mixin btn-block($color)
|
@mixin btn-block($color)
|
||||||
color: RGB($color)
|
color: RGB($color)
|
||||||
|
box-shadow: 0 1px 0 RGBA($black, 0.2), 0 -1px 0 RGBA($white, 0.2)
|
||||||
|
|
||||||
&:hover
|
&:hover, &:focus-visible
|
||||||
background-color: RGBA($color, 0.1)
|
background-color: RGBA($color, 0.1)
|
||||||
color: RGB($color)
|
color: RGB($color)
|
||||||
|
box-shadow: 0 1px 0 RGBA($black, 0.2), 0 -1px 0 RGBA($color, 0.2)
|
||||||
|
|
||||||
&:focus-visible
|
|
||||||
outline: 2px solid RGBA($color, 0.3)
|
|
||||||
|
|
||||||
.btn-block
|
.btn-block
|
||||||
padding: 0.4rem 0.7rem
|
padding: 0.4rem 0.7rem
|
||||||
|
@ -25,19 +25,19 @@
|
||||||
font-weight: 400
|
font-weight: 400
|
||||||
text-align: center
|
text-align: center
|
||||||
|
|
||||||
background-color: transparent
|
background-color: RGBA($white, 0.1)
|
||||||
color: RGB($white)
|
color: RGB($white)
|
||||||
border: none
|
border: none
|
||||||
border-radius: $rad-inner
|
border-radius: $rad-inner
|
||||||
|
box-shadow: 0 1px 0 RGBA($black, 0.2), 0 -1px 0 RGBA($white, 0.2)
|
||||||
|
outline: none
|
||||||
|
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out
|
transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out, box-shadow 0.15s ease-in-out
|
||||||
|
|
||||||
&:hover
|
&:hover, &:focus-visible
|
||||||
background-color: RGBA($white, 0.1)
|
background-color: RGBA($white, 0.2)
|
||||||
|
box-shadow: 0 1px 0 RGBA($black, 0.3), 0 -1px 0 RGBA($white, 0.3)
|
||||||
&:focus-visible
|
|
||||||
outline: 2px solid RGBA($white, 0.3)
|
|
||||||
|
|
||||||
&.primary
|
&.primary
|
||||||
@include btn-block($primary)
|
@include btn-block($primary)
|
||||||
|
@ -91,10 +91,11 @@
|
||||||
|
|
||||||
background-color: RGBA($white, 0.1)
|
background-color: RGBA($white, 0.1)
|
||||||
color: RGB($white)
|
color: RGB($white)
|
||||||
|
|
||||||
border: none
|
border: none
|
||||||
border-bottom: 3px solid RGBA($white, 0.1)
|
border-bottom: 3px solid RGBA($white, 0.1)
|
||||||
border-radius: $rad-inner
|
border-radius: $rad-inner
|
||||||
|
box-shadow: 0 1px 0 RGBA($black, 0.2), 0 -1px 0 RGBA($white, 0.2)
|
||||||
|
outline: none
|
||||||
|
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out
|
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out
|
||||||
|
@ -107,7 +108,6 @@
|
||||||
|
|
||||||
&:focus
|
&:focus
|
||||||
border-color: RGB($primary)
|
border-color: RGB($primary)
|
||||||
outline: none
|
|
||||||
|
|
||||||
&.black
|
&.black
|
||||||
@include btn-block($black)
|
@include btn-block($black)
|
||||||
|
@ -132,13 +132,14 @@
|
||||||
|
|
||||||
background-color: RGBA($white, 0.1)
|
background-color: RGBA($white, 0.1)
|
||||||
color: RGB($white)
|
color: RGB($white)
|
||||||
|
|
||||||
border: none
|
border: none
|
||||||
border-radius: $rad-inner
|
border-radius: $rad-inner
|
||||||
|
box-shadow: 0 1px 0 RGBA($black, 0.2), 0 -1px 0 RGBA($white, 0.2)
|
||||||
|
outline: none
|
||||||
|
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out
|
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out, box-shadow 0.15s ease-in-out
|
||||||
|
|
||||||
input
|
input
|
||||||
position: absolute
|
position: absolute
|
||||||
|
@ -153,20 +154,20 @@
|
||||||
text-align: center
|
text-align: center
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
|
|
||||||
&:hover
|
&:hover, &:focus-visible
|
||||||
background-color: RGBA($white, 0.2)
|
background-color: RGBA($white, 0.2)
|
||||||
color: RGB($white)
|
color: RGB($white)
|
||||||
|
box-shadow: 0 1px 0 RGBA($black, 0.3), 0 -1px 0 RGBA($white, 0.3)
|
||||||
&:focus-visible
|
|
||||||
outline: 2px solid RGBA($white, 0.3)
|
|
||||||
|
|
||||||
&.active
|
&.active
|
||||||
background-color: RGBA($primary, 0.2)
|
background-color: RGBA($primary, 0.2)
|
||||||
color: RGB($primary)
|
color: RGB($primary)
|
||||||
|
box-shadow: 0 1px 0 RGBA($black, 0.3), 0 -1px 0 RGBA($primary, 0.3)
|
||||||
|
|
||||||
&.edging
|
&.edging
|
||||||
background-color: RGBA($white, 0.2)
|
background-color: RGBA($white, 0.2)
|
||||||
color: RGB($white)
|
color: RGB($white)
|
||||||
|
box-shadow: 0 1px 0 RGBA($black, 0.3), 0 -1px 0 RGBA($white, 0.3)
|
||||||
|
|
||||||
input
|
input
|
||||||
display: none // So it doesnt get in the way of the drop as that breaks things
|
display: none // So it doesnt get in the way of the drop as that breaks things
|
||||||
|
@ -174,3 +175,4 @@
|
||||||
&.error
|
&.error
|
||||||
background-color: RGBA($critical, 0.2)
|
background-color: RGBA($critical, 0.2)
|
||||||
color: RGB($critical)
|
color: RGB($critical)
|
||||||
|
box-shadow: 0 1px 0 RGBA($black, 0.3), 0 -1px 0 RGBA($critical, 0.3)
|
||||||
|
|
|
@ -18,10 +18,11 @@
|
||||||
|
|
||||||
background-color: RGB($bg-200)
|
background-color: RGB($bg-200)
|
||||||
border-radius: $rad
|
border-radius: $rad
|
||||||
|
box-shadow: 0 1px 0 RGB($bg-100), 0 -1px 0 RGB($bg-300)
|
||||||
|
|
||||||
.pill-text
|
.pill-text
|
||||||
margin: 0
|
margin: 0
|
||||||
padding: 0.5rem
|
padding: 0.5rem 1rem
|
||||||
|
|
||||||
width: auto
|
width: auto
|
||||||
height: 2.5rem
|
height: 2.5rem
|
||||||
|
@ -36,7 +37,9 @@
|
||||||
font-size: 1rem
|
font-size: 1rem
|
||||||
font-weight: 400
|
font-weight: 400
|
||||||
|
|
||||||
|
background-color: RGB($bg-200)
|
||||||
color: RGB($fg-white)
|
color: RGB($fg-white)
|
||||||
|
border-radius: $rad
|
||||||
|
|
||||||
.pill-item
|
.pill-item
|
||||||
margin: 0
|
margin: 0
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
.gallery-grid
|
.gallery-grid
|
||||||
margin: 0
|
margin: 0
|
||||||
padding: 0.65rem
|
padding: 0.35rem
|
||||||
|
|
||||||
width: 100%
|
width: 100%
|
||||||
|
|
||||||
display: grid
|
display: grid
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
|
||||||
gap: 0.65rem
|
|
||||||
|
|
||||||
.gallery-item
|
.gallery-item
|
||||||
margin: 0
|
margin: 0.35rem
|
||||||
padding: 0
|
padding: 0
|
||||||
|
|
||||||
height: auto
|
height: auto
|
||||||
|
@ -97,7 +96,7 @@
|
||||||
opacity: 1
|
opacity: 1
|
||||||
|
|
||||||
.group-item
|
.group-item
|
||||||
margin: 0
|
margin: 0.35rem
|
||||||
padding: 0
|
padding: 0
|
||||||
|
|
||||||
height: auto
|
height: auto
|
||||||
|
@ -161,8 +160,6 @@
|
||||||
|
|
||||||
display: block
|
display: block
|
||||||
|
|
||||||
background-color: RGB($bg-bright)
|
|
||||||
|
|
||||||
img
|
img
|
||||||
width: 100%
|
width: 100%
|
||||||
height: 100%
|
height: 100%
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
background-color: RGB($fg-white)
|
background-color: RGB($fg-white)
|
||||||
|
|
||||||
filter: blur(1rem)
|
filter: blur(1rem) saturate(1.2)
|
||||||
transform: scale(1.1)
|
transform: scale(1.1)
|
||||||
|
|
||||||
object-fit: cover
|
object-fit: cover
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
.image-fullscreen
|
|
||||||
margin: 0
|
|
||||||
padding: 0
|
|
||||||
|
|
||||||
width: 100%
|
|
||||||
height: 100%
|
|
||||||
height: 100dvh
|
|
||||||
|
|
||||||
position: fixed
|
|
||||||
top: 0
|
|
||||||
left: 0
|
|
||||||
|
|
||||||
display: none
|
|
||||||
opacity: 0 // hide
|
|
||||||
|
|
||||||
background-color: $bg-transparent
|
|
||||||
z-index: 100
|
|
||||||
|
|
||||||
box-sizing: border-box
|
|
||||||
|
|
||||||
transition: opacity 0.2s cubic-bezier(.79, .14, .15, .86)
|
|
||||||
|
|
||||||
img
|
|
||||||
margin: auto
|
|
||||||
padding: 0
|
|
||||||
|
|
||||||
width: auto
|
|
||||||
height: auto
|
|
||||||
max-width: 100%
|
|
||||||
max-height: 100%
|
|
||||||
|
|
||||||
object-fit: contain
|
|
||||||
object-position: center
|
|
||||||
|
|
||||||
transform: scale(0.8)
|
|
||||||
transition: transform 0.2s cubic-bezier(.68,-0.55,.27,1.55)
|
|
||||||
|
|
||||||
&.active
|
|
||||||
opacity: 1 // show
|
|
||||||
|
|
||||||
img
|
|
||||||
transform: scale(1)
|
|
|
@ -1,5 +1,5 @@
|
||||||
.info-container
|
.info-container
|
||||||
width: 25rem
|
width: 27rem
|
||||||
height: 100vh
|
height: 100vh
|
||||||
|
|
||||||
position: absolute
|
position: absolute
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
transition: left 0.3s cubic-bezier(0.76, 0, 0.17, 1)
|
transition: left 0.3s cubic-bezier(0.76, 0, 0.17, 1)
|
||||||
|
|
||||||
&.collapsed
|
&.collapsed
|
||||||
left: -25rem
|
left: -27rem
|
||||||
|
|
||||||
.info-tab
|
.info-tab
|
||||||
width: 100%
|
width: 100%
|
||||||
|
@ -39,8 +39,8 @@
|
||||||
transform: rotate(90deg)
|
transform: rotate(90deg)
|
||||||
|
|
||||||
.info-table
|
.info-table
|
||||||
|
height: 0
|
||||||
padding: 0
|
padding: 0
|
||||||
opacity: 0
|
|
||||||
|
|
||||||
.collapse-indicator
|
.collapse-indicator
|
||||||
margin: 0
|
margin: 0
|
||||||
|
@ -126,11 +126,6 @@
|
||||||
.link
|
.link
|
||||||
margin: 0
|
margin: 0
|
||||||
padding: 0
|
padding: 0
|
||||||
|
|
||||||
font-size: 1rem
|
|
||||||
font-weight: 500
|
|
||||||
text-align: center
|
|
||||||
line-height: 1
|
|
||||||
|
|
||||||
color: RGB($primary)
|
color: RGB($primary)
|
||||||
|
|
||||||
|
@ -150,43 +145,31 @@
|
||||||
border-collapse: collapse
|
border-collapse: collapse
|
||||||
|
|
||||||
tr
|
tr
|
||||||
margin: 0
|
|
||||||
padding: 0
|
|
||||||
|
|
||||||
width: 100%
|
|
||||||
|
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
|
|
||||||
td
|
td
|
||||||
padding-bottom: 0.5rem
|
padding-bottom: 0.5rem
|
||||||
|
|
||||||
|
max-width: 0
|
||||||
|
|
||||||
|
font-size: 1rem
|
||||||
|
font-weight: 400
|
||||||
|
|
||||||
|
vertical-align: top
|
||||||
|
|
||||||
td:first-child
|
td:first-child
|
||||||
padding-right: 0.5rem
|
padding-right: 0.5rem
|
||||||
|
|
||||||
width: 50%
|
width: 50%
|
||||||
max-width: 0
|
|
||||||
|
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
text-overflow: ellipsis
|
text-overflow: ellipsis
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
|
|
||||||
font-size: 1rem
|
|
||||||
font-weight: 400
|
|
||||||
td:last-child
|
td:last-child
|
||||||
padding: 0
|
|
||||||
|
|
||||||
width: 50%
|
width: 50%
|
||||||
max-width: 0
|
|
||||||
|
|
||||||
overflow: hidden
|
white-space: normal
|
||||||
text-overflow: ellipsis
|
word-break: break-word
|
||||||
white-space: nowrap
|
|
||||||
|
|
||||||
font-size: 1rem
|
|
||||||
font-weight: 400
|
|
||||||
|
|
||||||
td.empty-table
|
|
||||||
opacity: 0.3
|
|
||||||
|
|
||||||
tr:last-of-type td
|
tr:last-of-type td
|
||||||
padding-bottom: 0
|
padding-bottom: 0
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
@import 'background'
|
@import 'background'
|
||||||
@import 'fullscreen'
|
|
||||||
@import 'info-tab'
|
@import 'info-tab'
|
||||||
@import 'image'
|
@import 'image'
|
||||||
|
|
||||||
|
@ -18,10 +17,10 @@
|
||||||
z-index: 3
|
z-index: 3
|
||||||
|
|
||||||
.image-block
|
.image-block
|
||||||
margin: 0 0 0 25rem
|
margin: 0 0 0 27rem
|
||||||
padding: 0
|
padding: 0
|
||||||
|
|
||||||
width: calc(100% - 25rem)
|
width: calc(100% - 27rem)
|
||||||
height: 100vh
|
height: 100vh
|
||||||
|
|
||||||
position: relative
|
position: relative
|
||||||
|
@ -79,4 +78,4 @@
|
||||||
.info-tab.collapsed .info-header
|
.info-tab.collapsed .info-header
|
||||||
border-radius: $rad
|
border-radius: $rad
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
&::after
|
&::after
|
||||||
content: ""
|
content: ""
|
||||||
|
|
||||||
width: 450px
|
width: 100%
|
||||||
height: 3px
|
height: 3px
|
||||||
|
|
||||||
position: absolute
|
position: absolute
|
|
@ -94,10 +94,6 @@
|
||||||
vertical-align: middle
|
vertical-align: middle
|
||||||
|
|
||||||
a, .link
|
a, .link
|
||||||
font-size: 1rem
|
|
||||||
font-weight: 500
|
|
||||||
line-height: 1
|
|
||||||
|
|
||||||
color: RGB($primary)
|
color: RGB($primary)
|
||||||
|
|
||||||
cursor: pointer
|
cursor: pointer
|
|
@ -26,7 +26,7 @@
|
||||||
padding: 0
|
padding: 0
|
||||||
|
|
||||||
font-size: 1rem
|
font-size: 1rem
|
||||||
font-weight: 500
|
font-weight: 400
|
||||||
|
|
||||||
form
|
form
|
||||||
margin: 0
|
margin: 0
|
||||||
|
@ -57,9 +57,9 @@
|
||||||
|
|
||||||
position: absolute
|
position: absolute
|
||||||
bottom: 0
|
bottom: 0
|
||||||
left: -25rem
|
left: -27rem
|
||||||
|
|
||||||
width: 25rem
|
width: 27rem
|
||||||
height: 100%
|
height: 100%
|
||||||
|
|
||||||
display: flex
|
display: flex
|
||||||
|
@ -67,11 +67,10 @@
|
||||||
gap: 1rem
|
gap: 1rem
|
||||||
|
|
||||||
background-color: RGB($bg-200)
|
background-color: RGB($bg-200)
|
||||||
opacity: 0
|
|
||||||
|
|
||||||
z-index: +2
|
z-index: +2
|
||||||
|
|
||||||
transition: left 0.25s cubic-bezier(0.76, 0, 0.17, 1), bottom 0.25s cubic-bezier(0.76, 0, 0.17, 1), opacity 0.25s cubic-bezier(0.76, 0, 0.17, 1)
|
transition: left 0.25s cubic-bezier(0.76, 0, 0.17, 1), bottom 0.25s cubic-bezier(0.76, 0, 0.17, 1)
|
||||||
|
|
||||||
#dragIndicator
|
#dragIndicator
|
||||||
display: none
|
display: none
|
||||||
|
@ -197,7 +196,6 @@
|
||||||
|
|
||||||
.container
|
.container
|
||||||
left: 0
|
left: 0
|
||||||
opacity: 1
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint)
|
@media (max-width: $breakpoint)
|
||||||
.upload-panel
|
.upload-panel
|
|
@ -4,10 +4,10 @@
|
||||||
@import "variables"
|
@import "variables"
|
||||||
@import "animations"
|
@import "animations"
|
||||||
|
|
||||||
@import "components/elements/notification"
|
@import "components/notification"
|
||||||
@import "components/elements/pop-up"
|
@import "components/pop-up"
|
||||||
@import "components/elements/upload-panel"
|
@import "components/upload-panel"
|
||||||
@import "components/elements/tags"
|
@import "components/tags"
|
||||||
|
|
||||||
@import "components/navigation"
|
@import "components/navigation"
|
||||||
@import "components/banner"
|
@import "components/banner"
|
||||||
|
|
|
@ -74,13 +74,3 @@ $breakpoint: 800px
|
||||||
|
|
||||||
--breakpoint: 800px
|
--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: 'Rubik'
|
|
||||||
src: url('../../../../static/fonts/Rubik.ttf') format('truetype')
|
|
||||||
font-style: normal
|
|
||||||
font-display: swap
|
|
||||||
font-weight: 300 900
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{% if current_user.id == group.author_id %}
|
{% if current_user.id == group.author.id %}
|
||||||
function groupDelete() {
|
function groupDelete() {
|
||||||
cancelBtn = document.createElement('button');
|
cancelBtn = document.createElement('button');
|
||||||
cancelBtn.classList.add('btn-block');
|
cancelBtn.classList.add('btn-block');
|
||||||
|
@ -223,7 +223,7 @@
|
||||||
<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('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 %}"/>
|
||||||
<span class="banner-filter"></span>
|
<span class="banner-filter"></span>
|
||||||
<div class="banner-content">
|
<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>
|
<p class="banner-info"><a href="{{ url_for('profile.profile', id=group.author.id) }}" class="link">By {{ group.author.username }}</a></p>
|
||||||
<h1 class="banner-header">{{ group.name }}</h1>
|
<h1 class="banner-header">{{ group.name }}</h1>
|
||||||
<p class="banner-subtitle">{{ images|length }} Images · {{ group.description }}</p>
|
<p class="banner-subtitle">{{ images|length }} Images · {{ group.description }}</p>
|
||||||
<div class="pill-row">
|
<div class="pill-row">
|
||||||
|
@ -232,7 +232,7 @@
|
||||||
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{% if current_user.id == group.author_id %}
|
{% if current_user.id == group.author.id %}
|
||||||
<div>
|
<div>
|
||||||
<button class="pill-item pill__critical" onclick="groupDelete()">
|
<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>
|
<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>
|
||||||
|
@ -249,14 +249,14 @@
|
||||||
<div class="banner-small">
|
<div class="banner-small">
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<h1 class="banner-header">{{ group.name }}</h1>
|
<h1 class="banner-header">{{ group.name }}</h1>
|
||||||
<p class="banner-info">By {{ group.author_username }}</p>
|
<p class="banner-info">By {{ group.author.username }}</p>
|
||||||
<div class="pill-row">
|
<div class="pill-row">
|
||||||
<div>
|
<div>
|
||||||
<button class="pill-item" onclick="groupShare()">
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{% if current_user.id == group.author_id %}
|
{% if current_user.id == group.author.id %}
|
||||||
<div>
|
<div>
|
||||||
<button class="pill-item pill__critical" onclick="groupDelete()">
|
<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>
|
<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>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{% if current_user.id == image.author_id %}
|
{% if current_user.id == image.author.id %}
|
||||||
function imageDelete() {
|
function imageDelete() {
|
||||||
cancelBtn = document.createElement('button');
|
cancelBtn = document.createElement('button');
|
||||||
cancelBtn.classList.add('btn-block');
|
cancelBtn.classList.add('btn-block');
|
||||||
|
@ -85,10 +85,6 @@
|
||||||
<span></span>
|
<span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="image-fullscreen" onclick="imageFullscreenOff()">
|
|
||||||
<img src="" alt="{{ image.alt }}"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="image-grid">
|
<div class="image-grid">
|
||||||
<div class="image-block">
|
<div class="image-block">
|
||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
|
@ -124,7 +120,7 @@
|
||||||
<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>
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% if current_user.id == image.author_id %}
|
{% if current_user.id == image.author.id %}
|
||||||
<div>
|
<div>
|
||||||
<button class="pill-item pill__critical" onclick="imageDelete()">
|
<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>
|
<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>
|
||||||
|
@ -145,22 +141,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-container">
|
<div class="info-container">
|
||||||
{% if image.post_description %}
|
|
||||||
<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="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,160H40V56H216V200ZM184,96a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,96Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,128Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,160Z"></path></svg>
|
|
||||||
<h2>Description</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>
|
|
||||||
</div>
|
|
||||||
<div class="info-table">
|
|
||||||
<p>{{ image.post_description }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="info-tab">
|
<div class="info-tab">
|
||||||
<div class="info-header">
|
<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>
|
<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>
|
||||||
|
@ -175,12 +155,18 @@
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Author</td>
|
<td>Author</td>
|
||||||
<td><a href="{{ url_for('profile.profile', id=image.author_id) }}" class="link">{{ image.author_username }}</a></td>
|
<td><a href="{{ url_for('profile.profile', id=image.author.id) }}" class="link">{{ image.author.username }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Upload date</td>
|
<td>Upload date</td>
|
||||||
<td><span class="time">{{ image.created_at }}</span></td>
|
<td><span class="time">{{ image.created_at }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if image.description %}
|
||||||
|
<tr>
|
||||||
|
<td>Description</td>
|
||||||
|
<td>{{ image.description }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
<div class="img-colours">
|
<div class="img-colours">
|
||||||
{% for col in image.colours %}
|
{% for col in image.colours %}
|
||||||
|
|
|
@ -30,11 +30,12 @@
|
||||||
type="image/svg+xml"
|
type="image/svg+xml"
|
||||||
media="(prefers-color-scheme: dark)"/>
|
media="(prefers-color-scheme: dark)"/>
|
||||||
|
|
||||||
{% assets "lib" %}
|
<link
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
rel="prefetch"
|
||||||
{% endassets %}
|
href="{{url_for('static', filename='fonts/font.css')}}"
|
||||||
|
type="stylesheet"/>
|
||||||
|
|
||||||
{% assets "js" %}
|
{% assets "scripts" %}
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
||||||
{% endassets %}
|
{% endassets %}
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@
|
||||||
<div class="navigation">
|
<div class="navigation">
|
||||||
<!--<img src="{{url_for('static', filename='icon.png')}}" alt="Logo" class="logo" onload="this.style.opacity=1;" style="opacity:0">-->
|
<!--<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 %}">
|
<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>
|
<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">
|
<span class="tool-tip">
|
||||||
Home
|
Home
|
||||||
|
@ -77,7 +78,7 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{url_for('group.groups')}}" class="navigation-item {% block nav_groups %}{% endblock %}">
|
<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>
|
<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">
|
<span class="tool-tip">
|
||||||
Groups
|
Groups
|
||||||
|
@ -86,66 +87,66 @@
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
<button class="navigation-item {% block nav_upload %}{% endblock %}" onclick="toggleUploadTab()">
|
<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>
|
<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">
|
<span class="tool-tip">
|
||||||
Upload
|
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>
|
<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>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<span class="navigation-spacer"></span>
|
<span class="navigation-spacer"></span>
|
||||||
|
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
<a href="{{url_for('profile.profile')}}" class="navigation-item {% block nav_profile %}{% endblock %}">
|
<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>
|
<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">
|
<span class="tool-tip">
|
||||||
Profile
|
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>
|
<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>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{url_for('settings.general')}}" class="navigation-item {% block nav_settings %}{% endblock %}">
|
<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>
|
<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">
|
<span class="tool-tip">
|
||||||
Settings
|
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>
|
<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>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="navigation-item {% block nav_login %}{% endblock %}" onclick="showLogin()">
|
<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>
|
<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">
|
<span class="tool-tip">
|
||||||
Login
|
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>
|
<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>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
<div class="upload-panel">
|
<div class="upload-panel">
|
||||||
<span class="click-off" onclick="closeUploadTab()"></span>
|
<span class="click-off" onclick="closeUploadTab()"></span>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span id="dragIndicator"></span>
|
<span id="dragIndicator"></span>
|
||||||
<h3>Upload stuffs</h3>
|
<h3>Upload stuffs</h3>
|
||||||
<p>May the world see your stuff 👀</p>
|
<p>May the world see your stuff 👀</p>
|
||||||
<form id="uploadForm">
|
<form id="uploadForm">
|
||||||
<button class="fileDrop-block" type="button">
|
<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>
|
<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>
|
||||||
<span class="status">Choose or Drop file</span>
|
<span class="status">Choose or Drop file</span>
|
||||||
<input type="file" id="file" tab-index="-1"/>
|
<input type="file" id="file" tab-index="-1"/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<input class="input-block" type="text" placeholder="alt" id="alt"/>
|
<input class="input-block" type="text" placeholder="alt" id="alt"/>
|
||||||
<input class="input-block" type="text" placeholder="description" id="description"/>
|
<input class="input-block" type="text" placeholder="description" id="description"/>
|
||||||
<input class="input-block" type="text" placeholder="tags" id="tags"/>
|
<input class="input-block" type="text" placeholder="tags" id="tags"/>
|
||||||
<button class="btn-block" type="submit">Upload</button>
|
<button class="btn-block primary" type="submit">Upload</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="upload-jobs"></div>
|
<div class="upload-jobs"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|
|
@ -119,9 +119,9 @@
|
||||||
{% if groups %}
|
{% if groups %}
|
||||||
<div class="gallery-grid">
|
<div class="gallery-grid">
|
||||||
{% for group in groups %}
|
{% for group in groups %}
|
||||||
<a id="group-{{ group.id }}" class="group-item" href="{{ url_for('group.group', group_id=group.id) }}">
|
<a id="group-{{ group.id }}" class="group-item" href="{{ url_for('group.group', group_id=group.id) }}" {% if group.images|length > 0 %} style="background-color: rgba({{ group.images.0.colours.0.0 }}, {{ group.images.0.colours.0.1 }}, {{ group.images.0.colours.0.2 }}, 0.4);" {% endif %}>
|
||||||
<div class="image-filter">
|
<div class="image-filter">
|
||||||
<p class="image-subtitle">By {{ group.author_username }}</p>
|
<p class="image-subtitle">By {{ group.author.username }}</p>
|
||||||
<p class="image-title">{{ group.name }}</p>
|
<p class="image-title">{{ group.name }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="images size-{{ group.images|length }}">
|
<div class="images size-{{ group.images|length }}">
|
||||||
|
|
|
@ -77,10 +77,9 @@ class Metadata:
|
||||||
"raw": value,
|
"raw": value,
|
||||||
"formatted": (
|
"formatted": (
|
||||||
getattr(
|
getattr(
|
||||||
helpers, mapping_val[key][1] # pylint: disable=E0602
|
helpers, # pylint: disable=E0602
|
||||||
)(
|
mapping_val[key][1],
|
||||||
value
|
)(value)
|
||||||
)
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -42,7 +42,7 @@ def shutter(value):
|
||||||
"""
|
"""
|
||||||
Formats the shutter speed into a standard format
|
Formats the shutter speed into a standard format
|
||||||
"""
|
"""
|
||||||
return str(value) + "s"
|
return str(value) + " s"
|
||||||
|
|
||||||
|
|
||||||
def focal_length(value):
|
def focal_length(value):
|
||||||
|
@ -50,16 +50,18 @@ def focal_length(value):
|
||||||
Formats the focal length into a standard format
|
Formats the focal length into a standard format
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return str(value[0] / value[1]) + "mm"
|
calculated = value[0] / value[1]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return str(value) + "mm"
|
calculated = value
|
||||||
|
|
||||||
|
return str(calculated) + " mm"
|
||||||
|
|
||||||
|
|
||||||
def exposure(value):
|
def exposure(value):
|
||||||
"""
|
"""
|
||||||
Formats the exposure value into a standard format
|
Formats the exposure value into a standard format
|
||||||
"""
|
"""
|
||||||
return str(value) + "EV"
|
return str(value) + " EV"
|
||||||
|
|
||||||
|
|
||||||
def color_space(value):
|
def color_space(value):
|
||||||
|
@ -78,28 +80,28 @@ def flash(value):
|
||||||
Maps the value of the flash to a human readable format
|
Maps the value of the flash to a human readable format
|
||||||
"""
|
"""
|
||||||
value_map = {
|
value_map = {
|
||||||
0: "Flash did not fire",
|
0: "Did not fire",
|
||||||
1: "Flash fired",
|
1: "Fired",
|
||||||
5: "Strobe return light not detected",
|
5: "Strobe return light not detected",
|
||||||
7: "Strobe return light detected",
|
7: "Strobe return light detected",
|
||||||
9: "Flash fired, compulsory flash mode",
|
9: "Fired, compulsory",
|
||||||
13: "Flash fired, compulsory flash mode, return light not detected",
|
13: "Fired, compulsory, return light not detected",
|
||||||
15: "Flash fired, compulsory flash mode, return light detected",
|
15: "Fired, compulsory, return light detected",
|
||||||
16: "Flash did not fire, compulsory flash mode",
|
16: "Did not fire, compulsory",
|
||||||
24: "Flash did not fire, auto mode",
|
24: "Did not fire, auto mode",
|
||||||
25: "Flash fired, auto mode",
|
25: "Fired, auto mode",
|
||||||
29: "Flash fired, auto mode, return light not detected",
|
29: "Fired, auto mode, return light not detected",
|
||||||
31: "Flash fired, auto mode, return light detected",
|
31: "Fired, auto mode, return light detected",
|
||||||
32: "No flash function",
|
32: "No function",
|
||||||
65: "Flash fired, red-eye reduction mode",
|
65: "Fired, red-eye reduction mode",
|
||||||
69: "Flash fired, red-eye reduction mode, return light not detected",
|
69: "Fired, red-eye reduction mode, return light not detected",
|
||||||
71: "Flash fired, red-eye reduction mode, return light detected",
|
71: "Fired, red-eye reduction mode, return light detected",
|
||||||
73: "Flash fired, compulsory flash mode, red-eye reduction mode",
|
73: "Fired, compulsory, red-eye reduction mode",
|
||||||
77: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
|
77: "Fired, compulsory, red-eye reduction mode, return light not detected",
|
||||||
79: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
|
79: "Fired, compulsory, red-eye reduction mode, return light detected",
|
||||||
89: "Flash fired, auto mode, red-eye reduction mode",
|
89: "Fired, auto mode, red-eye reduction mode",
|
||||||
93: "Flash fired, auto mode, return light not detected, red-eye reduction mode",
|
93: "Fired, auto mode, return light not detected, red-eye reduction mode",
|
||||||
95: "Flash fired, auto mode, return light detected, red-eye reduction mode",
|
95: "Fired, auto mode, return light detected, red-eye reduction mode",
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
return value_map[int(value)]
|
return value_map[int(value)]
|
||||||
|
@ -213,8 +215,8 @@ def white_balance(value):
|
||||||
Maps the value of the white balance to a human readable format
|
Maps the value of the white balance to a human readable format
|
||||||
"""
|
"""
|
||||||
value_map = {
|
value_map = {
|
||||||
0: "Auto white balance",
|
0: "Auto",
|
||||||
1: "Manual white balance",
|
1: "Manual",
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
return value_map[int(value)]
|
return value_map[int(value)]
|
||||||
|
@ -227,8 +229,8 @@ def exposure_mode(value):
|
||||||
Maps the value of the exposure mode to a human readable format
|
Maps the value of the exposure mode to a human readable format
|
||||||
"""
|
"""
|
||||||
value_map = {
|
value_map = {
|
||||||
0: "Auto exposure",
|
0: "Auto",
|
||||||
1: "Manual exposure",
|
1: "Manual",
|
||||||
2: "Auto bracket",
|
2: "Auto bracket",
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
|
@ -386,4 +388,34 @@ def pixel_dimension(value):
|
||||||
"""
|
"""
|
||||||
Maps the value of the pixel dimension to a human readable format
|
Maps the value of the pixel dimension to a human readable format
|
||||||
"""
|
"""
|
||||||
return str(value) + "px"
|
return str(value) + " px"
|
||||||
|
|
||||||
|
|
||||||
|
def title(value):
|
||||||
|
"""
|
||||||
|
Maps the value of the title to a human readable format
|
||||||
|
"""
|
||||||
|
return str(value.title())
|
||||||
|
|
||||||
|
|
||||||
|
def subject_distance(value):
|
||||||
|
"""
|
||||||
|
Maps the value of the subject distance to a human readable format
|
||||||
|
"""
|
||||||
|
return str(value) + " m"
|
||||||
|
|
||||||
|
|
||||||
|
def subject_distance_range(value):
|
||||||
|
"""
|
||||||
|
Maps the value of the subject distance range to a human readable format
|
||||||
|
"""
|
||||||
|
value_map = {
|
||||||
|
0: "Unknown",
|
||||||
|
1: "Macro",
|
||||||
|
2: "Close view",
|
||||||
|
3: "Distant view",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
return value_map[int(value)]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
|
@ -10,22 +10,22 @@ PHOTOGRAHER_MAPPING = {
|
||||||
"Copyright": ["Copyright"],
|
"Copyright": ["Copyright"],
|
||||||
}
|
}
|
||||||
CAMERA_MAPPING = {
|
CAMERA_MAPPING = {
|
||||||
"Model": ["Model"],
|
"Model": ["Model", "title"],
|
||||||
"Make": ["Make"],
|
"Make": ["Manifacturer", "title"],
|
||||||
"BodySerialNumber": ["Camera Type"],
|
"BodySerialNumber": ["Camera Type"],
|
||||||
"LensMake": ["Lens Make"],
|
"LensMake": ["Lens Make", "title"],
|
||||||
"LenseModel": ["Lens Model"],
|
"LensModel": ["Lens Model", "title"],
|
||||||
"LensSpecification": ["Lens Specification", "lens_specification"],
|
"LensSpecification": ["Lens Specification", "lens_specification"],
|
||||||
"ComponentsConfiguration": ["Components Configuration", "components_configuration"],
|
"ComponentsConfiguration": ["Components Configuration", "components_configuration"],
|
||||||
"DateTime": ["Date Processed", "date_format"],
|
"DateTime": ["Date and Time", "date_format"],
|
||||||
"DateTimeDigitized": ["Time Digitized", "date_format"],
|
"DateTimeOriginal": ["Date and Time (Original)", "date_format"],
|
||||||
|
"DateTimeDigitized": ["Date and Time (Digitized)", "date_format"],
|
||||||
"OffsetTime": ["Time Offset"],
|
"OffsetTime": ["Time Offset"],
|
||||||
"OffsetTimeOriginal": ["Time Offset - Original"],
|
"OffsetTimeOriginal": ["Time Offset (Original)"],
|
||||||
"OffsetTimeDigitized": ["Time Offset - Digitized"],
|
"OffsetTimeDigitized": ["Time Offset (Digitized)"],
|
||||||
"DateTimeOriginal": ["Date Original", "date_format"],
|
"FNumber": ["FNumber", "fnumber"],
|
||||||
"FNumber": ["F-Stop", "fnumber"],
|
|
||||||
"FocalLength": ["Focal Length", "focal_length"],
|
"FocalLength": ["Focal Length", "focal_length"],
|
||||||
"FocalLengthIn35mmFilm": ["Focal Length (35mm format)", "focal_length"],
|
"FocalLengthIn35mmFilm": ["Focal Length in 35mm format", "focal_length"],
|
||||||
"MaxApertureValue": ["Max Aperture", "fnumber"],
|
"MaxApertureValue": ["Max Aperture", "fnumber"],
|
||||||
"ApertureValue": ["Aperture", "fnumber"],
|
"ApertureValue": ["Aperture", "fnumber"],
|
||||||
"ShutterSpeedValue": ["Shutter Speed", "shutter"],
|
"ShutterSpeedValue": ["Shutter Speed", "shutter"],
|
||||||
|
@ -41,6 +41,8 @@ CAMERA_MAPPING = {
|
||||||
"MeteringMode": ["Metering Mode", "metering_mode"],
|
"MeteringMode": ["Metering Mode", "metering_mode"],
|
||||||
"LightSource": ["Light Source", "light_source"],
|
"LightSource": ["Light Source", "light_source"],
|
||||||
"SceneCaptureType": ["Scene Capture Type", "scene_capture_type"],
|
"SceneCaptureType": ["Scene Capture Type", "scene_capture_type"],
|
||||||
|
"SubjectDistance": ["Subject Distance", "subject_distance"],
|
||||||
|
"SubjectDistanceRange": ["Subject Distance Range", "subject_distance_range"],
|
||||||
}
|
}
|
||||||
SOFTWARE_MAPPING = {
|
SOFTWARE_MAPPING = {
|
||||||
"Software": ["Software"],
|
"Software": ["Software"],
|
||||||
|
|
|
@ -3,16 +3,14 @@ Onlylegs - Image Groups
|
||||||
Why groups? Because I don't like calling these albums
|
Why groups? Because I don't like calling these albums
|
||||||
sounds more limiting that it actually is in this gallery
|
sounds more limiting that it actually is in this gallery
|
||||||
"""
|
"""
|
||||||
from flask import Blueprint, abort, render_template, url_for
|
from flask import Blueprint, render_template, url_for
|
||||||
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
from gallery.models import Post, User, GroupJunction, Group
|
||||||
from gallery import db
|
from gallery.extensions import db
|
||||||
from gallery.utils import contrast
|
from gallery.utils import contrast
|
||||||
|
|
||||||
|
|
||||||
blueprint = Blueprint("group", __name__, url_prefix="/group")
|
blueprint = Blueprint("group", __name__, url_prefix="/group")
|
||||||
db_session = sessionmaker(bind=db.engine)
|
|
||||||
db_session = db_session()
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/", methods=["GET"])
|
@blueprint.route("/", methods=["GET"])
|
||||||
|
@ -20,21 +18,21 @@ def groups():
|
||||||
"""
|
"""
|
||||||
Group overview, shows all image groups
|
Group overview, shows all image groups
|
||||||
"""
|
"""
|
||||||
groups = db_session.query(db.Groups).all()
|
groups = Group.query.all()
|
||||||
|
|
||||||
# For each group, get the 3 most recent images
|
# For each group, get the 3 most recent images
|
||||||
for group in groups:
|
for group in groups:
|
||||||
group.author_username = (
|
group.author_username = (
|
||||||
db_session.query(db.Users.username)
|
User.query.with_entities(User.username)
|
||||||
.filter(db.Users.id == group.author_id)
|
.filter(User.id == group.author_id)
|
||||||
.first()[0]
|
.first()[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the 3 most recent images
|
# Get the 3 most recent images
|
||||||
images = (
|
images = (
|
||||||
db_session.query(db.GroupJunction.post_id)
|
GroupJunction.query.with_entities(GroupJunction.post_id)
|
||||||
.filter(db.GroupJunction.group_id == group.id)
|
.filter(GroupJunction.group_id == group.id)
|
||||||
.order_by(db.GroupJunction.date_added.desc())
|
.order_by(GroupJunction.date_added.desc())
|
||||||
.limit(3)
|
.limit(3)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,10 +40,8 @@ def groups():
|
||||||
group.images = []
|
group.images = []
|
||||||
for image in images:
|
for image in images:
|
||||||
group.images.append(
|
group.images.append(
|
||||||
db_session.query(
|
Post.query.with_entities(Post.filename, Post.alt, Post.colours, Post.id)
|
||||||
db.Posts.filename, db.Posts.alt, db.Posts.colours, db.Posts.id
|
.filter(Post.id == image[0])
|
||||||
)
|
|
||||||
.filter(db.Posts.id == image[0])
|
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -58,32 +54,20 @@ def group(group_id):
|
||||||
Group view, shows all images in a group
|
Group view, shows all images in a group
|
||||||
"""
|
"""
|
||||||
# Get the group, if it doesn't exist, 404
|
# Get the group, if it doesn't exist, 404
|
||||||
group = db_session.query(db.Groups).filter(db.Groups.id == group_id).first()
|
group = db.get_or_404(Group, group_id, description="Group not found! D:")
|
||||||
|
|
||||||
if group is None:
|
|
||||||
abort(404, "Group not found! D:")
|
|
||||||
|
|
||||||
# Get the group's author username
|
|
||||||
group.author_username = (
|
|
||||||
db_session.query(db.Users.username)
|
|
||||||
.filter(db.Users.id == group.author_id)
|
|
||||||
.first()[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get all images in the group from the junction table
|
# Get all images in the group from the junction table
|
||||||
junction = (
|
junction = (
|
||||||
db_session.query(db.GroupJunction.post_id)
|
GroupJunction.query.with_entities(GroupJunction.post_id)
|
||||||
.filter(db.GroupJunction.group_id == group_id)
|
.filter(GroupJunction.group_id == group_id)
|
||||||
.order_by(db.GroupJunction.date_added.desc())
|
.order_by(GroupJunction.date_added.desc())
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the image data for each image in the group
|
# Get the image data for each image in the group
|
||||||
images = []
|
images = []
|
||||||
for image in junction:
|
for image in junction:
|
||||||
images.append(
|
images.append(Post.query.filter(Post.id == image[0]).first())
|
||||||
db_session.query(db.Posts).filter(db.Posts.id == image[0]).first()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check contrast for the first image in the group for the banner
|
# Check contrast for the first image in the group for the banner
|
||||||
text_colour = "rgb(var(--fg-black))"
|
text_colour = "rgb(var(--fg-black))"
|
||||||
|
@ -103,21 +87,12 @@ def group_post(group_id, image_id):
|
||||||
Image view, shows the image and its metadata from a specific group
|
Image view, shows the image and its metadata from a specific group
|
||||||
"""
|
"""
|
||||||
# Get the image, if it doesn't exist, 404
|
# Get the image, if it doesn't exist, 404
|
||||||
image = db_session.query(db.Posts).filter(db.Posts.id == image_id).first()
|
image = db.get_or_404(Post, image_id, description="Image not found :<")
|
||||||
if image is None:
|
|
||||||
abort(404, "Image not found")
|
|
||||||
|
|
||||||
# Get the image's author username
|
|
||||||
image.author_username = (
|
|
||||||
db_session.query(db.Users.username)
|
|
||||||
.filter(db.Users.id == image.author_id)
|
|
||||||
.first()[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get all groups the image is in
|
# Get all groups the image is in
|
||||||
groups = (
|
groups = (
|
||||||
db_session.query(db.GroupJunction.group_id)
|
GroupJunction.query.with_entities(GroupJunction.group_id)
|
||||||
.filter(db.GroupJunction.post_id == image_id)
|
.filter(GroupJunction.post_id == image_id)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -125,24 +100,24 @@ def group_post(group_id, image_id):
|
||||||
image.groups = []
|
image.groups = []
|
||||||
for group in groups:
|
for group in groups:
|
||||||
image.groups.append(
|
image.groups.append(
|
||||||
db_session.query(db.Groups.id, db.Groups.name)
|
Group.query.with_entities(Group.id, Group.name)
|
||||||
.filter(db.Groups.id == group[0])
|
.filter(Group.id == group[0])
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the next and previous images in the group
|
# Get the next and previous images in the group
|
||||||
next_url = (
|
next_url = (
|
||||||
db_session.query(db.GroupJunction.post_id)
|
GroupJunction.query.with_entities(GroupJunction.post_id)
|
||||||
.filter(db.GroupJunction.group_id == group_id)
|
.filter(GroupJunction.group_id == group_id)
|
||||||
.filter(db.GroupJunction.post_id > image_id)
|
.filter(GroupJunction.post_id > image_id)
|
||||||
.order_by(db.GroupJunction.date_added.asc())
|
.order_by(GroupJunction.date_added.asc())
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
prev_url = (
|
prev_url = (
|
||||||
db_session.query(db.GroupJunction.post_id)
|
GroupJunction.query.with_entities(GroupJunction.post_id)
|
||||||
.filter(db.GroupJunction.group_id == group_id)
|
.filter(GroupJunction.group_id == group_id)
|
||||||
.filter(db.GroupJunction.post_id < image_id)
|
.filter(GroupJunction.post_id < image_id)
|
||||||
.order_by(db.GroupJunction.date_added.desc())
|
.order_by(GroupJunction.date_added.desc())
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,12 @@
|
||||||
Onlylegs - Image View
|
Onlylegs - Image View
|
||||||
"""
|
"""
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
from flask import Blueprint, render_template, url_for, current_app
|
||||||
from flask import Blueprint, abort, render_template, url_for, current_app
|
from gallery.models import Post, GroupJunction, Group
|
||||||
|
from gallery.extensions import db
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
from gallery import db
|
|
||||||
|
|
||||||
|
|
||||||
blueprint = Blueprint("image", __name__, url_prefix="/image")
|
blueprint = Blueprint("image", __name__, url_prefix="/image")
|
||||||
db_session = sessionmaker(bind=db.engine)
|
|
||||||
db_session = db_session()
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/<int:image_id>")
|
@blueprint.route("/<int:image_id>")
|
||||||
|
@ -20,45 +16,36 @@ def image(image_id):
|
||||||
Image view, shows the image and its metadata
|
Image view, shows the image and its metadata
|
||||||
"""
|
"""
|
||||||
# Get the image, if it doesn't exist, 404
|
# Get the image, if it doesn't exist, 404
|
||||||
image = db_session.query(db.Posts).filter(db.Posts.id == image_id).first()
|
image = db.get_or_404(Post, image_id, description="Image not found :<")
|
||||||
if not image:
|
|
||||||
abort(404, "Image not found :<")
|
|
||||||
|
|
||||||
# Get the image's author username
|
# Get all groups the image is in
|
||||||
image.author_username = (
|
|
||||||
db_session.query(db.Users.username)
|
|
||||||
.filter(db.Users.id == image.author_id)
|
|
||||||
.first()[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the image's groups
|
|
||||||
groups = (
|
groups = (
|
||||||
db_session.query(db.GroupJunction.group_id)
|
GroupJunction.query.with_entities(GroupJunction.group_id)
|
||||||
.filter(db.GroupJunction.post_id == image_id)
|
.filter(GroupJunction.post_id == image_id)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
# For each group, get the group data and add it to the image item
|
# Get the group data for each group the image is in
|
||||||
image.groups = []
|
image.groups = []
|
||||||
for group in groups:
|
for group in groups:
|
||||||
image.groups.append(
|
image.groups.append(
|
||||||
db_session.query(db.Groups.id, db.Groups.name)
|
Group.query.with_entities(Group.id, Group.name)
|
||||||
.filter(db.Groups.id == group[0])
|
.filter(Group.id == group[0])
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the next and previous images
|
# Get the next and previous images
|
||||||
# Check if there is a group ID set
|
# Check if there is a group ID set
|
||||||
next_url = (
|
next_url = (
|
||||||
db_session.query(db.Posts.id)
|
Post.query.with_entities(Post.id)
|
||||||
.filter(db.Posts.id > image_id)
|
.filter(Post.id > image_id)
|
||||||
.order_by(db.Posts.id.asc())
|
.order_by(Post.id.asc())
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
prev_url = (
|
prev_url = (
|
||||||
db_session.query(db.Posts.id)
|
Post.query.with_entities(Post.id)
|
||||||
.filter(db.Posts.id < image_id)
|
.filter(Post.id < image_id)
|
||||||
.order_by(db.Posts.id.desc())
|
.order_by(Post.id.desc())
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -69,7 +56,7 @@ def image(image_id):
|
||||||
prev_url = url_for("image.image", image_id=prev_url[0])
|
prev_url = url_for("image.image", image_id=prev_url[0])
|
||||||
|
|
||||||
# Yoink all the images in the database
|
# Yoink all the images in the database
|
||||||
total_images = db_session.query(db.Posts.id).order_by(db.Posts.id.desc()).all()
|
total_images = Post.query.with_entities(Post.id).order_by(Post.id.desc()).all()
|
||||||
limit = current_app.config["UPLOAD_CONF"]["max-load"]
|
limit = current_app.config["UPLOAD_CONF"]["max-load"]
|
||||||
|
|
||||||
# If the number of items is less than the limit, no point of calculating the page
|
# If the number of items is less than the limit, no point of calculating the page
|
||||||
|
@ -81,7 +68,7 @@ def image(image_id):
|
||||||
# Slice the list of IDs into chunks of the limit
|
# Slice the list of IDs into chunks of the limit
|
||||||
for j in total_images[i * limit : (i + 1) * limit]:
|
for j in total_images[i * limit : (i + 1) * limit]:
|
||||||
# Is our image in this chunk?
|
# Is our image in this chunk?
|
||||||
if image_id in j:
|
if not image_id > j[-1]:
|
||||||
return_page = i + 1
|
return_page = i + 1
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,10 @@ from math import ceil
|
||||||
from flask import Blueprint, render_template, request, current_app
|
from flask import Blueprint, render_template, request, current_app
|
||||||
from werkzeug.exceptions import abort
|
from werkzeug.exceptions import abort
|
||||||
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
from gallery.models import Post
|
||||||
from gallery import db
|
|
||||||
|
|
||||||
|
|
||||||
blueprint = Blueprint("gallery", __name__)
|
blueprint = Blueprint("gallery", __name__)
|
||||||
db_session = sessionmaker(bind=db.engine)
|
|
||||||
db_session = db_session()
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/")
|
@blueprint.route("/")
|
||||||
|
@ -30,7 +27,7 @@ def index():
|
||||||
|
|
||||||
# get the total number of images in the database
|
# get the total number of images in the database
|
||||||
# calculate the total number of pages, and make sure the page number is valid
|
# calculate the total number of pages, and make sure the page number is valid
|
||||||
total_images = db_session.query(db.Posts.id).count()
|
total_images = Post.query.with_entities(Post.id).count()
|
||||||
pages = ceil(max(total_images, limit) / limit)
|
pages = ceil(max(total_images, limit) / limit)
|
||||||
if page > pages:
|
if page > pages:
|
||||||
abort(
|
abort(
|
||||||
|
@ -41,14 +38,10 @@ def index():
|
||||||
|
|
||||||
# get the images for the current page
|
# get the images for the current page
|
||||||
images = (
|
images = (
|
||||||
db_session.query(
|
Post.query.with_entities(
|
||||||
db.Posts.filename,
|
Post.filename, Post.alt, Post.colours, Post.created_at, Post.id
|
||||||
db.Posts.alt,
|
|
||||||
db.Posts.colours,
|
|
||||||
db.Posts.created_at,
|
|
||||||
db.Posts.id,
|
|
||||||
)
|
)
|
||||||
.order_by(db.Posts.id.desc())
|
.order_by(Post.id.desc())
|
||||||
.offset((page - 1) * limit)
|
.offset((page - 1) * limit)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.all()
|
.all()
|
||||||
|
|
|
@ -5,16 +5,13 @@ from flask import Blueprint, render_template, request
|
||||||
from werkzeug.exceptions import abort
|
from werkzeug.exceptions import abort
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
from gallery.models import Post, User
|
||||||
from gallery import db
|
|
||||||
|
|
||||||
|
|
||||||
blueprint = Blueprint("profile", __name__, url_prefix="/profile")
|
blueprint = Blueprint("profile", __name__, url_prefix="/profile")
|
||||||
db_session = sessionmaker(bind=db.engine)
|
|
||||||
db_session = db_session()
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/profile")
|
@blueprint.route("/")
|
||||||
def profile():
|
def profile():
|
||||||
"""
|
"""
|
||||||
Profile overview, shows all profiles on the onlylegs gallery
|
Profile overview, shows all profiles on the onlylegs gallery
|
||||||
|
@ -29,11 +26,11 @@ def profile():
|
||||||
abort(404, "You must be logged in to view your own profile!")
|
abort(404, "You must be logged in to view your own profile!")
|
||||||
|
|
||||||
# Get the user's data
|
# Get the user's data
|
||||||
user = db_session.query(db.Users).filter(db.Users.id == user_id).first()
|
user = User.query.filter(User.id == user_id).first()
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
abort(404, "User not found :c")
|
abort(404, "User not found :c")
|
||||||
|
|
||||||
images = db_session.query(db.Posts).filter(db.Posts.author_id == user_id).all()
|
images = Post.query.filter(Post.author_id == user_id).all()
|
||||||
|
|
||||||
return render_template("profile.html", user=user, images=images)
|
return render_template("profile.html", user=user, images=images)
|
||||||
|
|
200
poetry.lock
generated
200
poetry.lock
generated
|
@ -1,15 +1,37 @@
|
||||||
# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alembic"
|
||||||
|
version = "1.10.3"
|
||||||
|
description = "A database migration tool for SQLAlchemy."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "alembic-1.10.3-py3-none-any.whl", hash = "sha256:b2e0a6cfd3a8ce936a1168320bcbe94aefa3f4463cd773a968a55071beb3cd37"},
|
||||||
|
{file = "alembic-1.10.3.tar.gz", hash = "sha256:32a69b13a613aeb7e8093f242da60eff9daed13c0df02fff279c1b06c32965d2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
importlib-metadata = {version = "*", markers = "python_version < \"3.9\""}
|
||||||
|
importlib-resources = {version = "*", markers = "python_version < \"3.9\""}
|
||||||
|
Mako = "*"
|
||||||
|
SQLAlchemy = ">=1.3.0"
|
||||||
|
typing-extensions = ">=4"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tz = ["python-dateutil"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "astroid"
|
name = "astroid"
|
||||||
version = "2.15.1"
|
version = "2.15.2"
|
||||||
description = "An abstract syntax tree for Python with inference support."
|
description = "An abstract syntax tree for Python with inference support."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7.2"
|
python-versions = ">=3.7.2"
|
||||||
files = [
|
files = [
|
||||||
{file = "astroid-2.15.1-py3-none-any.whl", hash = "sha256:89860bda98fe2bbd1f5d262229be7629d778ce280de68d95d4a73d1f592ad268"},
|
{file = "astroid-2.15.2-py3-none-any.whl", hash = "sha256:dea89d9f99f491c66ac9c04ebddf91e4acf8bd711722175fe6245c0725cc19bb"},
|
||||||
{file = "astroid-2.15.1.tar.gz", hash = "sha256:af4e0aff46e2868218502789898269ed95b663fba49e65d91c1e09c966266c34"},
|
{file = "astroid-2.15.2.tar.gz", hash = "sha256:6e61b85c891ec53b07471aec5878f4ac6446a41e590ede0f2ce095f39f7d49dd"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -279,6 +301,39 @@ files = [
|
||||||
Flask = ">=1.0.4"
|
Flask = ">=1.0.4"
|
||||||
Werkzeug = ">=1.0.1"
|
Werkzeug = ">=1.0.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flask-migrate"
|
||||||
|
version = "4.0.4"
|
||||||
|
description = "SQLAlchemy database migrations for Flask applications using Alembic."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "Flask-Migrate-4.0.4.tar.gz", hash = "sha256:73293d40b10ac17736e715b377e7b7bde474cb8105165d77474df4c3619b10b3"},
|
||||||
|
{file = "Flask_Migrate-4.0.4-py3-none-any.whl", hash = "sha256:77580f27ab39bc68be4906a43c56d7674b45075bc4f883b1d0b985db5164d58f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
alembic = ">=1.9.0"
|
||||||
|
Flask = ">=0.9"
|
||||||
|
Flask-SQLAlchemy = ">=1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flask-sqlalchemy"
|
||||||
|
version = "3.0.3"
|
||||||
|
description = "Add SQLAlchemy support to your Flask application."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "Flask-SQLAlchemy-3.0.3.tar.gz", hash = "sha256:2764335f3c9d7ebdc9ed6044afaf98aae9fa50d7a074cef55dde307ec95903ec"},
|
||||||
|
{file = "Flask_SQLAlchemy-3.0.3-py3-none-any.whl", hash = "sha256:add5750b2f9cd10512995261ee2aa23fab85bd5626061aa3c564b33bb4aa780a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Flask = ">=2.2"
|
||||||
|
SQLAlchemy = ">=1.4.18"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "greenlet"
|
name = "greenlet"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
|
@ -376,14 +431,14 @@ tornado = ["tornado (>=0.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "importlib-metadata"
|
name = "importlib-metadata"
|
||||||
version = "6.1.0"
|
version = "6.2.1"
|
||||||
description = "Read metadata from Python packages"
|
description = "Read metadata from Python packages"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "importlib_metadata-6.1.0-py3-none-any.whl", hash = "sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09"},
|
{file = "importlib_metadata-6.2.1-py3-none-any.whl", hash = "sha256:f65e478a7c2177bd19517a3a15dac094d253446d8690c5f3e71e735a04312374"},
|
||||||
{file = "importlib_metadata-6.1.0.tar.gz", hash = "sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20"},
|
{file = "importlib_metadata-6.2.1.tar.gz", hash = "sha256:5a66966b39ff1c14ef5b2d60c1d842b0141fefff0f4cc6365b4bc9446c652807"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -394,6 +449,25 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker
|
||||||
perf = ["ipython"]
|
perf = ["ipython"]
|
||||||
testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
|
testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "importlib-resources"
|
||||||
|
version = "5.12.0"
|
||||||
|
description = "Read resources from Python packages"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"},
|
||||||
|
{file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||||
|
testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "isort"
|
name = "isort"
|
||||||
version = "5.12.0"
|
version = "5.12.0"
|
||||||
|
@ -514,6 +588,26 @@ files = [
|
||||||
{file = "libsass-0.22.0.tar.gz", hash = "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425"},
|
{file = "libsass-0.22.0.tar.gz", hash = "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mako"
|
||||||
|
version = "1.2.4"
|
||||||
|
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"},
|
||||||
|
{file = "Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
MarkupSafe = ">=0.9.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
babel = ["Babel"]
|
||||||
|
lingua = ["lingua"]
|
||||||
|
testing = ["pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "markupsafe"
|
||||||
version = "2.1.2"
|
version = "2.1.2"
|
||||||
|
@ -684,18 +778,18 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pylint"
|
name = "pylint"
|
||||||
version = "2.17.1"
|
version = "2.17.2"
|
||||||
description = "python code static checker"
|
description = "python code static checker"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7.2"
|
python-versions = ">=3.7.2"
|
||||||
files = [
|
files = [
|
||||||
{file = "pylint-2.17.1-py3-none-any.whl", hash = "sha256:8660a54e3f696243d644fca98f79013a959c03f979992c1ab59c24d3f4ec2700"},
|
{file = "pylint-2.17.2-py3-none-any.whl", hash = "sha256:001cc91366a7df2970941d7e6bbefcbf98694e00102c1f121c531a814ddc2ea8"},
|
||||||
{file = "pylint-2.17.1.tar.gz", hash = "sha256:d4d009b0116e16845533bc2163493d6681846ac725eab8ca8014afb520178ddd"},
|
{file = "pylint-2.17.2.tar.gz", hash = "sha256:1b647da5249e7c279118f657ca28b6aaebb299f86bf92affc632acf199f7adbb"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
astroid = ">=2.15.0,<=2.17.0-dev0"
|
astroid = ">=2.15.2,<=2.17.0-dev0"
|
||||||
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
|
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
|
||||||
dill = [
|
dill = [
|
||||||
{version = ">=0.2", markers = "python_version < \"3.11\""},
|
{version = ">=0.2", markers = "python_version < \"3.11\""},
|
||||||
|
@ -796,53 +890,53 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlalchemy"
|
name = "sqlalchemy"
|
||||||
version = "2.0.8"
|
version = "2.0.9"
|
||||||
description = "Database Abstraction Library"
|
description = "Database Abstraction Library"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:42207a5237d8b211df1c04da4a8700bb9f366f5d7be14b1a2e39cc33d330428c"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:734805708632e3965c2c40081f9a59263c29ffa27cba9b02d4d92dfd57ba869f"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:09b6a94bbb1507b3fc75a3c98491f990b80a859eb5227378f6dac908dff74396"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d3ece5960b3e821e43a4927cc851b6e84a431976d3ffe02aadb96519044807e"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd405d224c0f83f267bfcc430bd275195cb48bb9576f6949911faec16bba1216"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d118e233f416d713aac715e2c1101e17f91e696ff315fc9efbc75b70d11e740"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e424a385a234e0bbaac69b6b09894ce99aa285518dd9397846e78214671ec0b4"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f005245e1cb9b8ca53df73ee85e029ac43155e062405015e49ec6187a2e3fb44"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6d82bb74a44f103d566f6c86d6db0ed944e99a7da93d6490c1e987a2b91d5613"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:34eb96c1de91d8f31e988302243357bef3f7785e1b728c7d4b98bd0c117dafeb"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c8e546c249f0d23821f4b31e5adfba8e39bc90140d39f7086d4a8422d1f0fa6"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7e472e9627882f2d75b87ff91c5a2bc45b31a226efc7cc0a054a94fffef85862"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-win32.whl", hash = "sha256:32af8923cfe7b115d40b88604412450d2e4714f4b9a69b1966dfe6012e254789"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-win32.whl", hash = "sha256:0a865b5ec4ba24f57c33b633b728e43fde77b968911a6046443f581b25d29dd9"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:692a2b1e0c82c678c84ccb3a9a226e0553dd115be3d5c54b1bb6ef35bc464da9"},
|
{file = "SQLAlchemy-2.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:6e84ab63d25d8564d7a8c05dc080659931a459ee27f6ed1cf4c91f292d184038"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a215d3e9de2af881c4eee60bf2b3576fb2eeb591a9af37ff2919cd74b68f162"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db4bd1c4792da753f914ff0b688086b9a8fd78bb9bc5ae8b6d2e65f176b81eb9"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1906c954ee7287d46877636a66da157fc83553851013e42c862653ddacb380b"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad5363a1c65fde7b7466769d4261126d07d872fc2e816487ae6cec93da604b6b"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a64345a6f41c8c011df3f498cee8de39d9682ca2de14698b68a50bf3a03f6e5"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebc4eeb1737a5a9bdb0c24f4c982319fa6edd23cdee27180978c29cbb026f2bd"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4223002ab7faac63384019feb31eab4ae2233753fd06ea95690d4e148b5bf80d"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbda1da8d541904ba262825a833c9f619e93cb3fd1156be0a5e43cd54d588dcd"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3a1eb8ed5dc2a7bc554a612a57253f3682aea7e4c54c742153aef6a12fde10ee"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d5327f54a9c39e7871fc532639616f3777304364a0bb9b89d6033ad34ef6c5f8"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:af0683c905c856db70adf6d0f84398785f7be6cb4ffdad20d8e16fe426bdf6e8"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ac6a0311fb21a99855953f84c43fcff4bdca27a2ffcc4f4d806b26b54b5cddc9"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-win32.whl", hash = "sha256:6c5e056a6dc1ce29d5e0e54bc1ce669f3035ec18cb7e5c853122b5fdfe216f8f"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-win32.whl", hash = "sha256:d209594e68bec103ad5243ecac1b40bf5770c9ebf482df7abf175748a34f4853"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:21698ac65afe477400aacb56244b607477c192892e2f7a70492cae1e13f73040"},
|
{file = "SQLAlchemy-2.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:865392a50a721445156809c1a6d6ab6437be70c1c2599f591a8849ed95d3c693"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df6722580ff89295e9d11b781d7a50ff6e48fdfe1c81362000925b4d4cbab31"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0b49f1f71d7a44329a43d3edd38cc5ee4c058dfef4487498393d16172007954b"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8707ba6e783d144209dab0324cc72147a1de34d404a61a08eef96fbb3c8909f"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a019f723b6c1e6b3781be00fb9e0844bc6156f9951c836ff60787cc3938d76"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cc70331b7aed25fed5260cf30a69e42eaf3d887a23a6e4f29a51167ae8a00e1"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9838bd247ee42eb74193d865e48dd62eb50e45e3fdceb0fdef3351133ee53dcf"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6f33587541eb6eb0292e82b314f5ef53159f63f14ae1c08b5ef6195732523afa"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:78612edf4ba50d407d0eb3a64e9ec76e6efc2b5d9a5c63415d53e540266a230a"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0cdc65cbb95ae6813c51acffa65dd8d8272a52da42e3967fc98788fe185a0e0c"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f61ab84956dc628c8dfe9d105b6aec38afb96adae3e5e7da6085b583ff6ea789"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-win32.whl", hash = "sha256:9a981a65dc0082009399eac6600b0983120bc3b850f3c1ed986f68648d9dbb4c"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-win32.whl", hash = "sha256:07950fc82f844a2de67ddb4e535f29b65652b4d95e8b847823ce66a6d540a41d"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-win_amd64.whl", hash = "sha256:b3bdbd9a5b2788b5c51c4fffa2f875103d3c6d01b8f0dc4abbf075cea11741f5"},
|
{file = "SQLAlchemy-2.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:e62c4e762d6fd2901692a093f208a6a6575b930e9458ad58c2a7f080dd6132da"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3ca04c2ccf6f70278905a371ae6eb61494e8c388e98cba909bc9458ceb73dda5"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b3e5864eba71a3718236a120547e52c8da2ccb57cc96cecd0480106a0c799c92"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5b030f91dd310c0f1d7da0654fcc9d8574fe430693e29c544f5558c2426e08eb"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d06e119cf79a3d80ab069f064a07152eb9ba541d084bdaee728d8a6f03fd03d"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa72fb1be5e6704d697471eb5cc07bf9cd98689be11ffa724efe2a3666d0f6c1"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee2946042cc7851842d7a086a92b9b7b494cbe8c3e7e4627e27bc912d3a7655e"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf42183f93ad3801bc08c8a111de16802a2c32d16dcb9700daf1a8ae12d9d28"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13f984a190d249769a050634b248aef8991acc035e849d02b634ea006c028fa8"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2590fd682d3b9dbecb2710ba83b28ba9df18e0c3c26c78bb950715566784723"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e4780be0f19e5894c17f75fc8de2fe1ae233ab37827125239ceb593c6f6bd1e2"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d6753512214d0de6a5a1c05fe716a7d643c927eb0495341db6526577960cbb55"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:68ed381bc340b4a3d373dbfec1a8b971f6350139590c4ca3cb722fdb50035777"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-win32.whl", hash = "sha256:cf1447ec09d7c9ef0b3a65cea905690ed78ed88717beaac537271c005b0e4ea2"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-win32.whl", hash = "sha256:aa5c270ece17c0c0e0a38f2530c16b20ea05d8b794e46c79171a86b93b758891"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:b4c73a3537f928ebf687244f2af9d477b3b14640b4472fcb2456ee0193850ba1"},
|
{file = "SQLAlchemy-2.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:1b69666e25cc03c602d9d3d460e1281810109e6546739187044fc256c67941ef"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:50fa610ac1e45e9adc161227cccbd5c75f8eef25b2ad4a0e080d48c27023415d"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6e27189ff9aebfb2c02fd252c629ea58657e7a5ff1a321b7fc9c2bf6dc0b5f3"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:337ae79dc63b1c957dd2e9e7eed5235492886e2af45d8df4b28748ee4859b588"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8239ce63a90007bce479adf5460d48c1adae4b933d8e39a4eafecfc084e503c"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a091b9166ab9d8ff77c3155140105e76df1d585632fb9367a31b517960df78a"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f759eccb66e6d495fb622eb7f4ac146ae674d829942ec18b7f5a35ddf029597"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0f74c66b2d0081cee5093150407ec43d23d0d5c70aa5b41f438ead1d39bc8d"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246712af9fc761d6c13f4f065470982e175d902e77aa4218c9cb9fc9ff565a0c"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ea2976a98076f44e7e8ab9a90b6dfa357253edddf687294b1bcd852ffe20e526"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6b72dccc5864ea95c93e0a9c4e397708917fb450f96737b4a8395d009f90b868"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d713d8c4b36c4f69eef33a70a4521e37b75a3ac9c86ee96626c4b052337978cc"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:93c78d42c14aa9a9e0866eacd5b48df40a50d0e2790ee377af7910d224afddcf"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-win32.whl", hash = "sha256:81ada150345ef9c709b647e2bcc8f688d62de6b8f06a7544bed76bef328c6636"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-win32.whl", hash = "sha256:f49c5d3c070a72ecb96df703966c9678dda0d4cb2e2736f88d15f5e1203b4159"},
|
||||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:68b328500a20f328db6af2c6839eb69e3791b2cd544f5808a4b0123b130c7571"},
|
{file = "SQLAlchemy-2.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:4c3020afb144572c7bfcba9d7cce57ad42bff6e6115dffcfe2d4ae6d444a214f"},
|
||||||
{file = "SQLAlchemy-2.0.8-py3-none-any.whl", hash = "sha256:f12cd526188f36f161eb71fb275b06d1879c565b07cf0ab2c2408c5059df1fed"},
|
{file = "SQLAlchemy-2.0.9-py3-none-any.whl", hash = "sha256:e730603cae5747bc6d6dece98b45a57d647ed553c8d5ecef602697b1c1501cf2"},
|
||||||
{file = "SQLAlchemy-2.0.8.tar.gz", hash = "sha256:250e158a1f36c965dde1f949366eae9a57504a8cd7a4d968e66c2d0b3c18198d"},
|
{file = "SQLAlchemy-2.0.9.tar.gz", hash = "sha256:95719215e3ec7337b9f57c3c2eda0e6a7619be194a5166c07c1e599f6afc20fa"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -1042,4 +1136,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.8"
|
python-versions = "^3.8"
|
||||||
content-hash = "58c3430743ce1cfd8e5b89db371a0d454a478cbe79ced08c645b4628980ca9f1"
|
content-hash = "88387c49c901feebd4685ee75f6b79c0bbc8cc9c1a64798cd0394b140e165603"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "onlylegs"
|
name = "onlylegs"
|
||||||
version = "23.04.08"
|
version = "23.04.10"
|
||||||
description = "Gallery built for fast and simple image management"
|
description = "Gallery built for fast and simple image management"
|
||||||
authors = ["Fluffy-Bean <michal-gdula@protonmail.com>"]
|
authors = ["Fluffy-Bean <michal-gdula@protonmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
@ -9,19 +9,20 @@ readme = ".github/README.md"
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = "^3.8"
|
||||||
Flask = "^2.2.2"
|
Flask = "^2.2.2"
|
||||||
|
Flask-Sqlalchemy = "^3.0.3"
|
||||||
|
Flask-Migrate = "^4.0.4"
|
||||||
Flask-Compress = "^1.13"
|
Flask-Compress = "^1.13"
|
||||||
Flask-Caching = "^2.0.2"
|
Flask-Caching = "^2.0.2"
|
||||||
Flask-Assets = "^2.0"
|
Flask-Assets = "^2.0"
|
||||||
Flask-Login = "^0.6.2"
|
Flask-Login = "^0.6.2"
|
||||||
SQLAlchemy = "^2.0.3"
|
|
||||||
python-dotenv = "^0.21.0"
|
python-dotenv = "^0.21.0"
|
||||||
gunicorn = "^20.1.0"
|
gunicorn = "^20.1.0"
|
||||||
pyyaml = "^6.0"
|
pyyaml = "^6.0"
|
||||||
libsass = "^0.22.0"
|
|
||||||
colorthief = "^0.2.1"
|
colorthief = "^0.2.1"
|
||||||
Pillow = "^9.4.0"
|
Pillow = "^9.4.0"
|
||||||
platformdirs = "^3.0.0"
|
platformdirs = "^3.0.0"
|
||||||
pylint = "^2.16.3"
|
pylint = "^2.16.3"
|
||||||
|
libsass = "^0.22.0"
|
||||||
jsmin = "^3.0.1"
|
jsmin = "^3.0.1"
|
||||||
cssmin = "^0.2.0"
|
cssmin = "^0.2.0"
|
||||||
|
|
||||||
|
|
2
run.py
2
run.py
|
@ -15,7 +15,7 @@ print(
|
||||||
#+# #+# #+# #+#+# #+# #+# #+# #+# #+# #+# #+# #+#
|
#+# #+# #+# #+#+# #+# #+# #+# #+# #+# #+# #+# #+#
|
||||||
######## ### #### ########## ### ########## ######### ######### ########
|
######## ### #### ########## ### ########## ######### ######### ########
|
||||||
|
|
||||||
Created by Fluffy Bean - Version 23.04.08
|
Created by Fluffy Bean - Version 23.04.10
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue