Bundle JS into a compact format

Run file checks on startup
Fix visual bugs in Sass
This commit is contained in:
Michał Gdula 2023-03-10 11:10:43 +00:00
parent e3a0eaf60b
commit e6d289ed64
16 changed files with 283 additions and 221 deletions

View file

@ -10,12 +10,12 @@ Created by Fluffy Bean - Version 23.03.09
# Import system modules
import os
import sys
import logging
# Flask
from flask_compress import Compress
from flask_caching import Cache
from flask_assets import Environment, Bundle
from flask import Flask, render_template
# Configuration
@ -24,57 +24,32 @@ import platformdirs
import yaml
from . import theme_manager
from . import setup
# Run setup checks
setup.SetupApp()
USER_DIR = platformdirs.user_config_dir('onlylegs')
INSTANCE_PATH = os.path.join(USER_DIR, 'instance')
# Check if any of the required files are missing
if not os.path.exists(platformdirs.user_config_dir('onlylegs')):
from . import setup
setup.SetupApp()
# Get environment variables
if os.path.exists(os.path.join(USER_DIR, '.env')):
load_dotenv(os.path.join(USER_DIR, '.env'))
print("Loaded environment variables")
else:
print("No environment variables found!")
sys.exit(1)
load_dotenv(os.path.join(USER_DIR, '.env'))
print("Loaded environment variables")
# Get config file
if os.path.exists(os.path.join(USER_DIR, 'conf.yml')):
with open(os.path.join(USER_DIR, 'conf.yml'), encoding='utf-8') as f:
conf = yaml.load(f, Loader=yaml.FullLoader)
print("Loaded gallery config")
else:
print("No config file found!")
sys.exit(1)
# Setup 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)
logging.getLogger('werkzeug').disabled = True
logging.basicConfig(
filename=os.path.join(LOGS_PATH, 'only.log'),
level=logging.INFO,
datefmt='%Y-%m-%d %H:%M:%S',
format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s',
encoding='utf-8')
with open(os.path.join(USER_DIR, 'conf.yml'), encoding='utf-8') as f:
conf = yaml.load(f, Loader=yaml.FullLoader)
print("Loaded gallery config")
def create_app(test_config=None):
"""
Create and configure the main app
"""
app = Flask(__name__,instance_path=INSTANCE_PATH)
compress = Compress()
app = Flask(__name__,instance_path=os.path.join(USER_DIR, 'instance'))
assets = Environment()
cache = Cache(config={'CACHE_TYPE': 'SimpleCache', 'CACHE_DEFAULT_TIMEOUT': 69})
compress = Compress()
# App configuration
app.config.from_mapping(
@ -100,7 +75,12 @@ def create_app(test_config=None):
theme_manager.CompileTheme('default', app.root_path)
# Bundle JS files
js = Bundle('js/*.js', output='gen/packed.js')
assets.register('js_all', js)
@app.errorhandler(405)
def method_not_allowed(err):
@ -152,7 +132,11 @@ def create_app(test_config=None):
# Load APIs
from . import api
app.register_blueprint(api.blueprint)
logging.info('Gallery started successfully!')
compress.init_app(app)
assets.init_app(app)
cache.init_app(app)
compress.init_app(app)
return app

View file

@ -5,6 +5,7 @@ Runs when the app detects that there is no user directory
import os
import sys
import platformdirs
import logging
import yaml
USER_DIR = platformdirs.user_config_dir('onlylegs')
@ -18,6 +19,8 @@ class SetupApp:
Main setup function
"""
print("Running setup...")
self.requires_restart = False
if not os.path.exists(USER_DIR):
self.make_dir()
@ -25,6 +28,13 @@ class SetupApp:
self.make_env()
if not os.path.exists(os.path.join(USER_DIR, 'conf.yml')):
self.make_yaml()
self.logging_config()
if self.requires_restart:
print("WARNING: You need to restart and edit the config files before running the app again!")
print("You can find the config files at:", USER_DIR)
sys.exit()
def make_dir(self):
"""
@ -37,7 +47,7 @@ class SetupApp:
print("Created user directory at:", USER_DIR)
except Exception as err:
print("Error creating user directory:", err)
sys.exit(1) # exit with error code
sys.exit(1)
def make_env(self):
"""
@ -55,7 +65,7 @@ class SetupApp:
print("Error creating environment variables:", err)
sys.exit(1)
print("Generated default .env file. EDIT IT BEFORE RUNNING THE APP AGAIN!")
print("Generated default .env file, please edit!")
def make_yaml(self):
"""
@ -95,4 +105,19 @@ class SetupApp:
print("Error creating default gallery config:", err)
sys.exit(1)
print("Generated default YAML config. EDIT IT BEFORE RUNNING THE APP AGAIN!")
print("Generated default YAML config, please edit!")
def logging_config(self):
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'),
level=logging.INFO,
datefmt='%Y-%m-%d %H:%M:%S',
format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s',
encoding='utf-8')

