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

Theme Toggling Feature #2972

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b85d15e
Intial theme toggle within settings page
ayanaar Oct 3, 2023
9605154
Add theme selector mockups (#2971)
mathjazz Oct 3, 2023
09a018a
Intial theme toggle within settings page
ayanaar Oct 3, 2023
b169c96
Make suggested changes and add updated UI
ayanaar Oct 3, 2023
b309221
Resolving conflicts
ayanaar Oct 3, 2023
776df7f
Deleting old migration
ayanaar Oct 3, 2023
b308185
Update UserProfile model
ayanaar Oct 3, 2023
4793129
Satisfy linters
ayanaar Oct 3, 2023
e7f93df
Add requested changes
ayanaar Oct 3, 2023
0ff60fd
Satisfy linters
ayanaar Oct 3, 2023
b8865f8
Remove extra space
ayanaar Oct 3, 2023
771f4d1
Add link to dark-theme.css in base.html
ayanaar Oct 3, 2023
4f14364
Add updates
ayanaar Oct 10, 2023
2982ad1
Remove extra theme-switcher.js file
ayanaar Oct 10, 2023
9340418
Update theme-switcher.js and translate.html
ayanaar Oct 12, 2023
c99db80
Implemented dynamic theme switching based on user preferences and sys…
ayanaar Oct 12, 2023
b7bbe71
Fix spacing in settings.html
ayanaar Oct 12, 2023
5a99d21
Fixing header and inconsistencies
ayanaar Oct 16, 2023
90b707c
Add system theme switching in translate view
ayanaar Oct 16, 2023
34f18a6
Remove console.log statements
ayanaar Oct 16, 2023
8b5a713
Satisfy linters
ayanaar Oct 16, 2023
5e8960e
Satisfy prettier
ayanaar Oct 16, 2023
d8b3a81
Modify templates and update Theme.tsx logic
ayanaar Oct 17, 2023
05b8510
Fix insights page
ayanaar Oct 17, 2023
832b621
Fix profile and insights page, refactor templates, and modify Theme.tsx
ayanaar Oct 17, 2023
6f370dd
Fix migrations and inconsistencies
ayanaar Oct 18, 2023
9737f59
Update model to fix pytests
ayanaar Oct 18, 2023
afcd620
Remove theme field from forms
ayanaar Oct 18, 2023
b4e63cc
Fix python linter
ayanaar Oct 18, 2023
f590ee1
Remove spec files
ayanaar Oct 19, 2023
ba8a87b
Update light theme file
ayanaar Oct 19, 2023
2d73318
Add light theme spec
ayanaar Oct 19, 2023
d99376e
Update light theme spec
ayanaar Oct 19, 2023
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
1 change: 1 addition & 0 deletions pontoon/base/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ class Meta:
"chat",
"github",
"bugzilla",
"theme",
)


Expand Down
22 changes: 22 additions & 0 deletions pontoon/base/migrations/0047_userprofile_theme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.2.15 on 2023-10-03 16:56

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("base", "0046_projectslughistory"),
]

operations = [
migrations.AddField(
model_name="userprofile",
name="theme",
field=models.CharField(
choices=[("dark", "Dark"), ("light", "Light"), ("system", "System")],
default="dark",
max_length=20,
),
),
]
9 changes: 9 additions & 0 deletions pontoon/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1569,12 +1569,21 @@ class UserProfile(models.Model):
User, models.CASCADE, related_name="profile", primary_key=True
)

# Themes
class Themes(models.TextChoices):
DARK = "dark", "Dark"
LIGHT = "light", "Light"
SYSTEM = "system", "System"

# Personal information
username = models.SlugField(unique=True, blank=True, null=True)
contact_email = models.EmailField("Contact email address", blank=True, null=True)
contact_email_verified = models.BooleanField(default=False)
bio = models.TextField(max_length=160, blank=True, null=True)

# Theme
theme = models.CharField(choices=Themes.choices, max_length=20, default=Themes.DARK)
mathjazz marked this conversation as resolved.
Show resolved Hide resolved

