Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions intranet/apps/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from intranet.apps.notifications.models import NotificationConfig
from intranet.apps.oauth.models import CSLApplication

from ..utils.helpers import dark_mode_enabled, get_theme, get_theme_name, get_warning_html
from ..utils.helpers import get_theme, get_theme_name, get_warning_html
from .announcements.models import WarningAnnouncement
from .schedule.models import Day

Expand Down Expand Up @@ -178,11 +178,12 @@ def show_bus_button(request):
return {"show_bus_nav": is_bus_admin and settings.ENABLE_BUS_APP}


def enable_dark_mode(request):
"""
Export whether dark mode is enabled.
"""
return {"dark_mode_enabled": dark_mode_enabled(request)}
def user_theme_choice(request):
if request.user.is_authenticated:
choice = request.user.theme_properties.theme_choice
else:
choice = request.COOKIES.get("theme_choice", "light")
return {"theme_choice": choice}


def oauth_toolkit(request):
Expand Down
12 changes: 9 additions & 3 deletions intranet/apps/preferences/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,17 @@ def flag(label, default):
)


class DarkModeForm(forms.Form):
class ThemeForm(forms.Form):
THEME_CHOICES = [
("light", "Light Theme"),
("dark", "Dark Theme"),
("twilight", "Twilight Theme"),
]

def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["dark_mode_enabled"] = forms.BooleanField(
initial=user.dark_mode_properties.dark_mode_enabled, label="Enable dark mode?", required=False
self.fields["theme_choice"] = forms.ChoiceField(
choices=self.THEME_CHOICES, initial=user.theme_properties.theme_choice, label="Select your preferred theme:", required=True
)


Expand Down
28 changes: 14 additions & 14 deletions intranet/apps/preferences/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
from ..auth.decorators import eighth_admin_required
from ..bus.models import Route
from ..users.models import Email
from .forms import BusRouteForm, DarkModeForm, EmailFormset, NotificationOptionsForm, PreferredPictureForm, PrivacyOptionsForm
from .forms import BusRouteForm, EmailFormset, NotificationOptionsForm, PreferredPictureForm, PrivacyOptionsForm, ThemeForm

# from .forms import (BusRouteForm, DarkModeForm, EmailFormset, NotificationOptionsForm, PhoneFormset, PreferredPictureForm, PrivacyOptionsForm,
# from .forms import (BusRouteForm, EmailFormset, NotificationOptionsForm, PhoneFormset, PreferredPictureForm, PrivacyOptionsForm,
# WebsiteFormset)


Expand Down Expand Up @@ -278,16 +278,16 @@ def save_gcm_options(request, user):
messages.success(request, "Disabled push notifications")


def save_dark_mode_settings(request, user):
dark_mode_form = DarkModeForm(user, data=request.POST, initial={"dark_mode_enabled": user.dark_mode_properties.dark_mode_enabled})
if dark_mode_form.is_valid():
if dark_mode_form.has_changed():
user.dark_mode_properties.dark_mode_enabled = dark_mode_form.cleaned_data["dark_mode_enabled"]
user.dark_mode_properties.save()
invalidate_obj(request.user.dark_mode_properties)
messages.success(request, ("Dark mode enabled" if user.dark_mode_properties.dark_mode_enabled else "Dark mode disabled"))
def save_theme_settings(request, user):
theme_form = ThemeForm(user, data=request.POST, initial={"theme_choice": user.theme_properties.theme_choice})
if theme_form.is_valid():
if theme_form.has_changed():
user.theme_properties.theme_choice = theme_form.cleaned_data["theme_choice"]
user.theme_properties.save()
invalidate_obj(request.user.theme_properties)
messages.success(request, f"Theme set to {user.theme_properties.theme_choice}")

return dark_mode_form
return theme_form


@login_required
Expand All @@ -314,7 +314,7 @@ def preferences_view(request):
privacy_options_form = None
notification_options_form = save_notification_options(request, user)

dark_mode_form = save_dark_mode_settings(request, user)
theme_form = save_theme_settings(request, user)

for error in errors:
messages.error(request, error)
Expand Down Expand Up @@ -355,7 +355,7 @@ def preferences_view(request):
notification_options = get_notification_options(user)
notification_options_form = NotificationOptionsForm(user, initial=notification_options)

dark_mode_form = DarkModeForm(user, initial={"dark_mode_enabled": user.dark_mode_properties.dark_mode_enabled})
theme_form = ThemeForm(user, initial={"theme_choice": user.theme_properties.theme_choice})

context = {
# "phone_formset": phone_formset,
Expand All @@ -365,7 +365,7 @@ def preferences_view(request):
"privacy_options_form": privacy_options_form,
"notification_options_form": notification_options_form,
"bus_route_form": bus_route_form if settings.ENABLE_BUS_APP else None,
"dark_mode_form": dark_mode_form,
"theme_form": theme_form,
}
return render(request, "preferences/preferences.html", context)

Expand Down
23 changes: 23 additions & 0 deletions intranet/apps/users/migrations/0043_userthemeproperties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.2.4 on 2025-10-18 16:00

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('users', '0042_user_seen_april_fools'),
]

