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 8 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
41 changes: 41 additions & 0 deletions pontoon/base/static/js/theme-switcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
$(function () {
$('.appearance .toggle-button button').click(function (e) {
e.preventDefault();

var self = $(this);

// If the clicked button is already active, do nothing
if (self.is('.active')) {
return;
}

var theme = self.attr('class').split(' ')[0]; // gets the theme (dark, light, system) based on the button's class

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

self.addClass('active');

$('body')
.removeClass('dark-theme light-theme system-theme')
.addClass(`${theme}-theme`);

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');
}
},
});
});
});
4 changes: 2 additions & 2 deletions pontoon/base/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
</head>

<body
class="{% block class %}{% endblock %}"
class="{% block class %}{% endblock %} {% if request.user.is_authenticated %}{{ request.user.profile.theme }}-theme{% endif %}"
{% 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
3 changes: 2 additions & 1 deletion pontoon/base/templates/django/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<link rel="stylesheet" href="{% static 'css/fonts.css' %}" title="" type="text/css" />
<link rel="stylesheet" href="{% static 'css/boilerplate.css' %}" title="" type="text/css" />
<link rel="stylesheet" href="{% static 'css/style.css' %}" title="" type="text/css" />
<link rel="stylesheet" href="{% static 'css/dark-theme.css' %}" title="" type="text/css" />

mathjazz marked this conversation as resolved.
Show resolved Hide resolved

{% include "tracker.html" %}

Expand Down Expand Up @@ -121,5 +121,6 @@
<script type="text/javascript" charset="utf-8" src="{% static 'js/lib/jquery-3.6.1.js' %}"></script>
<script type="text/javascript" charset="utf-8" src="{% static 'js/lib/nprogress.js' %}"></script>
<script type="text/javascript" charset="utf-8" src="{% static 'js/main.js' %}"></script>
<script type="text/javascript" charset="utf-8" src="{% static 'js/theme-switcher.js' %}"></script>
mathjazz marked this conversation as resolved.
Show resolved Hide resolved
</body>
</html>
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
17 changes: 15 additions & 2 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 Pontoon’s appearance should be dark, light, or follow your system’s settings</a></p>
mathjazz marked this conversation as resolved.
Show resolved Hide resolved
</div>
<span class="toggle-button">
<button type="button" class="dark active" title="Use a dark theme in Pontoon"><i class="icon far fa-moon"></i>Dark theme</button>
<button type="button" class="light" title="Use a light theme in Pontoon"><i class="icon fa fa-sun"></i>Light theme</button>
<button type="button" class="system" title="Use a theme in Pontoon 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 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 All @@ -160,4 +172,5 @@ <h3><span class="small stress">Preferred locales to get suggestions from</span><

{% block extend_js %}
{% javascript 'settings' %}
{% javascript 'theme-switcher' %}
mathjazz marked this conversation as resolved.
Show resolved Hide resolved
{% endblock %}
6 changes: 6 additions & 0 deletions pontoon/contributors/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ class UsernameConverter(StringConverter):
views.toggle_user_profile_attribute,
name="pontoon.contributors.toggle_user_profile_attribute",
),
# API: Toggle user theme preference
path(
"api/v1/user/theme/<username:username>/",
mathjazz marked this conversation as resolved.
Show resolved Hide resolved
views.toggle_theme,
name="pontoon.contributors.toggle_theme",
),
# AJAX: Save custom homepage
path(
"save-custom-homepage/",
Expand Down
31 changes: 30 additions & 1 deletion pontoon/contributors/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,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)
allowed_themes = ["light", "dark", "system"]

if theme not in allowed_themes:
return JsonResponse(
{"status": False, "message": "Bad Request: Invalid theme"},
status=400,
)
mathjazz marked this conversation as resolved.
Show resolved Hide resolved

profile = user.profile
profile.theme = theme
profile.save()

return JsonResponse({"status": True})


@login_required(redirect_field_name="", login_url="/403")
@require_POST
@transaction.atomic
Expand Down Expand Up @@ -250,7 +280,6 @@ def settings(request):
request.POST,
instance=profile,
)

mathjazz marked this conversation as resolved.
Show resolved Hide resolved
if (
locales_form.is_valid()
and user_form.is_valid()
Expand Down
2 changes: 2 additions & 0 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 Down Expand Up @@ -515,6 +516,7 @@ 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",
},
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.