# External accounts
chat = models.CharField("Chat username", max_length=255, blank=True, null=True)
github = models.CharField("GitHub username", max_length=255, blank=True, null=True)
Expand Down
2 changes: 1 addition & 1 deletion pontoon/base/static/css/dark-theme.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Dark Theme Variables */
:root {
.dark-theme {
--black-1: #1c1e21;
--black-2: #000000;
--black-3: #272a2f;
Expand Down
12 changes: 12 additions & 0 deletions pontoon/base/static/css/light-theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* Light Theme Variables */
.light-theme {
--dark-grey-2: #fbfbfb;
--dark-grey-1: #eee;
--light-grey-7: #aaaaaa;
--grey-3: #eee;
--light-grey-1: #e8e8e8;
--white-1: #222;
--white-3: #222;
--light-grey-7: #888;
--light-grey-2: #888888;
}
2 changes: 1 addition & 1 deletion pontoon/base/static/css/toggle.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
border-bottom-right-radius: 0;
}

.toggle-button button:nth-child(2) {
.toggle-button button:last-child {
border-left: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
Expand Down
79 changes: 79 additions & 0 deletions pontoon/base/static/js/theme-switcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
$(function () {
function getSystemTheme() {
if (
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: light)').matches
mathjazz marked this conversation as resolved.
Show resolved Hide resolved
) {
return 'dark';
} else {
return 'light';
}
}

function applyTheme(newTheme) {
if (newTheme === 'system') {
newTheme = getSystemTheme();
}
$('body')
.removeClass('dark-theme light-theme system-theme')
.addClass(`${newTheme}-theme`)
.data('theme', newTheme);
mathjazz marked this conversation as resolved.
Show resolved Hide resolved
}

window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', function (e) {
// Check the 'data-theme' attribute on the body element
let userThemeSetting = $('body').data('theme');

if (userThemeSetting === 'system') {
// Apply the system theme based on current system settings
if (e.matches) {
applyTheme('dark');
} else {
applyTheme('light');
}
}
});

if ($('body').hasClass('system-theme')) {
let systemTheme = getSystemTheme();
$('body').removeClass('system-theme').addClass(`${systemTheme}-theme`);
}

$('.appearance .toggle-button button').click(function (e) {
e.preventDefault();

var self = $(this);

if (self.is('.active')) {
return;
}

var theme = self.val();

$.ajax({
url: '/api/v1/user/' + $('#server').data('username') + '/theme/',
type: 'POST',
data: {
csrfmiddlewaretoken: $('body').data('csrf'),
theme: theme,
},
success: function () {
$('.appearance .toggle-button button').removeClass('active');
self.addClass('active');
applyTheme(theme);

// Notify the user about the theme change after AJAX success
Pontoon.endLoader(`Theme changed to ${theme}.`);
},
error: function (request) {
if (request.responseText === 'error') {
Pontoon.endLoader('Oops, something went wrong.', 'error');
} else {
Pontoon.endLoader(request.responseText, 'error');
}
},
});
});
});
3 changes: 2 additions & 1 deletion pontoon/base/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@
</head>

<body
class="{% block class %}{% endblock %}"
class="{% block class %}{% endblock %} {% if request.user.is_authenticated %}{{ user.profile.theme }}-theme{% endif %}"
mathjazz marked this conversation as resolved.
Show resolved Hide resolved
{% if csrf_token %}data-csrf="{{ csrf_token }}"{% endif %}
>

mathjazz marked this conversation as resolved.
Show resolved Hide resolved
{% block content %}

{% include "addon_promotion.html" %}
Expand Down
20 changes: 20 additions & 0 deletions pontoon/contributors/static/css/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@
font-style: italic;
}

#main .appearance .field .help {
margin: 0 0 -12px;
}

#main .appearance .toggle-button button {
width: 207px;
}

#main .appearance .toggle-button button:first-child {
margin-right: -1px;
}

#main .appearance .toggle-button button:nth-child(2) {
border-radius: 0;
}

#main .appearance .toggle-button button .icon {
margin-right: 10px;
}

