From b4bc8c61ecde06843c6fa1a6dc9093b85cfb0d3c Mon Sep 17 00:00:00 2001
From: Fluffy-Bean <michal-gdula@protonmail.com>
Date: Wed, 27 Sep 2023 13:59:31 +0100
Subject: [PATCH] Allow for styling

---
 onlylegs/config.py                            | 47 +++++------
 .../static/sass/components/buttons/pill.sass  |  7 +-
 .../static/sass/components/image-view.sass    |  2 +-
 onlylegs/static/sass/style.sass               |  8 +-
 onlylegs/static/sass/variables.sass           |  4 +-
 onlylegs/templates/base.html                  | 18 +++-
 onlylegs/templates/group.html                 | 31 -------
 onlylegs/templates/image.html                 | 16 +++-
 onlylegs/templates/settings.html              | 32 ++++++-
 onlylegs/utils/startup.py                     | 83 +++++++++----------
 onlylegs/views/settings.py                    | 32 +++++++
 11 files changed, 161 insertions(+), 119 deletions(-)

diff --git a/onlylegs/config.py b/onlylegs/config.py
index 3300411..a24c24d 100644
--- a/onlylegs/config.py
+++ b/onlylegs/config.py
@@ -17,41 +17,32 @@ startup.check_conf()
 
 # Set dirs
 APPLICATION_ROOT = platformdirs.user_config_dir("onlylegs")
-
-# Load environment variables
-# print("Loading environment variables...")
-load_dotenv(os.path.join(APPLICATION_ROOT, ".env"))
-
-# Load config from user dir
-# print("Loading config...")
-with open(
-    os.path.join(APPLICATION_ROOT, "conf.yml"), encoding="utf-8", mode="r"
-) as file:
-    conf = safe_load(file)
-
-
-# Flask config
-SECRET_KEY = os.environ.get("FLASK_SECRET")
-DATABASE_NAME = "gallery.sqlite3"
-SQLALCHEMY_DATABASE_URI = "sqlite:///" + DATABASE_NAME
-MAX_CONTENT_LENGTH = 1024 * 1024 * conf["upload"]["max-size"]
-ALLOWED_EXTENSIONS = conf["upload"]["allowed-extensions"]
-
-# Pass YAML config to app
-ADMIN_CONF = conf["admin"]
-UPLOAD_CONF = conf["upload"]
-WEBSITE_CONF = conf["website"]
-
-# Directories
 UPLOAD_FOLDER = os.path.join(APPLICATION_ROOT, "media", "uploads")
 MEDIA_FOLDER = os.path.join(APPLICATION_ROOT, "media")
 CACHE_FOLDER = os.path.join(APPLICATION_ROOT, "media", "cache")
 PFP_FOLDER = os.path.join(APPLICATION_ROOT, "media", "pfp")
 BANNER_FOLDER = os.path.join(APPLICATION_ROOT, "media", "banner")
 
+# Load env and config files
+load_dotenv(os.path.join(APPLICATION_ROOT, ".env"))
+
+config_file = os.path.join(APPLICATION_ROOT, "conf.yml")
+with open(config_file, encoding="utf-8", mode="r") as file:
+    conf = safe_load(file)
+
+# Flask config
+SECRET_KEY = os.environ.get("FLASK_SECRET")
+MAX_CONTENT_LENGTH = 1024 * 1024 * conf["upload"]["max-size"]
+ALLOWED_EXTENSIONS = conf["upload"]["allowed-extensions"]
+APP_VERSION = importlib.metadata.version("OnlyLegs")
+
 # Database
+DATABASE_NAME = "gallery.sqlite3"
+SQLALCHEMY_DATABASE_URI = "sqlite:///" + DATABASE_NAME
 INSTANCE_DIR = os.path.join(APPLICATION_ROOT, "instance")
 MIGRATIONS_DIR = os.path.join(INSTANCE_DIR, "migrations")
 