operations = [
migrations.CreateModel(
name='UserThemeProperties',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('theme_choice', models.CharField(default='light_theme', max_length=100)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='theme_properties', to=settings.AUTH_USER_MODEL)),
],
),
]
14 changes: 14 additions & 0 deletions intranet/apps/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,8 @@ def __getattr__(self, name):
return UserProperties.objects.get_or_create(user=self)[0]
elif name == "dark_mode_properties":
return UserDarkModeProperties.objects.get_or_create(user=self)[0]
elif name == "theme_properties":
return UserThemeProperties.objects.get_or_create(user=self)[0]
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")

def __str__(self):
Expand Down Expand Up @@ -1289,6 +1291,18 @@ def __str__(self):
return str(self.user)


class UserThemeProperties(models.Model):
"""
Contains user properties relating to themes
"""

user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name="theme_properties", on_delete=models.CASCADE)
theme_choice = models.CharField(max_length=100, default="light_theme")

def __str__(self):
return str(self.user)


class Email(models.Model):
"""Represents an email address"""

Expand Down
18 changes: 17 additions & 1 deletion intranet/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,22 @@
"dark/oauth",
"dark/sessionmgmt",
"dark/logs",
"twilight/base",
"twilight/nav",
"twilight/cke",
"twilight/dashboard",
"twilight/preferences",
"twilight/eighth.signup",
"twilight/about",
"twilight/login",
"twilight/events",
"twilight/select",
"twilight/enrichment",
"twilight/files",
"twilight/lostfound",
"twilight/polls",
"twilight/printing",
"twilight/welcome"
]

for name in LIST_OF_INDEPENDENT_CSS:
Expand Down Expand Up @@ -452,7 +468,7 @@
"intranet.apps.context_processors.show_homecoming", # Sitewide custom themes (special events, etc)
"intranet.apps.context_processors.global_custom_theme", # Sitewide custom themes (special events, etc)
"intranet.apps.context_processors.show_bus_button",
"intranet.apps.context_processors.enable_dark_mode",
"intranet.apps.context_processors.user_theme_choice",
"intranet.apps.context_processors.oauth_toolkit", # Django OAuth Toolkit-related middleware
"intranet.apps.context_processors.settings_export", # "Exports" django.conf.settings as DJANGO_SETTINGS
"intranet.apps.features.context_processors.feature_announcements", # Feature announcements that need to be shown on the current page
Expand Down
7 changes: 7 additions & 0 deletions intranet/static/css/twilight/_colors.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
$page_background: #171717;
$widget_background: #202020;
$announcement_background: #181818;
$dark_border: #404040;
$text_primary: #B7B7B7;
$text_high_contrast: #FFF;
$accent_color: #2b7fff;
5 changes: 5 additions & 0 deletions intranet/static/css/twilight/about.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import './colors';