View file

@ -1,64 +1,38 @@
function imgFade(obj) {
$(obj).animate({opacity: 1}, 250);
// fade in images
function imgFade(obj, time = 250) {
$(obj).animate({ opacity: 1 }, time);
}
let times = document.getElementsByClassName('time');
for (let i = 0; i < times.length; i++) {
// Remove milliseconds
const raw = times[i].innerHTML.split('.')[0];
// Parse YYYY-MM-DD HH:MM:SS to Date object
const time = raw.split(' ')[1]
const date = raw.split(' ')[0].split('-');
// Format to YYYY/MM/DD HH:MM:SS
let formatted = date[0] + '/' + date[1] + '/' + date[2] + ' ' + time + ' UTC';
// Convert to UTC Date object
let dateTime = new Date(formatted);
// Convert to local time
times[i].innerHTML = dateTime.toLocaleDateString() + ' ' + dateTime.toLocaleTimeString();
// https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color
function colourContrast(bgColor, lightColor, darkColor, threshold = 0.179) {
var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
var r = parseInt(color.substring(0, 2), 16); // hexToR
var g = parseInt(color.substring(2, 4), 16); // hexToG
var b = parseInt(color.substring(4, 6), 16); // hexToB
var uicolors = [r / 255, g / 255, b / 255];
var c = uicolors.map((col) => {
if (col <= 0.03928) {
return col / 12.92;
}
return Math.pow((col + 0.055) / 1.055, 2.4);
});
var L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
return (L > threshold) ? darkColor : lightColor;
}
let images = document.querySelectorAll('.gallery-item img');
// Lazy load images when they are in view
function loadOnView() {
for (let i = 0; i < images.length; i++) {
let image = images[i];
let 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/uploads/${image.getAttribute('data-src')}?w=500&h=500`
image.src = `/api/uploads/${image.getAttribute('data-src')}?w=400&h=400`
}
}
}
}
if (images.length > 0) {
window.onload = function() {
loadOnView();
};
window.onscroll = function() {
loadOnView();
};
window.onresize = function() {
loadOnView();
};
}
document.onscroll = function() {
if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) {
document.querySelector('.jumpUp').classList = 'jumpUp jumpUp--show';
} else {
document.querySelector('.jumpUp').classList = 'jumpUp';
}
}
document.querySelector('.jumpUp').onclick = function() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
function uploadFile(){
// Function to upload images
function uploadFile() {
// AJAX takes control of subby form
event.preventDefault();
@ -75,7 +49,7 @@ function uploadFile(){
formData.append("description", $("#description").val());
formData.append("tags", $("#tags").val());
formData.append("submit", $("#submit").val());
// Upload the information
$.ajax({
url: '/api/upload',
@ -83,14 +57,14 @@ function uploadFile(){
data: formData,
contentType: false,
processData: false,
beforeSend: function() {
beforeSend: function () {
jobContainer = document.createElement("div");
jobContainer.classList.add("job");
jobStatus = document.createElement("span");
jobStatus.classList.add("job__status");
jobStatus.innerHTML = "Uploading...";
jobProgress = document.createElement("span");
jobProgress.classList.add("progress");
@ -146,7 +120,7 @@ function uploadFile(){
$("#tags").val("");
}
};
// open upload tab
function openUploadTab() {
// Stop scrolling
document.querySelector("html").style.overflow = "hidden";
@ -155,11 +129,11 @@ function openUploadTab() {
const uploadTab = document.querySelector(".upload-panel");
uploadTab.style.display = "block";
setTimeout( function() {
setTimeout(function () {
uploadTab.classList.add("open");
}, 10);
}
// close upload tab
function closeUploadTab() {
// un-Stop scrolling
document.querySelector("html").style.overflow = "auto";
@ -168,11 +142,11 @@ function closeUploadTab() {
const uploadTab = document.querySelector(".upload-panel");
uploadTab.classList.remove("open");
setTimeout( function() {
setTimeout(function () {
uploadTab.style.display = "none";
}, 250);
}
// toggle upload tab
function toggleUploadTab() {
if (document.querySelector(".upload-panel").classList.contains("open")) {
closeUploadTab();
@ -180,7 +154,7 @@ function toggleUploadTab() {
openUploadTab();
}
}
// Function to show login
function showLogin() {
popUpShow(
'idk what to put here, just login please',
@ -192,6 +166,49 @@ function showLogin() {
</form>'
);
};
// Function to login
function login(event) {
// AJAX takes control of subby form :3
event.preventDefault();
let formUsername = document.querySelector("#username").value;
let formPassword = document.querySelector("#password").value;
if (formUsername === "" || formPassword === "") {
addNotification("Please fill in all fields!!!!", 3);
return;
}
// Make form
var formData = new FormData();
formData.append("username", formUsername);
formData.append("password", formPassword);
$.ajax({
url: '/auth/login',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
location.reload();
},
error: function (response) {
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;
}
}
});
}
// Function to show register
function showRegister() {
popUpShow(
'Who are you?',
@ -205,87 +222,104 @@ function showRegister() {
</form>'
);
};
function login(event) {
// AJAX takes control of subby form
event.preventDefault();
if ($("#username").val() === "" || $("#password").val() === "") {
addNotification("Please fill in all fields", 3);
} else {
// Make form
var formData = new FormData();
formData.append("username", $("#username").val());
formData.append("password", $("#password").val());
$.ajax({
url: '/auth/login',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
location.reload();
},
error: function (response) {
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;
}
}
});
}
}
// Function to register
function register(obj) {
// AJAX takes control of subby form
event.preventDefault();
if ($("#username").val() === "" || $("#email").val() === "" || $("#password").val() === "" || $("#password-repeat").val() === "") {
addNotification("Please fill in all fields", 3);
} else {
// Make form
var formData = new FormData();
formData.append("username", $("#username").val());
formData.append("email", $("#email").val());
formData.append("password", $("#password").val());
formData.append("password-repeat", $("#password-repeat").val());
let formUsername = document.querySelector("#username").value;
let formEmail = document.querySelector("#email").value;
let formPassword = document.querySelector("#password").value;
let formPasswordRepeat = document.querySelector("#password-repeat").value;
$.ajax({
url: '/auth/register',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
if (response === "gwa gwa") {
addNotification('Registered successfully! Now please login to continue', 1);
showLogin();
} else {
for (var i = 0; i < response.length; i++) {
addNotification(response[i], 2);
}
}
},
error: function (response) {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here...', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
if (formUsername === "" || formEmail === "" || formPassword === "" || formPasswordRepeat === "") {
addNotification("Please fill in all fields!!!!", 3);
return;
}
// Make form
var formData = new FormData();
formData.append("username", formUsername);
formData.append("email", formEmail);
formData.append("password", formPassword);
formData.append("password-repeat", formPasswordRepeat);
$.ajax({
url: '/auth/register',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (response) {
if (response === "gwa gwa") {
addNotification('Registered successfully! Now please login to continue', 1);
showLogin();
} else {
for (var i = 0; i < response.length; i++) {
addNotification(response[i], 2);
}
}
});
}
},
error: function (response) {
switch (response.status) {
case 500:
addNotification('Server exploded, F\'s in chat', 2);
break;
case 403:
addNotification('None but devils play past here...', 2);
break;
default:
addNotification('Error logging in, blame someone', 2);
break;
}
}
});
}
window.onload = function () {
loadOnView();
const darkColor = '#151515';
const lightColor = '#E8E3E3';
let contrastCheck = document.querySelectorAll('#contrast-check');
for (let i = 0; i < contrastCheck.length; i++) {
bgColor = contrastCheck[i].getAttribute('data-color');
contrastCheck[i].style.color = colourContrast(bgColor, lightColor, darkColor, 0.9);
}
let times = document.querySelectorAll('.time');
for (let i = 0; i < times.length; i++) {
// Remove milliseconds
const raw = times[i].innerHTML.split('.')[0];
// Parse YYYY-MM-DD HH:MM:SS to Date object
const time = raw.split(' ')[1]
const date = raw.split(' ')[0].split('-');
// Format to YYYY/MM/DD HH:MM:SS
let formatted = date[0] + '/' + date[1] + '/' + date[2] + ' ' + time + ' UTC';
// Convert to UTC Date object
let dateTime = new Date(formatted);
// Convert to local time
times[i].innerHTML = dateTime.toLocaleDateString() + ' ' + dateTime.toLocaleTimeString();
}
};
window.onscroll = function () {
loadOnView();
// Jump to top button
if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) {
document.querySelector('.jumpUp').classList = 'jumpUp jumpUp--show';
} else {
document.querySelector('.jumpUp').classList = 'jumpUp';
}
document.querySelector('.jumpUp').onclick = function () {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
};
window.onresize = function () {
loadOnView();
};

View file

@ -11,7 +11,8 @@
style="opacity:0; background-color:rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }})"
/>
<span
style="background-image: linear-gradient(to right, rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}), transparent)"
class="banner-filter"
style="background: linear-gradient(to right, rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}), transparent)"
></span>
{% else %}
<img src="{{ url_for('static', filename='images/bg.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
@ -39,7 +40,7 @@
<p></p>
<h2><span class="time">{{ image.created_at }}</span></h2>
</span>
<img data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;"/>
<img data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
</a>
{% endfor %}
</div>

View file

@ -5,7 +5,7 @@
{% block content %}
<div class="banner">
<img src="{{ url_for('static', filename='images/bg.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
<span class="banner-filter"></span>
<div class="banner-content">
<p>{{ config.WEBSITE.motto }}</p>
@ -28,7 +28,7 @@
<h2>{{ group.name }}</h2>
</span>
{% if group.thumbnail %}
<img data-src="{{ group.thumbnail }}" onload="imgFade(this)" style="opacity:0;"/>
<img data-src="{{ group.thumbnail }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
{% else %}
<img src="{{ url_for('static', filename='images/error.png') }}" onload="imgFade(this)" style="opacity:0;"/>
{% endif %}

View file

@ -5,7 +5,7 @@
{% block content %}
<div class="banner">
<img src="{{ url_for('static', filename='images/bg.svg') }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
<span class="banner-filter"></span>
<div class="banner-content">
<p>{{ config.WEBSITE.motto }}</p>
@ -22,7 +22,7 @@
<p></p>
<h2><span class="time">{{ image.created_at }}</span></h2>
</span>
<img data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;"/>
<img data-src="{{ image.file_name }}" onload="imgFade(this)" style="opacity:0;" id="lazy-load"/>
</a>
{% endfor %}
</div>

View file

@ -7,15 +7,9 @@
<link rel="icon" href="{{url_for('static', filename='images/icon.png')}}">
<link rel="stylesheet" href="{{url_for('static', filename='theme/style.css')}}" defer>
<!-- Jquery for AJAX -->
<script src="{{url_for('static', filename='js/jquery-3.6.3.min.js')}}"></script>
<!-- Main Script -->
<script src="{{ url_for('static', filename='js/main.js') }}" defer></script>
<!-- UI -->
<script src="{{ url_for('static', filename='js/ui/popup.js') }}"></script>
<script src="{{ url_for('static', filename='js/ui/notifications.js') }}"></script>
{% assets "js_all" %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
</head>
<body>
<div class="notifications"></div>

View file

@ -23,7 +23,7 @@
object-fit: cover
object-position: center center
span
.banner-filter
position: absolute
top: 0
left: 0
@ -77,9 +77,6 @@
width: 100vw
height: 25vh
span
background-image: linear-gradient(to bottom, rgba($primary, 1), rgba($primary, 0))
.banner-content
padding: 0.5rem

View file

@ -233,6 +233,8 @@
left: 0
bottom: calc(-100vh + 3.5rem)
border-radius: $rad $rad 0 0
&.open
.container
left: 0

View file

@ -105,7 +105,7 @@
opacity: 1
img
transform: scale(1.1)
transform: scale(1.05)
@media (max-width: 800px)
.gallery-grid

View file

@ -18,12 +18,11 @@ $info: $blue
$rad: 6px
$rad-inner: 3px
//$font: "Work Sans", sans-serif
$font: "Work Sans", sans-serif
$breakpoint: 800px // responsive breakpoint for mobile
// responsive breakpoint for mobile
$breakpoint: 800px
// Work Sans
@font-face
font-family: 'Work Sans'
src: url('fonts/worksans-regular.woff2')