-# App
-APP_VERSION = importlib.metadata.version("OnlyLegs")
+# Pass YAML config to app
+ADMIN_CONF = conf["admin"]
+UPLOAD_CONF = conf["upload"]
+WEBSITE_CONF = conf["website"]
diff --git a/onlylegs/static/sass/components/buttons/pill.sass b/onlylegs/static/sass/components/buttons/pill.sass
index f63b02e..25fc812 100644
--- a/onlylegs/static/sass/components/buttons/pill.sass
+++ b/onlylegs/static/sass/components/buttons/pill.sass
@@ -64,12 +64,11 @@
 
     &:hover
         cursor: pointer
-
         color: var(--primary)
 
-    &.disabled, &:disabled
-        color: var(--foreground-dim)
-        cursor: unset
+    &:disabled, &[disabled], &.disabled
+        color: var(--foreground-gray)
+        cursor: default
 
 .pill__critical
     color: var(--danger)
diff --git a/onlylegs/static/sass/components/image-view.sass b/onlylegs/static/sass/components/image-view.sass
index 473bdc6..249f04c 100644
--- a/onlylegs/static/sass/components/image-view.sass
+++ b/onlylegs/static/sass/components/image-view.sass
@@ -1,5 +1,5 @@
 .info-container
-    padding: 0.5rem 0 0 0.5rem
+    padding: 0.5rem 0 0.5rem 0.5rem
     width: 27rem
     position: absolute
     top: 0
diff --git a/onlylegs/static/sass/style.sass b/onlylegs/static/sass/style.sass
index 19a6ec1..159aa1e 100644
--- a/onlylegs/static/sass/style.sass
+++ b/onlylegs/static/sass/style.sass
@@ -75,9 +75,11 @@ main
     main
         margin: 0 0.5rem
 
-header
-    position: sticky
-    top: 0
+// This is very broken, as it breaks when you open any context menu/popup
+// I need to fix this at some point as it looks really nice
+//header
+//    position: sticky
+//    top: 0
 
 .error-page
     min-height: 100%
diff --git a/onlylegs/static/sass/variables.sass b/onlylegs/static/sass/variables.sass
index 8f5818f..a31a7bd 100644
--- a/onlylegs/static/sass/variables.sass
+++ b/onlylegs/static/sass/variables.sass
@@ -26,8 +26,8 @@
     --red-transparent: rgba(182, 100, 103, 0.1)
     --orange: rgb(217, 140, 95)
     --orange-transparent: rgba(217, 140, 95, 0.1)
-    --yellow: rgb(217, 188, 140)
-    --yellow-transparent: rgba(217, 188, 140, 0.1)
+    --yellow: rgb(198, 185, 166)
+    --yellow-transparent: rgba(198, 185, 166, 0.1)
     --green: rgb(140, 151, 125)
     --green-transparent: rgba(140, 151, 125, 0.1)
     --blue: rgb(141, 163, 185)
diff --git a/onlylegs/templates/base.html b/onlylegs/templates/base.html
index 403288f..3fb4855 100644
--- a/onlylegs/templates/base.html
+++ b/onlylegs/templates/base.html
@@ -28,10 +28,20 @@
     {% assets "scripts" %} <script type="text/javascript" src="{{ ASSET_URL }}"></script> {% endassets %}
     {% assets "styles" %} <link rel="stylesheet" href="{{ ASSET_URL }}" type="text/css" defer> {% endassets %}
 
-    <style type="text/css">
-        :root{
-            --primary: {{ config.WEBSITE_CONF.primary_color | default('rgb(198, 185, 166)') }};
-        }
+    <style>
+        {% if config.WEBSITE_CONF.styling.force %}
+            :root{
+                --background-hsl-hue: {{ config.WEBSITE_CONF.styling.hue }} !important;
+                --background-hsl-saturation: {{ config.WEBSITE_CONF.styling.saturation }}% !important;
+                --rad: {{ config.WEBSITE_CONF.styling.rad }} !important;
+            }
+        {% else %}
+            :root{
+                --background-hsl-hue: {{ config.WEBSITE_CONF.styling.hue }};
+                --background-hsl-saturation: {{ config.WEBSITE_CONF.styling.saturation }}%;
+                --rad: {{ config.WEBSITE_CONF.styling.rad }};
+            }
+        {% endif %}
     </style>
 
     {% block head %}{% endblock %}
