dynamic colours in website settings

This commit is contained in:
2025-05-26 08:52:04 +02:00
parent 3fe3037aed
commit 026b899db3
19 changed files with 617 additions and 119 deletions

Binary file not shown.

View File

@@ -0,0 +1,42 @@
"""Add site_settings table
Revision ID: 9faab7ef6036
Revises: ca9026520dad
Create Date: 2025-05-25 21:16:39.683736
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '9faab7ef6036'
down_revision = 'ca9026520dad'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('site_settings',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('primary_color', sa.String(length=7), nullable=True),
sa.Column('secondary_color', sa.String(length=7), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.drop_table('color_settings')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('color_settings',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('primary_color', sa.VARCHAR(length=7), autoincrement=False, nullable=True),
sa.Column('secondary_color', sa.VARCHAR(length=7), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('color_settings_pkey'))
)
op.drop_table('site_settings')
# ### end Alembic commands ###

View File

@@ -124,4 +124,19 @@ class TrashedFile(db.Model):
deleter = db.relationship('User', foreign_keys=[deleted_by], backref='deleted_trashed_files') # Changed from deleted_files to deleted_trashed_files deleter = db.relationship('User', foreign_keys=[deleted_by], backref='deleted_trashed_files') # Changed from deleted_files to deleted_trashed_files
def __repr__(self): def __repr__(self):
return f'<TrashedFile {self.name} ({self.type}) from {self.original_path}>' return f'<TrashedFile {self.name} ({self.type}) from {self.original_path}>'
class SiteSettings(db.Model):
id = db.Column(db.Integer, primary_key=True)
primary_color = db.Column(db.String(7), default='#16767b') # Default from colors.css
secondary_color = db.Column(db.String(7), default='#741b5f') # Default from colors.css
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
@classmethod
def get_settings(cls):
settings = cls.query.first()
if not settings:
settings = cls()
db.session.add(settings)
db.session.commit()
return settings

View File

@@ -1,6 +1,6 @@
from flask import render_template, Blueprint, redirect, url_for, request, flash from flask import render_template, Blueprint, redirect, url_for, request, flash, Response
from flask_login import current_user, login_required from flask_login import current_user, login_required
from models import User, db, Room, RoomFile, RoomMemberPermission from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings
import os import os
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from sqlalchemy import func, case, literal_column, text from sqlalchemy import func, case, literal_column, text
@@ -326,4 +326,128 @@ def init_routes(main_bp):
@main_bp.route('/settings') @main_bp.route('/settings')
@login_required @login_required
def settings(): def settings():
return render_template('settings/settings.html') if not current_user.is_admin:
flash('Only administrators can access settings.', 'error')
return redirect(url_for('main.dashboard'))
site_settings = SiteSettings.get_settings()
return render_template('settings/settings.html',
primary_color=site_settings.primary_color,
secondary_color=site_settings.secondary_color)
@main_bp.route('/settings/colors', methods=['POST'])
@login_required
def update_colors():
if not current_user.is_admin:
flash('Only administrators can update settings.', 'error')
return redirect(url_for('main.dashboard'))
primary_color = request.form.get('primary_color')
secondary_color = request.form.get('secondary_color')
if not primary_color or not secondary_color:
flash('Both primary and secondary colors are required.', 'error')
return redirect(url_for('main.settings'))
site_settings = SiteSettings.get_settings()
site_settings.primary_color = primary_color
site_settings.secondary_color = secondary_color
try:
db.session.commit()
flash('Color settings updated successfully!', 'success')
except Exception as e:
db.session.rollback()
flash('An error occurred while updating color settings.', 'error')
return redirect(url_for('main.settings'))
@main_bp.route('/settings/colors/reset', methods=['POST'])
@login_required
def reset_colors():
if not current_user.is_admin:
flash('Only administrators can update settings.', 'error')
return redirect(url_for('main.dashboard'))
site_settings = SiteSettings.get_settings()
site_settings.primary_color = '#16767b' # Default from colors.css
site_settings.secondary_color = '#741b5f' # Default from colors.css
try:
db.session.commit()
flash('Colors reset to defaults successfully!', 'success')
except Exception as e:
db.session.rollback()
flash('An error occurred while resetting colors.', 'error')
return redirect(url_for('main.settings'))
@main_bp.route('/dynamic-colors.css')
def dynamic_colors():
site_settings = SiteSettings.get_settings()
# Calculate derived colors
def hex_to_rgb(hex_color):
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
def rgb_to_hex(rgb):
return '#{:02x}{:02x}{:02x}'.format(rgb[0], rgb[1], rgb[2])
def lighten_color(hex_color, amount):
rgb = hex_to_rgb(hex_color)
return rgb_to_hex(tuple(min(255, int(c + (255 - c) * amount)) for c in rgb))
# Calculate all color variants
primary_light = lighten_color(site_settings.primary_color, 0.15)
primary_bg_light = lighten_color(site_settings.primary_color, 0.9)
primary_opacity_15 = site_settings.primary_color + '26'
secondary_light = lighten_color(site_settings.secondary_color, 0.15)
secondary_bg_light = lighten_color(site_settings.secondary_color, 0.9)
secondary_opacity_15 = site_settings.secondary_color + '26'
# Calculate chart colors
primary_chart_light = lighten_color(site_settings.primary_color, 0.2)
primary_chart_lighter = lighten_color(site_settings.primary_color, 0.4)
primary_chart_lightest = lighten_color(site_settings.primary_color, 0.6)
primary_chart_pale = lighten_color(site_settings.primary_color, 0.8)
secondary_chart_light = lighten_color(site_settings.secondary_color, 0.2)
secondary_chart_lighter = lighten_color(site_settings.secondary_color, 0.4)
secondary_chart_lightest = lighten_color(site_settings.secondary_color, 0.6)
secondary_chart_pale = lighten_color(site_settings.secondary_color, 0.8)
css = f"""
:root {{
/* Primary Colors */
--primary-color: {site_settings.primary_color};
--primary-light: {primary_light};
--primary-bg-light: {primary_bg_light};
--primary-opacity-15: {primary_opacity_15};
/* Secondary Colors */
--secondary-color: {site_settings.secondary_color};
--secondary-light: {secondary_light};
--secondary-bg-light: {secondary_bg_light};
--secondary-opacity-15: {secondary_opacity_15};
/* Chart Colors */
--chart-primary: {site_settings.primary_color};
--chart-secondary: {site_settings.secondary_color};
--chart-warning: #ffd700;
/* Primary Chart Colors */
--chart-primary-light: {primary_chart_light};
--chart-primary-lighter: {primary_chart_lighter};
--chart-primary-lightest: {primary_chart_lightest};
--chart-primary-pale: {primary_chart_pale};
/* Secondary Chart Colors */
--chart-secondary-light: {secondary_chart_light};
--chart-secondary-lighter: {secondary_chart_lighter};
--chart-secondary-lightest: {secondary_chart_lightest};
--chart-secondary-pale: {secondary_chart_pale};
}}
"""
return Response(css, mimetype='text/css')

View File

@@ -39,9 +39,9 @@
height: 32px; height: 32px;
border-radius: 5px; border-radius: 5px;
padding: 0 18px; padding: 0 18px;
background-color: rgba(22,118,123,0.08); background-color: var(--primary-opacity-8);
color: #16767b; color: var(--primary-color);
border: 1px solid #16767b22; border: 1px solid var(--primary-opacity-15);
} }
.btn-save-member { .btn-save-member {

View File

@@ -1,11 +1,11 @@
.btn-group .btn.active { .btn-group .btn.active {
background-color: #e6f3f4 !important; background-color: var(--primary-bg-light) !important;
border-color: #16767b !important; border-color: var(--primary-color) !important;
color: #16767b !important; color: var(--primary-color) !important;
} }
.btn-group .btn:not(.active) { .btn-group .btn:not(.active) {
background-color: #fff !important; background-color: var(--white) !important;
border-color: #e9ecef !important; border-color: var(--border-light) !important;
color: #6c757d !important; color: var(--text-muted) !important;
} }

View File

@@ -126,8 +126,8 @@ function renderFiles(files) {
</tr></thead><tbody>`; </tr></thead><tbody>`;
files.forEach((file, idx) => { files.forEach((file, idx) => {
let icon = file.type === 'folder' let icon = file.type === 'folder'
? `<i class='fas fa-folder' style='font-size:1.5rem;color:#16767b;'></i>` ? `<i class='fas fa-folder' style='font-size:1.5rem;color:var(--primary-color);'></i>`
: `<i class='fas fa-file-alt' style='font-size:1.5rem;color:#741b5f;'></i>`; : `<i class='fas fa-file-alt' style='font-size:1.5rem;color:var(--secondary-color);'></i>`;
let size = file.size !== '-' ? (file.size > 0 ? (file.size < 1024*1024 ? (file.size/1024).toFixed(1)+' KB' : (file.size/1024/1024).toFixed(2)+' MB') : '0 KB') : '-'; let size = file.size !== '-' ? (file.size > 0 ? (file.size < 1024*1024 ? (file.size/1024).toFixed(1)+' KB' : (file.size/1024/1024).toFixed(2)+' MB') : '0 KB') : '-';
let actionsArr = []; let actionsArr = [];
let dblClickAction = ''; let dblClickAction = '';
@@ -138,16 +138,16 @@ function renderFiles(files) {
} }
if (isTrashPage) { if (isTrashPage) {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Restore' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='event.stopPropagation();restoreFile("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-undo'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Restore' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='event.stopPropagation();restoreFile("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-undo'></i></button>`);
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete Permanently' style='background-color:rgba(220,53,69,0.08);color:#dc3545;' onclick='event.stopPropagation();showPermanentDeleteModal("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-trash'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete Permanently' style='background-color:var(--danger-opacity-15);color:var(--danger-color);' onclick='event.stopPropagation();showPermanentDeleteModal("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-trash'></i></button>`);
} else { } else {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'rgba(255,215,0,0.15)' : 'rgba(22,118,123,0.08)'};color:${file.starred ? '#ffd700' : '#16767b'};' onclick='event.stopPropagation();toggleStar("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-star'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'var(--warning-opacity-15)' : 'var(--primary-opacity-8)'};color:${file.starred ? 'var(--warning-color)' : 'var(--primary-color)'};' onclick='event.stopPropagation();toggleStar("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-star'></i></button>`);
} }
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='event.stopPropagation();showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='event.stopPropagation();showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`);
const actions = actionsArr.join(''); const actions = actionsArr.join('');
table += `<tr ${dblClickAction} onclick='navigateToFile(${file.room_id}, "${file.name}", "${file.path}", "${file.type}")'> table += `<tr ${dblClickAction} onclick='navigateToFile(${file.room_id}, "${file.name}", "${file.path}", "${file.type}")'>
<td class='file-icon'><span style="display:inline-flex;align-items:center;gap:1.2rem;">${icon}</span></td> <td class='file-icon'><span style="display:inline-flex;align-items:center;gap:1.2rem;">${icon}</span></td>
<td class='room-name'><button class='btn btn-sm' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='event.stopPropagation();window.location.href="/rooms/${file.room_id}"'><i class='fas fa-door-open me-1'></i>${file.room_name || 'Room ' + file.room_id}</button></td> <td class='room-name'><button class='btn btn-sm' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='event.stopPropagation();window.location.href="/rooms/${file.room_id}"'><i class='fas fa-door-open me-1'></i>${file.room_name || 'Room ' + file.room_id}</button></td>
<td class='file-name' title='${file.name}'>${file.name}</td> <td class='file-name' title='${file.name}'>${file.name}</td>
<td class='file-date'>${formatDate(file.modified)}</td> <td class='file-date'>${formatDate(file.modified)}</td>
<td class='file-type'>${file.type}</td> <td class='file-type'>${file.type}</td>
@@ -161,8 +161,8 @@ function renderFiles(files) {
} else { } else {
files.forEach((file, idx) => { files.forEach((file, idx) => {
let icon = file.type === 'folder' let icon = file.type === 'folder'
? `<i class='fas fa-folder' style='font-size:2.5rem;color:#16767b;'></i>` ? `<i class='fas fa-folder' style='font-size:2.5rem;color:var(--primary-color);'></i>`
: `<i class='fas fa-file-alt' style='font-size:2.5rem;color:#741b5f;'></i>`; : `<i class='fas fa-file-alt' style='font-size:2.5rem;color:var(--secondary-color);'></i>`;
let size = file.size !== '-' ? (file.size > 0 ? (file.size < 1024*1024 ? (file.size/1024).toFixed(1)+' KB' : (file.size/1024/1024).toFixed(2)+' MB') : '0 KB') : '-'; let size = file.size !== '-' ? (file.size > 0 ? (file.size < 1024*1024 ? (file.size/1024).toFixed(1)+' KB' : (file.size/1024/1024).toFixed(2)+' MB') : '0 KB') : '-';
let actionsArr = []; let actionsArr = [];
let dblClickAction = ''; let dblClickAction = '';
@@ -173,12 +173,12 @@ function renderFiles(files) {
} }
if (isTrashPage) { if (isTrashPage) {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Restore' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='event.stopPropagation();restoreFile("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-undo'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Restore' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='event.stopPropagation();restoreFile("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-undo'></i></button>`);
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete Permanently' style='background-color:rgba(220,53,69,0.08);color:#dc3545;' onclick='event.stopPropagation();showPermanentDeleteModal("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-trash'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete Permanently' style='background-color:var(--danger-opacity-15);color:var(--danger-color);' onclick='event.stopPropagation();showPermanentDeleteModal("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-trash'></i></button>`);
} else { } else {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'rgba(255,215,0,0.15)' : 'rgba(22,118,123,0.08)'};color:${file.starred ? '#ffd700' : '#16767b'};' onclick='event.stopPropagation();toggleStar("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-star'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'var(--warning-opacity-15)' : 'var(--primary-opacity-8)'};color:${file.starred ? 'var(--warning-color)' : 'var(--primary-color)'};' onclick='event.stopPropagation();toggleStar("${file.name}", "${file.path}", ${file.room_id})'><i class='fas fa-star'></i></button>`);
} }
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='event.stopPropagation();showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='event.stopPropagation();showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`);
const actions = actionsArr.join(''); const actions = actionsArr.join('');
grid.innerHTML += ` grid.innerHTML += `
<div class='col'> <div class='col'>
@@ -189,7 +189,7 @@ function renderFiles(files) {
<div class='text-muted small'>${formatDate(file.modified)}</div> <div class='text-muted small'>${formatDate(file.modified)}</div>
<div class='text-muted small'>${size}</div> <div class='text-muted small'>${size}</div>
${isTrashPage ? `<div class='text-muted small'>Auto Delete: ${file.auto_delete ? formatDate(file.auto_delete) : 'Never'}</div>` : ''} ${isTrashPage ? `<div class='text-muted small'>Auto Delete: ${file.auto_delete ? formatDate(file.auto_delete) : 'Never'}</div>` : ''}
<div class='mt-2'><button class='btn btn-sm' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='event.stopPropagation();window.location.href="/rooms/${file.room_id}"'><i class='fas fa-door-open me-1'></i>${file.room_name || 'Room ' + file.room_id}</button></div> <div class='mt-2'><button class='btn btn-sm' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='event.stopPropagation();window.location.href="/rooms/${file.room_id}"'><i class='fas fa-door-open me-1'></i>${file.room_name || 'Room ' + file.room_id}</button></div>
</div> </div>
<div class='card-footer bg-white border-0 d-flex justify-content-center gap-2'>${actions}</div> <div class='card-footer bg-white border-0 d-flex justify-content-center gap-2'>${actions}</div>
</div> </div>
@@ -428,8 +428,8 @@ function emptyTrash() {
function showDetailsModal(idx) { function showDetailsModal(idx) {
const item = currentFiles[idx]; const item = currentFiles[idx];
const icon = item.type === 'folder' const icon = item.type === 'folder'
? `<i class='fas fa-folder' style='font-size:2.2rem;color:#16767b;'></i>` ? `<i class='fas fa-folder' style='font-size:2.2rem;color:var(--primary-color);'></i>`
: `<i class='fas fa-file-alt' style='font-size:2.2rem;color:#741b5f;'></i>`; : `<i class='fas fa-file-alt' style='font-size:2.2rem;color:var(--secondary-color);'></i>`;
const uploaderPic = item.uploader_profile_pic const uploaderPic = item.uploader_profile_pic
? `/uploads/profile_pics/${item.uploader_profile_pic}` ? `/uploads/profile_pics/${item.uploader_profile_pic}`
: '/static/default-avatar.png'; : '/static/default-avatar.png';
@@ -446,12 +446,12 @@ function showDetailsModal(idx) {
<span class='fw-semibold' style='font-size:0.98rem;'>${item.uploaded_by || '-'}</span> <span class='fw-semibold' style='font-size:0.98rem;'>${item.uploaded_by || '-'}</span>
</div> </div>
<div class='mb-2 text-muted' style='font-size:0.92rem;'><i class='far fa-clock me-1'></i>${formatDate(item.modified)}</div> <div class='mb-2 text-muted' style='font-size:0.92rem;'><i class='far fa-clock me-1'></i>${formatDate(item.modified)}</div>
<hr style='margin:0.7rem 0 0.5rem 0; border-color:#e6f3f4;'> <hr style='margin:0.7rem 0 0.5rem 0; border-color:var(--primary-bg-light);'>
<div style='font-size:0.91rem;color:#555;'><strong style='color:#16767b;'>Room:</strong> <button class='btn btn-sm' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='window.location.href=\"/room/${item.room_id}\"'><i class='fas fa-door-open me-1'></i>${item.room_name || 'Room ' + item.room_id}</button></div> <div style='font-size:0.91rem;color:var(--text-dark);'><strong style='color:var(--primary-color);'>Room:</strong> <button class='btn btn-sm' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='window.location.href=\"/room/${item.room_id}\"'><i class='fas fa-door-open me-1'></i>${item.room_name || 'Room ' + item.room_id}</button></div>
<div style='font-size:0.91rem;color:#555;'><strong style='color:#16767b;'>Path:</strong> <span style='word-break:break-all;'>${(item.path ? item.path + '/' : '') + item.name}</span></div> <div style='font-size:0.91rem;color:var(--text-dark);'><strong style='color:var(--primary-color);'>Path:</strong> <span style='word-break:break-all;'>${(item.path ? item.path + '/' : '') + item.name}</span></div>
<div style='font-size:0.91rem;color:#555;'><strong style='color:#16767b;'>Size:</strong> ${item.size === '-' ? '-' : (item.size > 0 ? (item.size < 1024*1024 ? (item.size/1024).toFixed(1)+' KB' : (item.size/1024/1024).toFixed(2)+' MB') : '0 KB')}</div> <div style='font-size:0.91rem;color:var(--text-dark);'><strong style='color:var(--primary-color);'>Size:</strong> ${item.size === '-' ? '-' : (item.size > 0 ? (item.size < 1024*1024 ? (item.size/1024).toFixed(1)+' KB' : (item.size/1024/1024).toFixed(2)+' MB') : '0 KB')}</div>
<div style='font-size:0.91rem;color:#555;'><strong style='color:#16767b;'>Uploaded at:</strong> ${item.uploaded_at ? new Date(item.uploaded_at).toLocaleString() : '-'}</div> <div style='font-size:0.91rem;color:var(--text-dark);'><strong style='color:var(--primary-color);'>Uploaded at:</strong> ${item.uploaded_at ? new Date(item.uploaded_at).toLocaleString() : '-'}</div>
${isTrashPage ? `<div style='font-size:0.91rem;color:#555;'><strong style='color:#16767b;'>Auto Delete:</strong> ${item.auto_delete ? formatDate(item.auto_delete) : 'Never'}</div>` : ''} ${isTrashPage ? `<div style='font-size:0.91rem;color:var(--text-dark);'><strong style='color:var(--primary-color);'>Auto Delete:</strong> ${item.auto_delete ? formatDate(item.auto_delete) : 'Never'}</div>` : ''}
`; `;
document.getElementById('detailsModalBody').innerHTML = detailsHtml; document.getElementById('detailsModalBody').innerHTML = detailsHtml;
var modal = new bootstrap.Modal(document.getElementById('detailsModal')); var modal = new bootstrap.Modal(document.getElementById('detailsModal'));

View File

@@ -10,6 +10,8 @@
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="{{ url_for('static', filename='css/file-grid.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='css/file-grid.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/base.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='css/base.css') }}" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/colors.css') }}">
<link rel="stylesheet" href="{{ url_for('main.dynamic_colors') }}">
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
</head> </head>
<body> <body>

View File

@@ -4,7 +4,7 @@
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<div> <div>
<h3 class="mb-0"> <h3 class="mb-0">
<i class="fas {{ icon }} me-2" style="color:#16767b;"></i> <i class="fas {{ icon }} me-2" style="color: var(--primary-color);"></i>
{{ title }} {{ title }}
</h3> </h3>
{% if description %} {% if description %}
@@ -16,7 +16,7 @@
{% if button_url == "#" %} {% if button_url == "#" %}
<button id="emptyTrashBtn" <button id="emptyTrashBtn"
class="btn {{ button_class if button_class else '' }}" class="btn {{ button_class if button_class else '' }}"
style="{{ 'background-color: #16767b; color: white;' if not button_class else '' }}{{ '; ' + button_style if button_style else '' }}"> style="{{ 'background-color: var(--primary-color); color: white;' if not button_class else '' }}{{ '; ' + button_style if button_style else '' }}">
{% if button_icon %} {% if button_icon %}
<i class="fas {{ button_icon }} me-1"></i> <i class="fas {{ button_icon }} me-1"></i>
{% endif %} {% endif %}
@@ -25,7 +25,7 @@
{% else %} {% else %}
<button onclick="window.location.href='{{ button_url }}'" <button onclick="window.location.href='{{ button_url }}'"
class="btn {{ button_class if button_class else '' }}" class="btn {{ button_class if button_class else '' }}"
style="{{ 'background-color: #16767b; color: white;' if not button_class else '' }}{{ '; ' + button_style if button_style else '' }}"> style="{{ 'background-color: var(--primary-color); color: white;' if not button_class else '' }}{{ '; ' + button_style if button_style else '' }}">
{% if button_icon %} {% if button_icon %}
<i class="fas {{ button_icon }} me-1"></i> <i class="fas {{ button_icon }} me-1"></i>
{% endif %} {% endif %}

View File

@@ -16,7 +16,7 @@
{% for type in storage_by_type %} {% for type in storage_by_type %}
<div class="d-flex justify-content-between align-items-center mb-2"> <div class="d-flex justify-content-between align-items-center mb-2">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<i class="fas fa-file me-2 icon-primary"></i> <div class="color-indicator me-2" style="width: 12px; height: 12px; border-radius: 50%; background-color: var(--chart-{% if loop.index0 == 0 %}primary{% elif loop.index0 == 1 %}primary-light{% elif loop.index0 == 2 %}primary-lighter{% elif loop.index0 == 3 %}primary-lightest{% elif loop.index0 == 4 %}primary-pale{% elif loop.index0 == 5 %}secondary{% elif loop.index0 == 6 %}secondary-light{% elif loop.index0 == 7 %}secondary-lighter{% elif loop.index0 == 8 %}secondary-lightest{% else %}secondary-pale{% endif %});"></div>
<span class="text-muted">{{ type.extension|upper }}:</span> <span class="text-muted">{{ type.extension|upper }}:</span>
</div> </div>
<div class="fw-bold text-primary">{{ format_size(type.total_size) }}</div> <div class="fw-bold text-primary">{{ format_size(type.total_size) }}</div>

View File

@@ -14,7 +14,7 @@
{% for type in trash_by_type %} {% for type in trash_by_type %}
<div class="d-flex justify-content-between align-items-center mb-2"> <div class="d-flex justify-content-between align-items-center mb-2">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<i class="fas fa-file me-2 icon-primary"></i> <div class="color-indicator me-2" style="width: 12px; height: 12px; border-radius: 50%; background-color: var(--chart-{% if loop.index0 == 0 %}primary{% elif loop.index0 == 1 %}primary-light{% elif loop.index0 == 2 %}primary-lighter{% elif loop.index0 == 3 %}primary-lightest{% elif loop.index0 == 4 %}primary-pale{% elif loop.index0 == 5 %}secondary{% elif loop.index0 == 6 %}secondary-light{% elif loop.index0 == 7 %}secondary-lighter{% elif loop.index0 == 8 %}secondary-lightest{% else %}secondary-pale{% endif %});"></div>
<span class="text-muted">{{ type.extension|upper }}:</span> <span class="text-muted">{{ type.extension|upper }}:</span>
</div> </div>
<div class="fw-bold text-primary">{{ type.count }}</div> <div class="fw-bold text-primary">{{ type.count }}</div>

View File

@@ -41,9 +41,9 @@
<option value="user" {% if request.args.get('role') == 'user' %}selected{% endif %}>User</option> <option value="user" {% if request.args.get('role') == 'user' %}selected{% endif %}>User</option>
</select> </select>
<button type="button" id="clearFilters" class="px-4 py-2 rounded-lg text-white font-medium transition-colors duration-200" <button type="button" id="clearFilters" class="px-4 py-2 rounded-lg text-white font-medium transition-colors duration-200"
style="background-color: #16767b; border: 1px solid #16767b;" style="background-color: var(--primary-color); border: 1px solid var(--primary-color);"
onmouseover="this.style.backgroundColor='#1a8a90'" onmouseover="this.style.backgroundColor='var(--primary-light)'"
onmouseout="this.style.backgroundColor='#16767b'"> onmouseout="this.style.backgroundColor='var(--primary-color)'">
Clear Clear
</button> </button>
</div> </div>
@@ -132,14 +132,14 @@
<div class="flex flex-row flex-wrap gap-1.5"> <div class="flex flex-row flex-wrap gap-1.5">
<a href="mailto:{{ user.email }}" <a href="mailto:{{ user.email }}"
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm no-underline transition-colors duration-200" class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm no-underline transition-colors duration-200"
style="background-color: rgba(22,118,123,0.08); color: #16767b;"> style="background-color: var(--primary-opacity-8); color: var(--primary-color);">
<i class="fas fa-envelope" style="font-size: 0.85em; opacity: 0.7;"></i> <i class="fas fa-envelope" style="font-size: 0.85em; opacity: 0.7;"></i>
{{ user.email }} {{ user.email }}
</a> </a>
{% if user.phone %} {% if user.phone %}
<a href="tel:{{ user.phone }}" <a href="tel:{{ user.phone }}"
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm no-underline transition-colors duration-200" class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm no-underline transition-colors duration-200"
style="background-color: rgba(22,118,123,0.08); color: #16767b;"> style="background-color: var(--primary-opacity-8); color: var(--primary-color);">
<i class="fas fa-phone" style="font-size: 0.85em; opacity: 0.7;"></i> <i class="fas fa-phone" style="font-size: 0.85em; opacity: 0.7;"></i>
{{ user.phone }} {{ user.phone }}
</a> </a>
@@ -182,7 +182,7 @@
<div class="flex justify-end gap-1.5"> <div class="flex justify-end gap-1.5">
<a href="{{ url_for('contacts.edit_contact', id=user.id) }}" <a href="{{ url_for('contacts.edit_contact', id=user.id) }}"
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm no-underline transition-colors duration-200" class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm no-underline transition-colors duration-200"
style="background-color: rgba(22,118,123,0.08); color: #16767b;"> style="background-color: var(--primary-opacity-8); color: var(--primary-color);">
<i class="fas fa-edit" style="font-size: 0.85em; opacity: 0.7;"></i> <i class="fas fa-edit" style="font-size: 0.85em; opacity: 0.7;"></i>
Edit Edit
</a> </a>

View File

@@ -5,6 +5,7 @@
{% block extra_css %} {% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/dashboard.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/dashboard.css') }}">
<link rel="stylesheet" href="{{ url_for('main.dynamic_colors') }}">
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -31,28 +31,28 @@
</div> </div>
<div class="d-flex gap-2" id="actionButtonsRow"> <div class="d-flex gap-2" id="actionButtonsRow">
{% if current_user.is_admin %} {% if current_user.is_admin %}
<a href="{{ url_for('rooms.room_members', room_id=room.id) }}" class="btn btn-outline-primary d-flex align-items-center gap-2" style="border-color:#16767b; color:#16767b;" onmouseover="this.style.backgroundColor='#16767b'; this.style.color='white'" onmouseout="this.style.backgroundColor='transparent'; this.style.color='#16767b'"> <a href="{{ url_for('rooms.room_members', room_id=room.id) }}" class="btn btn-outline-primary d-flex align-items-center gap-2" style="border-color:var(--primary-color); color:var(--primary-color);" onmouseover="this.style.backgroundColor='var(--primary-color)'; this.style.color='white'" onmouseout="this.style.backgroundColor='transparent'; this.style.color='var(--primary-color)'">
<i class="fas fa-users"></i> Manage Members <i class="fas fa-users"></i> Manage Members
</a> </a>
{% endif %} {% endif %}
{% if current_user.is_admin or can_upload %} {% if current_user.is_admin or can_upload %}
<button type="button" id="newFolderBtn" class="btn btn-outline-primary d-flex align-items-center gap-2" style="border-color:#16767b; color:#16767b;" onmouseover="this.style.backgroundColor='#16767b'; this.style.color='white'" onmouseout="this.style.backgroundColor='transparent'; this.style.color='#16767b'"> <button type="button" id="newFolderBtn" class="btn btn-outline-primary d-flex align-items-center gap-2" style="border-color:var(--primary-color); color:var(--primary-color);" onmouseover="this.style.backgroundColor='var(--primary-color)'; this.style.color='white'" onmouseout="this.style.backgroundColor='transparent'; this.style.color='var(--primary-color)'">
<i class="fas fa-folder-plus"></i> New Folder <i class="fas fa-folder-plus"></i> New Folder
</button> </button>
<form id="uploadForm" enctype="multipart/form-data" class="d-inline"> <form id="uploadForm" enctype="multipart/form-data" class="d-inline">
<input type="file" id="fileInput" name="file" multiple style="display:none;" /> <input type="file" id="fileInput" name="file" multiple style="display:none;" />
<button type="button" id="uploadBtn" class="btn btn-primary d-flex align-items-center gap-2" style="background-color:#16767b; border:1px solid #16767b;" onmouseover="this.style.backgroundColor='#1a8a90'" onmouseout="this.style.backgroundColor='#16767b'"> <button type="button" id="uploadBtn" class="btn btn-primary d-flex align-items-center gap-2" style="background-color:var(--primary-color); border:1px solid var(--primary-color);" onmouseover="this.style.backgroundColor='var(--primary-light)'" onmouseout="this.style.backgroundColor='var(--primary-color)'">
<i class="fas fa-upload"></i> Upload <i class="fas fa-upload"></i> Upload
</button> </button>
</form> </form>
{% endif %} {% endif %}
{% if current_user.is_admin or can_download %} {% if current_user.is_admin or can_download %}
<button id="downloadSelectedBtn" class="btn btn-outline-primary btn-sm d-flex align-items-center gap-2" style="display:none; border-color:#16767b; color:#16767b;" onmouseover="this.style.backgroundColor='#16767b'; this.style.color='white'" onmouseout="this.style.backgroundColor='transparent'; this.style.color='#16767b'"> <button id="downloadSelectedBtn" class="btn btn-outline-primary btn-sm d-flex align-items-center gap-2" style="display:none; border-color:var(--primary-color); color:var(--primary-color);" onmouseover="this.style.backgroundColor='var(--primary-color)'; this.style.color='white'" onmouseout="this.style.backgroundColor='transparent'; this.style.color='var(--primary-color)'">
<i class="fas fa-download"></i> Download Selected <i class="fas fa-download"></i> Download Selected
</button> </button>
{% endif %} {% endif %}
{% if current_user.is_admin or can_delete %} {% if current_user.is_admin or can_delete %}
<button id="deleteSelectedBtn" class="btn btn-outline-danger btn-sm d-flex align-items-center gap-2" style="display:none; border-color:#b91c1c; color:#b91c1c;" onmouseover="this.style.backgroundColor='#b91c1c'; this.style.color='white'" onmouseout="this.style.backgroundColor='transparent'; this.style.color='#b91c1c'"> <button id="deleteSelectedBtn" class="btn btn-outline-danger btn-sm d-flex align-items-center gap-2" style="display:none; border-color:var(--danger-color); color:var(--danger-color);" onmouseover="this.style.backgroundColor='var(--danger-color)'; this.style.color='white'" onmouseout="this.style.backgroundColor='transparent'; this.style.color='var(--danger-color)'">
<i class="fas fa-trash"></i> Delete Selected <i class="fas fa-trash"></i> Delete Selected
</button> </button>
{% endif %} {% endif %}
@@ -60,8 +60,8 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="uploadProgressContainer" style="display:none; margin-bottom: 1rem; width: 100%;"> <div id="uploadProgressContainer" style="display:none; margin-bottom: 1rem; width: 100%;">
<div class="progress" style="height: 1.25rem; background-color: #e6f3f4;"> <div class="progress" style="height: 1.25rem; background-color: var(--primary-bg-light);">
<div id="uploadProgressBar" class="progress-bar" role="progressbar" style="width:0%; background-color:#16767b; color:#fff;">0%</div> <div id="uploadProgressBar" class="progress-bar" role="progressbar" style="width:0%; background-color:var(--primary-color); color:var(--white);">0%</div>
</div> </div>
<div id="uploadProgressText" class="small text-muted mt-1"></div> <div id="uploadProgressText" class="small text-muted mt-1"></div>
<div id="uploadError" class="alert alert-danger alert-dismissible fade show mt-2" style="display:none;" role="alert"> <div id="uploadError" class="alert alert-danger alert-dismissible fade show mt-2" style="display:none;" role="alert">
@@ -101,7 +101,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="d-flex align-items-center gap-3 mb-3"> <div class="d-flex align-items-center gap-3 mb-3">
<i class="fas fa-trash text-danger" style="font-size: 2rem;"></i> <i class="fas fa-trash" style="font-size: 2rem; color: var(--danger-color);"></i>
<div> <div>
<h6 class="mb-1">Move to Trash</h6> <h6 class="mb-1">Move to Trash</h6>
<p class="text-muted mb-0" id="deleteFileName"></p> <p class="text-muted mb-0" id="deleteFileName"></p>
@@ -114,7 +114,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmDeleteBtn" style="background-color: #b91c1c; border-color: #b91c1c;"> <button type="button" class="btn btn-danger" id="confirmDeleteBtn" style="background-color: var(--danger-color); border-color: var(--danger-color);">
<i class="fas fa-trash me-1"></i>Move to Trash <i class="fas fa-trash me-1"></i>Move to Trash
</button> </button>
</div> </div>
@@ -135,7 +135,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="createFolderBtn" style="background-color: #16767b; border-color: #16767b;">Create</button> <button type="button" class="btn btn-primary" id="createFolderBtn" style="background-color: var(--primary-color); border-color: var(--primary-color);">Create</button>
</div> </div>
</div> </div>
</div> </div>
@@ -154,7 +154,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirmRenameBtn" style="background-color: #16767b; border-color: #16767b;">Rename</button> <button type="button" class="btn btn-primary" id="confirmRenameBtn" style="background-color: var(--primary-color); border-color: var(--primary-color);">Rename</button>
</div> </div>
</div> </div>
</div> </div>
@@ -191,8 +191,8 @@
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" id="skipOverwriteBtn">Skip</button> <button type="button" class="btn btn-secondary" id="skipOverwriteBtn">Skip</button>
<button type="button" class="btn btn-secondary" id="skipAllOverwriteBtn">Skip All</button> <button type="button" class="btn btn-secondary" id="skipAllOverwriteBtn">Skip All</button>
<button type="button" class="btn btn-danger" id="confirmOverwriteBtn" style="background-color: #b91c1c; border-color: #b91c1c;">Overwrite</button> <button type="button" class="btn btn-danger" id="confirmOverwriteBtn" style="background-color: var(--danger-color); border-color: var(--danger-color);">Overwrite</button>
<button type="button" class="btn btn-danger" id="confirmAllOverwriteBtn" style="background-color: #b91c1c; border-color: #b91c1c;">Overwrite All</button> <button type="button" class="btn btn-danger" id="confirmAllOverwriteBtn" style="background-color: var(--danger-color); border-color: var(--danger-color);">Overwrite All</button>
</div> </div>
</div> </div>
</div> </div>
@@ -217,7 +217,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirmMoveBtn" style="background-color:#16767b; border:1px solid #16767b;">Move</button> <button type="button" class="btn btn-primary" id="confirmMoveBtn" style="background-color: var(--primary-color); border-color: var(--primary-color);">Move</button>
</div> </div>
</div> </div>
</div> </div>
@@ -270,22 +270,22 @@
#fileGrid.list-view table { #fileGrid.list-view table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
background: #fff; background: var(--white);
} }
#fileGrid.list-view th, #fileGrid.list-view td { #fileGrid.list-view th, #fileGrid.list-view td {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border-bottom: 1px solid #e9ecef; border-bottom: 1px solid var(--border-light);
text-align: left; text-align: left;
font-size: 0.95rem; font-size: 0.95rem;
vertical-align: middle; vertical-align: middle;
} }
#fileGrid.list-view th { #fileGrid.list-view th {
background: #f8f9fa; background: var(--bg-color);
color: #6c757d; color: var(--text-muted);
font-weight: 500; font-weight: 500;
} }
#fileGrid.list-view tr:hover td { #fileGrid.list-view tr:hover td {
background-color: rgba(22, 118, 123, 0.08); background-color: var(--primary-opacity-8);
transition: background 0.15s; transition: background 0.15s;
} }
#fileGrid.list-view .file-icon { #fileGrid.list-view .file-icon {
@@ -308,31 +308,31 @@
display: grid; display: grid;
grid-template-columns: 40px 2fr 1fr 1fr 1fr; grid-template-columns: 40px 2fr 1fr 1fr 1fr;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
background-color: #f8f9fa; background-color: var(--bg-color);
border-bottom: 1px solid #e9ecef; border-bottom: 1px solid var(--border-light);
color: #6c757d; color: var(--text-muted);
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500; font-weight: 500;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.btn-group.btn-group-sm .btn { .btn-group.btn-group-sm .btn {
background-color: #fff; background-color: var(--white);
border-color: #e9ecef; border-color: var(--border-light);
color: #6c757d; color: var(--text-muted);
transition: background-color 0.15s, color 0.15s; transition: background-color 0.15s, color 0.15s;
} }
.btn-group.btn-group-sm .btn.active, .btn-group.btn-group-sm .btn:active { .btn-group.btn-group-sm .btn.active, .btn-group.btn-group-sm .btn:active {
background-color: #e6f3f4 !important; background-color: var(--primary-bg-light) !important;
color: #16767b !important; color: var(--primary-color) !important;
border-color: #16767b !important; border-color: var(--primary-color) !important;
box-shadow: none; box-shadow: none;
} }
.btn-group.btn-group-sm .btn:focus { .btn-group.btn-group-sm .btn:focus {
box-shadow: 0 0 0 0.1rem #16767b33; box-shadow: 0 0 0 0.1rem var(--primary-opacity-20);
} }
.btn-group.btn-group-sm .btn:hover:not(.active) { .btn-group.btn-group-sm .btn:hover:not(.active) {
background-color: #f8f9fa; background-color: var(--bg-color);
color: #16767b; color: var(--primary-color);
} }
#fileGrid.table-mode { #fileGrid.table-mode {
padding: 0; padding: 0;
@@ -340,22 +340,22 @@
#fileGrid.table-mode table { #fileGrid.table-mode table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
background: #fff; background: var(--white);
} }
#fileGrid.table-mode th, #fileGrid.table-mode td { #fileGrid.table-mode th, #fileGrid.table-mode td {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border-bottom: 1px solid #e9ecef; border-bottom: 1px solid var(--border-light);
text-align: left; text-align: left;
font-size: 0.95rem; font-size: 0.95rem;
vertical-align: middle; vertical-align: middle;
} }
#fileGrid.table-mode th { #fileGrid.table-mode th {
background: #f8f9fa; background: var(--bg-color);
color: #6c757d; color: var(--text-muted);
font-weight: 500; font-weight: 500;
} }
#fileGrid.table-mode tr:hover td { #fileGrid.table-mode tr:hover td {
background-color: rgba(22, 118, 123, 0.08); background-color: var(--primary-opacity-8);
transition: background 0.15s; transition: background 0.15s;
} }
#fileGrid.table-mode .file-icon { #fileGrid.table-mode .file-icon {
@@ -374,6 +374,9 @@
font-size: 0.875rem; font-size: 0.875rem;
margin-left: 0.25rem; margin-left: 0.25rem;
} }
#fileGrid.table-mode tr.selected {
background-color: var(--primary-bg-light) !important;
}
/* Disable text selection for file grid and table rows/cards */ /* Disable text selection for file grid and table rows/cards */
#fileGrid, #fileGrid * { #fileGrid, #fileGrid * {
user-select: none; user-select: none;
@@ -490,8 +493,8 @@ function showDetailsModal(idx) {
} }
const icon = item.type === 'folder' const icon = item.type === 'folder'
? `<i class='fas fa-folder' style='font-size:2.2rem;color:#16767b;'></i>` ? `<i class='fas fa-folder' style='font-size:2.2rem;color:var(--primary-color);'></i>`
: `<i class='fas fa-file-alt' style='font-size:2.2rem;color:#741b5f;'></i>`; : `<i class='fas fa-file-alt' style='font-size:2.2rem;color:var(--secondary-color);'></i>`;
const uploaderPic = item.uploader_profile_pic const uploaderPic = item.uploader_profile_pic
? `/uploads/profile_pics/${item.uploader_profile_pic}` ? `/uploads/profile_pics/${item.uploader_profile_pic}`
: '/static/default-avatar.png'; : '/static/default-avatar.png';
@@ -513,11 +516,11 @@ function showDetailsModal(idx) {
<span class='fw-semibold' style='font-size:0.98rem;'>${item.uploaded_by || '-'}</span> <span class='fw-semibold' style='font-size:0.98rem;'>${item.uploaded_by || '-'}</span>
</div> </div>
<div class='mb-2 text-muted' style='font-size:0.92rem;'><i class='far fa-clock me-1'></i>${formatDate(item.modified)}</div> <div class='mb-2 text-muted' style='font-size:0.92rem;'><i class='far fa-clock me-1'></i>${formatDate(item.modified)}</div>
<hr style='margin:0.7rem 0 0.5rem 0; border-color:#e6f3f4;'> <hr style='margin:0.7rem 0 0.5rem 0; border-color:var(--border-light);'>
<div style='font-size:0.91rem;color:#555;'><strong style='color:#16767b;'>Room:</strong> <button class='btn btn-sm' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='window.location.href=\"/room/${roomId}\"'><i class='fas fa-door-open me-1'></i>${roomName}</button></div> <div style='font-size:0.91rem;color:var(--text-muted);'><strong style='color:var(--primary-color);'>Room:</strong> <button class='btn btn-sm' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='window.location.href=\"/room/${roomId}\"'><i class='fas fa-door-open me-1'></i>${roomName}</button></div>
<div style='font-size:0.91rem;color:#555;'><strong style='color:#16767b;'>Path:</strong> <span style='word-break:break-all;'>${(item.path ? item.path + '/' : '') + item.name}</span></div> <div style='font-size:0.91rem;color:var(--text-muted);'><strong style='color:var(--primary-color);'>Path:</strong> <span style='word-break:break-all;'>${(item.path ? item.path + '/' : '') + item.name}</span></div>
<div style='font-size:0.91rem;color:#555;'><strong style='color:#16767b;'>Size:</strong> ${item.size === '-' ? '-' : (item.size > 0 ? (item.size < 1024*1024 ? (item.size/1024).toFixed(1)+' KB' : (item.size/1024/1024).toFixed(2)+' MB') : '0 KB')}</div> <div style='font-size:0.91rem;color:var(--text-muted);'><strong style='color:var(--primary-color);'>Size:</strong> ${item.size === '-' ? '-' : (item.size > 0 ? (item.size < 1024*1024 ? (item.size/1024).toFixed(1)+' KB' : (item.size/1024/1024).toFixed(2)+' MB') : '0 KB')}</div>
<div style='font-size:0.91rem;color:#555;'><strong style='color:#16767b;'>Uploaded at:</strong> ${item.uploaded_at ? new Date(item.uploaded_at).toLocaleString() : '-'}</div> <div style='font-size:0.91rem;color:var(--text-muted);'><strong style='color:var(--primary-color);'>Uploaded at:</strong> ${item.uploaded_at ? new Date(item.uploaded_at).toLocaleString() : '-'}</div>
`; `;
document.getElementById('detailsModalBody').innerHTML = detailsHtml; document.getElementById('detailsModalBody').innerHTML = detailsHtml;
var modal = new bootstrap.Modal(document.getElementById('detailsModal')); var modal = new bootstrap.Modal(document.getElementById('detailsModal'));
@@ -589,10 +592,10 @@ function renderBreadcrumb() {
bc.innerHTML = ''; bc.innerHTML = '';
const parts = currentPath ? currentPath.split('/') : []; const parts = currentPath ? currentPath.split('/') : [];
let pathSoFar = ''; let pathSoFar = '';
bc.innerHTML += `<a href="#" onclick="navigateTo('')" class="text-decoration-none" style="color:#16767b;">Root</a>`; bc.innerHTML += `<a href="#" onclick="navigateTo('')" class="text-decoration-none" style="color:var(--primary-color);">Root</a>`;
parts.forEach((part, idx) => { parts.forEach((part, idx) => {
pathSoFar += (pathSoFar ? '/' : '') + part; pathSoFar += (pathSoFar ? '/' : '') + part;
bc.innerHTML += ` <span class="text-muted">/</span> <a href="#" onclick="navigateTo('${pathSoFar}')" class="text-decoration-none" style="color:#16767b;">${part}</a>`; bc.innerHTML += ` <span class="text-muted">/</span> <a href="#" onclick="navigateTo('${pathSoFar}')" class="text-decoration-none" style="color:var(--primary-color);">${part}</a>`;
}); });
// Show/hide up button // Show/hide up button
const upBtn = document.getElementById('upBtn'); const upBtn = document.getElementById('upBtn');
@@ -723,8 +726,8 @@ function renderFiles(files) {
</tr></thead><tbody>`; </tr></thead><tbody>`;
filesToRender.forEach((file, idx) => { filesToRender.forEach((file, idx) => {
let icon = file.type === 'folder' let icon = file.type === 'folder'
? `<i class='fas fa-folder' style='font-size:1.5rem;color:#16767b;'></i>` ? `<i class='fas fa-folder' style='font-size:1.5rem;color:var(--primary-color);'></i>`
: `<i class='fas fa-file-alt' style='font-size:1.5rem;color:#741b5f;'></i>`; : `<i class='fas fa-file-alt' style='font-size:1.5rem;color:var(--secondary-color);'></i>`;
let size = file.size !== '-' ? (file.size > 0 ? (file.size < 1024*1024 ? (file.size/1024).toFixed(1)+' KB' : (file.size/1024/1024).toFixed(2)+' MB') : '0 KB') : '-'; let size = file.size !== '-' ? (file.size > 0 ? (file.size < 1024*1024 ? (file.size/1024).toFixed(1)+' KB' : (file.size/1024/1024).toFixed(2)+' MB') : '0 KB') : '-';
let actionsArr = []; let actionsArr = [];
const canRenameAction = (canRename === 'true'); const canRenameAction = (canRename === 'true');
@@ -737,28 +740,28 @@ function renderFiles(files) {
} }
if (file.type === 'file') { if (file.type === 'file') {
if (canDownload === 'true') { if (canDownload === 'true') {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Download' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='downloadFile("${file.name}")'><i class='fas fa-download'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Download' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='downloadFile("${file.name}")'><i class='fas fa-download'></i></button>`);
} }
if (canRenameAction) { if (canRenameAction) {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Rename' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='showRenameModal("${file.name}")'><i class='fas fa-pen'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Rename' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='showRenameModal("${file.name}")'><i class='fas fa-pen'></i></button>`);
} }
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`);
if (canMove === 'true') { if (canMove === 'true') {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Move' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='showMoveModal("${file.name}", "${file.path}")'><i class='fas fa-arrows-alt'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Move' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='showMoveModal("${file.name}", "${file.path}")'><i class='fas fa-arrows-alt'></i></button>`);
} }
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'rgba(255,215,0,0.15)' : 'rgba(22,118,123,0.08)'};color:${file.starred ? '#ffd700' : '#16767b'};' onclick='event.stopPropagation(); toggleStar("${file.name}", "${file.path || ''}")'><i class='fas fa-star'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'var(--warning-opacity-15)' : 'var(--primary-opacity-8)'};color:${file.starred ? 'var(--warning-color)' : 'var(--primary-color)'};' onclick='event.stopPropagation(); toggleStar("${file.name}", "${file.path || ''}")'><i class='fas fa-star'></i></button>`);
if (canDelete === true || canDelete === 'true') { if (canDelete === true || canDelete === 'true') {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete' style='background-color:rgba(239,68,68,0.1);color:#b91c1c;' onclick='showDeleteModal("${file.name}", "${file.path}")'><i class='fas fa-trash'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete' style='background-color:var(--danger-opacity-15);color:var(--danger-color);' onclick='showDeleteModal("${file.name}", "${file.path}")'><i class='fas fa-trash'></i></button>`);
} }
} else { } else {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Open Folder' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='navigateTo(\"${currentPath ? currentPath + '/' : ''}${file.name}\")'><i class='fas fa-folder-open'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Open Folder' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='navigateTo(\"${currentPath ? currentPath + '/' : ''}${file.name}\")'><i class='fas fa-folder-open'></i></button>`);
if (canRenameAction) { if (canRenameAction) {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Rename' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='showRenameModal(\"${file.name}\")'><i class='fas fa-pen'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Rename' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='showRenameModal(\"${file.name}\")'><i class='fas fa-pen'></i></button>`);
} }
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`);
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'rgba(255,215,0,0.15)' : 'rgba(22,118,123,0.08)'};color:${file.starred ? '#ffd700' : '#16767b'};' onclick='event.stopPropagation(); toggleStar("${file.name}", "${file.path || ''}")'><i class='fas fa-star'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'var(--warning-opacity-15)' : 'var(--primary-opacity-8)'};color:${file.starred ? 'var(--warning-color)' : 'var(--primary-color)'};' onclick='event.stopPropagation(); toggleStar("${file.name}", "${file.path || ''}")'><i class='fas fa-star'></i></button>`);
if (canDelete === true || canDelete === 'true') { if (canDelete === true || canDelete === 'true') {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete' style='background-color:rgba(239,68,68,0.1);color:#b91c1c;' onclick='showDeleteModal("${file.name}", "${file.path}")'><i class='fas fa-trash'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete' style='background-color:var(--danger-opacity-15);color:var(--danger-color);' onclick='showDeleteModal("${file.name}", "${file.path}")'><i class='fas fa-trash'></i></button>`);
} }
} }
// Move Delete to the end if present // Move Delete to the end if present
@@ -806,28 +809,28 @@ function renderFiles(files) {
} }
if (file.type === 'file') { if (file.type === 'file') {
if (canDownload === 'true') { if (canDownload === 'true') {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Download' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='downloadFile("${file.name}")'><i class='fas fa-download'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Download' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='downloadFile("${file.name}")'><i class='fas fa-download'></i></button>`);
} }
if (canRenameAction) { if (canRenameAction) {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Rename' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='showRenameModal("${file.name}")'><i class='fas fa-pen'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Rename' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='showRenameModal("${file.name}")'><i class='fas fa-pen'></i></button>`);
} }
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`);
if (canMove === 'true') { if (canMove === 'true') {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Move' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='showMoveModal("${file.name}", "${file.path}")'><i class='fas fa-arrows-alt'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Move' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='showMoveModal("${file.name}", "${file.path}")'><i class='fas fa-arrows-alt'></i></button>`);
} }
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'rgba(255,215,0,0.15)' : 'rgba(22,118,123,0.08)'};color:${file.starred ? '#ffd700' : '#16767b'};' onclick='event.stopPropagation(); toggleStar("${file.name}", "${file.path || ''}")'><i class='fas fa-star'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'var(--warning-opacity-15)' : 'var(--primary-opacity-8)'};color:${file.starred ? 'var(--warning-color)' : 'var(--primary-color)'};' onclick='event.stopPropagation(); toggleStar("${file.name}", "${file.path || ''}")'><i class='fas fa-star'></i></button>`);
if (canDelete === true || canDelete === 'true') { if (canDelete === true || canDelete === 'true') {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete' style='background-color:rgba(239,68,68,0.1);color:#b91c1c;' onclick='showDeleteModal("${file.name}", "${file.path}")'><i class='fas fa-trash'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete' style='background-color:var(--danger-opacity-15);color:var(--danger-color);' onclick='showDeleteModal("${file.name}", "${file.path}")'><i class='fas fa-trash'></i></button>`);
} }
} else { } else {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Open Folder' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='navigateTo(\"${currentPath ? currentPath + '/' : ''}${file.name}\")'><i class='fas fa-folder-open'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Open Folder' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='navigateTo(\"${currentPath ? currentPath + '/' : ''}${file.name}\")'><i class='fas fa-folder-open'></i></button>`);
if (canRenameAction) { if (canRenameAction) {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Rename' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='showRenameModal(\"${file.name}\")'><i class='fas fa-pen'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Rename' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='showRenameModal(\"${file.name}\")'><i class='fas fa-pen'></i></button>`);
} }
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:rgba(22,118,123,0.08);color:#16767b;' onclick='showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Details' style='background-color:var(--primary-opacity-8);color:var(--primary-color);' onclick='showDetailsModal(${idx})'><i class='fas fa-info-circle'></i></button>`);
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'rgba(255,215,0,0.15)' : 'rgba(22,118,123,0.08)'};color:${file.starred ? '#ffd700' : '#16767b'};' onclick='event.stopPropagation(); toggleStar("${file.name}", "${file.path || ''}")'><i class='fas fa-star'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='${file.starred ? 'Unstar' : 'Star'}' style='background-color:${file.starred ? 'var(--warning-opacity-15)' : 'var(--primary-opacity-8)'};color:${file.starred ? 'var(--warning-color)' : 'var(--primary-color)'};' onclick='event.stopPropagation(); toggleStar("${file.name}", "${file.path || ''}")'><i class='fas fa-star'></i></button>`);
if (canDelete === true || canDelete === 'true') { if (canDelete === true || canDelete === 'true') {
actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete' style='background-color:rgba(239,68,68,0.1);color:#b91c1c;' onclick='showDeleteModal("${file.name}", "${file.path}")'><i class='fas fa-trash'></i></button>`); actionsArr.push(`<button class='btn btn-sm file-action-btn' title='Delete' style='background-color:var(--danger-opacity-15);color:var(--danger-color);' onclick='showDeleteModal("${file.name}", "${file.path}")'><i class='fas fa-trash'></i></button>`);
} }
} }
// Move Delete to the end if present // Move Delete to the end if present

View File

@@ -25,9 +25,9 @@
<form method="GET" class="d-flex align-items-center w-100 justify-content-between" id="roomFilterForm" style="gap: 1rem;"> <form method="GET" class="d-flex align-items-center w-100 justify-content-between" id="roomFilterForm" style="gap: 1rem;">
<input type="text" name="search" placeholder="Search rooms..." value="{{ search }}" class="form-control flex-grow-1" id="roomSearchInput" autocomplete="off" style="min-width: 0;" /> <input type="text" name="search" placeholder="Search rooms..." value="{{ search }}" class="form-control flex-grow-1" id="roomSearchInput" autocomplete="off" style="min-width: 0;" />
<button type="button" id="clearRoomsFilter" class="px-4 py-2 rounded-lg text-white font-medium transition-colors duration-200 ms-2 flex-shrink-0" <button type="button" id="clearRoomsFilter" class="px-4 py-2 rounded-lg text-white font-medium transition-colors duration-200 ms-2 flex-shrink-0"
style="background-color: #16767b; border: 1px solid #16767b;" style="background-color: var(--primary-color); border: 1px solid var(--primary-color);"
onmouseover="this.style.backgroundColor='#1a8a90'" onmouseover="this.style.backgroundColor='var(--primary-light)'"
onmouseout="this.style.backgroundColor='#16767b'"> onmouseout="this.style.backgroundColor='var(--primary-color)'">
Clear Clear
</button> </button>
</form> </form>

View File

@@ -13,6 +13,316 @@
) }} ) }}
<div class="container-fluid"> <div class="container-fluid">
<!-- Settings content will go here --> <div class="row">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-header">
<h5 class="card-title mb-0">Theme Colors</h5>
</div>
<div class="card-body">
<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>
</div>
</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 %}

View File

@@ -5,6 +5,7 @@
{% block extra_css %} {% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/trash.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/trash.css') }}">
<link rel="stylesheet" href="{{ url_for('main.dynamic_colors') }}">
{% endblock %} {% endblock %}
{% block content %} {% block content %}