#main .field .verify {
color: var(--light-green-1);
font-style: italic;
Expand Down
2 changes: 1 addition & 1 deletion pontoon/contributors/static/js/settings.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
$(function () {
// Toggle visibility
$('.toggle-button button').click(function (e) {
$('.data-visibility .toggle-button button').click(function (e) {
e.preventDefault();

var self = $(this);
Expand Down
18 changes: 15 additions & 3 deletions pontoon/contributors/templates/contributors/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ <h3>Personal information</h3>
</div>
</section>

<section class="appearance" >
mathjazz marked this conversation as resolved.
Show resolved Hide resolved
<h3>Appearance</h3>
<div class="field">
<p class="help">Choose if appearance should be dark, light, or follow your system’s settings</a></p>
</div>
<span class="toggle-button">
<button type="button" value ="dark" class="dark {% if user.profile.theme == "dark" %}active{% endif %}" title="Use a dark theme"><i class="icon far fa-moon"></i>Dark theme</button>
<button type="button" value="light"class="light {% if user.profile.theme == "light" %}active{% endif %}" title="Use a light theme"><i class="icon fa fa-sun"></i>Light theme</button>
<button type="button" value ="system" class="system {% if user.profile.theme == "system" %}active{% endif %}" title="Use a theme that matches your system settings"><i class="icon fa fa-laptop"></i>Match system</button>
mathjazz marked this conversation as resolved.
Show resolved Hide resolved
</span>
</section>

<section>
<h3>External accounts</h3>
<div class="field">
Expand All @@ -83,7 +95,7 @@ <h3>External accounts</h3>
</div>
</section>

<section>
<section class="data-visibility">
<h3>Visibility of data on the Profile page</h3>
<div class="field">
<span class="toggle-label">{{ user_profile_visibility_form.visibility_email.label }}</span>
Expand Down Expand Up @@ -128,7 +140,7 @@ <h3>Editor settings</h3>
</section>

<section>
<h3>Locale settings</h3>
<h3>Default locales</h3>
<div id="locale-settings" class="clearfix">
<div id="homepage">
<span class="label">Homepage</span>
Expand All @@ -142,7 +154,7 @@ <h3>Locale settings</h3>
</section>

<section>
<h3><span class="small stress">Preferred locales to get suggestions from</span></h3>
<h3>Preferred locales to get suggestions from</h3>
{{ multiple_team_selector.render(available_locales, selected_locales, form_field='locales_order', sortable=True) }}
</section>

Expand Down
6 changes: 6 additions & 0 deletions pontoon/contributors/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ class UsernameConverter(StringConverter):
views.mark_all_notifications_as_read,
name="pontoon.contributors.notifications.mark.all.as.read",
),
# API: Toggle user theme preference
path(
"api/v1/user/<username:username>/theme/",
views.toggle_theme,
name="pontoon.contributors.toggle_theme",
),
# API: Toggle user profile attribute
path(
"api/v1/user/<username:username>/",
Expand Down
31 changes: 31 additions & 0 deletions pontoon/contributors/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Q
from django.http import (
Expand Down Expand Up @@ -187,6 +188,36 @@ def toggle_user_profile_attribute(request, username):
return JsonResponse({"status": True})


@login_required(redirect_field_name="", login_url="/403")
@require_POST
@transaction.atomic
def toggle_theme(request, username):
user = get_object_or_404(User, username=username)
if user != request.user:
return JsonResponse(
{
"status": False,
"message": "Forbidden: You don't have permission to edit this user",
},
status=403,
)

theme = request.POST.get("theme", None)

try:
profile = user.profile
profile.theme = theme
profile.full_clean()
profile.save()
except ValidationError:
return JsonResponse(
{"status": False, "message": "Bad Request: Invalid theme"},
status=400,
)

return JsonResponse({"status": True})


@login_required(redirect_field_name="", login_url="/403")
@require_POST
@transaction.atomic
Expand Down
10 changes: 8 additions & 2 deletions pontoon/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ def _default_from_email():
"base": {
"source_filenames": (
"css/dark-theme.css",
"css/light-theme.css",
"css/fontawesome-all.css",
"css/nprogress.css",
"css/boilerplate.css",
Expand All @@ -372,7 +373,11 @@ def _default_from_email():
"output_filename": "css/base.min.css",
},
"translate": {
"source_filenames": ("translate.css", "css/dark-theme.css"),
"source_filenames": (
"translate.css",
"css/dark-theme.css",
"css/light-theme.css",
),
"output_filename": "css/translate.min.css",
},
"admin": {
Expand Down Expand Up @@ -515,11 +520,12 @@ def _default_from_email():
"js/lib/jquery.color-2.1.2.js",
"js/lib/nprogress.js",
"js/main.js",
"js/theme-switcher.js",
),
"output_filename": "js/base.min.js",
},
"translate": {
"source_filenames": ("translate.js",),
"source_filenames": ("translate.js"),
mathjazz marked this conversation as resolved.
Show resolved Hide resolved
"output_filename": "js/translate.min.js",
},
"admin": {
Expand Down
4 changes: 4 additions & 0 deletions pontoon/translate/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def translate(request, locale, project, resource):
# Validate Locale
locale = get_object_or_404(Locale, code=locale)

# Get user theme
if request.user.is_authenticated:
user_theme = request.user.profile.theme + "-theme"

mathjazz marked this conversation as resolved.
Show resolved Hide resolved
# Validate Project
if project.lower() != "all-projects":
project = get_project_or_redirect(
Expand Down
8 changes: 7 additions & 1 deletion specs/0114-light-theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Over the years, many users have expressed preference for a light color theme ove

# Feature explanation

Users are able to switch between `Dark theme`, `Light theme` and `Match system` in Settings and the Profile menu. Each theme is represented with a title and a radio box. As soon as the theme is selected, it gets applied and the setting get saved. The default setting is the `Dark theme`.
Users are able to switch between `Dark theme`, `Light theme` and `Match system` in Settings and the Profile menu. Each theme is represented with a title and an icon. Tooltip shows more information about each theme, e.g. "Use a theme that matches your system settings". As soon as the theme is selected, it gets applied and the setting get saved. The default setting is the `Dark theme`.

The `Match system` setting lets the browser pick the theme from the OS setting using the [prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) CSS media feature.

Expand Down Expand Up @@ -45,3 +45,9 @@ The following is a non-exhaustive list of color transformations between the dark

![](0114/dashboard.png)
*Team Dashboard*

![](0114/theme-selector-profile.png)
*Theme selector in the Profile menu*

![](0114/theme-selector-settings.png)
*Theme selector in the Settings*
Binary file added specs/0114/theme-selector-profile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added specs/0114/theme-selector-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion translate/public/translate.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

{% include "tracker.html" %}
</head>
<body>
<body class="{% block class %}{% endblock %} {% if request.user.is_authenticated %}{{ user.profile.theme }}-theme{% endif %}">
<noscript>
You need to enable JavaScript to run this app.
</noscript>
Expand Down
Loading