diff --git a/onlylegs/templates/group.html b/onlylegs/templates/group.html
index 077ea93..afaec5c 100644
--- a/onlylegs/templates/group.html
+++ b/onlylegs/templates/group.html
@@ -24,37 +24,6 @@
                 --background-hsl-hue: {{ images.0.colours.0 | hsl_hue }};
                 --background-hsl-saturation: {{ images.0.colours.0 | hsl_saturation }}%;
             }
-
-            /*
-            body {
-                color: {{ text_colour }} !important;
-            }
-
-            .navigation-item.selected {
-                color: {{ text_colour }} !important;
-            }
-
-            .banner .banner-content .banner-header,
-            .banner .banner-content .banner-info,
-            .banner .banner-content .banner-subtitle {
-                color: {{ text_colour }} !important;
-            }
-            .banner-content .link {
-                color: rgb{{ images.0.colours.0 }} !important;
-            }
-            .banner-content .link:hover {
-                color: {{ text_colour }} !important;
-            }
-
-            .banner-filter {
-                background: linear-gradient(90deg, rgb{{ images.0.colours.0 }}, rgba({{ images.0.colours.1.0 }}, {{ images.0.colours.1.1 }}, {{ images.0.colours.1.2 }}, 0.3)) !important;
-            }
-            @media (max-width: 800px) {
-                .banner-filter {
-                    background: linear-gradient(180deg, rgba({{ images.0.colours.1.0 }}, {{ images.0.colours.1.1 }}, {{ images.0.colours.1.2 }}, 0.4), rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 0.3)) !important;
-                }
-            }
-            */
         {% endif %}
     </style>
 {% endblock %}
diff --git a/onlylegs/templates/image.html b/onlylegs/templates/image.html
index 2070353..725a363 100644
--- a/onlylegs/templates/image.html
+++ b/onlylegs/templates/image.html
@@ -18,6 +18,10 @@
     </script>
 
     <style>
+        :root {
+            --background-hsl-hue: {{ image.colours.2 | hsl_hue }};
+            --background-hsl-saturation: {{ image.colours.2 | hsl_saturation }}%;
+        }
         .background::after {
             background-image: linear-gradient(to top, rgba({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }}, 0.8),
                                                       rgba({{ image.colours.1.0 }}, {{ image.colours.1.1 }}, {{ image.colours.1.2 }}, 0.2));
@@ -30,7 +34,11 @@
         <div class="banner-content">
             <h1 class="banner-header">{{ config.WEBSITE_CONF.name }}</h1>
             <div class="pill-row">
-                {% if next_url %}<div><a class="pill-item" href="{{ next_url }}"><i class="ph ph-arrow-left"></i></a></div>{% endif %}
+                <div>
+                    <a {% if next_url %}class="pill-item" href="{{ next_url }}"{% else %}class="pill-item disabled"{% endif %}>
+                        <i class="ph ph-arrow-left"></i>
+                    </a>
+                </div>
                 <div>
                     <button class="pill-item" onclick="imageFullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button>
                     <button class="pill-item" onclick="copyToClipboard(window.location.href)"><i class="ph ph-export"></i></button>
@@ -38,7 +46,11 @@
                         <button class="pill-item" onclick="imageShowOptionsPopup(this)"><i class="ph ph-list"></i></button>
                     {% endif %}
                 </div>
-                {% if prev_url %}<div><a class="pill-item" href="{{ prev_url }}"><i class="ph ph-arrow-right"></i></a></div>{% endif %}
+                <div>
+                    <a {% if prev_url %}class="pill-item" href="{{ prev_url }}"{% else %}class="pill-item disabled"{% endif %}>
+                        <i class="ph ph-arrow-right"></i>
+                    </a>
+                </div>
             </div>
         </div>
     </div>
