Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dark mode Implementation #83

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
4 changes: 4 additions & 0 deletions tin/apps/users/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@
class UserMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, user): # pylint: disable=arguments-differ
return f"{user.full_name} ({user.username})"


class ThemeForm(forms.Form):
hsna674 marked this conversation as resolved.
Show resolved Hide resolved
dark_mode = forms.IntegerField()
19 changes: 19 additions & 0 deletions tin/apps/users/migrations/0003_user_dark_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 4.2.15 on 2024-09-04 12:28

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('users', '0002_remove_user_is_sysadmin'),
]

operations = [
migrations.AddField(
model_name='user',
name='dark_mode',
field=models.PositiveIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(1)]),
),
]
3 changes: 3 additions & 0 deletions tin/apps/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import requests
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.core.validators import MaxValueValidator
from django.db import models
from django.utils import timezone
from social_django.utils import load_strategy
Expand All @@ -30,6 +31,8 @@ class User(AbstractBaseUser, PermissionsMixin):
is_teacher = models.BooleanField(default=False)
is_student = models.BooleanField(default=False)
date_joined = models.DateTimeField(default=timezone.now)
# 0 = Light mode, 1 = Dark Mode
dark_mode = models.PositiveIntegerField(default=0, validators=[MaxValueValidator(1)])

USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
Expand Down
11 changes: 11 additions & 0 deletions tin/apps/users/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from __future__ import annotations

from django.urls import path

from . import views

app_name = "users"

urlpatterns = [
path("theme/", views.change_theme, name="theme"),
]
20 changes: 20 additions & 0 deletions tin/apps/users/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from __future__ import annotations

from django import http
from django.contrib.auth.decorators import login_required

from tin.apps.users.forms import ThemeForm


@login_required
def change_theme(request):
"""Sets the color theme"""
if request.method == "POST":
form = ThemeForm(request.POST)
if form.is_valid():
request.user.dark_mode = form.cleaned_data["dark_mode"]
request.user.save()
return http.JsonResponse({"success": True})
else:
return http.JsonResponse({"success": False, "errors": form.errors}, status=400)
hsna674 marked this conversation as resolved.
Show resolved Hide resolved
raise http.Http404
17 changes: 17 additions & 0 deletions tin/static/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
color: white;
line-height: 40px;
background: #4fab4f;
position: relative;
}

#nav ul {
Expand Down Expand Up @@ -413,3 +414,19 @@ ul.errors {
white-space: pre-wrap;
}
}

#theme-toggle {
width: 30px;
height: 30px;
}

.theme-toggle-button {
background: transparent;
border: none;
cursor: pointer;
padding: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-85%, 12%);
}
27 changes: 27 additions & 0 deletions tin/static/css/dark/base.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#nav {
color: white;
background: #4fab4f;
}

body {
background-color: #182c25;
}

#footer {
background-color: #182c25;
border-top: #182c25;
color: white;
}

#main {
color: white;
}

ul#course-list > li a:not(.tin-btn),
ul#assignment-list > li a {
color: white;
}

a:not(.tin-btn) {
color: white;
}
3 changes: 3 additions & 0 deletions tin/static/css/dark/edit.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.content .field {
color: black;
}
59 changes: 59 additions & 0 deletions tin/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
{% include "meta.html" %}

<link rel="stylesheet" href="{% static 'css/base.css' %}">
{% if request.user.dark_mode %}
<link rel="stylesheet" href="{% static 'css/dark/base.css' %}">
<link rel="stylesheet" href="{% static 'css/dark/edit.css' %}">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dark/edit.css should only be loaded wherever edit.css is loaded. The way I suggest doing this is adding a context processor that adds the context variable dark_mode_enabled to every template, and use that to optionally load themes.
More information here
https://docs.djangoproject.com/en/4.2/ref/templates/api/#using-requestcontext.
Then, wherever edit.css is loaded, just do

{% if dark_mode_enabled %}
  {# link dark/edit.css #}
{% endif %}

{% endif %}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<link rel="stylesheet" type="text/css"
href="https://fonts.googleapis.com/css?family=Open+Sans:100italic,400italic,700italic,100,400,700"/>
Expand Down Expand Up @@ -44,8 +48,56 @@
$(".continuous-progress").css({height: "15px"}).progressbar({value: false})
});
</script>
<script>
function changeTheme() {
const $themeToggle = $('#theme-toggle use');
const isDarkMode = $themeToggle.attr('href') === '#svg-moon';

$themeToggle.attr('href', isDarkMode ? '#svg-sun' : '#svg-moon');

$.post(
"{% url 'users:theme' %}",
{
dark_mode: isDarkMode ? 1 : 0,
csrfmiddlewaretoken: "{{ csrf_token }}"
},
function () {
location.reload();
}
);
}
</script>
{% block head %}{% endblock %}
</head>
<div style="display: none">
<svg>
<symbol id="svg-sun" viewBox="0 0 24 24">
<title>Light mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="feather-sun">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
</svg>
<svg>
<symbol id="svg-moon" viewBox="0 0 24 24">
<title>Dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-moon">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
</svg>
</symbol>
</svg>
</div>

<body>

Expand Down Expand Up @@ -90,6 +142,13 @@
{% endif %}
<li class="right"><i class="fa fa-sign-out"></i><a href="{% url 'auth:logout' %}">Logout
({{ request.user.username }})</a></li>
<li class="right">
<button class="theme-toggle-button" onclick="changeTheme()">
<svg id="theme-toggle">
<use href="{% if request.user.dark_mode == 1 %}#svg-sun{% else %}#svg-moon{% endif %}"></use>
</svg>
</button>
</li>
{% endif %}
</ul>
</div>
Expand Down
1 change: 1 addition & 0 deletions tin/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
path("docs/", include("tin.apps.docs.urls", namespace="docs")),
path("", include("tin.apps.auth.urls", namespace="auth")),
path("", include("social_django.urls", namespace="social")),
path("", include("tin.apps.users.urls", namespace="users")),
]

handler404 = handle_404_view
Expand Down
Loading