BIN
.github/images/group-mobile.png
vendored
Normal file
After Width: | Height: | Size: 592 KiB |
BIN
.github/images/group.png
vendored
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
.github/images/homepage.png
vendored
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.2 MiB |
BIN
.github/images/imageview.png
vendored
Before Width: | Height: | Size: 1.4 MiB |
BIN
.github/images/photo-mobile.png
vendored
Normal file
After Width: | Height: | Size: 353 KiB |
BIN
.github/images/photo.png
vendored
Normal file
After Width: | Height: | Size: 1.6 MiB |
22
README.md
|
@ -32,13 +32,27 @@
|
|||
|
||||
And many more planned things!
|
||||
|
||||
## screenshots
|
||||
<h2>Screenshots</h2>
|
||||
|
||||
<details><summary>Home Screen</summary>
|
||||
|
||||
Home-screen
|
||||
![screenshot](.github/images/homepage.png)
|
||||
|
||||
Image view
|
||||
![screenshot](.github/images/imageview.png)
|
||||
</details>
|
||||
|
||||
<details><summary>Photo View</summary>
|
||||
|
||||
![screenshot](.github/images/photo.png)
|
||||
![screenshot](.github/images/photo-mobile.png)
|
||||
|
||||
</details>
|
||||
<details><summary>Photo Group</summary>
|
||||
|
||||
![screenshot](.github/images/group.png)
|
||||
![screenshot](.github/images/group-mobile.png)
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import logging
|
|||
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 werkzeug.exceptions import HTTPException
|
||||
|
||||
|
@ -19,35 +20,41 @@ import platformdirs
|
|||
from dotenv import load_dotenv
|
||||
from yaml import safe_load
|
||||
|
||||
# Utils
|
||||
from gallery.utils import theme_manager
|
||||
# Import database
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from gallery import db
|
||||
|
||||
|
||||
USER_DIR = platformdirs.user_config_dir('onlylegs')
|
||||
|
||||
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
login_manager = LoginManager()
|
||||
assets = Environment()
|
||||
cache = Cache(config={'CACHE_TYPE': 'SimpleCache', 'CACHE_DEFAULT_TIMEOUT': 300})
|
||||
compress = Compress()
|
||||
|
||||
|
||||
def create_app(test_config=None):
|
||||
"""
|
||||
Create and configure the main app
|
||||
"""
|
||||
app = Flask(__name__, instance_path=os.path.join(USER_DIR, 'instance'))
|
||||
assets = Environment()
|
||||
cache = Cache(config={'CACHE_TYPE': 'SimpleCache', 'CACHE_DEFAULT_TIMEOUT': 300})
|
||||
compress = Compress()
|
||||
|
||||
# Get environment variables
|
||||
load_dotenv(os.path.join(USER_DIR, '.env'))
|
||||
print("Loaded environment variables")
|
||||
|
||||
# Get config file
|
||||
with open(os.path.join(USER_DIR, 'conf.yml'), encoding='utf-8') as file:
|
||||
with open(os.path.join(USER_DIR, 'conf.yml'), encoding='utf-8', mode='r') as file:
|
||||
conf = safe_load(file)
|
||||
print("Loaded gallery config")
|
||||
print("Loaded config")
|
||||
|
||||
# App configuration
|
||||
app.config.from_mapping(
|
||||
SECRET_KEY=os.environ.get('FLASK_SECRET'),
|
||||
DATABASE=os.path.join(app.instance_path, 'gallery.sqlite'),
|
||||
DATABASE=os.path.join(app.instance_path, 'gallery.sqlite3'),
|
||||
UPLOAD_FOLDER=os.path.join(USER_DIR, 'uploads'),
|
||||
ALLOWED_EXTENSIONS=conf['upload']['allowed-extensions'],
|
||||
MAX_CONTENT_LENGTH=1024 * 1024 * conf['upload']['max-size'],
|
||||
|
@ -59,28 +66,54 @@ def create_app(test_config=None):
|
|||
else:
|
||||
app.config.from_mapping(test_config)
|
||||
|
||||
# Load theme
|
||||
theme_manager.compile_theme('default', app.root_path)
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = 'gallery.index'
|
||||
login_manager.session_protection = 'strong'
|
||||
|
||||
# Bundle JS files
|
||||
js_scripts = Bundle('js/*.js', output='gen/packed.js')
|
||||
assets.register('js_all', js_scripts)
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return db_session.query(db.Users).filter_by(alt_id=user_id).first()
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized():
|
||||
error = 401
|
||||
msg = 'You are not authorized to view this page!!!!'
|
||||
return render_template('error.html', error=error, msg=msg), error
|
||||
|
||||
js_pre = Bundle(
|
||||
'js/pre/*.js',
|
||||
output='gen/pre_packed.js',
|
||||
depends='js/pre/*.js'
|
||||
)
|
||||
js_post = Bundle(
|
||||
'js/post/*.js',
|
||||
output='gen/post_packed.js',
|
||||
depends='js/post/*.js'
|
||||
)
|
||||
styles = Bundle(
|
||||
'sass/*.sass',
|
||||
filters='libsass',
|
||||
output='gen/styles.css',
|
||||
depends='sass/**/*.sass'
|
||||
)
|
||||
|
||||
assets.register('js_pre', js_pre)
|
||||
assets.register('js_post', js_post)
|
||||
assets.register('styles', styles)
|
||||
|
||||
# Error handlers, if the error is not a HTTP error, return 500
|
||||
@app.errorhandler(Exception)
|
||||
def error_page(err): # noqa
|
||||
if not isinstance(err, HTTPException):
|
||||
abort(500)
|
||||
return render_template('error.html',
|
||||
error=err.code,
|
||||
msg=err.description), err.code
|
||||
return render_template('error.html', error=err.code, msg=err.description), err.code
|
||||
|
||||
# Load login, registration and logout manager
|
||||
from gallery import auth
|
||||
app.register_blueprint(auth.blueprint)
|
||||
|
||||
# Load the different routes
|
||||
from gallery.routes import api, groups, routing, settings
|
||||
from gallery.views import api, groups, routing, settings
|
||||
app.register_blueprint(api.blueprint)
|
||||
app.register_blueprint(groups.blueprint)
|
||||
app.register_blueprint(routing.blueprint)
|
||||
|
|
138
gallery/auth.py
|
@ -3,17 +3,14 @@ OnlyLegs - Authentication
|
|||
User registration, login and logout and locking access to pages behind a login
|
||||
"""
|
||||
import re
|
||||
import uuid
|
||||
import logging
|
||||
from datetime import datetime as dt
|
||||
|
||||
import functools
|
||||
from flask import Blueprint, flash, g, redirect, request, session, url_for, abort, jsonify
|
||||
from flask import Blueprint, flash, redirect, request, url_for, abort, jsonify
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import exc
|
||||
from flask_login import login_user, logout_user, login_required
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from gallery import db
|
||||
|
||||
|
||||
|
@ -22,42 +19,31 @@ db_session = sessionmaker(bind=db.engine)
|
|||
db_session = db_session()
|
||||
|
||||
|
||||
def login_required(view):
|
||||
@blueprint.route('/login', methods=['POST'])
|
||||
def login():
|
||||
"""
|
||||
Decorator to check if a user is logged in before accessing a page
|
||||
Log in a registered user by adding the user id to the session
|
||||
"""
|
||||
@functools.wraps(view)
|
||||
def wrapped_view(**kwargs):
|
||||
if g.user is None or session.get('uuid') is None:
|
||||
logging.error('Authentication failed')
|
||||
session.clear()
|
||||
return redirect(url_for('gallery.index'))
|
||||
error = []
|
||||
|
||||
return view(**kwargs)
|
||||
username = request.form['username'].strip()
|
||||
password = request.form['password'].strip()
|
||||
remember = bool(request.form['remember-me'])
|
||||
|
||||
return wrapped_view
|
||||
user = db_session.query(db.Users).filter_by(username=username).first()
|
||||
|
||||
if not user or not check_password_hash(user.password, password):
|
||||
logging.error('Login attempt from %s', request.remote_addr)
|
||||
error.append('Username or Password is incorrect!')
|
||||
|
||||
@blueprint.before_app_request
|
||||
def load_logged_in_user():
|
||||
"""
|
||||
Runs before every request and checks if a user is logged in
|
||||
"""
|
||||
user_id = session.get('user_id')
|
||||
user_uuid = session.get('uuid')
|
||||
if error:
|
||||
abort(403)
|
||||
|
||||
if user_id is None or user_uuid is None:
|
||||
g.user = None
|
||||
session.clear()
|
||||
else:
|
||||
is_alive = db_session.query(db.Sessions).filter_by(session_uuid=user_uuid).first()
|
||||
login_user(user, remember=remember)
|
||||
|
||||
if is_alive is None:
|
||||
logging.info('Session expired')
|
||||
flash(['Session expired!', '3'])
|
||||
session.clear()
|
||||
else:
|
||||
g.user = db_session.query(db.Users).filter_by(id=user_id).first()
|
||||
logging.info('User %s logged in from %s', username, request.remote_addr)
|
||||
flash(['Logged in successfully!', '4'])
|
||||
return 'ok', 200
|
||||
|
||||
|
||||
@blueprint.route('/register', methods=['POST'])
|
||||
|
@ -65,17 +51,18 @@ def register():
|
|||
"""
|
||||
Register a new user
|
||||
"""
|
||||
error = []
|
||||
|
||||
# Thanks Fennec for reminding me to strip out the whitespace lol
|
||||
username = request.form['username'].strip()
|
||||
email = request.form['email'].strip()
|
||||
password = request.form['password'].strip()
|
||||
password_repeat = request.form['password-repeat'].strip()
|
||||
|
||||
error = []
|
||||
|
||||
email_regex = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
|
||||
username_regex = re.compile(r'\b[A-Za-z0-9._-]+\b')
|
||||
|
||||
# Validate the form
|
||||
if not username or not username_regex.match(username):
|
||||
error.append('Username is invalid!')
|
||||
|
||||
|
@ -92,77 +79,30 @@ def register():
|
|||
elif password_repeat != password:
|
||||
error.append('Passwords do not match!')
|
||||
|
||||
if error:
|
||||
return jsonify(error)
|
||||
user_exists = db_session.query(db.Users).filter_by(username=username).first()
|
||||
if user_exists:
|
||||
error.append('User already exists!')
|
||||
|
||||
try:
|
||||
register_user = db.Users(username=username,
|
||||
email=email,
|
||||
password=generate_password_hash(password),
|
||||
created_at=dt.utcnow())
|
||||
db_session.add(register_user)
|
||||
db_session.commit()
|
||||
except exc.IntegrityError:
|
||||
return f'User {username} is already registered!'
|
||||
except Exception as err:
|
||||
logging.error('User %s could not be registered: %s', username, err)
|
||||
return 'Something went wrong!'
|
||||
# If there are errors, return them
|
||||
if error:
|
||||
print(error)
|
||||
return jsonify(error), 400
|
||||
|
||||
register_user = db.Users(username=username, email=email,
|
||||
password=generate_password_hash(password, method='sha256'))
|
||||
db_session.add(register_user)
|
||||
db_session.commit()
|
||||
|
||||
logging.info('User %s registered', username)
|
||||
return 'gwa gwa'
|
||||
|
||||
|
||||
@blueprint.route('/login', methods=['POST'])
|
||||
def login():
|
||||
"""
|
||||
Log in a registered user by adding the user id to the session
|
||||
"""
|
||||
username = request.form['username'].strip()
|
||||
password = request.form['password'].strip()
|
||||
|
||||
user = db_session.query(db.Users).filter_by(username=username).first()
|
||||
error = []
|
||||
|
||||
if user is None:
|
||||
logging.error('User %s does not exist. Login attempt from %s',
|
||||
username, request.remote_addr)
|
||||
error.append('Username or Password is incorrect!')
|
||||
elif not check_password_hash(user.password, password):
|
||||
logging.error('User %s entered wrong password. Login attempt from %s',
|
||||
username, request.remote_addr)
|
||||
error.append('Username or Password is incorrect!')
|
||||
|
||||
if error:
|
||||
abort(403)
|
||||
|
||||
try:
|
||||
session.clear()
|
||||
session['user_id'] = user.id
|
||||
session['uuid'] = str(uuid.uuid4())
|
||||
|
||||
session_query = db.Sessions(user_id=user.id,
|
||||
session_uuid=session.get('uuid'),
|
||||
ip_address=request.remote_addr,
|
||||
user_agent=request.user_agent.string,
|
||||
active=True,
|
||||
created_at=dt.utcnow())
|
||||
|
||||
db_session.add(session_query)
|
||||
db_session.commit()
|
||||
except Exception as err:
|
||||
logging.error('User %s could not be logged in: %s', username, err)
|
||||
abort(500)
|
||||
|
||||
logging.info('User %s logged in from %s', username, request.remote_addr)
|
||||
flash(['Logged in successfully!', '4'])
|
||||
return 'gwa gwa'
|
||||
return 'ok', 200
|
||||
|
||||
|
||||
@blueprint.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
"""
|
||||
Clear the current session, including the stored user id
|
||||
"""
|
||||
logging.info('User (%s) %s logged out', session.get('user_id'), g.user.username)
|
||||
session.clear()
|
||||
logout_user()
|
||||
flash(['Goodbye!!!', '4'])
|
||||
return redirect(url_for('gallery.index'))
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
"""
|
||||
OnlyLegs - Database models and functions for SQLAlchemy
|
||||
OnlyLegs - Database models and ions for SQLAlchemy
|
||||
"""
|
||||
from uuid import uuid4
|
||||
import os
|
||||
import platformdirs
|
||||
|
||||
from sqlalchemy import (
|
||||
create_engine, Column, Integer, String, Boolean, DateTime, ForeignKey, PickleType)
|
||||
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, 'gallery.sqlite')
|
||||
DB_PATH = os.path.join(USER_DIR, 'instance', 'gallery.sqlite3')
|
||||
|
||||
|
||||
# In the future, I want to add support for other databases
|
||||
|
@ -18,24 +20,29 @@ engine = create_engine(f'sqlite:///{DB_PATH}', echo=False)
|
|||
base = declarative_base()
|
||||
|
||||
|
||||
class Users (base): # pylint: disable=too-few-public-methods, C0103
|
||||
class Users (base, UserMixin): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
User table
|
||||
Joins with post, groups, session and log
|
||||
"""
|
||||
__tablename__ = 'users'
|
||||
|
||||
# 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)
|
||||
created_at = Column(DateTime, 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')
|
||||
session = relationship('Sessions', 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
|
||||
"""
|
||||
|
@ -46,20 +53,16 @@ class Posts (base): # pylint: disable=too-few-public-methods, C0103
|
|||
|
||||
id = Column(Integer, primary_key=True)
|
||||
author_id = Column(Integer, ForeignKey('users.id'))
|
||||
created_at = Column(DateTime, nullable=False)
|
||||
|
||||
file_name = Column(String, unique=True, nullable=False)
|
||||
file_type = Column(String, nullable=False)
|
||||
|
||||
image_exif = Column(PickleType, nullable=False)
|
||||
image_colours = Column(PickleType, nullable=False)
|
||||
|
||||
post_description = Column(String, nullable=False)
|
||||
post_alt = Column(String, nullable=False)
|
||||
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
|
||||
|
@ -71,7 +74,7 @@ class Groups (base): # pylint: disable=too-few-public-methods, C0103
|
|||
name = Column(String, nullable=False)
|
||||
description = Column(String, nullable=False)
|
||||
author_id = Column(Integer, ForeignKey('users.id'))
|
||||
created_at = Column(DateTime, nullable=False)
|
||||
created_at = Column(DateTime, nullable=False, server_default=func.now()) # pylint: disable=E1102
|
||||
|
||||
junction = relationship('GroupJunction', backref='groups')
|
||||
|
||||
|
@ -84,27 +87,11 @@ class GroupJunction (base): # pylint: disable=too-few-public-methods, C0103
|
|||
__tablename__ = 'group_junction'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
date_added = Column(DateTime, nullable=False)
|
||||
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 Sessions (base): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
Session table
|
||||
Joins with user
|
||||
"""
|
||||
__tablename__ = 'sessions'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey('users.id'))
|
||||
session_uuid = Column(String, nullable=False)
|
||||
ip_address = Column(String, nullable=False)
|
||||
user_agent = Column(String, nullable=False)
|
||||
active = Column(Boolean, nullable=False)
|
||||
created_at = Column(DateTime, nullable=False)
|
||||
|
||||
|
||||
class Logs (base): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
Log table
|
||||
|
@ -116,8 +103,8 @@ class Logs (base): # pylint: disable=too-few-public-methods, C0103
|
|||
user_id = Column(Integer, ForeignKey('users.id'))
|
||||
ip_address = Column(String, nullable=False)
|
||||
code = Column(Integer, nullable=False)
|
||||
msg = Column(String, nullable=False)
|
||||
created_at = Column(DateTime, 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
|
||||
|
@ -129,8 +116,8 @@ class Bans (base): # pylint: disable=too-few-public-methods, C0103
|
|||
id = Column(Integer, primary_key=True)
|
||||
ip_address = Column(String, nullable=False)
|
||||
code = Column(Integer, nullable=False)
|
||||
msg = Column(String, nullable=False)
|
||||
created_at = Column(DateTime, 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
|
||||
|
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 29 KiB |
2
gallery/static/js/jquery-3.6.3.min.js
vendored
|
@ -1,25 +0,0 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let labels = document.querySelectorAll('[data-label]');
|
||||
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
labels[i].addEventListener('mouseover', function() {
|
||||
let label = document.createElement('div');
|
||||
label.classList.add('label');
|
||||
label.innerHTML = this.dataset.label;
|
||||
|
||||
document.body.appendChild(label);
|
||||
|
||||
label.style.left = (this.offsetLeft + this.offsetWidth + 8) + 'px';
|
||||
label.style.top = (this.offsetTop + (label.offsetHeight / 2) - 2) + 'px';
|
||||
|
||||
setTimeout(function() {
|
||||
label.style.opacity = 1;
|
||||
}, 250);
|
||||
});
|
||||
|
||||
labels[i].addEventListener('mouseout', function() {
|
||||
let label = document.querySelector('.label');
|
||||
label.parentNode.removeChild(label);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -30,8 +30,24 @@ function showLogin() {
|
|||
passwordInput.placeholder = 'Passywassy';
|
||||
passwordInput.id = 'password';
|
||||
|
||||
// Container for remember me checkbox
|
||||
rememberMeSpan = document.createElement('span');
|
||||
rememberMeSpan.classList.add('input-checkbox');
|
||||
|
||||
rememberMeInput = document.createElement('input');
|
||||
rememberMeInput.type = 'checkbox';
|
||||
rememberMeInput.id = 'remember-me';
|
||||
|
||||
rememberMeLabel = document.createElement('label');
|
||||
rememberMeLabel.innerHTML = 'No forgetty me pls';
|
||||
rememberMeLabel.setAttribute('for', 'remember-me');
|
||||
|
||||
rememberMeSpan.appendChild(rememberMeInput);
|
||||
rememberMeSpan.appendChild(rememberMeLabel);
|
||||
|
||||
loginForm.appendChild(usernameInput);
|
||||
loginForm.appendChild(passwordInput);
|
||||
loginForm.appendChild(rememberMeSpan);
|
||||
|
||||
popUpShow(
|
||||
'Login!',
|
||||
|
@ -47,6 +63,7 @@ function login(event) {
|
|||
|
||||
let formUsername = document.querySelector("#username").value;
|
||||
let formPassword = document.querySelector("#password").value;
|
||||
let formRememberMe = document.querySelector("#remember-me").checked;
|
||||
|
||||
if (formUsername === "" || formPassword === "") {
|
||||
addNotification("Please fill in all fields!!!!", 3);
|
||||
|
@ -54,27 +71,24 @@ function login(event) {
|
|||
}
|
||||
|
||||
// Make form
|
||||
var formData = new FormData();
|
||||
const formData = new FormData();
|
||||
formData.append("username", formUsername);
|
||||
formData.append("password", formPassword);
|
||||
formData.append("remember-me", formRememberMe);
|
||||
|
||||
fetch('/auth/login', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => {
|
||||
if (response.status === 200) {
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
switch (response.status) {
|
||||
case 500:
|
||||
addNotification('Server exploded, F\'s in chat', 2);
|
||||
break;
|
||||
case 403:
|
||||
addNotification('None but devils play past here... Wrong information', 2);
|
||||
break;
|
||||
default:
|
||||
addNotification('Error logging in, blame someone', 2);
|
||||
break;
|
||||
if (response.status === 403) {
|
||||
addNotification('None but devils play past here... Wrong information', 2);
|
||||
} else if (response.status === 500) {
|
||||
addNotification('Server exploded, F\'s in chat', 2);
|
||||
} else {
|
||||
addNotification('Error logging in, blame someone', 2);
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
|
@ -129,7 +143,7 @@ function showRegister() {
|
|||
registerForm.appendChild(emailInput);
|
||||
registerForm.appendChild(passwordInput);
|
||||
registerForm.appendChild(passwordInputRepeat);
|
||||
|
||||
|
||||
popUpShow(
|
||||
'Who are you?',
|
||||
'Already have an account? <span class="link" onclick="showLogin()">Login!</span>',
|
||||
|
@ -153,37 +167,33 @@ function register(event) {
|
|||
}
|
||||
|
||||
// Make form
|
||||
var formData = new FormData();
|
||||
const formData = new FormData();
|
||||
formData.append("username", formUsername);
|
||||
formData.append("email", formEmail);
|
||||
formData.append("password", formPassword);
|
||||
formData.append("password-repeat", formPasswordRepeat);
|
||||
|
||||
// Send form to server
|
||||
fetch('/auth/login', {
|
||||
fetch('/auth/register', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => {
|
||||
if (response.status === 200) {
|
||||
if (response === "gwa gwa") {
|
||||
addNotification('Registered successfully! Now please login to continue', 1);
|
||||
showLogin();
|
||||
} else {
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
addNotification(response[i], 2);
|
||||
}
|
||||
}
|
||||
if (response.ok) {
|
||||
addNotification('Registered successfully! Now please login to continue', 1);
|
||||
showLogin();
|
||||
} else {
|
||||
switch (response.status) {
|
||||
case 500:
|
||||
addNotification('Server exploded, F\'s in chat', 2);
|
||||
break;
|
||||
case 403:
|
||||
addNotification('None but devils play past here... Wrong information', 2);
|
||||
break;
|
||||
default:
|
||||
addNotification('Error logging in, blame someone', 2);
|
||||
break;
|
||||
if (response.status === 400) {
|
||||
response.json().then(data => {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
addNotification(data[i], 2);
|
||||
}
|
||||
});
|
||||
} else if (response.status === 403) {
|
||||
addNotification('None but devils play past here... Wrong information', 2);
|
||||
} else if (response.status === 500) {
|
||||
addNotification('Server exploded, F\'s in chat', 2);
|
||||
} else {
|
||||
addNotification('Error logging in, blame someone', 2);
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
|
@ -141,38 +141,41 @@ function clearUpload() {
|
|||
}
|
||||
|
||||
|
||||
function createJob(file) {
|
||||
jobContainer = document.createElement("div");
|
||||
jobContainer.classList.add("job");
|
||||
// function createJob(file) {
|
||||
// jobContainer = document.createElement("div");
|
||||
// jobContainer.classList.add("job");
|
||||
|
||||
jobStatus = document.createElement("span");
|
||||
jobStatus.classList.add("job__status");
|
||||
jobStatus.innerHTML = "Uploading...";
|
||||
// jobStatus = document.createElement("span");
|
||||
// jobStatus.classList.add("job__status");
|
||||
// jobStatus.innerHTML = "Uploading...";
|
||||
|
||||
jobProgress = document.createElement("span");
|
||||
jobProgress.classList.add("progress");
|
||||
// jobProgress = document.createElement("span");
|
||||
// jobProgress.classList.add("progress");
|
||||
|
||||
jobImg = document.createElement("img");
|
||||
jobImg.src = URL.createObjectURL(file);
|
||||
// jobImg = document.createElement("img");
|
||||
// jobImg.src = URL.createObjectURL(file);
|
||||
|
||||
jobImgFilter = document.createElement("span");
|
||||
jobImgFilter.classList.add("img-filter");
|
||||
// jobImgFilter = document.createElement("span");
|
||||
// jobImgFilter.classList.add("img-filter");
|
||||
|
||||
jobContainer.appendChild(jobStatus);
|
||||
jobContainer.appendChild(jobProgress);
|
||||
jobContainer.appendChild(jobImg);
|
||||
jobContainer.appendChild(jobImgFilter);
|
||||
// jobContainer.appendChild(jobStatus);
|
||||
// jobContainer.appendChild(jobProgress);
|
||||
// jobContainer.appendChild(jobImg);
|
||||
// jobContainer.appendChild(jobImgFilter);
|
||||
|
||||
return jobContainer;
|
||||
}
|
||||
// return jobContainer;
|
||||
// }
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Function to upload images
|
||||
let uploadTab = document.querySelector(".upload-panel");
|
||||
|
||||
if (!uploadTab) { return; } // If upload tab doesn't exist, don't run this code :3
|
||||
|
||||
let uploadTabDrag = uploadTab.querySelector("#dragIndicator");
|
||||
let uploadForm = uploadTab.querySelector('#uploadForm');
|
||||
let jobList = document.querySelector(".upload-jobs");
|
||||
// let jobList = document.querySelector(".upload-jobs");
|
||||
|
||||
let fileDrop = uploadForm.querySelector('.fileDrop-block');
|
||||
let fileDropTitle = fileDrop.querySelector('.status');
|
||||
|
@ -225,53 +228,82 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
formData.append("description", fileDescription.value);
|
||||
formData.append("tags", fileTags.value);
|
||||
|
||||
jobItem = createJob(fileUpload.files[0]);
|
||||
jobStatus = jobItem.querySelector(".job__status");
|
||||
// jobItem = createJob(fileUpload.files[0]);
|
||||
// jobStatus = jobItem.querySelector(".job__status");
|
||||
|
||||
// Upload the information
|
||||
$.ajax({
|
||||
url: '/api/upload',
|
||||
type: 'post',
|
||||
data: formData,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
beforeSend: function () {
|
||||
// Add job to list
|
||||
jobList.appendChild(jobItem);
|
||||
},
|
||||
success: function (response) {
|
||||
jobItem.classList.add("success");
|
||||
jobStatus.innerHTML = "Uploaded successfully";
|
||||
if (!document.querySelector(".upload-panel").classList.contains("open")) {
|
||||
addNotification("Image uploaded successfully", 1);
|
||||
}
|
||||
},
|
||||
error: function (response) {
|
||||
jobItem.classList.add("critical");
|
||||
switch (response.status) {
|
||||
case 500:
|
||||
jobStatus.innerHTML = "Server exploded, F's in chat";
|
||||
break;
|
||||
case 400:
|
||||
case 404:
|
||||
jobStatus.innerHTML = "Error uploading. Blame yourself";
|
||||
break;
|
||||
case 403:
|
||||
jobStatus.innerHTML = "None but devils play past here...";
|
||||
break;
|
||||
case 413:
|
||||
jobStatus.innerHTML = "File too large!!!!!!";
|
||||
break;
|
||||
default:
|
||||
jobStatus.innerHTML = "Error uploading file, blame someone";
|
||||
break;
|
||||
}
|
||||
if (!document.querySelector(".upload-panel").classList.contains("open")) {
|
||||
addNotification("Error uploading file", 2);
|
||||
}
|
||||
},
|
||||
// $.ajax({
|
||||
// url: '/api/upload',
|
||||
// type: 'post',
|
||||
// data: formData,
|
||||
// contentType: false,
|
||||
// processData: false,
|
||||
// beforeSend: function () {
|
||||
// // Add job to list
|
||||
// jobList.appendChild(jobItem);
|
||||
// },
|
||||
// success: function (response) {
|
||||
// jobItem.classList.add("success");
|
||||
// jobStatus.innerHTML = "Uploaded successfully";
|
||||
// if (!document.querySelector(".upload-panel").classList.contains("open")) {
|
||||
// addNotification("Image uploaded successfully", 1);
|
||||
// }
|
||||
// },
|
||||
// error: function (response) {
|
||||
// jobItem.classList.add("critical");
|
||||
// switch (response.status) {
|
||||
// case 500:
|
||||
// jobStatus.innerHTML = "Server exploded, F's in chat";
|
||||
// break;
|
||||
// case 400:
|
||||
// case 404:
|
||||
// jobStatus.innerHTML = "Error uploading. Blame yourself";
|
||||
// break;
|
||||
// case 403:
|
||||
// jobStatus.innerHTML = "None but devils play past here...";
|
||||
// break;
|
||||
// case 413:
|
||||
// jobStatus.innerHTML = "File too large!!!!!!";
|
||||
// break;
|
||||
// default:
|
||||
// jobStatus.innerHTML = "Error uploading file, blame someone";
|
||||
// break;
|
||||
// }
|
||||
// if (!document.querySelector(".upload-panel").classList.contains("open")) {
|
||||
// addNotification("Error uploading file", 2);
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
|
||||
|
||||
fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
// .then(response => response.json())
|
||||
.then(data => { addNotification("Image uploaded successfully", 1); })
|
||||
.catch(error => {
|
||||
switch (response.status) {
|
||||
case 500:
|
||||
addNotification("Server exploded, F's in chat", 2)
|
||||
break;
|
||||
case 400:
|
||||
case 404:
|
||||
addNotification("Error uploading. Blame yourself", 2)
|
||||
break;
|
||||
case 403:
|
||||
addNotification("None but devils play past here...", 2)
|
||||
break;
|
||||
case 413:
|
||||
addNotification("File too large!!!!!!", 2);
|
||||
break;
|
||||
default:
|
||||
addNotification("Error uploading file, blame someone", 2)
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
clearUpload();
|
||||
|
||||
// Reset drop
|
|
@ -1,16 +1,27 @@
|
|||
let webpSupport = false;
|
||||
try {
|
||||
new Image().src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=';
|
||||
webpSupport = true;
|
||||
} catch (e) {
|
||||
webpSupport = false;
|
||||
}
|
||||
|
||||
// fade in images
|
||||
function imgFade(obj, time = 250) {
|
||||
$(obj).animate({ opacity: 1 }, time);
|
||||
obj.style.transition = `opacity ${time}ms`;
|
||||
obj.style.opacity = 1;
|
||||
}
|
||||
// Lazy load images when they are in view
|
||||
function loadOnView() {
|
||||
let lazyLoad = document.querySelectorAll('#lazy-load');
|
||||
const lazyLoad = document.querySelectorAll('#lazy-load');
|
||||
|
||||
for (let i = 0; i < lazyLoad.length; i++) {
|
||||
let image = lazyLoad[i];
|
||||
if (image.getBoundingClientRect().top < window.innerHeight && image.getBoundingClientRect().bottom > 0) {
|
||||
if (!image.src) {
|
||||
image.src = `/api/file/${image.getAttribute('data-src')}?r=thumb` // e=webp
|
||||
if (!image.src && webpSupport) {
|
||||
image.src = image.getAttribute('data-src') + '&e=webp'
|
||||
} else if (!image.src) {
|
||||
image.src = image.getAttribute('data-src')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +75,7 @@ window.onload = function () {
|
|||
'Using <a href="https://phosphoricons.com/">Phosphoricons</a> and ' +
|
||||
'<a href="https://www.gent.media/manrope">Manrope</a> <br>' +
|
||||
'Made by Fluffy and others with ❤️ <br>' +
|
||||
'<a href="https://github.com/Fluffy-Bean/onlylegs">V23.03.30</a>');
|
||||
'<a href="https://github.com/Fluffy-Bean/onlylegs">V23.04.05</a>');
|
||||
}
|
||||
}
|
||||
};
|
|
@ -25,13 +25,13 @@ function addNotification(notificationText, notificationLevel) {
|
|||
iconElement.classList.add('sniffle__notification-icon');
|
||||
notification.appendChild(iconElement);
|
||||
// Set the icon based on the notification level, not pretty but it works :3
|
||||
if (notificationLevel == 1) {
|
||||
if (notificationLevel === 1) {
|
||||
notification.classList.add('success');
|
||||
iconElement.innerHTML = successIcon;
|
||||
} else if (notificationLevel == 2) {
|
||||
} else if (notificationLevel === 2) {
|
||||
notification.classList.add('critical');
|
||||
iconElement.innerHTML = criticalIcon;
|
||||
} else if (notificationLevel == 3) {
|
||||
} else if (notificationLevel === 3) {
|
||||
notification.classList.add('warning');
|
||||
iconElement.innerHTML = warningIcon;
|
||||
} else {
|
181
gallery/static/sass/components/banner.sass
Normal file
|
@ -0,0 +1,181 @@
|
|||
.banner,
|
||||
.banner-small
|
||||
width: 100%
|
||||
position: relative
|
||||
color: RGB($fg-white)
|
||||
|
||||
&::after
|
||||
content: ''
|
||||
|
||||
width: $rad
|
||||
height: calc(#{$rad} * 2)
|
||||
|
||||
position: absolute
|
||||
bottom: calc(#{$rad} * -2)
|
||||
left: 0
|
||||
|
||||
background-color: RGB($bg-bright)
|
||||
border-radius: $rad 0 0 0
|
||||
box-shadow: 0 calc(#{$rad} * -1) 0 0 RGB($bg-100)
|
||||
|
||||
.banner
|
||||
height: 30rem
|
||||
background-color: RGB($bg-300)
|
||||
|
||||
img
|
||||
position: absolute
|
||||
inset: 0
|
||||
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
background-color: inherit
|
||||
|
||||
object-fit: cover
|
||||
object-position: center center
|
||||
|
||||
.banner-filter
|
||||
position: absolute
|
||||
inset: 0
|
||||
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
background: linear-gradient(to right, RGB($primary), transparent)
|
||||
|
||||
z-index: +1
|
||||
|
||||
.banner-content
|
||||
padding: 0.5rem
|
||||
|
||||
width: 100%
|
||||
height: auto
|
||||
|
||||
position: absolute
|
||||
left: 0
|
||||
bottom: 0
|
||||
|
||||
display: grid
|
||||
grid-template-columns: 1fr auto
|
||||
grid-template-rows: 1fr auto auto
|
||||
grid-template-areas: 'info info' 'header header' 'subtitle options'
|
||||
gap: 0.5rem
|
||||
|
||||
z-index: +2
|
||||
|
||||
.banner-header,
|
||||
.banner-info,
|
||||
.banner-subtitle
|
||||
margin: 0
|
||||
padding: 0
|
||||
width: 100%
|
||||
|
||||
.banner-header
|
||||
grid-area: header
|
||||
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
overflow: hidden
|
||||
text-align: left
|
||||
font-size: 6.9rem
|
||||
font-weight: 800
|
||||
|
||||
color: RGB($primary)
|
||||
|
||||
.banner-info
|
||||
grid-area: info
|
||||
font-size: 1rem
|
||||
font-weight: 600
|
||||
.banner-subtitle
|
||||
grid-area: subtitle
|
||||
font-size: 1rem
|
||||
font-weight: 600
|
||||
|
||||
.pill-row
|
||||
margin-top: auto
|
||||
grid-area: options
|
||||
|
||||
.banner-small
|
||||
height: 3.5rem
|
||||
background-color: RGB($bg-100)
|
||||
|
||||
.banner-content
|
||||
padding: 0.5rem
|
||||
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
position: absolute
|
||||
inset: 0
|
||||
|
||||
display: flex
|
||||
flex-direction: row
|
||||
justify-content: flex-start
|
||||
gap: 1rem
|
||||
|
||||
z-index: +2
|
||||
|
||||
.banner-header,
|
||||
.banner-info
|
||||
margin: auto 0
|
||||
padding: 0
|
||||
width: auto
|
||||
height: auto
|
||||
justify-self: flex-start
|
||||
|
||||
.banner-header
|
||||
padding-bottom: 0.25rem
|
||||
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
overflow: hidden
|
||||
text-align: left
|
||||
font-weight: 800
|
||||
font-size: 1.5rem
|
||||
|
||||
color: RGB($primary)
|
||||
|
||||
.banner-info
|
||||
font-size: 0.9rem
|
||||
font-weight: 600
|
||||
|
||||
.pill-row
|
||||
margin-left: auto
|
||||
width: auto
|
||||
|
||||
@media (max-width: $breakpoint)
|
||||
.banner,
|
||||
.banner-small
|
||||
&::after
|
||||
display: none
|
||||
|
||||
.banner
|
||||
min-height: 17rem
|
||||
height: auto
|
||||
|
||||
.banner-content
|
||||
padding: 0.5rem
|
||||
height: 100%
|
||||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: center
|
||||
align-items: center
|
||||
gap: 0.25rem
|
||||
|
||||
.banner-header
|
||||
font-size: 3rem
|
||||
text-align: center
|
||||
|
||||
.banner-info,
|
||||
.banner-subtitle
|
||||
font-size: 1.1rem
|
||||
text-align: center
|
||||
|
||||
.pill-row
|
||||
margin-top: 1rem
|
||||
|
||||
.banner-small
|
||||
.banner-content
|
||||
.banner-info
|
||||
display: none
|
|
@ -57,6 +57,21 @@
|
|||
&.black
|
||||
@include btn-block($black)
|
||||
|
||||
.input-checkbox
|
||||
padding: 0
|
||||
display: flex
|
||||
justify-content: flex-start
|
||||
align-items: center
|
||||
gap: 0.5rem
|
||||
|
||||
position: relative
|
||||
|
||||
label
|
||||
font-size: 1rem
|
||||
font-weight: 600
|
||||
text-align: left
|
||||
|
||||
color: RGB($fg-white)
|
||||
|
||||
.input-block
|
||||
padding: 0.5rem 1rem
|
||||
|
@ -122,6 +137,7 @@
|
|||
border-radius: $rad-inner
|
||||
|
||||
cursor: pointer
|
||||
overflow: hidden
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out
|
||||
|
||||
input
|
||||
|
@ -130,6 +146,13 @@
|
|||
opacity: 0
|
||||
cursor: pointer
|
||||
|
||||
.status
|
||||
width: 100%
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
text-align: center
|
||||
overflow: hidden
|
||||
|
||||
&:hover
|
||||
background-color: RGBA($white, 0.2)
|
||||
color: RGB($white)
|
|
@ -9,6 +9,7 @@
|
|||
height: 100vh
|
||||
|
||||
background-color: transparent
|
||||
color: RGB($fg-white)
|
||||
|
||||
overflow: hidden
|
||||
z-index: 68
|
||||
|
@ -20,9 +21,6 @@
|
|||
|
||||
font-size: 1.5rem
|
||||
font-weight: 700
|
||||
|
||||
color: RGB($primary)
|
||||
|
||||
p
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
@ -30,8 +28,6 @@
|
|||
font-size: 1rem
|
||||
font-weight: 500
|
||||
|
||||
color: RGB($fg-white)
|
||||
|
||||
form
|
||||
margin: 0
|
||||
padding: 0
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
position: relative
|
||||
|
||||
border-radius: $rad
|
||||
border-radius: $rad-inner
|
||||
|
||||
box-sizing: border-box
|
||||
overflow: hidden
|
||||
|
@ -26,7 +26,8 @@
|
|||
padding: 0.5rem
|
||||
|
||||
width: 100%
|
||||
height: 30%
|
||||
min-height: 30%
|
||||
height: auto
|
||||
|
||||
position: absolute
|
||||
left: 0
|
||||
|
@ -104,7 +105,7 @@
|
|||
|
||||
position: relative
|
||||
|
||||
border-radius: $rad
|
||||
border-radius: $rad-inner
|
||||
|
||||
box-sizing: border-box
|
||||
overflow: hidden
|
||||
|
@ -114,7 +115,8 @@
|
|||
padding: 0.5rem
|
||||
|
||||
width: 100%
|
||||
height: 30%
|
||||
min-height: 30%
|
||||
height: auto
|
||||
|
||||
position: absolute
|
||||
left: 0
|
||||
|
@ -205,7 +207,7 @@
|
|||
transform: scale(0.6) rotate(-5deg) translate(25%, 10%)
|
||||
z-index: +2
|
||||
.data-3
|
||||
transform: scale(0.6) rotate(-1deg) translate(-15%, -25%)
|
||||
transform: scale(0.6) rotate(-1deg) translate(-15%, -23%)
|
||||
z-index: +1
|
||||
|
||||
&:after
|
|
@ -61,12 +61,12 @@
|
|||
width: 2.5rem
|
||||
height: 2.5rem
|
||||
|
||||
color: RGB($fg-white)
|
||||
border-radius: $rad-inner
|
||||
color: RGB($fg-white)
|
||||
|
||||
transition: color 0.2s ease-out, transform 0.2s ease-out
|
||||
|
||||
span
|
||||
.tool-tip
|
||||
margin: 0
|
||||
padding: 0.35rem 0.7rem
|
||||
|
||||
|
@ -89,7 +89,7 @@
|
|||
|
||||
pointer-events: none
|
||||
|
||||
svg
|
||||
> svg
|
||||
margin: 0
|
||||
font-size: 1rem
|
||||
|
||||
|
@ -107,7 +107,7 @@
|
|||
|
||||
&:hover
|
||||
> svg
|
||||
background: RGB($bg-300)
|
||||
background: RGBA($fg-white, 0.1)
|
||||
|
||||
span
|
||||
opacity: 1
|
||||
|
@ -129,7 +129,7 @@
|
|||
height: calc(100% - 6px)
|
||||
|
||||
background-color: RGB($primary)
|
||||
border-radius: 0 $rad-inner $rad-inner 0
|
||||
border-radius: $rad-inner
|
||||
|
||||
@media (max-width: $breakpoint)
|
||||
.navigation
|
||||
|
@ -158,7 +158,7 @@
|
|||
height: 3rem
|
||||
min-height: 3rem
|
||||
|
||||
span
|
||||
.tool-tip
|
||||
display: none
|
||||
|
||||
&.selected::before
|
||||
|
@ -167,6 +167,4 @@
|
|||
left: 0
|
||||
|
||||
width: 100%
|
||||
height: 3px
|
||||
|
||||
border-radius: $rad-inner
|
||||
height: 3px
|
|
@ -8,7 +8,6 @@
|
|||
@import "components/elements/pop-up"
|
||||
@import "components/elements/upload-panel"
|
||||
@import "components/elements/tags"
|
||||
@import "components/elements/labels"
|
||||
|
||||
@import "components/navigation"
|
||||
@import "components/banner"
|
|
@ -74,24 +74,11 @@ $breakpoint: 800px
|
|||
|
||||
--breakpoint: 800px
|
||||
|
||||
|
||||
@font-face
|
||||
font-family: 'Work Sans'
|
||||
src: url('fonts/worksans-regular.woff2')
|
||||
font-weight: 400
|
||||
|
||||
@font-face
|
||||
font-family: 'Work Sans'
|
||||
src: url('fonts/worksans-bold.woff2')
|
||||
font-weight: 600
|
||||
|
||||
@font-face
|
||||
font-family: 'Work Sans'
|
||||
src: url('fonts/worksans-black.woff2')
|
||||
font-weight: 900
|
||||
|
||||
// I have no clue if its webassets or libsass thats doing this shit
|
||||
// But one of them is trying to "correct" the path, and 404-ing the
|
||||
// font, so enjoy this path fuckery
|
||||
@font-face
|
||||
font-family: 'Manrope'
|
||||
src: url('fonts/Manrope[wght].woff2') format('woff2')
|
||||
src: url('../../../../static/fonts/Manrope[wght].woff2') format('woff2')
|
||||
font-style: normal
|
||||
font-display: swap
|
|
@ -1,8 +1,182 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block nav_groups %}selected{% endblock %}
|
||||
{% block head %}
|
||||
{% if images %}
|
||||
<meta name="theme-color" content="rgb({{ images.0.colours.0.0 }}{{ images.0.colours.0.1 }}{{ images.0.colours.0.2 }})"/>
|
||||
{% endif %}
|
||||
|
||||
<script type="text/javascript">
|
||||
function groupShare() {
|
||||
try {
|
||||
navigator.clipboard.writeText(window.location.href)
|
||||
addNotification("Copied link!", 4);
|
||||
} catch (err) {
|
||||
addNotification("Failed to copy link! Are you on HTTP?", 2);
|
||||
}
|
||||
}
|
||||
|
||||
{% if current_user.id == group.author_id %}
|
||||
function groupDelete() {
|
||||
cancelBtn = document.createElement('button');
|
||||
cancelBtn.classList.add('btn-block');
|
||||
cancelBtn.innerHTML = 'AAAAAAAAAA';
|
||||
cancelBtn.onclick = popupDissmiss;
|
||||
|
||||
deleteBtn = document.createElement('button');
|
||||
deleteBtn.classList.add('btn-block');
|
||||
deleteBtn.classList.add('critical');
|
||||
deleteBtn.innerHTML = 'No ragrats!';
|
||||
deleteBtn.onclick = deleteConfirm;
|
||||
|
||||
popUpShow('Yeet!',
|
||||
'Are you surrrre? This action is irreversible and very final.' +
|
||||
' This wont delete the images, but it will remove them from this group.',
|
||||
null,
|
||||
[cancelBtn, deleteBtn]);
|
||||
}
|
||||
|
||||
function deleteConfirm(event) {
|
||||
// AJAX takes control of subby form :3
|
||||
event.preventDefault();
|
||||
|
||||
let formID = {{ group.id }};
|
||||
|
||||
if (!formID) {
|
||||
addNotification("Dont tamper with the JavaScript pls!", 3);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make form
|
||||
const formData = new FormData();
|
||||
formData.append("group", formID);
|
||||
|
||||
fetch('{{ url_for('api.delete_group') }}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => {
|
||||
if (response.status === 200) {
|
||||
// Redirect to groups page
|
||||
window.location.href = '{{ url_for('group.groups') }}';
|
||||
} else {
|
||||
switch (response.status) {
|
||||
case 500:
|
||||
addNotification('Server exploded, F\'s in chat', 2);
|
||||
break;
|
||||
case 403:
|
||||
addNotification('None but devils play past here... Bad information', 2);
|
||||
break;
|
||||
default:
|
||||
addNotification('Error logging in, blame someone', 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
addNotification('Error yeeting group!', 2);
|
||||
});
|
||||
}
|
||||
|
||||
function groupEdit() {
|
||||
// Create elements
|
||||
cancelBtn = document.createElement('button');
|
||||
cancelBtn.classList.add('btn-block');
|
||||
cancelBtn.innerHTML = 'go baaaaack';
|
||||
cancelBtn.onclick = popupDissmiss;
|
||||
|
||||
submitBtn = document.createElement('button');
|
||||
submitBtn.classList.add('btn-block');
|
||||
submitBtn.classList.add('primary');
|
||||
submitBtn.innerHTML = 'Saveeee';
|
||||
submitBtn.type = 'submit';
|
||||
submitBtn.setAttribute('form', 'editForm');
|
||||
|
||||
// Create form
|
||||
editForm = document.createElement('form');
|
||||
editForm.id = 'editForm';
|
||||
editForm.setAttribute('onsubmit', 'return edit(event);');
|
||||
|
||||
groupInput = document.createElement('input');
|
||||
groupInput.classList.add('input-block');
|
||||
groupInput.type = 'text';
|
||||
groupInput.placeholder = 'Group ID';
|
||||
groupInput.value = {{ group.id }};
|
||||
groupInput.id = 'group';
|
||||
|
||||
imageInput = document.createElement('input');
|
||||
imageInput.classList.add('input-block');
|
||||
imageInput.type = 'text';
|
||||
imageInput.placeholder = 'Image ID';
|
||||
imageInput.id = 'image';
|
||||
|
||||
actionInput = document.createElement('input');
|
||||
actionInput.classList.add('input-block');
|
||||
actionInput.type = 'text';
|
||||
actionInput.placeholder = 'add/remove';
|
||||
actionInput.value = 'add';
|
||||
actionInput.id = 'action';
|
||||
|
||||
editForm.appendChild(groupInput);
|
||||
editForm.appendChild(imageInput);
|
||||
editForm.appendChild(actionInput);
|
||||
|
||||
popUpShow(
|
||||
'Nothing stays the same',
|
||||
'Add, remove, or change, the power is in your hands...',
|
||||
editForm,
|
||||
[cancelBtn, submitBtn]
|
||||
);
|
||||
}
|
||||
|
||||
function edit(event) {
|
||||
// AJAX takes control of subby form :3
|
||||
event.preventDefault();
|
||||
|
||||
let formGroup = document.querySelector("#group").value;
|
||||
let formImage = document.querySelector("#image").value;
|
||||
let formAction = document.querySelector("#action").value;
|
||||
|
||||
if (!formGroup || !formImage || !formAction) {
|
||||
addNotification("All values must be set!", 3);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make form
|
||||
const formData = new FormData();
|
||||
formData.append("group", formGroup);
|
||||
formData.append("image", formImage);
|
||||
formData.append("action", formAction);
|
||||
|
||||
fetch('{{ url_for('api.modify_group') }}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => {
|
||||
if (response.status === 200) {
|
||||
addNotification('Group edited!!!', 1);
|
||||
popupDissmiss();
|
||||
} else {
|
||||
switch (response.status) {
|
||||
case 500:
|
||||
addNotification('Server exploded, F\'s in chat', 2);
|
||||
break;
|
||||
case 403:
|
||||
addNotification('None but devils play past here... Bad information', 2);
|
||||
break;
|
||||
default:
|
||||
addNotification('Error logging in, blame someone', 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
addNotification('Error!!!!! Panic!!!!', 2);
|
||||
});
|
||||
}
|
||||
{% endif %}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
{% if images %}
|
||||
.banner::after {
|
||||
box-shadow: 0 calc(var(--rad) * -1) 0 0 rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }});
|
||||
}
|
||||
.banner-content p {
|
||||
color: {{ text_colour }} !important;
|
||||
}
|
||||
|
@ -11,57 +185,97 @@
|
|||
}
|
||||
|
||||
.banner-filter {
|
||||
background: linear-gradient(90deg, rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}), rgba({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}, 0.3)) !important;
|
||||
background: linear-gradient(90deg, rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}), rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 0.3)) !important;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
.banner-filter {
|
||||
background: linear-gradient(180deg, rgba({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}, 0.8), rgba({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}, 0.5)) !important;
|
||||
background: linear-gradient(180deg, rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 0.8), rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 0.5)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation {
|
||||
background-color: rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}) !important;
|
||||
}
|
||||
.navigation-item > svg {
|
||||
fill: {{ text_colour }} !important;
|
||||
color: {{ text_colour }} !important;
|
||||
}
|
||||
.navigation-item.selected::before {
|
||||
background-color: {{ text_colour }} !important;
|
||||
}
|
||||
{% endif %}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="banner {% if not images %}small{% endif %}">
|
||||
{% if not images %}
|
||||
<div class="banner-content">
|
||||
<h1>{{ group.name }}</h1>
|
||||
<p>By {{ group.author_username }}</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<img src="{{ url_for('api.file', file_name=images.0.file_name ) }}?r=prev" onload="imgFade(this)" style="opacity:0;"/>
|
||||
{% if images %}
|
||||
<div class="banner">
|
||||
<img src="{{ url_for('api.file', file_name=images.0.filename ) }}?r=prev" onload="imgFade(this)" style="opacity:0;"/>
|
||||
<span class="banner-filter"></span>
|
||||
<div class="banner-content">
|
||||
<p>By {{ group.author_username }} - {{ images|length }} Images</p>
|
||||
<h1>{{ group.name }}</h1>
|
||||
<p>{{ group.description }}</p>
|
||||
<p class="banner-info">By {{ group.author_username }} - {{ images|length }} Images</p>
|
||||
<h1 class="banner-header">{{ group.name }}</h1>
|
||||
<p class="banner-subtitle">{{ group.description }}</p>
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<button class="pill-item" onclick="groupShare()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,112v96a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V112A16,16,0,0,1,56,96H80a8,8,0,0,1,0,16H56v96H200V112H176a8,8,0,0,1,0-16h24A16,16,0,0,1,216,112ZM93.66,69.66,120,43.31V136a8,8,0,0,0,16,0V43.31l26.34,26.35a8,8,0,0,0,11.32-11.32l-40-40a8,8,0,0,0-11.32,0l-40,40A8,8,0,0,0,93.66,69.66Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
{% if current_user.id == group.author_id %}
|
||||
<div>
|
||||
<button class="pill-item pill__critical" onclick="groupDelete()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item pill__critical" onclick="groupEdit()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form id="modifyGroup">
|
||||
<input type="text" name="group" placeholder="group id" value="{{ group.id }}">
|
||||
<input type="text" name="image" placeholder="image id">
|
||||
<input type="text" name="action" placeholder="add/remove" value="add">
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="banner-small">
|
||||
<div class="banner-content">
|
||||
<h1 class="banner-header">{{ group.name }}</h1>
|
||||
<p class="banner-info">By {{ group.author_username }}</p>
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<button class="pill-item" onclick="groupShare()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,112v96a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V112A16,16,0,0,1,56,96H80a8,8,0,0,1,0,16H56v96H200V112H176a8,8,0,0,1,0-16h24A16,16,0,0,1,216,112ZM93.66,69.66,120,43.31V136a8,8,0,0,0,16,0V43.31l26.34,26.35a8,8,0,0,0,11.32-11.32l-40-40a8,8,0,0,0-11.32,0l-40,40A8,8,0,0,0,93.66,69.66Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
{% if current_user.id == group.author_id %}
|
||||
<div>
|
||||
<button class="pill-item pill__critical" onclick="groupDelete()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
</button>
|
||||
<button class="pill-item pill__critical" onclick="groupEdit()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if images %}
|
||||
<div class="gallery-grid">
|
||||
{% for image in images %}
|
||||
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('group.group_post', group_id=group.id, image_id=image.id) }}" style="background-color: rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }})">
|
||||
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('group.group_post', group_id=group.id, image_id=image.id) }}" style="background-color: rgb({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }})">
|
||||
<div class="image-filter">
|
||||
<p class="image-subtitle"></p>
|
||||
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
|
||||
</div>
|
||||
<img alt="{{ image.post_alt }}" data-src="{{ image.file_name }}" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
<img alt="{{ image.alt }}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="big-text">
|
||||
<h1>*crickets chirping*</h1>
|
||||
{% if g.user %}
|
||||
{% if current_user.is_authenticated %}
|
||||
<p>Add some images to the group!</p>
|
||||
{% else %}
|
||||
<p>Login to start managing this image group!</p>
|
||||
|
@ -69,32 +283,3 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
// /api/group/modify
|
||||
|
||||
modForm = document.querySelector('#modifyGroup');
|
||||
|
||||
modForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('group', modForm.group.value);
|
||||
formData.append('image', modForm.image.value);
|
||||
formData.append('action', modForm.action.value);
|
||||
|
||||
$.ajax({
|
||||
url: '/api/group/modify',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function (data) {
|
||||
addNotification('Image added to group', 1);
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,25 +1,121 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block nav_groups %}selected{% endblock %}
|
||||
{% block head %}
|
||||
{% if images %}
|
||||
<meta name="theme-color" content="rgb({{ images.0.colours.0.0 }}{{ images.0.colours.0.1 }}{{ images.0.colours.0.2 }})"/>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<script type="text/javascript">
|
||||
function showCreate() {
|
||||
// Create elements
|
||||
cancelBtn = document.createElement('button');
|
||||
cancelBtn.classList.add('btn-block');
|
||||
cancelBtn.innerHTML = 'nuuuuuuuu';
|
||||
cancelBtn.onclick = popupDissmiss;
|
||||
|
||||
submitBtn = document.createElement('button');
|
||||
submitBtn.classList.add('btn-block');
|
||||
submitBtn.classList.add('primary');
|
||||
submitBtn.innerHTML = 'Submit!!';
|
||||
submitBtn.type = 'submit';
|
||||
submitBtn.setAttribute('form', 'createForm');
|
||||
|
||||
// Create form
|
||||
createForm = document.createElement('form');
|
||||
createForm.id = 'createForm';
|
||||
createForm.setAttribute('onsubmit', 'return create(event);');
|
||||
|
||||
titleInput = document.createElement('input');
|
||||
titleInput.classList.add('input-block');
|
||||
titleInput.type = 'text';
|
||||
titleInput.placeholder = 'Group namey';
|
||||
titleInput.id = 'name';
|
||||
|
||||
descriptionInput = document.createElement('input');
|
||||
descriptionInput.classList.add('input-block');
|
||||
descriptionInput.type = 'text';
|
||||
descriptionInput.placeholder = 'What it about????';
|
||||
descriptionInput.id = 'description';
|
||||
|
||||
createForm.appendChild(titleInput);
|
||||
createForm.appendChild(descriptionInput);
|
||||
|
||||
popUpShow(
|
||||
'New stuff!',
|
||||
'Image groups are a simple way to "group" images together, are you ready?',
|
||||
createForm,
|
||||
[cancelBtn, submitBtn]
|
||||
);
|
||||
}
|
||||
|
||||
function create(event) {
|
||||
// AJAX takes control of subby form :3
|
||||
event.preventDefault();
|
||||
|
||||
let formName = document.querySelector("#name").value;
|
||||
let formDescription = document.querySelector("#description").value;
|
||||
|
||||
if (!formName) {
|
||||
addNotification("Group name must be set!", 3);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make form
|
||||
const formData = new FormData();
|
||||
formData.append("name", formName);
|
||||
formData.append("description", formDescription);
|
||||
|
||||
fetch('{{ url_for('api.create_group') }}', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => {
|
||||
if (response.status === 200) {
|
||||
addNotification('Group created!', 1);
|
||||
popupDissmiss();
|
||||
} else {
|
||||
switch (response.status) {
|
||||
case 500:
|
||||
addNotification('Server exploded, F\'s in chat', 2);
|
||||
break;
|
||||
case 403:
|
||||
addNotification('None but devils play past here... Bad information', 2);
|
||||
break;
|
||||
default:
|
||||
addNotification('Error logging in, blame someone', 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
addNotification('Error making group! :c', 2);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="banner small">
|
||||
<div class="banner-small">
|
||||
<div class="banner-content">
|
||||
<h1>{{ config.WEBSITE.name }}</h1>
|
||||
<h1 class="banner-header">{{ config.WEBSITE.name }}</h1>
|
||||
{% if groups|length == 0 %}
|
||||
<p>0 groups :<</p>
|
||||
<p class="banner-info">No groups!!!!</p>
|
||||
{% elif groups|length == 69 %}
|
||||
<p>{{ groups|length }} groups, uwu</p>
|
||||
<p class="banner-info">{{ groups|length }} groups, uwu</p>
|
||||
{% else %}
|
||||
<p>{{ groups|length }} groups</p>
|
||||
<p class="banner-info">{{ groups|length }} groups</p>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<button class="pill-item" onclick="showCreate()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form action="/api/group/create" method="post" enctype="multipart/form-data">
|
||||
<input class="input-block black" type="text" name="name" placeholder="name">
|
||||
<input class="input-block black" type="text" name="description" placeholder="description">
|
||||
<button class="btn-block black" type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
{% if groups %}
|
||||
<div class="gallery-grid">
|
||||
{% for group in groups %}
|
||||
|
@ -31,7 +127,7 @@
|
|||
<div class="images size-{{ group.images|length }}">
|
||||
{% if group.images|length > 0 %}
|
||||
{% for image in group.images %}
|
||||
<img data-src="{{ image.file_name }}" onload="this.classList.add('loaded');" id="lazy-load" class="data-{{ loop.index }}"/>
|
||||
<img data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load" class="data-{{ loop.index }}"/>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='error.png') }}" class="loaded"/>
|
||||
|
@ -43,7 +139,7 @@
|
|||
{% else %}
|
||||
<div class="big-text">
|
||||
<h1>*crickets chirping*</h1>
|
||||
{% if g.user %}
|
||||
{% if current_user.is_authenticated %}
|
||||
<p>You can get started by creating a new image group!</p>
|
||||
{% else %}
|
||||
<p>Login to start seeing anything here!</p>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block wrapper_class %}image-wrapper{% endblock %}
|
||||
{% block head %}
|
||||
<meta property="og:image" content="{{ url_for('api.file', file_name=image.file_name) }}"/>
|
||||
<meta name="theme-color" content="#{{ image.image_colours.0.0 }}{{ image.image_colours.0.1 }}{{ image.image_colours.0.2 }}"/>
|
||||
<meta property="og:image" content="{{ url_for('api.file', file_name=image.filename) }}"/>
|
||||
<meta name="theme-color" content="rgb({{ image.colours.0.0 }}{{ image.colours.0.1 }}{{ image.colours.0.2 }})"/>
|
||||
|
||||
<script type="text/javascript">
|
||||
function imageFullscreenOff() {
|
||||
|
@ -18,7 +18,7 @@
|
|||
function imageFullscreenOn() {
|
||||
let fullscreen = document.querySelector('.image-fullscreen')
|
||||
|
||||
fullscreen.querySelector('img').src = '{{ url_for('api.file', file_name=image.file_name) }}';
|
||||
fullscreen.querySelector('img').src = '{{ url_for('api.file', file_name=image.filename) }}';
|
||||
|
||||
document.querySelector("html").style.overflow = "hidden";
|
||||
fullscreen.style.display = 'flex';
|
||||
|
@ -33,39 +33,40 @@
|
|||
}
|
||||
}
|
||||
|
||||
{% if g.user['id'] == image['author_id'] %}
|
||||
cancelBtn = document.createElement('button');
|
||||
cancelBtn.classList.add('btn-block');
|
||||
cancelBtn.innerHTML = 'nuuuuuuuu';
|
||||
cancelBtn.onclick = popupDissmiss;
|
||||
|
||||
deleteBtn = document.createElement('button');
|
||||
deleteBtn.classList.add('btn-block');
|
||||
deleteBtn.classList.add('critical');
|
||||
deleteBtn.innerHTML = 'Dewww eeeet!';
|
||||
deleteBtn.onclick = deleteConfirm;
|
||||
|
||||
{% if current_user.id == image.author_id %}
|
||||
function imageDelete() {
|
||||
popUpShow(
|
||||
'DESTRUCTION!!!!!!',
|
||||
'Do you want to delete this image along with all of its data??? This action is irreversible!',
|
||||
null,
|
||||
[cancelBtn, deleteBtn]
|
||||
);
|
||||
cancelBtn = document.createElement('button');
|
||||
cancelBtn.classList.add('btn-block');
|
||||
cancelBtn.innerHTML = 'nuuuuuuuu';
|
||||
cancelBtn.onclick = popupDissmiss;
|
||||
|
||||
deleteBtn = document.createElement('button');
|
||||
deleteBtn.classList.add('btn-block');
|
||||
deleteBtn.classList.add('critical');
|
||||
deleteBtn.innerHTML = 'Dewww eeeet!';
|
||||
deleteBtn.onclick = deleteConfirm;
|
||||
|
||||
popUpShow('DESTRUCTION!!!!!!',
|
||||
'Do you want to delete this image along with all of its data??? ' +
|
||||
'This action is irreversible!',
|
||||
null,
|
||||
[cancelBtn, deleteBtn]);
|
||||
}
|
||||
function deleteConfirm() {
|
||||
popupDissmiss();
|
||||
|
||||
$.ajax({
|
||||
url: '{{ url_for('api.delete_image', image_id=image['id']) }}',
|
||||
type: 'post',
|
||||
data: {
|
||||
fetch('{{ url_for('api.delete_image', image_id=image['id']) }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'delete'
|
||||
},
|
||||
success: function (response) {
|
||||
})
|
||||
}).then(function(response) {
|
||||
if (response.ok) {
|
||||
window.location.href = '/';
|
||||
},
|
||||
error: function (response) {
|
||||
} else {
|
||||
addNotification(`Image *clings*: ${response}`, 2);
|
||||
}
|
||||
});
|
||||
|
@ -79,30 +80,31 @@
|
|||
|
||||
<style>
|
||||
.background span {
|
||||
background-image: linear-gradient(to top, rgba({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }}, 1), transparent);
|
||||
background-image: linear-gradient(to top, rgba({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }}, 1), transparent);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="background">
|
||||
<img src="{{ url_for('api.file', file_name=image.file_name) }}?r=prev" alt="{{ image.post_alt }}" onload="imgFade(this)" style="opacity:0;"/>
|
||||
<img src="{{ url_for('api.file', file_name=image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div class="image-fullscreen" onclick="imageFullscreenOff()">
|
||||
<img src="" alt="{{ image.post_alt }}" onload="imgFade(this);" style="opacity:0;" />
|
||||
<img src="" alt="{{ image.alt }}"/>
|
||||
</div>
|
||||
|
||||
<div class="image-grid">
|
||||
<div class="image-container" id="image-container">
|
||||
<img
|
||||
src="{{ url_for('api.file', file_name=image.file_name) }}?r=prev"
|
||||
alt="{{ image.post_alt }}"
|
||||
onload="imgFade(this)" style="opacity:0;"
|
||||
src="{{ url_for('api.file', file_name=image.filename) }}?r=prev"
|
||||
alt="{{ image.alt }}"
|
||||
onload="imgFade(this)"
|
||||
style="opacity: 0;"
|
||||
onerror="this.src='{{ url_for('static', filename='error.png')}}'"
|
||||
{% if "File" in image.image_exif %}
|
||||
width="{{ image.image_exif.File.Width.raw }}"
|
||||
height="{{ image.image_exif.File.Height.raw }}"
|
||||
{% if "File" in image.exif %}
|
||||
width="{{ image.exif.File.Width.raw }}"
|
||||
height="{{ image.exif.File.Height.raw }}"
|
||||
{% endif %}
|
||||
/>
|
||||
</div>
|
||||
|
@ -111,7 +113,7 @@
|
|||
{% if next_url %}
|
||||
<div>
|
||||
<a class="pill-item" href="{{ next_url }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm40,112H107.31l18.35,18.34a8,8,0,0,1-11.32,11.32l-32-32a8,8,0,0,1,0-11.32l32-32a8,8,0,0,1,11.32,11.32L107.31,120H168a8,8,0,0,1,0,16Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Previous
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg>
|
||||
|
@ -121,38 +123,38 @@
|
|||
{% endif %}
|
||||
<div>
|
||||
<button class="pill-item" onclick="imageFullscreenOn()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M117.66,138.34a8,8,0,0,1,0,11.32L83.31,184l18.35,18.34A8,8,0,0,1,96,216H48a8,8,0,0,1-8-8V160a8,8,0,0,1,13.66-5.66L72,172.69l34.34-34.35A8,8,0,0,1,117.66,138.34ZM208,40H160a8,8,0,0,0-5.66,13.66L172.69,72l-34.35,34.34a8,8,0,0,0,11.32,11.32L184,83.31l18.34,18.35A8,8,0,0,0,216,96V48A8,8,0,0,0,208,40Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48V96a8,8,0,0,1-16,0V67.31l-50.34,50.35a8,8,0,0,1-11.32-11.32L188.69,56H160a8,8,0,0,1,0-16h48A8,8,0,0,1,216,48ZM106.34,138.34,56,188.69V160a8,8,0,0,0-16,0v48a8,8,0,0,0,8,8H96a8,8,0,0,0,0-16H67.31l50.35-50.34a8,8,0,0,0-11.32-11.32Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Fullscreen
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
<button class="pill-item" onclick="imageShare()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M212,200a36,36,0,1,1-69.85-12.25l-53-34.05a36,36,0,1,1,0-51.4l53-34a36.09,36.09,0,1,1,8.67,13.45l-53,34.05a36,36,0,0,1,0,24.5l53,34.05A36,36,0,0,1,212,200Z"></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>
|
||||
<span class="tool-tip">
|
||||
Share
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
<a class="pill-item" href="/api/file/{{ image.file_name }}" download onclick="addNotification('Download started!', 4)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M74.34,85.66A8,8,0,0,1,85.66,74.34L120,108.69V24a8,8,0,0,1,16,0v84.69l34.34-34.35a8,8,0,0,1,11.32,11.32l-48,48a8,8,0,0,1-11.32,0ZM240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H84.4a4,4,0,0,1,2.83,1.17L111,145A24,24,0,0,0,145,145l23.8-23.8A4,4,0,0,1,171.6,120H224A16,16,0,0,1,240,136Zm-40,32a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<a class="pill-item" href="/api/file/{{ image.filename }}" download onclick="addNotification('Download started!', 4)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Zm-42.34-61.66a8,8,0,0,1,0,11.32l-24,24a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L120,164.69V120a8,8,0,0,1,16,0v44.69l10.34-10.35A8,8,0,0,1,157.66,154.34Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Download
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
{% if image.author_id == g.user.id %}
|
||||
{% if current_user.id == image.author_id %}
|
||||
<div>
|
||||
<button class="pill-item pill__critical" onclick="imageDelete()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM112,168a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm0-120H96V40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8Z"></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>
|
||||
<span class="tool-tip">
|
||||
Delete
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
<button class="pill-item pill__critical" onclick="imageEdit()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM51.31,160l90.35-90.35,16.68,16.69L68,176.68ZM48,179.31,76.69,208H48Zm48,25.38L79.31,188l90.35-90.35h0l16.68,16.69Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Edit
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg>
|
||||
|
@ -163,7 +165,7 @@
|
|||
{% if prev_url %}
|
||||
<div>
|
||||
<a class="pill-item" href="{{ prev_url }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm45.66,109.66-32,32a8,8,0,0,1-11.32-11.32L148.69,136H88a8,8,0,0,1,0-16h60.69l-18.35-18.34a8,8,0,0,1,11.32-11.32l32,32A8,8,0,0,1,173.66,133.66Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Next
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg>
|
||||
|
@ -200,7 +202,7 @@
|
|||
<table>
|
||||
<tr>
|
||||
<td>Author</td>
|
||||
<td><a href="{{ url_for('gallery.profile_id', user_id=image.author_id) }}" class="link">{{ image.author_username }}</a></td>
|
||||
<td><a href="{{ url_for('gallery.profile', id=image.author_id) }}" class="link">{{ image.author_username }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload date</td>
|
||||
|
@ -208,7 +210,7 @@
|
|||
</tr>
|
||||
</table>
|
||||
<div class="img-colours">
|
||||
{% for col in image.image_colours %}
|
||||
{% for col in image.colours %}
|
||||
<span style="background-color: rgb({{col.0}}, {{col.1}}, {{col.2}})"></span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -224,7 +226,7 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% for tag in image.image_exif %}
|
||||
{% for tag in image.exif %}
|
||||
<div class="info-tab">
|
||||
<div class="info-header">
|
||||
{% if tag == 'Photographer' %}
|
||||
|
@ -249,17 +251,17 @@
|
|||
</div>
|
||||
<div class="info-table">
|
||||
<table>
|
||||
{% for subtag in image.image_exif[tag] %}
|
||||
{% for subtag in image.exif[tag] %}
|
||||
<tr>
|
||||
<td>{{ subtag }}</td>
|
||||
{% if image.image_exif[tag][subtag]['formatted'] %}
|
||||
{% if image.image_exif[tag][subtag]['type'] == 'date' %}
|
||||
<td><span class="time">{{ image.image_exif[tag][subtag]['formatted'] }}</span></td>
|
||||
{% if image.exif[tag][subtag]['formatted'] %}
|
||||
{% if image.exif[tag][subtag]['type'] == 'date' %}
|
||||
<td><span class="time">{{ image.exif[tag][subtag]['formatted'] }}</span></td>
|
||||
{% else %}
|
||||
<td>{{ image.image_exif[tag][subtag]['formatted'] }}</td>
|
||||
<td>{{ image.exif[tag][subtag]['formatted'] }}</td>
|
||||
{% endif %}
|
||||
{% elif image.image_exif[tag][subtag]['raw'] %}
|
||||
<td>{{ image.image_exif[tag][subtag]['raw'] }}</td>
|
||||
{% elif image.exif[tag][subtag]['raw'] %}
|
||||
<td>{{ image.exif[tag][subtag]['raw'] }}</td>
|
||||
{% else %}
|
||||
<td class="empty-table">Oops, an error</td>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block nav_home %}selected{% endblock %}
|
||||
{% block content %}
|
||||
<div class="banner small">
|
||||
<div class="banner-small">
|
||||
<div class="banner-content">
|
||||
<h1>{{ config.WEBSITE.name }}</h1>
|
||||
<h1 class="banner-header">{{ config.WEBSITE.name }}</h1>
|
||||
{% if images|length == 0 %}
|
||||
<p>0 images :<</p>
|
||||
<p class="banner-info">0 images D:</p>
|
||||
{% elif images|length == 69 %}
|
||||
<p>{{ images|length }} images, nice</p>
|
||||
<p class="banner-info">{{ images|length }} images, nice</p>
|
||||
{% else %}
|
||||
<p>{{ images|length }} images</p>
|
||||
<p class="banner-info">{{ images|length }} images</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if images %}
|
||||
<div class="gallery-grid">
|
||||
{% for image in images %}
|
||||
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('gallery.image', image_id=image.id) }}" style="background-color: rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }})">
|
||||
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('gallery.image', image_id=image.id) }}" style="background-color: rgb({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }})">
|
||||
<div class="image-filter">
|
||||
<p class="image-subtitle"></p>
|
||||
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
|
||||
</div>
|
||||
<img alt="{{ image.post_alt }}" data-src="{{ image.file_name }}" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
<img fetchpriority="low" alt="{{ image.alt }}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ config.WEBSITE.name }}</title>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>{{ config.WEBSITE.name }}</title>
|
||||
<meta name="description" content="{{ config.WEBSITE.motto }}"/>
|
||||
<meta name="author" content="{{ config.WEBSITE.author }}"/>
|
||||
|
||||
<meta property="og:title" content="{{ config.WEBSITE.name }}"/>
|
||||
<meta property="og:description" content="{{ config.WEBSITE.motto }}"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
|
||||
<meta name="twitter:title" content="{{ config.WEBSITE.name }}"/>
|
||||
<meta name="twitter:description" content="{{ config.WEBSITE.motto }}"/>
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
@ -27,12 +25,18 @@
|
|||
type="image/svg+xml"
|
||||
media="(prefers-color-scheme: dark)"/>
|
||||
|
||||
<link rel="stylesheet" href="{{url_for('static', filename='theme/style.css')}}" defer>
|
||||
|
||||
{% assets "js_all" %}
|
||||
{% assets "js_pre" %}
|
||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
|
||||
{% assets "js_post" %}
|
||||
<script type="text/javascript" src="{{ ASSET_URL }}" defer></script>
|
||||
{% endassets %}
|
||||
|
||||
{% assets "styles" %}
|
||||
<link rel="stylesheet" href="{{ ASSET_URL }}" type="text/css" defer>
|
||||
{% endassets %}
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
@ -58,11 +62,11 @@
|
|||
<div class="wrapper">
|
||||
|
||||
<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')}}" class="navigation-item {% block nav_home %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M208,32H80A16,16,0,0,0,64,48V64H48A16,16,0,0,0,32,80V208a16,16,0,0,0,16,16H176a16,16,0,0,0,16-16V192h16a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM80,48H208v69.38l-16.7-16.7a16,16,0,0,0-22.62,0L93.37,176H80Zm96,160H48V80H64v96a16,16,0,0,0,16,16h96ZM104,88a16,16,0,1,1,16,16A16,16,0,0,1,104,88Z"></path></svg>
|
||||
<span>
|
||||
<span class="tool-tip">
|
||||
Home
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
|
@ -70,73 +74,73 @@
|
|||
|
||||
<a href="{{url_for('group.groups')}}" class="navigation-item {% block nav_groups %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M245,110.64A16,16,0,0,0,232,104H216V88a16,16,0,0,0-16-16H130.67L102.94,51.2a16.14,16.14,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V208h0a8,8,0,0,0,8,8H211.1a8,8,0,0,0,7.59-5.47l28.49-85.47A16.05,16.05,0,0,0,245,110.64ZM93.34,64l27.73,20.8a16.12,16.12,0,0,0,9.6,3.2H200v16H146.43a16,16,0,0,0-8.88,2.69l-20,13.31H69.42a15.94,15.94,0,0,0-14.86,10.06L40,166.46V64Z"></path></svg>
|
||||
<span>
|
||||
<span class="tool-tip">
|
||||
Groups
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
{% if g.user %}
|
||||
<button class="navigation-item {% block nav_upload %}{% endblock %}" onclick="toggleUploadTab()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M74.34,77.66a8,8,0,0,1,0-11.32l48-48a8,8,0,0,1,11.32,0l48,48a8,8,0,0,1-11.32,11.32L136,43.31V128a8,8,0,0,1-16,0V43.31L85.66,77.66A8,8,0,0,1,74.34,77.66ZM240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16h68a4,4,0,0,1,4,4v3.46c0,13.45,11,24.79,24.46,24.54A24,24,0,0,0,152,128v-4a4,4,0,0,1,4-4h68A16,16,0,0,1,240,136Zm-40,32a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<span>
|
||||
Upload
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
{% if current_user.is_authenticated %}
|
||||
<button class="navigation-item {% block nav_upload %}{% endblock %}" onclick="toggleUploadTab()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M74.34,77.66a8,8,0,0,1,0-11.32l48-48a8,8,0,0,1,11.32,0l48,48a8,8,0,0,1-11.32,11.32L136,43.31V128a8,8,0,0,1-16,0V43.31L85.66,77.66A8,8,0,0,1,74.34,77.66ZM240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16h68a4,4,0,0,1,4,4v3.46c0,13.45,11,24.79,24.46,24.54A24,24,0,0,0,152,128v-4a4,4,0,0,1,4-4h68A16,16,0,0,1,240,136Zm-40,32a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Upload
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<span class="navigation-spacer"></span>
|
||||
|
||||
{% if g.user %}
|
||||
<a href="{{url_for('gallery.profile')}}" class="navigation-item {% block nav_profile %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M231.73,221.94A8,8,0,0,1,224,232H160A8,8,0,0,1,152.27,222a40,40,0,0,1,17.11-23.33,32,32,0,1,1,45.24,0A40,40,0,0,1,231.73,221.94ZM216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16h80a8,8,0,0,0,0-16H40V64H93.33l27.74,20.8a16.12,16.12,0,0,0,9.6,3.2H216v32a8,8,0,0,0,16,0V88A16,16,0,0,0,216,72Z"></path></svg>
|
||||
<span>
|
||||
Profile
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a href="{{url_for('settings.general')}}" class="navigation-item {% block nav_settings %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,130.16q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.6,107.6,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.29,107.29,0,0,0-26.25-10.86,8,8,0,0,0-7.06,1.48L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.6,107.6,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06ZM128,168a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z"></path></svg>
|
||||
<span>
|
||||
Settings
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</a>
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{url_for('gallery.profile')}}" class="navigation-item {% block nav_profile %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M231.73,221.94A8,8,0,0,1,224,232H160A8,8,0,0,1,152.27,222a40,40,0,0,1,17.11-23.33,32,32,0,1,1,45.24,0A40,40,0,0,1,231.73,221.94ZM216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16h80a8,8,0,0,0,0-16H40V64H93.33l27.74,20.8a16.12,16.12,0,0,0,9.6,3.2H216v32a8,8,0,0,0,16,0V88A16,16,0,0,0,216,72Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Profile
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a href="{{url_for('settings.general')}}" class="navigation-item {% block nav_settings %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,130.16q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.6,107.6,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.29,107.29,0,0,0-26.25-10.86,8,8,0,0,0-7.06,1.48L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.6,107.6,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06ZM128,168a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Settings
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="navigation-item {% block nav_login %}{% endblock %}" onclick="showLogin()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M141.66,133.66l-40,40A8,8,0,0,1,88,168V136H24a8,8,0,0,1,0-16H88V88a8,8,0,0,1,13.66-5.66l40,40A8,8,0,0,1,141.66,133.66ZM192,32H136a8,8,0,0,0,0,16h56V208H136a8,8,0,0,0,0,16h56a16,16,0,0,0,16-16V48A16,16,0,0,0,192,32Z"></path></svg>
|
||||
<span>
|
||||
Login
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
<button class="navigation-item {% block nav_login %}{% endblock %}" onclick="showLogin()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M141.66,133.66l-40,40A8,8,0,0,1,88,168V136H24a8,8,0,0,1,0-16H88V88a8,8,0,0,1,13.66-5.66l40,40A8,8,0,0,1,141.66,133.66ZM192,32H136a8,8,0,0,0,0,16h56V208H136a8,8,0,0,0,0,16h56a16,16,0,0,0,16-16V48A16,16,0,0,0,192,32Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Login
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if g.user %}
|
||||
<div class="upload-panel">
|
||||
<span class="click-off" onclick="closeUploadTab()"></span>
|
||||
<div class="container">
|
||||
<span id="dragIndicator"></span>
|
||||
<h3>Upload stuffs</h3>
|
||||
<p>May the world see your stuff 👀</p>
|
||||
<form id="uploadForm">
|
||||
<button class="fileDrop-block" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H80a8,8,0,0,1,0,16H32v64H224V136H176a8,8,0,0,1,0-16h48A16,16,0,0,1,240,136ZM85.66,77.66,120,43.31V128a8,8,0,0,0,16,0V43.31l34.34,34.35a8,8,0,0,0,11.32-11.32l-48-48a8,8,0,0,0-11.32,0l-48,48A8,8,0,0,0,85.66,77.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<span class="status">Choose or Drop file</span>
|
||||
<input type="file" id="file" tab-index="-1"/>
|
||||
</button>
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="upload-panel">
|
||||
<span class="click-off" onclick="closeUploadTab()"></span>
|
||||
<div class="container">
|
||||
<span id="dragIndicator"></span>
|
||||
<h3>Upload stuffs</h3>
|
||||
<p>May the world see your stuff 👀</p>
|
||||
<form id="uploadForm">
|
||||
<button class="fileDrop-block" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H80a8,8,0,0,1,0,16H32v64H224V136H176a8,8,0,0,1,0-16h48A16,16,0,0,1,240,136ZM85.66,77.66,120,43.31V128a8,8,0,0,0,16,0V43.31l34.34,34.35a8,8,0,0,0,11.32-11.32l-48-48a8,8,0,0,0-11.32,0l-48,48A8,8,0,0,0,85.66,77.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<span class="status">Choose or Drop file</span>
|
||||
<input type="file" id="file" tab-index="-1"/>
|
||||
</button>
|
||||
|
||||
<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="tags" id="tags"/>
|
||||
<button class="btn-block primary" type="submit">Upload</button>
|
||||
</form>
|
||||
<div class="upload-jobs"></div>
|
||||
</div>
|
||||
<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="tags" id="tags"/>
|
||||
<button class="btn-block" type="submit">Upload</button>
|
||||
</form>
|
||||
<div class="upload-jobs"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="content">
|
||||
|
|
|
@ -2,17 +2,29 @@
|
|||
|
||||
{% block nav_profile %}selected{% endblock %}
|
||||
{% block content %}
|
||||
<div class="banner">
|
||||
<img src="{{ url_for('static', filename='') }}" onload="imgFade(this)" style="opacity:0;"/>
|
||||
<span></span>
|
||||
|
||||
<div class="banner-small">
|
||||
<div class="banner-content">
|
||||
<h1>Profile</h1>
|
||||
<p>Hello {{ g.user['username'] }}</p>
|
||||
<h1 class="banner-header">{{ user.username }}</h1>
|
||||
<p class="banner-info">Member since <span class="time">{{ user.joined_at }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>User</h1>
|
||||
<p>{{user_id}}</p>
|
||||
{% if images %}
|
||||
<div class="gallery-grid">
|
||||
{% for image in images %}
|
||||
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('gallery.image', image_id=image.id) }}" style="background-color: rgb({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }})">
|
||||
<div class="image-filter">
|
||||
<p class="image-subtitle"></p>
|
||||
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
|
||||
</div>
|
||||
<img fetchpriority="low" alt="{{ image.alt }}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="big-text">
|
||||
<h1>*crickets chirping*</h1>
|
||||
<p>There are no images here yet, upload some!</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -3,5 +3,10 @@
|
|||
{% block settings_account %}settings-nav__item-selected{% endblock %}
|
||||
{% block settings_content %}
|
||||
<h2>Account</h2>
|
||||
<a href="{{ url_for( 'auth.logout' ) }}">Logout</a>
|
||||
<p>Is session fresh?</p>
|
||||
{% if fresh %}
|
||||
<p>Yes</p>
|
||||
{% else %}
|
||||
<p>No</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -2,14 +2,17 @@
|
|||
|
||||
{% block nav_settings %}selected{% endblock %}
|
||||
{% block content %}
|
||||
<div class="banner">
|
||||
<img src="{{ url_for('static', filename='images/bg.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
|
||||
<span></span>
|
||||
|
||||
<div class="banner-small">
|
||||
<div class="banner-content">
|
||||
{% block banner_subtitle%}{% endblock %}
|
||||
<h1>Settings</h1>
|
||||
<p>All the red buttons in one place, what could go wrong?</p>
|
||||
<h1 class="banner-header">Settings</h1>
|
||||
<p class="banner-info">{% block banner_subtitle%}{% endblock %}</p>
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<a class="pill-item pill__critical" href="{{ url_for( 'auth.logout' ) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M112,216a8,8,0,0,1-8,8H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32h56a8,8,0,0,1,0,16H48V208h56A8,8,0,0,1,112,216Zm109.66-93.66-40-40a8,8,0,0,0-11.32,11.32L196.69,120H104a8,8,0,0,0,0,16h92.69l-26.35,26.34a8,8,0,0,0,11.32,11.32l40-40A8,8,0,0,0,221.66,122.34Z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
.banner
|
||||
width: 100%
|
||||
height: 50vh
|
||||
|
||||
position: relative
|
||||
|
||||
background-color: RGB($bg-300)
|
||||
color: RGB($fg-white)
|
||||
|
||||
overflow: hidden
|
||||
transition: opacity 0.3s ease-in-out
|
||||
|
||||
img
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
background-color: RGB($bg-300)
|
||||
|
||||
object-fit: cover
|
||||
object-position: center center
|
||||
|
||||
.banner-filter
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
background: linear-gradient(to right, RGB($primary), transparent)
|
||||
|
||||
z-index: +1
|
||||
|
||||
.banner-content
|
||||
padding: 1rem
|
||||
|
||||
width: 100%
|
||||
height: inherit
|
||||
|
||||
position: relative
|
||||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: flex-end
|
||||
|
||||
z-index: +2
|
||||
|
||||
h1
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
font-size: 6.9rem
|
||||
font-weight: 800
|
||||
text-align: left
|
||||
|
||||
color: RGB($primary)
|
||||
|
||||
p
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
font-size: 1rem
|
||||
font-weight: 600
|
||||
line-height: 1
|
||||
text-align: left
|
||||
|
||||
&.small
|
||||
height: 3.5rem
|
||||
background-color: RGB($bg-100)
|
||||
|
||||
.banner-content
|
||||
padding: 0.5rem
|
||||
|
||||
flex-direction: row
|
||||
justify-content: flex-start
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
|
||||
h1
|
||||
padding-bottom: 0.25rem
|
||||
font-size: 1.5rem
|
||||
text-align: left
|
||||
|
||||
p
|
||||
font-size: 0.9rem
|
||||
text-align: left
|
||||
|
||||
@media (max-width: $breakpoint)
|
||||
.banner
|
||||
width: 100vw
|
||||
height: 25vh
|
||||
|
||||
.banner-content
|
||||
padding: 0.5rem
|
||||
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
|
||||
h1
|
||||
font-size: 3rem
|
||||
text-align: center
|
||||
|
||||
p
|
||||
font-size: 1.1rem
|
||||
text-align: center
|
||||
|
||||
&.small .banner-content
|
||||
justify-content: center
|
||||
|
||||
h1
|
||||
text-align: center
|
||||
|
||||
p
|
||||
display: none
|
|
@ -1,33 +0,0 @@
|
|||
.label
|
||||
padding: 0.4rem 0.7rem
|
||||
|
||||
display: block
|
||||
position: absolute
|
||||
|
||||
font-size: 0.9rem
|
||||
font-weight: 600
|
||||
|
||||
background-color: RGB($bg-dim)
|
||||
color: RGB($fg-white)
|
||||
border-radius: $rad-inner
|
||||
opacity: 0
|
||||
|
||||
transition: opacity 0.2s cubic-bezier(.76,0,.17,1), left 0.2s cubic-bezier(.76,0,.17,1)
|
||||
pointer-events: none
|
||||
z-index: 999
|
||||
|
||||
svg
|
||||
margin: 0
|
||||
font-size: 1rem
|
||||
|
||||
width: 0.75rem
|
||||
height: 0.75rem
|
||||
|
||||
display: block
|
||||
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: -0.45rem
|
||||
transform: translateY(-50%)
|
||||
|
||||
color: RGB($bg-dim)
|
|
@ -3,7 +3,7 @@ Calculate the contrast between two colors
|
|||
"""
|
||||
|
||||
|
||||
def contrast(background, light, dark, threshold = 0.179):
|
||||
def contrast(background, light, dark, threshold=0.179):
|
||||
"""
|
||||
background: tuple of (r, g, b) values
|
||||
light: color to use if the background is light
|
||||
|
|
|
@ -4,7 +4,7 @@ Tools for generating images and thumbnails
|
|||
|
||||
import os
|
||||
import platformdirs
|
||||
from PIL import Image, ImageOps #, ImageFilter
|
||||
from PIL import Image, ImageOps
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
|
||||
|
@ -37,11 +37,9 @@ def generate_thumbnail(file_name, resolution, ext=None):
|
|||
if resolution in ['prev', 'preview']:
|
||||
res_x, res_y = (1920, 1080)
|
||||
elif resolution in ['thumb', 'thumbnail']:
|
||||
res_x, res_y = (400, 400)
|
||||
res_x, res_y = (350, 350)
|
||||
elif resolution in ['icon', 'favicon']:
|
||||
res_x, res_y = (10, 10)
|
||||
elif len(resolution.split('x')) == 2:
|
||||
res_x, res_y = resolution.split('x')
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -65,13 +63,13 @@ def generate_thumbnail(file_name, resolution, ext=None):
|
|||
# Save image to cache directory
|
||||
try:
|
||||
image.save(os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}'),
|
||||
icc_profile=image_icc)
|
||||
icc_profile=image_icc)
|
||||
except OSError:
|
||||
# This usually happens when saving a JPEG with an ICC profile,
|
||||
# so we convert to RGB and try again
|
||||
image = image.convert('RGB')
|
||||
image.save(os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}'),
|
||||
icc_profile=image_icc)
|
||||
icc_profile=image_icc)
|
||||
|
||||
# No need to keep the image in memory, learned the hard way
|
||||
image.close()
|
||||
|
|
|
@ -279,7 +279,7 @@ def lens_specification(value):
|
|||
"""
|
||||
try:
|
||||
return str(value[0] / value[1]) + 'mm - ' + str(value[2] / value[3]) + 'mm'
|
||||
except ValueError:
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
"""
|
||||
OnlyLegs - Theme Manager
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
import sass
|
||||
|
||||
|
||||
def compile_theme(theme_name, app_path):
|
||||
"""
|
||||
Compiles the theme into the static folder
|
||||
"""
|
||||
print(f"Loading '{theme_name}' theme...")
|
||||
|
||||
# Set Paths
|
||||
theme_source = os.path.join(app_path, 'themes', theme_name)
|
||||
theme_destination = os.path.join(app_path, 'static', 'theme')
|
||||
|
||||
# If the theme doesn't exist, exit
|
||||
if not os.path.exists(theme_source):
|
||||
print("Theme does not exist!")
|
||||
sys.exit(1)
|
||||
|
||||
# If the destination folder doesn't exist, create it
|
||||
if not os.path.exists(theme_destination):
|
||||
os.makedirs(theme_destination)
|
||||
|
||||
# Theme source file doesn't exist, exit
|
||||
if not os.path.join(theme_source, 'style.sass'):
|
||||
print("No sass file found!")
|
||||
sys.exit(1)
|
||||
|
||||
# Compile the theme
|
||||
with open(os.path.join(theme_destination, 'style.css'),
|
||||
encoding='utf-8', mode='w+') as file:
|
||||
try:
|
||||
file.write(sass.compile(filename=os.path.join(theme_source, 'style.sass'),
|
||||
output_style='compressed'))
|
||||
except sass.CompileError as err:
|
||||
print("Failed to compile!\n", err)
|
||||
sys.exit(1)
|
||||
print("Compiled successfully!")
|
||||
|
||||
# If the destination folder exists, remove it
|
||||
if os.path.exists(os.path.join(theme_destination, 'fonts')):
|
||||
shutil.rmtree(os.path.join(theme_destination, 'fonts'))
|
||||
|
||||
# Copy the fonts
|
||||
shutil.copytree(os.path.join(theme_source, 'fonts'),
|
||||
os.path.join(theme_destination, 'fonts'))
|
||||
print("Fonts copied successfully!")
|
||||
|
||||
print(f"{datetime.now().hour}:{datetime.now().minute}:{datetime.now().second} - Done!\n")
|
1
gallery/views/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# :3
|
|
@ -5,16 +5,16 @@ from uuid import uuid4
|
|||
import os
|
||||
import pathlib
|
||||
import logging
|
||||
from datetime import datetime as dt
|
||||
import platformdirs
|
||||
|
||||
from flask import Blueprint, send_from_directory, abort, flash, jsonify, request, g, current_app
|
||||
from flask import Blueprint, send_from_directory, abort, flash, request, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from colorthief import ColorThief
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from gallery.auth import login_required
|
||||
|
||||
from gallery import db
|
||||
from gallery.utils import metadata as mt
|
||||
|
@ -37,7 +37,7 @@ def file(file_name):
|
|||
file_name = secure_filename(file_name) # Sanitize file name
|
||||
|
||||
# if no args are passed, return the raw file
|
||||
if not request.args:
|
||||
if not res and not ext:
|
||||
if not os.path.exists(os.path.join(current_app.config['UPLOAD_FOLDER'], file_name)):
|
||||
abort(404)
|
||||
return send_from_directory(current_app.config['UPLOAD_FOLDER'], file_name)
|
||||
|
@ -64,7 +64,7 @@ def upload():
|
|||
|
||||
# Get file extension, generate random name and set file path
|
||||
img_ext = pathlib.Path(form_file.filename).suffix.replace('.', '').lower()
|
||||
img_name = "GWAGWA_"+str(uuid4())
|
||||
img_name = "GWAGWA_" + str(uuid4())
|
||||
img_path = os.path.join(current_app.config['UPLOAD_FOLDER'], img_name+'.'+img_ext)
|
||||
|
||||
# Check if file extension is allowed
|
||||
|
@ -83,14 +83,13 @@ def upload():
|
|||
img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette
|
||||
|
||||
# Save to database
|
||||
query = db.Posts(author_id=g.user.id,
|
||||
created_at=dt.utcnow(),
|
||||
file_name=img_name+'.'+img_ext,
|
||||
file_type=img_ext,
|
||||
image_exif=img_exif,
|
||||
image_colours=img_colors,
|
||||
post_description=form['description'],
|
||||
post_alt=form['alt'])
|
||||
query = db.Posts(author_id=current_user.id,
|
||||
filename=img_name + '.' + img_ext,
|
||||
mimetype=img_ext,
|
||||
exif=img_exif,
|
||||
colours=img_colors,
|
||||
description=form['description'],
|
||||
alt=form['alt'])
|
||||
|
||||
db_session.add(query)
|
||||
db_session.commit()
|
||||
|
@ -109,18 +108,18 @@ def delete_image(image_id):
|
|||
# Check if image exists and if user is allowed to delete it (author)
|
||||
if img is None:
|
||||
abort(404)
|
||||
if img.author_id != g.user.id:
|
||||
if img.author_id != current_user.id:
|
||||
abort(403)
|
||||
|
||||
# Delete file
|
||||
try:
|
||||
os.remove(os.path.join(current_app.config['UPLOAD_FOLDER'],img.file_name))
|
||||
os.remove(os.path.join(current_app.config['UPLOAD_FOLDER'], img.filename))
|
||||
except FileNotFoundError:
|
||||
logging.warning('File not found: %s, already deleted or never existed', img.file_name)
|
||||
logging.warning('File not found: %s, already deleted or never existed', img.filename)
|
||||
|
||||
# Delete cached files
|
||||
cache_path = os.path.join(platformdirs.user_config_dir('onlylegs'), 'cache')
|
||||
cache_name = img.file_name.rsplit('.')[0]
|
||||
cache_name = img.filename.rsplit('.')[0]
|
||||
for cache_file in pathlib.Path(cache_path).glob(cache_name + '*'):
|
||||
os.remove(cache_file)
|
||||
|
||||
|
@ -135,7 +134,7 @@ def delete_image(image_id):
|
|||
# Commit all changes
|
||||
db_session.commit()
|
||||
|
||||
logging.info('Removed image (%s) %s', image_id, img.file_name)
|
||||
logging.info('Removed image (%s) %s', image_id, img.filename)
|
||||
flash(['Image was all in Le Head!', '1'])
|
||||
return 'Gwa Gwa'
|
||||
|
||||
|
@ -148,8 +147,7 @@ def create_group():
|
|||
"""
|
||||
new_group = db.Groups(name=request.form['name'],
|
||||
description=request.form['description'],
|
||||
author_id=g.user.id,
|
||||
created_at=dt.utcnow())
|
||||
author_id=current_user.id)
|
||||
|
||||
db_session.add(new_group)
|
||||
db_session.commit()
|
||||
|
@ -165,21 +163,21 @@ def modify_group():
|
|||
"""
|
||||
group_id = request.form['group']
|
||||
image_id = request.form['image']
|
||||
action = request.form['action']
|
||||
|
||||
group = db_session.query(db.Groups).filter_by(id=group_id).first()
|
||||
|
||||
if group is None:
|
||||
abort(404)
|
||||
elif group.author_id != g.user.id:
|
||||
elif group.author_id != current_user.id:
|
||||
abort(403)
|
||||
|
||||
if request.form['action'] == 'add':
|
||||
if action == 'add':
|
||||
if not (db_session.query(db.GroupJunction)
|
||||
.filter_by(group_id=group_id, post_id=image_id)
|
||||
.first()):
|
||||
db_session.add(db.GroupJunction(group_id=group_id,
|
||||
post_id=image_id,
|
||||
date_added=dt.utcnow()))
|
||||
post_id=image_id))
|
||||
elif request.form['action'] == 'remove':
|
||||
(db_session.query(db.GroupJunction)
|
||||
.filter_by(group_id=group_id, post_id=image_id)
|
||||
|
@ -190,17 +188,23 @@ def modify_group():
|
|||
return ':3'
|
||||
|
||||
|
||||
@blueprint.route('/metadata/<int:img_id>', methods=['GET'])
|
||||
def metadata(img_id):
|
||||
@blueprint.route('/group/delete', methods=['POST'])
|
||||
def delete_group():
|
||||
"""
|
||||
Yoinks metadata from an image
|
||||
Deletes a group
|
||||
"""
|
||||
img = db_session.query(db.Posts).filter_by(id=img_id).first()
|
||||
group_id = request.form['group']
|
||||
|
||||
if not img:
|
||||
group = db_session.query(db.Groups).filter_by(id=group_id).first()
|
||||
|
||||
if group is None:
|
||||
abort(404)
|
||||
elif group.author_id != current_user.id:
|
||||
abort(403)
|
||||
|
||||
img_path = os.path.join(current_app.config['UPLOAD_FOLDER'], img.file_name)
|
||||
exif = mt.Metadata(img_path).yoink()
|
||||
db_session.query(db.Groups).filter_by(id=group_id).delete()
|
||||
db_session.query(db.GroupJunction).filter_by(group_id=group_id).delete()
|
||||
db_session.commit()
|
||||
|
||||
return jsonify(exif)
|
||||
flash(['Group yeeted!', '1'])
|
||||
return ':3'
|
|
@ -37,8 +37,8 @@ def groups():
|
|||
# For each image, get the image data and add it to the group item
|
||||
group.images = []
|
||||
for image in images:
|
||||
group.images.append(db_session.query(db.Posts.file_name, db.Posts.post_alt,
|
||||
db.Posts.image_colours, db.Posts.id)
|
||||
group.images.append(db_session.query(db.Posts.filename, db.Posts.alt,
|
||||
db.Posts.colours, db.Posts.id)
|
||||
.filter(db.Posts.id == image[0])
|
||||
.first())
|
||||
|
||||
|
@ -78,8 +78,8 @@ def group(group_id):
|
|||
|
||||
# Check contrast for the first image in the group for the banner
|
||||
text_colour = 'rgb(var(--fg-black))'
|
||||
if images[0]:
|
||||
text_colour = contrast.contrast(images[0].image_colours[0],
|
||||
if images:
|
||||
text_colour = contrast.contrast(images[0].colours[0],
|
||||
'rgb(var(--fg-black))',
|
||||
'rgb(var(--fg-white))')
|
||||
|
|
@ -3,6 +3,7 @@ Onlylegs Gallery - Routing
|
|||
"""
|
||||
from flask import Blueprint, render_template, url_for, request
|
||||
from werkzeug.exceptions import abort
|
||||
from flask_login import current_user
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from gallery import db
|
||||
|
@ -18,9 +19,9 @@ def index():
|
|||
"""
|
||||
Home page of the website, shows the feed of the latest images
|
||||
"""
|
||||
images = db_session.query(db.Posts.file_name,
|
||||
db.Posts.post_alt,
|
||||
db.Posts.image_colours,
|
||||
images = db_session.query(db.Posts.filename,
|
||||
db.Posts.alt,
|
||||
db.Posts.colours,
|
||||
db.Posts.created_at,
|
||||
db.Posts.id).order_by(db.Posts.id.desc()).all()
|
||||
|
||||
|
@ -81,12 +82,21 @@ def profile():
|
|||
"""
|
||||
Profile overview, shows all profiles on the onlylegs gallery
|
||||
"""
|
||||
return render_template('profile.html', user_id='gwa gwa')
|
||||
user_id = request.args.get('id', default=None, type=int)
|
||||
|
||||
# If there is no userID set, check if the user is logged in and display their profile
|
||||
if not user_id:
|
||||
if current_user.is_authenticated:
|
||||
user_id = current_user.id
|
||||
else:
|
||||
abort(404, 'You must be logged in to view your own profile!')
|
||||
|
||||
@blueprint.route('/profile/<int:user_id>')
|
||||
def profile_id(user_id):
|
||||
"""
|
||||
Shows user ofa given id, displays their uploads and other info
|
||||
"""
|
||||
return render_template('profile.html', user_id=user_id)
|
||||
# Get the user's data
|
||||
user = db_session.query(db.Users).filter(db.Users.id == user_id).first()
|
||||
|
||||
if not user:
|
||||
abort(404, 'User not found :c')
|
||||
|
||||
images = db_session.query(db.Posts).filter(db.Posts.author_id == user_id).all()
|
||||
|
||||
return render_template('profile.html', user=user, images=images)
|
|
@ -2,9 +2,7 @@
|
|||
OnlyLegs - Settings page
|
||||
"""
|
||||
from flask import Blueprint, render_template
|
||||
|
||||
from gallery.auth import login_required
|
||||
|
||||
from flask_login import login_required
|
||||
|
||||
blueprint = Blueprint('settings', __name__, url_prefix='/settings')
|
||||
|
33
poetry.lock
generated
|
@ -1,4 +1,4 @@
|
|||
# This file is automatically @generated by Poetry 1.4.0 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 = "astroid"
|
||||
|
@ -253,21 +253,20 @@ brotli = "*"
|
|||
flask = "*"
|
||||
|
||||
[[package]]
|
||||
name = "flask-session2"
|
||||
version = "1.3.1"
|
||||
description = "Adds server-side session support to your Flask application"
|
||||
name = "flask-login"
|
||||
version = "0.6.2"
|
||||
description = "User authentication and session management for Flask."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2,<4.0.0"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Flask-Session2-1.3.1.tar.gz", hash = "sha256:171e986d4e314795f448a527095e42df6abfba76c3e4ce5c8e4c61c857c59cb2"},
|
||||
{file = "Flask_Session2-1.3.1-py3-none-any.whl", hash = "sha256:6d1615dfc4b247759f31f89bf16aba96fa1294077e700771875abe952f291959"},
|
||||
{file = "Flask-Login-0.6.2.tar.gz", hash = "sha256:c0a7baa9fdc448cdd3dd6f0939df72eec5177b2f7abe6cb82fc934d29caac9c3"},
|
||||
{file = "Flask_Login-0.6.2-py3-none-any.whl", hash = "sha256:1ef79843f5eddd0f143c2cd994c1b05ac83c0401dc6234c143495af9a939613f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cachelib = ">=0.9.0,<0.10.0"
|
||||
Flask = ">=2.2.2,<3.0.0"
|
||||
pytz = ">=2022.2.1,<2023.0.0"
|
||||
Flask = ">=1.0.4"
|
||||
Werkzeug = ">=1.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
|
@ -706,18 +705,6 @@ files = [
|
|||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2022.7.1"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"},
|
||||
{file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0"
|
||||
|
@ -1033,4 +1020,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "fdc83d433d98ba079b4614e37da26b9c81f07e0a646606c17de5c0fb07ae483e"
|
||||
content-hash = "431b58579dc3ebde52c8f3905c851556a465d5a82796a8a26718d69cb4915959"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "onlylegs"
|
||||
version = "23.03.30"
|
||||
version = "23.04.05"
|
||||
description = "Gallery built for fast and simple image management"
|
||||
authors = ["Fluffy-Bean <michal-gdula@protonmail.com>"]
|
||||
license = "MIT"
|
||||
|
@ -12,7 +12,7 @@ Flask = "^2.2.2"
|
|||
Flask-Compress = "^1.13"
|
||||
Flask-Caching = "^2.0.2"
|
||||
Flask-Assets = "^2.0"
|
||||
Flask-Session2 = "^1.3.1"
|
||||
Flask-Login = "^0.6.2"
|
||||
SQLAlchemy = "^2.0.3"
|
||||
python-dotenv = "^0.21.0"
|
||||
gunicorn = "^20.1.0"
|
||||
|
|
13
run.py
|
@ -14,7 +14,7 @@ print("""
|
|||
#+# #+# #+# #+#+# #+# #+# #+# #+# #+# #+# #+# #+#
|
||||
######## ### #### ########## ### ########## ######### ######### ########
|
||||
|
||||
Created by Fluffy Bean - Version 23.03.30
|
||||
Created by Fluffy Bean - Version 23.04.05
|
||||
""")
|
||||
|
||||
|
||||
|
@ -24,18 +24,13 @@ Configuration()
|
|||
|
||||
if DEBUG:
|
||||
from gallery import create_app
|
||||
|
||||
# If no address is specified, use localhost
|
||||
if not ADDRESS:
|
||||
ADDRESS = 'localhost'
|
||||
|
||||
create_app().run(host=ADDRESS, port=PORT, debug=True, threaded=True)
|
||||
else:
|
||||
from setup.runner import OnlyLegs # pylint: disable=C0412
|
||||
import sys
|
||||
|
||||
# If no address is specified, bind the server to all interfaces
|
||||
if not ADDRESS:
|
||||
ADDRESS = '0.0.0.0'
|
||||
# Stop Gunicorn from reading the command line arguments as it causes errors
|
||||
sys.argv = [sys.argv[0]]
|
||||
|
||||
options = {
|
||||
'bind': f'{ADDRESS}:{PORT}',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Startup arguments for the OnlyLegs gallery
|
||||
|
||||
-p, --port: Port to run on (default: 5000)
|
||||
-a, --address: Address to run on (default: For Debug: localhost, For Production: 0.0.0.0)
|
||||
-a, --address: Address to run on (default: 127.0.0.0)
|
||||
-w, --workers: Number of workers to run (default: 4)
|
||||
|
||||
-d, --debug: Run as Flask app in debug mode (default: False)
|
||||
|
@ -15,7 +15,7 @@ import argparse
|
|||
|
||||
parser = argparse.ArgumentParser(description='Run the OnlyLegs gallery')
|
||||
parser.add_argument('-p', '--port', type=int, default=5000, help='Port to run on')
|
||||
parser.add_argument('-a', '--address', type=str, default=None, help='Address to run on')
|
||||
parser.add_argument('-a', '--address', type=str, default='127.0.0.0', help='Address to run on')
|
||||
parser.add_argument('-w', '--workers', type=int, default=4, help='Number of workers to run')
|
||||
parser.add_argument('-d', '--debug', action='store_true', help='Run as Flask app in debug mode')
|
||||
args = parser.parse_args()
|
||||
|
|
|
@ -135,15 +135,9 @@ class Configuration:
|
|||
"""
|
||||
Set the logging config
|
||||
"""
|
||||
logs_path = os.path.join(platformdirs.user_config_dir('onlylegs'), 'logs')
|
||||
|
||||
if not os.path.isdir(logs_path):
|
||||
os.mkdir(logs_path)
|
||||
print("Created logs directory at:", logs_path)
|
||||
|
||||
logging.getLogger('werkzeug').disabled = True
|
||||
logging.basicConfig(
|
||||
filename=os.path.join(logs_path, 'only.log'),
|
||||
filename=os.path.join(USER_DIR, 'only.log'),
|
||||
level=logging.INFO,
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s',
|
||||
|
|
|
@ -9,7 +9,7 @@ class OnlyLegs(Application):
|
|||
"""
|
||||
Gunicorn application
|
||||
"""
|
||||
def __init__(self, options={}): # pylint: disable=W0102, W0231 # noqa
|
||||
def __init__(self, options={}): # pylint: disable=W0102, W0231
|
||||
self.usage = None
|
||||
self.callable = None
|
||||
self.options = options
|
||||
|
@ -25,7 +25,8 @@ class OnlyLegs(Application):
|
|||
cfg[setting.lower()] = value
|
||||
return cfg
|
||||
|
||||
def prog(self): # pylint: disable=C0116, E0202 # noqa
|
||||
@staticmethod
|
||||
def prog(): # pylint: disable=C0116, E0202
|
||||
return 'OnlyLegs'
|
||||
|
||||
def load(self):
|
||||
|
|