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 17 commits into
base: master
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions tin/apps/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ def response_footer(_request):
"DEVELOPER_EMAIL": settings.DEVELOPER_EMAIL,
"REPO_URL": settings.REPO_URL,
}


def dark_mode(request):
return {"dark_mode_enabled": getattr(request.user, "dark_mode", False)}
8 changes: 8 additions & 0 deletions tin/apps/users/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@

from django import forms

from tin.apps.users.models import User


class UserMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, user): # pylint: disable=arguments-differ
return f"{user.full_name} ({user.username})"


class ThemeForm(forms.ModelForm):
class Meta:
model = User
fields = ["dark_mode"]
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
16 changes: 16 additions & 0 deletions tin/apps/users/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import annotations

import json

from django.urls import reverse

from tin.tests import login


@login("student")
def test_user_dark_mode(client, student):
assert student.dark_mode == 0
response = client.post(reverse("users:theme"), {"dark_mode": 1})
assert json.loads(response.content.decode("utf-8")).get("success") is True
student.refresh_from_db()
assert student.dark_mode == 1
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"),
]
22 changes: 22 additions & 0 deletions tin/apps/users/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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.as_json()}, status=400
)
raise http.Http404
1 change: 1 addition & 0 deletions tin/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"social_django.context_processors.backends",
"social_django.context_processors.login_redirect",
"tin.apps.context_processors.response_footer",
"tin.apps.context_processors.dark_mode",
]
},
}
Expand Down
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%);
}
32 changes: 32 additions & 0 deletions tin/static/css/dark/base.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#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;
}

table.has-border th,
table.has-border td {
border: 1px solid #ffffff;
}
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;
}
3 changes: 3 additions & 0 deletions tin/templates/assignments/edit_create.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

{% block head %}
<link rel="stylesheet" type="text/css" href="{% static 'css/edit.css' %}">
{% if dark_mode_enabled %}
<link rel="stylesheet" href="{% static 'css/dark/edit.css' %}">
{% endif %}
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.min.css"
integrity="sha512-f0tzWhCwVFS3WeYaofoLWkTP62ObhewQ1EZn65oSYDZUg1+CyywGKkWzm8BxaJj5HGKI72PnMH9jYyIFz+GH7g=="
Expand Down
58 changes: 58 additions & 0 deletions tin/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
{% include "meta.html" %}

<link rel="stylesheet" href="{% static 'css/base.css' %}">
{% if dark_mode_enabled %}
<link rel="stylesheet" href="{% static 'css/dark/base.css' %}">
{% 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 +47,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 +141,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