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 @@

{{ image.created_at }}

- + {% endfor %} diff --git a/gallery/templates/groups/list.html b/gallery/templates/groups/list.html index ac5b334..a1eea16 100644 --- a/gallery/templates/groups/list.html +++ b/gallery/templates/groups/list.html @@ -5,7 +5,7 @@ {% block content %}