diff --git a/onlylegs/templates/settings.html b/onlylegs/templates/settings.html
index 7cad7ab..521296a 100644
--- a/onlylegs/templates/settings.html
+++ b/onlylegs/templates/settings.html
@@ -66,6 +66,36 @@
             <i class="ph ph-caret-down collapse-indicator"></i>
         </summary>
 
-        <p>Nothing here :3</p>
+        <form method="POST" action="{{ url_for('settings.website_style') }}" enctype="multipart/form-data">
+            <h3>Style</h3>
+            <input type="range" name="hue" class="input-block" placeholder="Website Hue" value="{{ config.WEBSITE_CONF.styling.hue }}"  min="0" max="360" />
+            <input type="range" name="saturation" class="input-block" placeholder="Strength of Color" value="{{ config.WEBSITE_CONF.styling.saturation }}" min="0" max="100" />
+            <input type="text" name="rad" class="input-block" placeholder="Roundy roundy" value="{{ config.WEBSITE_CONF.styling.rad }}" />
+            <input type="checkbox" name="force" class="input-block" placeholder="Force styling?" {% if config.WEBSITE_CONF.styling.force %}checked{% endif %} />
+            <button type="submit" class="btn-block">Update style</button>
+        </form>
     </details>
+
+    <footer>
+        <p>Built on coffee and love, by Fluffy</p>
+    </footer>
 {% endblock %}
