diff --git a/.gitignore b/.gitignore
index 0801037..209a447 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,6 @@
-# Remove all development files
-gallery/user/logs/*
-gallery/user/uploads/*
-gallery/user/conf.yml
-gallery/user/conf.json
-gallery/static/theme/*
+gallery/static/theme
+gallery/static/.webassets-cache
+gallery/static/gen
.idea
.vscode
diff --git a/gallery/__init__.py b/gallery/__init__.py
index 3ade1ca..40d4d0a 100644
--- a/gallery/__init__.py
+++ b/gallery/__init__.py
@@ -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
diff --git a/gallery/setup.py b/gallery/setup.py
index fea6547..db53218 100644
--- a/gallery/setup.py
+++ b/gallery/setup.py
@@ -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')
\ No newline at end of file
diff --git a/gallery/static/js/main.js b/gallery/static/js/main.js
index e93970b..b79a9e9 100644
--- a/gallery/static/js/main.js
+++ b/gallery/static/js/main.js
@@ -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() {
'
);
};
+// 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() {
'
);
};
-
-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();
+};
\ No newline at end of file
diff --git a/gallery/static/js/ui/notifications.js b/gallery/static/js/notifications.js
similarity index 100%
rename from gallery/static/js/ui/notifications.js
rename to gallery/static/js/notifications.js
diff --git a/gallery/static/js/ui/popup.js b/gallery/static/js/popup.js
similarity index 100%
rename from gallery/static/js/ui/popup.js
rename to gallery/static/js/popup.js
diff --git a/gallery/templates/groups/group.html b/gallery/templates/groups/group.html
index aff0b3b..24b2a75 100644
--- a/gallery/templates/groups/group.html
+++ b/gallery/templates/groups/group.html
@@ -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 }})"
/>
{% else %}
@@ -39,7 +40,7 @@