.center {
background-color: $page_background;
}
78 changes: 78 additions & 0 deletions intranet/static/css/twilight/base.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
@import './colors';

body {
color: #B7B7B7;
background-color: $page_background;
}

button, a.button, input[type="button"], input[type="submit"], input[type="reset"] {
color: #B7B7B7;
background-color: #151515;
background-image: linear-gradient(to bottom, #0E0E0E 0%, #121212 100%);
text-shadow: none;
border-color: $dark_border;
}

button:hover, a.button:hover, input[type="button"]:hover, input[type="submit"]:hover, input[type="reset"]:hover {
color: #CDCDCD;
background-color: #1C1C1C;
background-image: linear-gradient(to bottom, #1C1C1C 0%, #202020 100%);
}

.widget, .widget-content {
background-color: $widget_background;
border-color: $dark_border;
border-width: 1px;
}

.widget-title {
background: #151515;
border-color: $dark_border;
}

input[type="text"] {
background-color: $widget_background;
border-color: $dark_border;
color: white;
}

.header .search input[type="text"] {
background-color: $widget_background;
color: white;
font-weight: bold;
}

.main ul.nav > li {
border-color: $dark_border;
}

textarea {
background-color: $widget_background;
border-color: $dark_border;
color: white;
}

.selectize-input {
background-color: $widget_background !important;
border-color: $dark_border !important;
color: $text_primary !important;
}

input[type="text"] .selectize-input {
background-color: $widget_background !important;
border-color: $dark_border !important;
color: $text_primary !important;
}

.selectize-dropdown {
background: $widget_background;
border: 1px solid $dark_border;
outline: $dark_border;
}

.selectize-dropdown-content {
background: $widget_background;
border-color: $dark_border;
color: $text_primary;
scrollbar-color: $dark_border $widget_background;
}
14 changes: 14 additions & 0 deletions intranet/static/css/twilight/cke.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@import './colors';

.cke {
background: $page_background !important;
border-color: $dark_border !important;
}

.cke_top {
background: $page_background !important;
}

.cke_contents iframe {
background-color: $widget_background !important;
}
33 changes: 33 additions & 0 deletions intranet/static/css/twilight/dashboard.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@import './colors';

.announcement, .club-announcements, .announcement-meta, .event {
background: $announcement_background;
border-color: $dark_border;
border-radius: 5px;

h3 > a.announcement-link {
color: #D8D8D8 !important;
}

&-metadata {
color: darkgray;
}

&.partially-hidden .announcement-toggle-content::after {
background-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 0),
black 80%
);
}

&.club-announcements {
background-color: rgb(24, 24, 24);
}
}

.club-announcement-filters > .club-announcement-filter {
background-color: $widget_background;
border-color: $dark_border;
outline-color: $dark_border;
}
38 changes: 38 additions & 0 deletions intranet/static/css/twilight/eighth.signup.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
@import './colors';

.day-picker {
border-color: $dark_border;

&-buttons button {
border-color: $dark_border;
}

&-buttons button:hover {
border-color: $dark_border;
}

.day {
outline-color: $dark_border;
scrollbar-color: $dark_border $widget_background;
}
}

.search-wrapper {
background-color: $widget_background;
border-color: $dark_border;
}

#activity-picker {
border-color: $dark_border;
scrollbar-color: $dark_border $widget_background;

* { /* for each child element */
border-color: $dark_border;
outline-color: $dark_border;
}
}

#activity-detail {
background-color: $widget_background;
border-color: $dark_border;
}
5 changes: 5 additions & 0 deletions intranet/static/css/twilight/enrichment.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import './colors';

.schedule tr td {
border: 1px solid $dark_border;
}
6 changes: 6 additions & 0 deletions intranet/static/css/twilight/events.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@import './colors';

.event {
background-color: $widget_background;
border-color: $dark_border;
}
Loading