+
+{% block script %}
+    <script>
+        let hue = document.querySelector('input[name=hue]');
+        let saturation = document.querySelector('input[name=saturation]');
+        let rad = document.querySelector('input[name=rad]');
+
+        hue.addEventListener('input', () => {
+            document.documentElement.style.setProperty('--background-hsl-hue', hue.value, 'important');
+        });
+        saturation.addEventListener('input', () => {
+            document.documentElement.style.setProperty('--background-hsl-saturation', saturation.value + '%', 'important');
+        });
+        rad.addEventListener('input', () => {
+            document.documentElement.style.setProperty('--rad', rad.value, 'important');
+        });
+    </script>
+{% endblock %}
+
diff --git a/onlylegs/utils/startup.py b/onlylegs/utils/startup.py
index c08fc3d..b8e30eb 100644
--- a/onlylegs/utils/startup.py
+++ b/onlylegs/utils/startup.py
@@ -22,12 +22,42 @@ REQUIRED_DIRS = {
 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")
 
+config = {
+    # Version of the config file
+    "version": "0.1.7",
+    # Not really used much, but good to have for future use
+    "admin": {
+        "username": "admin",
+        "email": "admin@example.com",
+    },
+    "upload": {
+        "allowed-extensions": {
+            "jpg": "jpeg",
+            "jpeg": "jpeg",
+            "png": "png",
+            "webp": "webp",
+        },
+        # Max size in MB
+        "max-size": 69,
+        # Max images to load per page
+        "max-load": 50,
+    },
+    "website": {
+        # Website name and motto
+        # Also CSS styling, hue is the color offset for hsl
+        "name": "OnlyLegs",
+        "motto": "A gallery built for fast and simple image management!",
+        "styling": {
+            "force": False,
+            "hue": "69",
+            "saturation": "25%",
+            "rad": "0.4rem",
+        },
+    },
+}
+
 
 def check_dirs():
-    """
-    Create the user directory
-    """
-
     for directory in REQUIRED_DIRS.values():
         if not os.path.exists(directory):
             os.makedirs(directory)
@@ -36,9 +66,6 @@ def check_dirs():
 
 
 def check_env():
-    """
-    Create the .env file with default values
-    """
     if os.path.exists(os.path.join(APPLICATION_ROOT, ".env")):
         print("Environment file already exists at:", APPLICATION_ROOT)
         return
@@ -65,30 +92,23 @@ def check_env():
 
 
 def check_conf():
-    """
-    Create the YAML config file with default values
-    """
-    if os.path.exists(os.path.join(APPLICATION_ROOT, "conf.yml")):
+    config_file = os.path.join(APPLICATION_ROOT, "conf.yml")
+    if os.path.exists(config_file):
         print("Config file already exists at:", APPLICATION_ROOT)
         return
 
     cant_continue = True
     username = "admin"
-    name = "Admin"
     email = "admin@example.com"
 
     print("No config file found, please enter the following information:")
     while cant_continue:
         username = input("Admin username: ").strip()
-        name = input("Admin name: ").strip()
         email = input("Admin email: ").strip()
 
         if not username or not USERNAME_REGEX.match(username):
             print("Username is invalid!")
             continue
-        if not name:
-            print("Name is invalid!")
-            continue
         if not email or not EMAIL_REGEX.match(email):
             print("Email is invalid!")
             continue
@@ -98,34 +118,11 @@ def check_conf():
         if is_correct == "y" or not is_correct:
             cant_continue = False
 
-    yaml_conf = {
-        "admin": {
-            "name": name,
-            "username": username,
-            "email": email,
-        },
-        "upload": {
-            "allowed-extensions": {
-                "jpg": "jpeg",
-                "jpeg": "jpeg",
-                "png": "png",
-                "webp": "webp",
-            },
-            "max-size": 69,
-            "max-load": 50,
-            "rename": "GWA_{{username}}_{{time}}",
-        },
-        "website": {
-            "name": "OnlyLegs",
-            "motto": "A gallery built for fast and simple image management!",
-            "language": "en",
-        },
-    }
+    config["admin"]["username"] = username
+    config["admin"]["email"] = email
 
-    with open(
-        os.path.join(APPLICATION_ROOT, "conf.yml"), encoding="utf-8", mode="w+"
-    ) as file:
-        yaml.dump(yaml_conf, file, default_flow_style=False)
+    with open(config_file, encoding="utf-8", mode="w+") as file:
+        yaml.dump(config, file, default_flow_style=False)
 
     print(
         "####################################################",
diff --git a/onlylegs/views/settings.py b/onlylegs/views/settings.py
index 7bf360e..092ed45 100644
--- a/onlylegs/views/settings.py
+++ b/onlylegs/views/settings.py
@@ -5,6 +5,7 @@ import os
 import pathlib
 import re
 import logging
+import yaml
 from colorthief import ColorThief
 from flask import (
     Blueprint,
@@ -19,6 +20,7 @@ from flask_login import login_required, current_user
 from werkzeug.security import check_password_hash, generate_password_hash
 from onlylegs.extensions import db
 from onlylegs.models import Users
+from onlylegs.config import APPLICATION_ROOT
 
 
 blueprint = Blueprint("settings", __name__, url_prefix="/settings")
@@ -157,3 +159,33 @@ def account_password():
 
     flash(["Password changed! You must login now", 0])
     return redirect(url_for("auth.logout"))
+
+
+@blueprint.route("/website/style", methods=["POST"])
+@login_required
+def website_style():
+    config_file = os.path.join(APPLICATION_ROOT, "conf.yml")
+
+    website_hue = request.form.get("hue", 69, type=int)
+    website_saturation = request.form.get("saturation", 25, type=int)
+    website_rad = request.form.get("rad", "0.4rem").strip()
+    website_force_styling = request.form.get("force", False)
+
+    config = None
+    with open(config_file, "r") as file:
+        config = yaml.safe_load(file)
+
+    current_app.config["WEBSITE_CONF"]["styling"]["hue"] = website_hue
+    current_app.config["WEBSITE_CONF"]["styling"]["saturation"] = website_saturation
+    current_app.config["WEBSITE_CONF"]["styling"]["rad"] = website_rad
+    current_app.config["WEBSITE_CONF"]["styling"]["force"] = website_force_styling
+
+    config["website"]["styling"]["hue"] = website_hue
+    config["website"]["styling"]["saturation"] = website_saturation
+    config["website"]["styling"]["rad"] = website_rad
+    config["website"]["styling"]["force"] = website_force_styling
+
+    with open(config_file, "w") as file:
+        yaml.dump(config, file)
+
+    return "Website style changed", 200