created space for more settings
This commit is contained in:
108
static/css/settings.css
Normal file
108
static/css/settings.css
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/* TEST - This should make all nav links bright red */
|
||||||
|
.card-header-tabs .nav-link {
|
||||||
|
color: var(--text-muted);
|
||||||
|
background: var(--bg-light);
|
||||||
|
border: none;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
border-radius: 0.375rem 0.375rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bs-nav-link-color: var(--text-muted) !important;
|
||||||
|
--bs-nav-link-hover-color: var(--primary-color) !important;
|
||||||
|
--bs-nav-link-disabled-color: var(--text-muted) !important;
|
||||||
|
--bs-nav-link-active-color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override base nav-link styles */
|
||||||
|
.card-header-tabs .nav-link,
|
||||||
|
.card-header-tabs .nav-link:focus,
|
||||||
|
.card-header-tabs .nav-link:hover,
|
||||||
|
.card-header-tabs .nav-link.active,
|
||||||
|
.card-header-tabs .nav-item.show .nav-link {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-tabs .nav-link:hover,
|
||||||
|
.card-header-tabs .nav-link:focus,
|
||||||
|
.card-header-tabs .nav-link.active,
|
||||||
|
.card-header-tabs .nav-item.show .nav-link {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs {
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-tabs .nav-link:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background: var(--primary-bg-light);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-tabs .nav-link.active {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background: var(--white);
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid var(--primary-color);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-tabs .nav-link i {
|
||||||
|
width: 1.25rem;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--primary-color);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-tabs .nav-link.active i {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-tabs {
|
||||||
|
margin: -0.5rem -1rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 0 0 0.375rem 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override Bootstrap's default tab colors */
|
||||||
|
.card-header-tabs .nav-link:focus,
|
||||||
|
.card-header-tabs .nav-link:hover {
|
||||||
|
border-color: transparent;
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-tabs .nav-link.active,
|
||||||
|
.card-header-tabs .nav-item.show .nav-link {
|
||||||
|
background-color: var(--white);
|
||||||
|
border-color: transparent transparent var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override Bootstrap's default text colors */
|
||||||
|
.nav-tabs .nav-link,
|
||||||
|
.nav-tabs .nav-link:focus,
|
||||||
|
.nav-tabs .nav-link:hover,
|
||||||
|
.nav-tabs .nav-link.active,
|
||||||
|
.nav-tabs .nav-item.show .nav-link {
|
||||||
|
color: var(--text-muted) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link:hover,
|
||||||
|
.nav-tabs .nav-link.active,
|
||||||
|
.nav-tabs .nav-item.show .nav-link {
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
138
static/js/settings.js
Normal file
138
static/js/settings.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const primaryColorInput = document.getElementById('primaryColor');
|
||||||
|
const secondaryColorInput = document.getElementById('secondaryColor');
|
||||||
|
|
||||||
|
// Color manipulation functions
|
||||||
|
function hexToRgb(hex) {
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return result ? {
|
||||||
|
r: parseInt(result[1], 16),
|
||||||
|
g: parseInt(result[2], 16),
|
||||||
|
b: parseInt(result[3], 16)
|
||||||
|
} : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHex(r, g, b) {
|
||||||
|
return '#' + [r, g, b].map(x => {
|
||||||
|
const hex = x.toString(16);
|
||||||
|
return hex.length === 1 ? '0' + hex : hex;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function lightenColor(color, amount) {
|
||||||
|
const rgb = hexToRgb(color);
|
||||||
|
if (!rgb) return color;
|
||||||
|
|
||||||
|
return rgbToHex(
|
||||||
|
Math.min(255, Math.round(rgb.r + (255 - rgb.r) * amount)),
|
||||||
|
Math.min(255, Math.round(rgb.g + (255 - rgb.g) * amount)),
|
||||||
|
Math.min(255, Math.round(rgb.b + (255 - rgb.b) * amount))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function darkenColor(color, amount) {
|
||||||
|
const rgb = hexToRgb(color);
|
||||||
|
if (!rgb) return color;
|
||||||
|
|
||||||
|
return rgbToHex(
|
||||||
|
Math.max(0, Math.round(rgb.r * (1 - amount))),
|
||||||
|
Math.max(0, Math.round(rgb.g * (1 - amount))),
|
||||||
|
Math.max(0, Math.round(rgb.b * (1 - amount)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAllColors(color, isPrimary) {
|
||||||
|
const prefix = isPrimary ? 'primary' : 'secondary';
|
||||||
|
|
||||||
|
// Calculate derived colors
|
||||||
|
const lightColor = lightenColor(color, 0.15);
|
||||||
|
const bgLightColor = lightenColor(color, 0.9);
|
||||||
|
const opacity15 = color + '26'; // 15% opacity in hex
|
||||||
|
|
||||||
|
// Calculate chart colors
|
||||||
|
const chartColors = {
|
||||||
|
base: color,
|
||||||
|
light: lightenColor(color, 0.2),
|
||||||
|
lighter: lightenColor(color, 0.4),
|
||||||
|
lightest: lightenColor(color, 0.6),
|
||||||
|
pale: lightenColor(color, 0.8)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update CSS variables for the entire website
|
||||||
|
document.documentElement.style.setProperty(`--${prefix}-color`, color);
|
||||||
|
document.documentElement.style.setProperty(`--${prefix}-light`, lightColor);
|
||||||
|
document.documentElement.style.setProperty(`--${prefix}-bg-light`, bgLightColor);
|
||||||
|
document.documentElement.style.setProperty(`--${prefix}-opacity-15`, opacity15);
|
||||||
|
|
||||||
|
// Update chart color variables
|
||||||
|
document.documentElement.style.setProperty(`--chart-${prefix}`, chartColors.base);
|
||||||
|
document.documentElement.style.setProperty(`--chart-${prefix}-light`, chartColors.light);
|
||||||
|
document.documentElement.style.setProperty(`--chart-${prefix}-lighter`, chartColors.lighter);
|
||||||
|
document.documentElement.style.setProperty(`--chart-${prefix}-lightest`, chartColors.lightest);
|
||||||
|
document.documentElement.style.setProperty(`--chart-${prefix}-pale`, chartColors.pale);
|
||||||
|
|
||||||
|
// Update derived colors in the preview section
|
||||||
|
const derivedColorsSection = document.getElementById(`${prefix}DerivedColors`);
|
||||||
|
if (derivedColorsSection) {
|
||||||
|
const previews = derivedColorsSection.querySelectorAll('.color-preview');
|
||||||
|
previews.forEach(preview => {
|
||||||
|
const colorType = preview.getAttribute('data-color-type');
|
||||||
|
let previewColor;
|
||||||
|
switch (colorType) {
|
||||||
|
case 'base':
|
||||||
|
previewColor = color;
|
||||||
|
break;
|
||||||
|
case 'light':
|
||||||
|
previewColor = lightColor;
|
||||||
|
break;
|
||||||
|
case 'bg-light':
|
||||||
|
previewColor = bgLightColor;
|
||||||
|
break;
|
||||||
|
case 'opacity':
|
||||||
|
previewColor = opacity15;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
preview.style.backgroundColor = previewColor;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the color input value
|
||||||
|
if (isPrimary) {
|
||||||
|
primaryColorInput.value = color;
|
||||||
|
} else {
|
||||||
|
secondaryColorInput.value = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners for color inputs
|
||||||
|
primaryColorInput.addEventListener('change', () => {
|
||||||
|
updateAllColors(primaryColorInput.value, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
secondaryColorInput.addEventListener('change', () => {
|
||||||
|
updateAllColors(secondaryColorInput.value, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also listen for input events for real-time updates
|
||||||
|
primaryColorInput.addEventListener('input', () => {
|
||||||
|
updateAllColors(primaryColorInput.value, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
secondaryColorInput.addEventListener('input', () => {
|
||||||
|
updateAllColors(secondaryColorInput.value, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize colors from database values
|
||||||
|
function initializeColors() {
|
||||||
|
// Reset inputs to original database values
|
||||||
|
primaryColorInput.value = primaryColorInput.dataset.originalValue;
|
||||||
|
secondaryColorInput.value = secondaryColorInput.dataset.originalValue;
|
||||||
|
|
||||||
|
// Update all color previews
|
||||||
|
updateAllColors(primaryColorInput.value, true);
|
||||||
|
updateAllColors(secondaryColorInput.value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize colors when the page loads
|
||||||
|
initializeColors();
|
||||||
|
});
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
button_icon="fa-arrow-left"
|
button_icon="fa-arrow-left"
|
||||||
) }}
|
) }}
|
||||||
|
|
||||||
<div class="container mt-4">
|
<div class="container-fluid mt-4">
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="card shadow-sm mb-4">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center bg-white">
|
<div class="card-header d-flex justify-content-between align-items-center bg-white">
|
||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-3">
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
<button id="clearSearchBtn" type="button" style="position: absolute; right: 0.25rem; top: 50%; transform: translateY(-50%); display: none; border: none; background: transparent; font-size: 1.2rem; color: #888; cursor: pointer; z-index: 2;">×</button>
|
<button id="clearSearchBtn" type="button" style="position: absolute; right: 0.25rem; top: 50%; transform: translateY(-50%); display: none; border: none; background: transparent; font-size: 1.2rem; color: #888; cursor: pointer; z-index: 2;">×</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="fileGrid" class="row g-3" style="min-height: 200px; position: relative;">
|
<div id="fileGrid" class="row g-3" style="min-height: 200px; position: relative; margin-top: 0.5rem;">
|
||||||
<div id="dropZoneOverlay" style="display: none; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(22, 118, 123, 0.1); border: 2px dashed #16767b; border-radius: 8px; z-index: 1000; pointer-events: none;">
|
<div id="dropZoneOverlay" style="display: none; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(22, 118, 123, 0.1); border: 2px dashed #16767b; border-radius: 8px; z-index: 1000; pointer-events: none;">
|
||||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #16767b;">
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #16767b;">
|
||||||
<i class="fas fa-cloud-upload-alt" style="font-size: 3rem; margin-bottom: 1rem;"></i>
|
<i class="fas fa-cloud-upload-alt" style="font-size: 3rem; margin-bottom: 1rem;"></i>
|
||||||
@@ -266,11 +266,13 @@
|
|||||||
#fileGrid.list-view {
|
#fileGrid.list-view {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
#fileGrid.list-view table {
|
#fileGrid.list-view table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
background: var(--white);
|
background: var(--white);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
#fileGrid.list-view th, #fileGrid.list-view td {
|
#fileGrid.list-view th, #fileGrid.list-view td {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
@@ -283,6 +285,9 @@
|
|||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
#fileGrid.list-view tr:hover td {
|
#fileGrid.list-view tr:hover td {
|
||||||
background-color: var(--primary-opacity-8);
|
background-color: var(--primary-opacity-8);
|
||||||
|
|||||||
34
templates/settings/components/reset_colors_modal.html
Normal file
34
templates/settings/components/reset_colors_modal.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{% macro reset_colors_modal(csrf_token) %}
|
||||||
|
<div id="resetColorsModal" class="modal fade" tabindex="-1" aria-labelledby="resetColorsModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="resetColorsModalLabel">Reset Colors</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="d-flex align-items-center gap-3 mb-3">
|
||||||
|
<i class="fas fa-exclamation-triangle text-warning" style="font-size: 2rem;"></i>
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-1">Are you sure you want to reset the colors?</h6>
|
||||||
|
<p class="text-muted mb-0">This will restore the default theme colors.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
The primary color will be reset to #16767b and the secondary color to #741b5f.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<form method="POST" action="{{ url_for('main.reset_colors') }}" class="d-inline">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||||
|
<button type="submit" class="btn btn-warning">
|
||||||
|
<i class="fas fa-undo me-1"></i> Reset Colors
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
{% extends "common/base.html" %}
|
{% extends "common/base.html" %}
|
||||||
{% from "components/header.html" import header %}
|
{% from "components/header.html" import header %}
|
||||||
|
{% from "settings/tabs/colors.html" import colors_tab %}
|
||||||
|
{% from "settings/tabs/general.html" import general_tab %}
|
||||||
|
{% from "settings/tabs/security.html" import security_tab %}
|
||||||
|
{% from "settings/components/reset_colors_modal.html" import reset_colors_modal %}
|
||||||
|
|
||||||
{% block title %}Settings - DocuPulse{% endblock %}
|
{% block title %}Settings - DocuPulse{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/settings.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ header(
|
{{ header(
|
||||||
title="Settings",
|
title="Settings",
|
||||||
@@ -16,70 +24,40 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-header bg-white">
|
||||||
<h5 class="card-title mb-0">Theme Colors</h5>
|
<ul class="nav nav-tabs card-header-tabs" id="settingsTabs" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="colors-tab" data-bs-toggle="tab" data-bs-target="#colors" type="button" role="tab" aria-controls="colors" aria-selected="true">
|
||||||
|
<i class="fas fa-palette me-2"></i>Theme Colors
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="general-tab" data-bs-toggle="tab" data-bs-target="#general" type="button" role="tab" aria-controls="general" aria-selected="false">
|
||||||
|
<i class="fas fa-sliders me-2"></i>General
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="security-tab" data-bs-toggle="tab" data-bs-target="#security" type="button" role="tab" aria-controls="security" aria-selected="false">
|
||||||
|
<i class="fas fa-shield-alt me-2"></i>Security
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="colorSettingsForm" method="POST" action="{{ url_for('main.update_colors') }}">
|
<div class="tab-content" id="settingsTabsContent">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
<!-- Colors Tab -->
|
||||||
|
<div class="tab-pane fade show active" id="colors" role="tabpanel" aria-labelledby="colors-tab">
|
||||||
|
{{ colors_tab(primary_color, secondary_color, csrf_token) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Primary Color Section -->
|
<!-- General Tab -->
|
||||||
<div class="mb-5">
|
<div class="tab-pane fade" id="general" role="tabpanel" aria-labelledby="general-tab">
|
||||||
<h6 class="mb-3">Primary Color</h6>
|
{{ general_tab() }}
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card h-100">
|
|
||||||
<div class="card-body">
|
|
||||||
<label class="form-label">Main Color</label>
|
|
||||||
<div class="d-flex align-items-center gap-3 mb-3">
|
|
||||||
<input type="color"
|
|
||||||
class="form-control form-control-color d-none"
|
|
||||||
id="primaryColor"
|
|
||||||
name="primary_color"
|
|
||||||
value="{{ primary_color }}"
|
|
||||||
data-original-value="{{ primary_color }}">
|
|
||||||
<button type="button"
|
|
||||||
class="btn btn-outline-secondary"
|
|
||||||
onclick="document.getElementById('primaryColor').click()">
|
|
||||||
<i class="fas fa-palette me-1"></i> Choose Color
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<small class="text-muted">Used for primary buttons, links, and accents</small>
|
|
||||||
</div>
|
<!-- Security Tab -->
|
||||||
</div>
|
<div class="tab-pane fade" id="security" role="tabpanel" aria-labelledby="security-tab">
|
||||||
</div>
|
{{ security_tab() }}
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="card h-100">
|
|
||||||
<div class="card-body">
|
|
||||||
<label class="form-label">Derived Colors</label>
|
|
||||||
<div class="d-flex gap-3" id="primaryDerivedColors">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
|
||||||
data-color-type="base"
|
|
||||||
style="background-color: var(--primary-color); width: 80px; height: 80px;">
|
|
||||||
</div>
|
|
||||||
<small class="d-block text-muted">Base</small>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
|
||||||
data-color-type="light"
|
|
||||||
style="background-color: var(--primary-light); width: 80px; height: 80px;">
|
|
||||||
</div>
|
|
||||||
<small class="d-block text-muted">Light</small>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
|
||||||
data-color-type="bg-light"
|
|
||||||
style="background-color: var(--primary-bg-light); width: 80px; height: 80px;">
|
|
||||||
</div>
|
|
||||||
<small class="d-block text-muted">Background</small>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
|
||||||
data-color-type="opacity"
|
|
||||||
style="background-color: var(--primary-opacity-15); width: 80px; height: 80px;">
|
|
||||||
</div>
|
|
||||||
<small class="d-block text-muted">Opacity 15%</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,241 +66,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Secondary Color Section -->
|
{{ reset_colors_modal(csrf_token) }}
|
||||||
<div class="mb-5">
|
|
||||||
<h6 class="mb-3">Secondary Color</h6>
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card h-100">
|
|
||||||
<div class="card-body">
|
|
||||||
<label class="form-label">Main Color</label>
|
|
||||||
<div class="d-flex align-items-center gap-3 mb-3">
|
|
||||||
<input type="color"
|
|
||||||
class="form-control form-control-color d-none"
|
|
||||||
id="secondaryColor"
|
|
||||||
name="secondary_color"
|
|
||||||
value="{{ secondary_color }}"
|
|
||||||
data-original-value="{{ secondary_color }}">
|
|
||||||
<button type="button"
|
|
||||||
class="btn btn-outline-secondary"
|
|
||||||
onclick="document.getElementById('secondaryColor').click()">
|
|
||||||
<i class="fas fa-palette me-1"></i> Choose Color
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
{% endblock %}
|
||||||
<small class="text-muted">Used for secondary elements and highlights</small>
|
|
||||||
</div>
|
{% block extra_js %}
|
||||||
</div>
|
<script src="{{ url_for('static', filename='js/settings.js') }}"></script>
|
||||||
</div>
|
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="card h-100">
|
|
||||||
<div class="card-body">
|
|
||||||
<label class="form-label">Derived Colors</label>
|
|
||||||
<div class="d-flex gap-3" id="secondaryDerivedColors">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
|
||||||
data-color-type="base"
|
|
||||||
style="background-color: var(--secondary-color); width: 80px; height: 80px;">
|
|
||||||
</div>
|
|
||||||
<small class="d-block text-muted">Base</small>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
|
||||||
data-color-type="light"
|
|
||||||
style="background-color: var(--secondary-light); width: 80px; height: 80px;">
|
|
||||||
</div>
|
|
||||||
<small class="d-block text-muted">Light</small>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
|
||||||
data-color-type="opacity"
|
|
||||||
style="background-color: var(--secondary-opacity-15); width: 80px; height: 80px;">
|
|
||||||
</div>
|
|
||||||
<small class="d-block text-muted">Opacity 15%</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#resetColorsModal">
|
|
||||||
<i class="fas fa-undo me-1"></i> Reset to Defaults
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-primary">
|
|
||||||
<i class="fas fa-save me-1"></i> Save Colors
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Reset Colors Confirmation Modal -->
|
|
||||||
<div id="resetColorsModal" class="modal fade" tabindex="-1" aria-labelledby="resetColorsModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="resetColorsModalLabel">Reset Colors</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="d-flex align-items-center gap-3 mb-3">
|
|
||||||
<i class="fas fa-exclamation-triangle text-warning" style="font-size: 2rem;"></i>
|
|
||||||
<div>
|
|
||||||
<h6 class="mb-1">Are you sure you want to reset the colors?</h6>
|
|
||||||
<p class="text-muted mb-0">This will restore the default theme colors.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
|
||||||
The primary color will be reset to #16767b and the secondary color to #741b5f.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
||||||
<form method="POST" action="{{ url_for('main.reset_colors') }}" class="d-inline">
|
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
||||||
<button type="submit" class="btn btn-warning">
|
|
||||||
<i class="fas fa-undo me-1"></i> Reset Colors
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const primaryColorInput = document.getElementById('primaryColor');
|
|
||||||
const secondaryColorInput = document.getElementById('secondaryColor');
|
|
||||||
|
|
||||||
// Color manipulation functions
|
|
||||||
function hexToRgb(hex) {
|
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
||||||
return result ? {
|
|
||||||
r: parseInt(result[1], 16),
|
|
||||||
g: parseInt(result[2], 16),
|
|
||||||
b: parseInt(result[3], 16)
|
|
||||||
} : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rgbToHex(r, g, b) {
|
|
||||||
return '#' + [r, g, b].map(x => {
|
|
||||||
const hex = x.toString(16);
|
|
||||||
return hex.length === 1 ? '0' + hex : hex;
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function lightenColor(color, amount) {
|
|
||||||
const rgb = hexToRgb(color);
|
|
||||||
if (!rgb) return color;
|
|
||||||
|
|
||||||
return rgbToHex(
|
|
||||||
Math.min(255, Math.round(rgb.r + (255 - rgb.r) * amount)),
|
|
||||||
Math.min(255, Math.round(rgb.g + (255 - rgb.g) * amount)),
|
|
||||||
Math.min(255, Math.round(rgb.b + (255 - rgb.b) * amount))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function darkenColor(color, amount) {
|
|
||||||
const rgb = hexToRgb(color);
|
|
||||||
if (!rgb) return color;
|
|
||||||
|
|
||||||
return rgbToHex(
|
|
||||||
Math.max(0, Math.round(rgb.r * (1 - amount))),
|
|
||||||
Math.max(0, Math.round(rgb.g * (1 - amount))),
|
|
||||||
Math.max(0, Math.round(rgb.b * (1 - amount)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateAllColors(color, isPrimary) {
|
|
||||||
const prefix = isPrimary ? 'primary' : 'secondary';
|
|
||||||
|
|
||||||
// Calculate derived colors
|
|
||||||
const lightColor = lightenColor(color, 0.15);
|
|
||||||
const bgLightColor = lightenColor(color, 0.9);
|
|
||||||
const opacity15 = color + '26'; // 15% opacity in hex
|
|
||||||
|
|
||||||
// Calculate chart colors
|
|
||||||
const chartColors = {
|
|
||||||
base: color,
|
|
||||||
light: lightenColor(color, 0.2),
|
|
||||||
lighter: lightenColor(color, 0.4),
|
|
||||||
lightest: lightenColor(color, 0.6),
|
|
||||||
pale: lightenColor(color, 0.8)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update CSS variables for the entire website
|
|
||||||
document.documentElement.style.setProperty(`--${prefix}-color`, color);
|
|
||||||
document.documentElement.style.setProperty(`--${prefix}-light`, lightColor);
|
|
||||||
document.documentElement.style.setProperty(`--${prefix}-bg-light`, bgLightColor);
|
|
||||||
document.documentElement.style.setProperty(`--${prefix}-opacity-15`, opacity15);
|
|
||||||
|
|
||||||
// Update chart color variables
|
|
||||||
document.documentElement.style.setProperty(`--chart-${prefix}`, chartColors.base);
|
|
||||||
document.documentElement.style.setProperty(`--chart-${prefix}-light`, chartColors.light);
|
|
||||||
document.documentElement.style.setProperty(`--chart-${prefix}-lighter`, chartColors.lighter);
|
|
||||||
document.documentElement.style.setProperty(`--chart-${prefix}-lightest`, chartColors.lightest);
|
|
||||||
document.documentElement.style.setProperty(`--chart-${prefix}-pale`, chartColors.pale);
|
|
||||||
|
|
||||||
// Update derived colors in the preview section
|
|
||||||
const derivedColorsSection = document.getElementById(`${prefix}DerivedColors`);
|
|
||||||
if (derivedColorsSection) {
|
|
||||||
const previews = derivedColorsSection.querySelectorAll('.color-preview');
|
|
||||||
previews.forEach(preview => {
|
|
||||||
const colorType = preview.getAttribute('data-color-type');
|
|
||||||
switch (colorType) {
|
|
||||||
case 'base':
|
|
||||||
preview.style.backgroundColor = color;
|
|
||||||
break;
|
|
||||||
case 'light':
|
|
||||||
preview.style.backgroundColor = lightColor;
|
|
||||||
break;
|
|
||||||
case 'bg-light':
|
|
||||||
preview.style.backgroundColor = bgLightColor;
|
|
||||||
break;
|
|
||||||
case 'opacity':
|
|
||||||
preview.style.backgroundColor = opacity15;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the color input value
|
|
||||||
if (isPrimary) {
|
|
||||||
primaryColorInput.value = color;
|
|
||||||
} else {
|
|
||||||
secondaryColorInput.value = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event listeners for color inputs
|
|
||||||
primaryColorInput.addEventListener('input', () => {
|
|
||||||
updateAllColors(primaryColorInput.value, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
secondaryColorInput.addEventListener('input', () => {
|
|
||||||
updateAllColors(secondaryColorInput.value, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize colors from database values
|
|
||||||
function initializeColors() {
|
|
||||||
// Reset inputs to original database values
|
|
||||||
primaryColorInput.value = primaryColorInput.dataset.originalValue;
|
|
||||||
secondaryColorInput.value = secondaryColorInput.dataset.originalValue;
|
|
||||||
|
|
||||||
// Update all color previews
|
|
||||||
updateAllColors(primaryColorInput.value, true);
|
|
||||||
updateAllColors(secondaryColorInput.value, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize colors when the page loads
|
|
||||||
initializeColors();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
137
templates/settings/tabs/colors.html
Normal file
137
templates/settings/tabs/colors.html
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
{% macro colors_tab(primary_color, secondary_color, csrf_token) %}
|
||||||
|
<form id="colorSettingsForm" method="POST" action="{{ url_for('main.update_colors') }}">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||||
|
|
||||||
|
<!-- Primary Color Section -->
|
||||||
|
<div class="mb-5">
|
||||||
|
<h6 class="mb-3">Primary Color</h6>
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<label class="form-label">Main Color</label>
|
||||||
|
<div class="d-flex align-items-center gap-3 mb-3">
|
||||||
|
<input type="color"
|
||||||
|
class="form-control form-control-color d-none"
|
||||||
|
id="primaryColor"
|
||||||
|
name="primary_color"
|
||||||
|
value="{{ primary_color }}"
|
||||||
|
data-original-value="{{ primary_color }}">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
onclick="document.getElementById('primaryColor').click()">
|
||||||
|
<i class="fas fa-palette me-1"></i> Choose Color
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">Used for primary buttons, links, and accents</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<label class="form-label">Derived Colors</label>
|
||||||
|
<div class="d-flex gap-3" id="primaryDerivedColors">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
||||||
|
data-color-type="base"
|
||||||
|
style="background-color: var(--primary-color); width: 80px; height: 80px;">
|
||||||
|
</div>
|
||||||
|
<small class="d-block text-muted">Base</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
||||||
|
data-color-type="light"
|
||||||
|
style="background-color: var(--primary-light); width: 80px; height: 80px;">
|
||||||
|
</div>
|
||||||
|
<small class="d-block text-muted">Light</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
||||||
|
data-color-type="bg-light"
|
||||||
|
style="background-color: var(--primary-bg-light); width: 80px; height: 80px;">
|
||||||
|
</div>
|
||||||
|
<small class="d-block text-muted">Background</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
||||||
|
data-color-type="opacity"
|
||||||
|
style="background-color: var(--primary-opacity-15); width: 80px; height: 80px;">
|
||||||
|
</div>
|
||||||
|
<small class="d-block text-muted">Opacity 15%</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Secondary Color Section -->
|
||||||
|
<div class="mb-5">
|
||||||
|
<h6 class="mb-3">Secondary Color</h6>
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<label class="form-label">Main Color</label>
|
||||||
|
<div class="d-flex align-items-center gap-3 mb-3">
|
||||||
|
<input type="color"
|
||||||
|
class="form-control form-control-color d-none"
|
||||||
|
id="secondaryColor"
|
||||||
|
name="secondary_color"
|
||||||
|
value="{{ secondary_color }}"
|
||||||
|
data-original-value="{{ secondary_color }}">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
onclick="document.getElementById('secondaryColor').click()">
|
||||||
|
<i class="fas fa-palette me-1"></i> Choose Color
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">Used for secondary elements and highlights</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<label class="form-label">Derived Colors</label>
|
||||||
|
<div class="d-flex gap-3" id="secondaryDerivedColors">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
||||||
|
data-color-type="base"
|
||||||
|
style="background-color: var(--secondary-color); width: 80px; height: 80px;">
|
||||||
|
</div>
|
||||||
|
<small class="d-block text-muted">Base</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
||||||
|
data-color-type="light"
|
||||||
|
style="background-color: var(--secondary-light); width: 80px; height: 80px;">
|
||||||
|
</div>
|
||||||
|
<small class="d-block text-muted">Light</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="color-preview p-3 rounded shadow-sm mb-2"
|
||||||
|
data-color-type="opacity"
|
||||||
|
style="background-color: var(--secondary-opacity-15); width: 80px; height: 80px;">
|
||||||
|
</div>
|
||||||
|
<small class="d-block text-muted">Opacity 15%</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-end gap-2">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#resetColorsModal">
|
||||||
|
<i class="fas fa-undo me-1"></i> Reset to Defaults
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i> Save Colors
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endmacro %}
|
||||||
6
templates/settings/tabs/general.html
Normal file
6
templates/settings/tabs/general.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% macro general_tab() %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
General settings will be available soon.
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
6
templates/settings/tabs/security.html
Normal file
6
templates/settings/tabs/security.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% macro security_tab() %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
Security settings will be available soon.
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
Reference in New Issue
Block a user