mirror of https://github.com/zulip/zulip.git
settings: Add automatic theme detection feature.
With this implementation of the feature of the automatic theme detection, we make the following changes in the backend, frontend and documentation. This replaces the previous night_mode boolean with an enum, with the default value being to use the prefers-color-scheme feature of the operating system to determine which theme to use. Fixes: #14451. Co-authored-by: @kPerikou <44238834+kPerikou@users.noreply.github.com>
This commit is contained in:
parent
9bd8ead32d
commit
b10f156250
|
@ -692,20 +692,30 @@ with_overrides(function (override) {
|
||||||
$("body").fadeIn = (secs) => { assert_same(secs, 300); };
|
$("body").fadeIn = (secs) => { assert_same(secs, 300); };
|
||||||
|
|
||||||
global.with_stub(function (stub) {
|
global.with_stub(function (stub) {
|
||||||
event = event_fixtures.update_display_settings__night_mode;
|
event = event_fixtures.update_display_settings__color_scheme_dark;
|
||||||
page_params.night_mode = false;
|
page_params.color_scheme = 1;
|
||||||
override('night_mode.enable', stub.f); // automatically checks if called
|
override('night_mode.enable', stub.f); // automatically checks if called
|
||||||
override('realm_logo.rerender', noop);
|
override('realm_logo.rerender', noop);
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
assert_same(page_params.night_mode, true);
|
assert(page_params.color_scheme, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
global.with_stub(function (stub) {
|
global.with_stub(function (stub) {
|
||||||
event = event_fixtures.update_display_settings__night_mode_false;
|
event = event_fixtures.update_display_settings__color_scheme_light;
|
||||||
page_params.night_mode = true;
|
page_params.color_scheme = 1;
|
||||||
override('night_mode.disable', stub.f); // automatically checks if called
|
override('night_mode.disable', stub.f); // automatically checks if called
|
||||||
|
override('realm_logo.rerender', noop);
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
assert(!page_params.night_mode);
|
assert(page_params.color_scheme, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
global.with_stub(function (stub) {
|
||||||
|
event = event_fixtures.update_display_settings__color_scheme_automatic;
|
||||||
|
page_params.color_scheme = 2;
|
||||||
|
override('night_mode.default_preference_checker', stub.f); // automatically checks if called
|
||||||
|
override('realm_logo.rerender', noop);
|
||||||
|
dispatch(event);
|
||||||
|
assert(page_params.color_scheme, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
global.with_stub(function (stub) {
|
global.with_stub(function (stub) {
|
||||||
|
|
|
@ -478,16 +478,22 @@ exports.fixtures = {
|
||||||
setting: true,
|
setting: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
update_display_settings__night_mode: {
|
update_display_settings__color_scheme_automatic: {
|
||||||
type: 'update_display_settings',
|
type: 'update_display_settings',
|
||||||
setting_name: 'night_mode',
|
setting_name: 'color_scheme',
|
||||||
setting: true,
|
setting: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
update_display_settings__night_mode_false: {
|
update_display_settings__color_scheme_dark: {
|
||||||
type: 'update_display_settings',
|
type: 'update_display_settings',
|
||||||
setting_name: 'night_mode',
|
setting_name: 'color_scheme',
|
||||||
setting: false,
|
setting: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
update_display_settings__color_scheme_light: {
|
||||||
|
type: 'update_display_settings',
|
||||||
|
setting_name: 'color_scheme',
|
||||||
|
setting: 3,
|
||||||
},
|
},
|
||||||
|
|
||||||
update_display_settings__starred_message_counts: {
|
update_display_settings__starred_message_counts: {
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
exports.enable = function () {
|
exports.enable = function () {
|
||||||
$("body").addClass("night-mode");
|
$("body").removeClass("color-scheme-automatic").addClass("night-mode");
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.disable = function () {
|
exports.disable = function () {
|
||||||
$("body").removeClass("night-mode");
|
$("body").removeClass("color-scheme-automatic").removeClass("night-mode");
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.default_preference_checker = function () {
|
||||||
|
$("body").removeClass("night-mode").addClass("color-scheme-automatic");
|
||||||
};
|
};
|
||||||
|
|
||||||
window.night_mode = exports;
|
window.night_mode = exports;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const settings_config = require("./settings_config");
|
||||||
|
|
||||||
exports.build_realm_logo_widget = function (upload_function, is_night) {
|
exports.build_realm_logo_widget = function (upload_function, is_night) {
|
||||||
let logo_section_id = '#realm-day-logo-upload-widget';
|
let logo_section_id = '#realm-day-logo-upload-widget';
|
||||||
let logo_source = page_params.realm_logo_source;
|
let logo_source = page_params.realm_logo_source;
|
||||||
|
@ -73,7 +75,11 @@ exports.rerender = function () {
|
||||||
$("#realm-night-logo-upload-widget .image-block").attr("src", page_params.realm_night_logo_url);
|
$("#realm-night-logo-upload-widget .image-block").attr("src", page_params.realm_night_logo_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page_params.night_mode && page_params.realm_night_logo_source !== 'D') {
|
if (page_params.color_scheme === settings_config.color_scheme_values.night.code &&
|
||||||
|
page_params.realm_night_logo_source !== 'D' ||
|
||||||
|
page_params.color_scheme === settings_config.color_scheme_values.automatic.code &&
|
||||||
|
page_params.realm_night_logo_source !== 'D' &&
|
||||||
|
(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
$("#realm-logo").attr("src", page_params.realm_night_logo_url);
|
$("#realm-logo").attr("src", page_params.realm_night_logo_url);
|
||||||
} else {
|
} else {
|
||||||
$("#realm-logo").attr("src", page_params.realm_logo_url);
|
$("#realm-logo").attr("src", page_params.realm_logo_url);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const settings_config = require("./settings_config");
|
||||||
|
|
||||||
exports.dispatch_normal_event = function dispatch_normal_event(event) {
|
exports.dispatch_normal_event = function dispatch_normal_event(event) {
|
||||||
const noop = function () {};
|
const noop = function () {};
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
|
@ -395,13 +397,13 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) {
|
||||||
|
|
||||||
case 'update_display_settings': {
|
case 'update_display_settings': {
|
||||||
const user_display_settings = [
|
const user_display_settings = [
|
||||||
|
'color_scheme',
|
||||||
'default_language',
|
'default_language',
|
||||||
'demote_inactive_streams',
|
'demote_inactive_streams',
|
||||||
'dense_mode',
|
'dense_mode',
|
||||||
'emojiset',
|
'emojiset',
|
||||||
'fluid_layout_width',
|
'fluid_layout_width',
|
||||||
'high_contrast_mode',
|
'high_contrast_mode',
|
||||||
'night_mode',
|
|
||||||
'left_side_userlist',
|
'left_side_userlist',
|
||||||
'timezone',
|
'timezone',
|
||||||
'twenty_four_hour_time',
|
'twenty_four_hour_time',
|
||||||
|
@ -433,15 +435,18 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) {
|
||||||
$("body").toggleClass("less_dense_mode");
|
$("body").toggleClass("less_dense_mode");
|
||||||
$("body").toggleClass("more_dense_mode");
|
$("body").toggleClass("more_dense_mode");
|
||||||
}
|
}
|
||||||
if (event.setting_name === 'night_mode') {
|
if (event.setting_name === 'color_scheme') {
|
||||||
$("body").fadeOut(300);
|
$("body").fadeOut(300);
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
if (event.setting === true) {
|
if (event.setting === settings_config.color_scheme_values.night.code) {
|
||||||
night_mode.enable();
|
night_mode.enable();
|
||||||
realm_logo.rerender();
|
realm_logo.rerender();
|
||||||
} else {
|
} else if (event.setting === settings_config.color_scheme_values.day.code) {
|
||||||
night_mode.disable();
|
night_mode.disable();
|
||||||
realm_logo.rerender();
|
realm_logo.rerender();
|
||||||
|
} else {
|
||||||
|
night_mode.default_preference_checker();
|
||||||
|
realm_logo.rerender();
|
||||||
}
|
}
|
||||||
$("body").fadeIn(300);
|
$("body").fadeIn(300);
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
|
@ -37,7 +37,6 @@ function setup_settings_label() {
|
||||||
fluid_layout_width: i18n.t("Use full width on wide screens"),
|
fluid_layout_width: i18n.t("Use full width on wide screens"),
|
||||||
high_contrast_mode: i18n.t("High contrast mode"),
|
high_contrast_mode: i18n.t("High contrast mode"),
|
||||||
left_side_userlist: i18n.t("Show user list on left sidebar in narrow windows"),
|
left_side_userlist: i18n.t("Show user list on left sidebar in narrow windows"),
|
||||||
night_mode: i18n.t("Night mode"),
|
|
||||||
starred_message_counts: i18n.t("Show counts for starred messages"),
|
starred_message_counts: i18n.t("Show counts for starred messages"),
|
||||||
twenty_four_hour_time: i18n.t("Time format"),
|
twenty_four_hour_time: i18n.t("Time format"),
|
||||||
translate_emoticons: i18n.t("Convert emoticons before sending (<code>:)</code> becomes 😃)"),
|
translate_emoticons: i18n.t("Convert emoticons before sending (<code>:)</code> becomes 😃)"),
|
||||||
|
@ -58,6 +57,7 @@ exports.build_page = function () {
|
||||||
can_create_new_bots: settings_bots.can_create_new_bots(),
|
can_create_new_bots: settings_bots.can_create_new_bots(),
|
||||||
settings_label: exports.settings_label,
|
settings_label: exports.settings_label,
|
||||||
demote_inactive_streams_values: settings_config.demote_inactive_streams_values,
|
demote_inactive_streams_values: settings_config.demote_inactive_streams_values,
|
||||||
|
color_scheme_values: settings_config.color_scheme_values,
|
||||||
twenty_four_hour_time_values: settings_config.twenty_four_hour_time_values,
|
twenty_four_hour_time_values: settings_config.twenty_four_hour_time_values,
|
||||||
general_settings: settings_config.all_notifications().general_settings,
|
general_settings: settings_config.all_notifications().general_settings,
|
||||||
notification_settings: settings_config.all_notifications().settings,
|
notification_settings: settings_config.all_notifications().settings,
|
||||||
|
|
|
@ -27,6 +27,21 @@ exports.demote_inactive_streams_values = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.color_scheme_values = {
|
||||||
|
automatic: {
|
||||||
|
code: 1,
|
||||||
|
description: i18n.t("Automatic"),
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
code: 2,
|
||||||
|
description: i18n.t("Night mode"),
|
||||||
|
},
|
||||||
|
day: {
|
||||||
|
code: 3,
|
||||||
|
description: i18n.t("Day mode"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
exports.twenty_four_hour_time_values = {
|
exports.twenty_four_hour_time_values = {
|
||||||
twenty_four_hour_clock: {
|
twenty_four_hour_clock: {
|
||||||
value: true,
|
value: true,
|
||||||
|
@ -42,7 +57,6 @@ exports.get_all_display_settings = () => ({
|
||||||
settings: {
|
settings: {
|
||||||
user_display_settings: [
|
user_display_settings: [
|
||||||
"dense_mode",
|
"dense_mode",
|
||||||
"night_mode",
|
|
||||||
"high_contrast_mode",
|
"high_contrast_mode",
|
||||||
"left_side_userlist",
|
"left_side_userlist",
|
||||||
"starred_message_counts",
|
"starred_message_counts",
|
||||||
|
|
|
@ -29,6 +29,8 @@ exports.set_up = function () {
|
||||||
|
|
||||||
$("#demote_inactive_streams").val(page_params.demote_inactive_streams);
|
$("#demote_inactive_streams").val(page_params.demote_inactive_streams);
|
||||||
|
|
||||||
|
$("#color_scheme").val(page_params.color_scheme);
|
||||||
|
|
||||||
$("#twenty_four_hour_time").val(JSON.stringify(page_params.twenty_four_hour_time));
|
$("#twenty_four_hour_time").val(JSON.stringify(page_params.twenty_four_hour_time));
|
||||||
|
|
||||||
$(".emojiset_choice[value=" + page_params.emojiset + "]").prop("checked", true);
|
$(".emojiset_choice[value=" + page_params.emojiset + "]").prop("checked", true);
|
||||||
|
@ -82,6 +84,11 @@ exports.set_up = function () {
|
||||||
change_display_setting(data, '#display-settings-status');
|
change_display_setting(data, '#display-settings-status');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#color_scheme').change(function () {
|
||||||
|
const data = {color_scheme: this.value};
|
||||||
|
change_display_setting(data, '#display-settings-status');
|
||||||
|
});
|
||||||
|
|
||||||
$('body').on('click', '.reload_link', function () {
|
$('body').on('click', '.reload_link', function () {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
@ -146,8 +153,8 @@ exports.update_page = function () {
|
||||||
$("#left_side_userlist").prop('checked', page_params.left_side_userlist);
|
$("#left_side_userlist").prop('checked', page_params.left_side_userlist);
|
||||||
$("#default_language_name").text(page_params.default_language_name);
|
$("#default_language_name").text(page_params.default_language_name);
|
||||||
$("#translate_emoticons").prop('checked', page_params.translate_emoticons);
|
$("#translate_emoticons").prop('checked', page_params.translate_emoticons);
|
||||||
$("#night_mode").prop('checked', page_params.night_mode);
|
|
||||||
$("#twenty_four_hour_time").val(JSON.stringify(page_params.twenty_four_hour_time));
|
$("#twenty_four_hour_time").val(JSON.stringify(page_params.twenty_four_hour_time));
|
||||||
|
$("#color_scheme").val(JSON.stringify(page_params.color_scheme));
|
||||||
|
|
||||||
// TODO: Set emojiset selector here.
|
// TODO: Set emojiset selector here.
|
||||||
// Longer term, we'll want to automate this function
|
// Longer term, we'll want to automate this function
|
||||||
|
|
|
@ -786,3 +786,9 @@ on a dark background, and don't change the dark labels dark either. */
|
||||||
background-color: hsla(0, 0%, 0%, 0.2);
|
background-color: hsla(0, 0%, 0%, 0.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.color-scheme-automatic {
|
||||||
|
@extend body.night-mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,14 @@
|
||||||
<h3 class="inline-block">{{t "Display settings" }}</h3>
|
<h3 class="inline-block">{{t "Display settings" }}</h3>
|
||||||
<div class="alert-notification" id="display-settings-status"></div>
|
<div class="alert-notification" id="display-settings-status"></div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="color_scheme" class="dropdown-title">{{t "Color scheme" }}
|
||||||
|
</label>
|
||||||
|
<select name="color_scheme" id="color_scheme">
|
||||||
|
{{> dropdown_options_widget option_values=color_scheme_values}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{#each display_settings.settings.user_display_settings}}
|
{{#each display_settings.settings.user_display_settings}}
|
||||||
{{> settings_checkbox
|
{{> settings_checkbox
|
||||||
setting_name=this
|
setting_name=this
|
||||||
|
|
|
@ -10,6 +10,11 @@ below features are supported.
|
||||||
|
|
||||||
## Changes in Zulip 2.2
|
## Changes in Zulip 2.2
|
||||||
|
|
||||||
|
**Feature level 21**
|
||||||
|
|
||||||
|
* `PATCH /settings/display`: Replaced the `night_mode` boolean with
|
||||||
|
`color_scheme` as part of supporting automatic night theme detection.
|
||||||
|
|
||||||
**Feature level 20**
|
**Feature level 20**
|
||||||
|
|
||||||
* Added support for inviting users as organization owners to the
|
* Added support for inviting users as organization owners to the
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body {% if night_mode %}class="night-mode"{% endif %}>
|
<body {% if color_scheme == 1 %} class="color-scheme-automatic" {% elif color_scheme == 2 %} class="night-mode" {% endif %}>
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
# Night mode
|
# Night mode
|
||||||
|
|
||||||
By default, Zulip has a white background. Zulip also provides a
|
Zulip provides both a light theme and a night theme, which is great
|
||||||
"night mode", which is great for working in a dark space.
|
for working in a dark space.
|
||||||
|
|
||||||
### Enable night mode
|
## Manage color theme
|
||||||
|
|
||||||
{start_tabs}
|
{start_tabs}
|
||||||
|
|
||||||
{settings_tab|display-settings}
|
{settings_tab|display-settings}
|
||||||
|
|
||||||
2. Under **Display settings**, select **Night mode**.
|
2. Under **Display settings**, configure **Color scheme**.
|
||||||
|
|
||||||
{end_tabs}
|
{end_tabs}
|
||||||
.
|
|
||||||
|
The default is **Automatic**, which detects which theme to use based
|
||||||
|
on the color scheme used by your operating system.
|
||||||
|
|
||||||
|
You can also specifc **Night mode** or **Day mode** if you'd like
|
||||||
|
Zulip to use the same color scheme regardless of your operating system
|
||||||
|
configuration.
|
||||||
|
|
|
@ -29,7 +29,7 @@ DESKTOP_WARNING_VERSION = "5.2.0"
|
||||||
#
|
#
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in templates/zerver/api/changelog.md.
|
# new level means in templates/zerver/api/changelog.md.
|
||||||
API_FEATURE_LEVEL = 20
|
API_FEATURE_LEVEL = 21
|
||||||
|
|
||||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||||
# only when going from an old version of the code to a newer version. Bump
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -9,7 +9,7 @@ from zerver.models import UserProfile
|
||||||
|
|
||||||
def process_zcommands(content: str, user_profile: UserProfile) -> Dict[str, Any]:
|
def process_zcommands(content: str, user_profile: UserProfile) -> Dict[str, Any]:
|
||||||
def change_mode_setting(command: str, switch_command: str,
|
def change_mode_setting(command: str, switch_command: str,
|
||||||
setting: str, setting_value: bool) -> str:
|
setting: str, setting_value: int) -> str:
|
||||||
msg = 'Changed to {command} mode! To revert ' \
|
msg = 'Changed to {command} mode! To revert ' \
|
||||||
'{command} mode, type `/{switch_command}`.'.format(
|
'{command} mode, type `/{switch_command}`.'.format(
|
||||||
command=command,
|
command=command,
|
||||||
|
@ -27,19 +27,19 @@ def process_zcommands(content: str, user_profile: UserProfile) -> Dict[str, Any]
|
||||||
if command == 'ping':
|
if command == 'ping':
|
||||||
return dict()
|
return dict()
|
||||||
elif command == 'night':
|
elif command == 'night':
|
||||||
if user_profile.night_mode:
|
if user_profile.color_scheme == UserProfile.COLOR_SCHEME_NIGHT:
|
||||||
return dict(msg='You are still in night mode.')
|
return dict(msg='You are still in night mode.')
|
||||||
return dict(msg=change_mode_setting(command=command,
|
return dict(msg=change_mode_setting(command=command,
|
||||||
switch_command='day',
|
switch_command='day',
|
||||||
setting='night_mode',
|
setting='color_scheme',
|
||||||
setting_value=True))
|
setting_value=UserProfile.COLOR_SCHEME_NIGHT))
|
||||||
elif command == 'day':
|
elif command == 'day':
|
||||||
if not user_profile.night_mode:
|
if user_profile.color_scheme == UserProfile.COLOR_SCHEME_LIGHT:
|
||||||
return dict(msg='You are still in day mode.')
|
return dict(msg='You are still in day mode.')
|
||||||
return dict(msg=change_mode_setting(command=command,
|
return dict(msg=change_mode_setting(command=command,
|
||||||
switch_command='night',
|
switch_command='night',
|
||||||
setting='night_mode',
|
setting='color_scheme',
|
||||||
setting_value=False))
|
setting_value=UserProfile.COLOR_SCHEME_LIGHT))
|
||||||
elif command == 'fluid-width':
|
elif command == 'fluid-width':
|
||||||
if user_profile.fluid_layout_width:
|
if user_profile.fluid_layout_width:
|
||||||
return dict(msg='You are still in fluid width mode.')
|
return dict(msg='You are still in fluid width mode.')
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 2.2.13 on 2020-06-20 15:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.backends.postgresql.schema import DatabaseSchemaEditor
|
||||||
|
from django.db.migrations.state import StateApps
|
||||||
|
|
||||||
|
COLOR_SCHEME_AUTOMATIC = 1
|
||||||
|
COLOR_SCHEME_NIGHT = 2
|
||||||
|
|
||||||
|
# Set color_scheme to night mode, if night_mode is True.
|
||||||
|
def set_color_scheme_to_night_mode(apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None:
|
||||||
|
UserProfile = apps.get_model("zerver", "UserProfile")
|
||||||
|
UserProfile.objects.filter(night_mode=True).update(color_scheme=COLOR_SCHEME_NIGHT)
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0289_tighten_attachment_size'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='color_scheme',
|
||||||
|
field=models.PositiveSmallIntegerField(default=COLOR_SCHEME_AUTOMATIC),
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
set_color_scheme_to_night_mode,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
elidable=True),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='night_mode',
|
||||||
|
),
|
||||||
|
]
|
|
@ -999,10 +999,18 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
|
||||||
dense_mode: bool = models.BooleanField(default=True)
|
dense_mode: bool = models.BooleanField(default=True)
|
||||||
fluid_layout_width: bool = models.BooleanField(default=False)
|
fluid_layout_width: bool = models.BooleanField(default=False)
|
||||||
high_contrast_mode: bool = models.BooleanField(default=False)
|
high_contrast_mode: bool = models.BooleanField(default=False)
|
||||||
night_mode: bool = models.BooleanField(default=False)
|
|
||||||
translate_emoticons: bool = models.BooleanField(default=False)
|
translate_emoticons: bool = models.BooleanField(default=False)
|
||||||
twenty_four_hour_time: bool = models.BooleanField(default=False)
|
twenty_four_hour_time: bool = models.BooleanField(default=False)
|
||||||
starred_message_counts: bool = models.BooleanField(default=False)
|
starred_message_counts: bool = models.BooleanField(default=False)
|
||||||
|
COLOR_SCHEME_AUTOMATIC = 1
|
||||||
|
COLOR_SCHEME_NIGHT = 2
|
||||||
|
COLOR_SCHEME_LIGHT = 3
|
||||||
|
COLOR_SCHEME_CHOICES = [
|
||||||
|
COLOR_SCHEME_AUTOMATIC,
|
||||||
|
COLOR_SCHEME_NIGHT,
|
||||||
|
COLOR_SCHEME_LIGHT
|
||||||
|
]
|
||||||
|
color_scheme = models.PositiveSmallIntegerField(default=COLOR_SCHEME_AUTOMATIC)
|
||||||
|
|
||||||
# UI setting controlling Zulip's behavior of demoting in the sort
|
# UI setting controlling Zulip's behavior of demoting in the sort
|
||||||
# order and graying out streams with no recent traffic. The
|
# order and graying out streams with no recent traffic. The
|
||||||
|
@ -1069,6 +1077,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
|
||||||
|
|
||||||
# Define the types of the various automatically managed properties
|
# Define the types of the various automatically managed properties
|
||||||
property_types = dict(
|
property_types = dict(
|
||||||
|
color_scheme=int,
|
||||||
default_language=str,
|
default_language=str,
|
||||||
demote_inactive_streams=int,
|
demote_inactive_streams=int,
|
||||||
dense_mode=bool,
|
dense_mode=bool,
|
||||||
|
@ -1076,7 +1085,6 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
|
||||||
fluid_layout_width=bool,
|
fluid_layout_width=bool,
|
||||||
high_contrast_mode=bool,
|
high_contrast_mode=bool,
|
||||||
left_side_userlist=bool,
|
left_side_userlist=bool,
|
||||||
night_mode=bool,
|
|
||||||
starred_message_counts=bool,
|
starred_message_counts=bool,
|
||||||
timezone=str,
|
timezone=str,
|
||||||
translate_emoticons=bool,
|
translate_emoticons=bool,
|
||||||
|
|
|
@ -1852,6 +1852,7 @@ class EventsRegisterTest(ZulipTestCase):
|
||||||
default_language = ['es', 'de', 'en'],
|
default_language = ['es', 'de', 'en'],
|
||||||
timezone = ['US/Mountain', 'US/Samoa', 'Pacific/Galapogos', ''],
|
timezone = ['US/Mountain', 'US/Samoa', 'Pacific/Galapogos', ''],
|
||||||
demote_inactive_streams = [2, 3, 1],
|
demote_inactive_streams = [2, 3, 1],
|
||||||
|
color_scheme = [2, 3, 1]
|
||||||
)
|
)
|
||||||
|
|
||||||
property_type = UserProfile.property_types[setting_name]
|
property_type = UserProfile.property_types[setting_name]
|
||||||
|
|
|
@ -61,6 +61,7 @@ class HomeTest(ZulipTestCase):
|
||||||
"bot_types",
|
"bot_types",
|
||||||
"can_create_streams",
|
"can_create_streams",
|
||||||
"can_subscribe_other_users",
|
"can_subscribe_other_users",
|
||||||
|
"color_scheme",
|
||||||
"cross_realm_bots",
|
"cross_realm_bots",
|
||||||
"custom_profile_field_types",
|
"custom_profile_field_types",
|
||||||
"custom_profile_fields",
|
"custom_profile_fields",
|
||||||
|
@ -117,7 +118,6 @@ class HomeTest(ZulipTestCase):
|
||||||
"narrow_stream",
|
"narrow_stream",
|
||||||
"needs_tutorial",
|
"needs_tutorial",
|
||||||
"never_subscribed",
|
"never_subscribed",
|
||||||
"night_mode",
|
|
||||||
"notification_sound",
|
"notification_sound",
|
||||||
"password_min_guesses",
|
"password_min_guesses",
|
||||||
"password_min_length",
|
"password_min_length",
|
||||||
|
@ -761,34 +761,34 @@ class HomeTest(ZulipTestCase):
|
||||||
def test_compute_navbar_logo_url(self) -> None:
|
def test_compute_navbar_logo_url(self) -> None:
|
||||||
user_profile = self.example_user("hamlet")
|
user_profile = self.example_user("hamlet")
|
||||||
|
|
||||||
page_params = {"night_mode": True}
|
page_params = {"color_scheme": user_profile.COLOR_SCHEME_NIGHT}
|
||||||
add_realm_logo_fields(page_params, user_profile.realm)
|
add_realm_logo_fields(page_params, user_profile.realm)
|
||||||
self.assertEqual(compute_navbar_logo_url(page_params),
|
self.assertEqual(compute_navbar_logo_url(page_params),
|
||||||
"/static/images/logo/zulip-org-logo.png?version=0")
|
"/static/images/logo/zulip-org-logo.png?version=0")
|
||||||
|
|
||||||
page_params = {"night_mode": False}
|
page_params = {"color_scheme": user_profile.COLOR_SCHEME_LIGHT}
|
||||||
add_realm_logo_fields(page_params, user_profile.realm)
|
add_realm_logo_fields(page_params, user_profile.realm)
|
||||||
self.assertEqual(compute_navbar_logo_url(page_params),
|
self.assertEqual(compute_navbar_logo_url(page_params),
|
||||||
"/static/images/logo/zulip-org-logo.png?version=0")
|
"/static/images/logo/zulip-org-logo.png?version=0")
|
||||||
|
|
||||||
do_change_logo_source(user_profile.realm, Realm.LOGO_UPLOADED, night=False)
|
do_change_logo_source(user_profile.realm, Realm.LOGO_UPLOADED, night=False)
|
||||||
page_params = {"night_mode": True}
|
page_params = {"color_scheme": user_profile.COLOR_SCHEME_NIGHT}
|
||||||
add_realm_logo_fields(page_params, user_profile.realm)
|
add_realm_logo_fields(page_params, user_profile.realm)
|
||||||
self.assertEqual(compute_navbar_logo_url(page_params),
|
self.assertEqual(compute_navbar_logo_url(page_params),
|
||||||
f"/user_avatars/{user_profile.realm_id}/realm/logo.png?version=2")
|
f"/user_avatars/{user_profile.realm_id}/realm/logo.png?version=2")
|
||||||
|
|
||||||
page_params = {"night_mode": False}
|
page_params = {"color_scheme": user_profile.COLOR_SCHEME_LIGHT}
|
||||||
add_realm_logo_fields(page_params, user_profile.realm)
|
add_realm_logo_fields(page_params, user_profile.realm)
|
||||||
self.assertEqual(compute_navbar_logo_url(page_params),
|
self.assertEqual(compute_navbar_logo_url(page_params),
|
||||||
f"/user_avatars/{user_profile.realm_id}/realm/logo.png?version=2")
|
f"/user_avatars/{user_profile.realm_id}/realm/logo.png?version=2")
|
||||||
|
|
||||||
do_change_logo_source(user_profile.realm, Realm.LOGO_UPLOADED, night=True)
|
do_change_logo_source(user_profile.realm, Realm.LOGO_UPLOADED, night=True)
|
||||||
page_params = {"night_mode": True}
|
page_params = {"color_scheme": user_profile.COLOR_SCHEME_NIGHT}
|
||||||
add_realm_logo_fields(page_params, user_profile.realm)
|
add_realm_logo_fields(page_params, user_profile.realm)
|
||||||
self.assertEqual(compute_navbar_logo_url(page_params),
|
self.assertEqual(compute_navbar_logo_url(page_params),
|
||||||
f"/user_avatars/{user_profile.realm_id}/realm/night_logo.png?version=2")
|
f"/user_avatars/{user_profile.realm_id}/realm/night_logo.png?version=2")
|
||||||
|
|
||||||
page_params = {"night_mode": False}
|
page_params = {"color_scheme": user_profile.COLOR_SCHEME_LIGHT}
|
||||||
add_realm_logo_fields(page_params, user_profile.realm)
|
add_realm_logo_fields(page_params, user_profile.realm)
|
||||||
self.assertEqual(compute_navbar_logo_url(page_params),
|
self.assertEqual(compute_navbar_logo_url(page_params),
|
||||||
f"/user_avatars/{user_profile.realm_id}/realm/logo.png?version=2")
|
f"/user_avatars/{user_profile.realm_id}/realm/logo.png?version=2")
|
||||||
|
@ -796,12 +796,12 @@ class HomeTest(ZulipTestCase):
|
||||||
# This configuration isn't super supported in the UI and is a
|
# This configuration isn't super supported in the UI and is a
|
||||||
# weird choice, but we have a test for it anyway.
|
# weird choice, but we have a test for it anyway.
|
||||||
do_change_logo_source(user_profile.realm, Realm.LOGO_DEFAULT, night=False)
|
do_change_logo_source(user_profile.realm, Realm.LOGO_DEFAULT, night=False)
|
||||||
page_params = {"night_mode": True}
|
page_params = {"color_scheme": user_profile.COLOR_SCHEME_NIGHT}
|
||||||
add_realm_logo_fields(page_params, user_profile.realm)
|
add_realm_logo_fields(page_params, user_profile.realm)
|
||||||
self.assertEqual(compute_navbar_logo_url(page_params),
|
self.assertEqual(compute_navbar_logo_url(page_params),
|
||||||
f"/user_avatars/{user_profile.realm_id}/realm/night_logo.png?version=2")
|
f"/user_avatars/{user_profile.realm_id}/realm/night_logo.png?version=2")
|
||||||
|
|
||||||
page_params = {"night_mode": False}
|
page_params = {"color_scheme": user_profile.COLOR_SCHEME_LIGHT}
|
||||||
add_realm_logo_fields(page_params, user_profile.realm)
|
add_realm_logo_fields(page_params, user_profile.realm)
|
||||||
self.assertEqual(compute_navbar_logo_url(page_params),
|
self.assertEqual(compute_navbar_logo_url(page_params),
|
||||||
"/static/images/logo/zulip-org-logo.png?version=0")
|
"/static/images/logo/zulip-org-logo.png?version=0")
|
||||||
|
|
|
@ -83,9 +83,8 @@ class OpenGraphTest(ZulipTestCase):
|
||||||
self.check_title_and_description(
|
self.check_title_and_description(
|
||||||
'/help/night-mode',
|
'/help/night-mode',
|
||||||
"Night mode (Zulip Help Center)",
|
"Night mode (Zulip Help Center)",
|
||||||
['By default, Zulip has a white background. ',
|
['Zulip provides both a white background and a "night mode", which is great for working in a dark space.'],
|
||||||
'Zulip also provides a "night mode", which is great for working in a dark space.'],
|
[]
|
||||||
[],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_settings_tab(self) -> None:
|
def test_settings_tab(self) -> None:
|
||||||
|
|
|
@ -322,6 +322,7 @@ class ChangeSettingsTest(ZulipTestCase):
|
||||||
emojiset = 'google',
|
emojiset = 'google',
|
||||||
timezone = 'US/Mountain',
|
timezone = 'US/Mountain',
|
||||||
demote_inactive_streams = 2,
|
demote_inactive_streams = 2,
|
||||||
|
color_scheme = 2,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.login('hamlet')
|
self.login('hamlet')
|
||||||
|
|
|
@ -1020,11 +1020,12 @@ class UserProfileTest(ZulipTestCase):
|
||||||
iago = self.example_user("iago")
|
iago = self.example_user("iago")
|
||||||
cordelia = self.example_user("cordelia")
|
cordelia = self.example_user("cordelia")
|
||||||
hamlet = self.example_user("hamlet")
|
hamlet = self.example_user("hamlet")
|
||||||
|
hamlet.color_scheme = UserProfile.COLOR_SCHEME_LIGHT
|
||||||
|
|
||||||
cordelia.default_language = "de"
|
cordelia.default_language = "de"
|
||||||
cordelia.emojiset = "twitter"
|
cordelia.emojiset = "twitter"
|
||||||
cordelia.timezone = "America/Phoenix"
|
cordelia.timezone = "America/Phoenix"
|
||||||
cordelia.night_mode = True
|
cordelia.color_scheme = UserProfile.COLOR_SCHEME_NIGHT
|
||||||
cordelia.enable_offline_email_notifications = False
|
cordelia.enable_offline_email_notifications = False
|
||||||
cordelia.enable_stream_push_notifications = True
|
cordelia.enable_stream_push_notifications = True
|
||||||
cordelia.enter_sends = False
|
cordelia.enter_sends = False
|
||||||
|
@ -1055,9 +1056,9 @@ class UserProfileTest(ZulipTestCase):
|
||||||
self.assertEqual(cordelia.timezone, "America/Phoenix")
|
self.assertEqual(cordelia.timezone, "America/Phoenix")
|
||||||
self.assertEqual(hamlet.timezone, "")
|
self.assertEqual(hamlet.timezone, "")
|
||||||
|
|
||||||
self.assertEqual(iago.night_mode, True)
|
self.assertEqual(iago.color_scheme, UserProfile.COLOR_SCHEME_NIGHT)
|
||||||
self.assertEqual(cordelia.night_mode, True)
|
self.assertEqual(cordelia.color_scheme, UserProfile.COLOR_SCHEME_NIGHT)
|
||||||
self.assertEqual(hamlet.night_mode, False)
|
self.assertEqual(hamlet.color_scheme, UserProfile.COLOR_SCHEME_LIGHT)
|
||||||
|
|
||||||
self.assertEqual(iago.enable_offline_email_notifications, False)
|
self.assertEqual(iago.enable_offline_email_notifications, False)
|
||||||
self.assertEqual(cordelia.enable_offline_email_notifications, False)
|
self.assertEqual(cordelia.enable_offline_email_notifications, False)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from zerver.lib.test_classes import ZulipTestCase
|
from zerver.lib.test_classes import ZulipTestCase
|
||||||
|
from zerver.models import UserProfile
|
||||||
|
|
||||||
|
|
||||||
class ZcommandTest(ZulipTestCase):
|
class ZcommandTest(ZulipTestCase):
|
||||||
|
@ -24,7 +25,7 @@ class ZcommandTest(ZulipTestCase):
|
||||||
def test_night_zcommand(self) -> None:
|
def test_night_zcommand(self) -> None:
|
||||||
self.login('hamlet')
|
self.login('hamlet')
|
||||||
user = self.example_user('hamlet')
|
user = self.example_user('hamlet')
|
||||||
user.night_mode = False
|
user.color_scheme = UserProfile.COLOR_SCHEME_LIGHT
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
payload = dict(command="/night")
|
payload = dict(command="/night")
|
||||||
|
@ -39,7 +40,7 @@ class ZcommandTest(ZulipTestCase):
|
||||||
def test_day_zcommand(self) -> None:
|
def test_day_zcommand(self) -> None:
|
||||||
self.login('hamlet')
|
self.login('hamlet')
|
||||||
user = self.example_user('hamlet')
|
user = self.example_user('hamlet')
|
||||||
user.night_mode = True
|
user.color_scheme = UserProfile.COLOR_SCHEME_NIGHT
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
payload = dict(command="/day")
|
payload = dict(command="/day")
|
||||||
|
|
|
@ -129,7 +129,7 @@ def get_bot_types(user_profile: Optional[UserProfile]) -> List[Dict[str, object]
|
||||||
return bot_types
|
return bot_types
|
||||||
|
|
||||||
def compute_navbar_logo_url(page_params: Dict[str, Any]) -> str:
|
def compute_navbar_logo_url(page_params: Dict[str, Any]) -> str:
|
||||||
if page_params["night_mode"] and page_params["realm_night_logo_source"] != Realm.LOGO_DEFAULT:
|
if page_params["color_scheme"] == 2 and page_params["realm_night_logo_source"] != Realm.LOGO_DEFAULT:
|
||||||
navbar_logo_url = page_params["realm_night_logo_url"]
|
navbar_logo_url = page_params["realm_night_logo_url"]
|
||||||
else:
|
else:
|
||||||
navbar_logo_url = page_params["realm_logo_url"]
|
navbar_logo_url = page_params["realm_logo_url"]
|
||||||
|
@ -315,13 +315,13 @@ def home_real(request: HttpRequest) -> HttpResponse:
|
||||||
|
|
||||||
csp_nonce = generate_random_token(48)
|
csp_nonce = generate_random_token(48)
|
||||||
if user_profile is not None:
|
if user_profile is not None:
|
||||||
night_mode = user_profile.night_mode
|
color_scheme = user_profile.color_scheme
|
||||||
is_guest = user_profile.is_guest
|
is_guest = user_profile.is_guest
|
||||||
is_realm_owner = user_profile.is_realm_owner
|
is_realm_owner = user_profile.is_realm_owner
|
||||||
is_realm_admin = user_profile.is_realm_admin
|
is_realm_admin = user_profile.is_realm_admin
|
||||||
show_webathena = user_profile.realm.webathena_enabled
|
show_webathena = user_profile.realm.webathena_enabled
|
||||||
else: # nocoverage
|
else: # nocoverage
|
||||||
night_mode = False
|
color_scheme = UserProfile.COLOR_SCHEME_AUTOMATIC
|
||||||
is_guest = False
|
is_guest = False
|
||||||
is_realm_admin = False
|
is_realm_admin = False
|
||||||
is_realm_owner = False
|
is_realm_owner = False
|
||||||
|
@ -342,7 +342,7 @@ def home_real(request: HttpRequest) -> HttpResponse:
|
||||||
'is_owner': is_realm_owner,
|
'is_owner': is_realm_owner,
|
||||||
'is_admin': is_realm_admin,
|
'is_admin': is_realm_admin,
|
||||||
'is_guest': is_guest,
|
'is_guest': is_guest,
|
||||||
'night_mode': night_mode,
|
'color_scheme': color_scheme,
|
||||||
'navbar_logo_url': navbar_logo_url,
|
'navbar_logo_url': navbar_logo_url,
|
||||||
'show_webathena': show_webathena,
|
'show_webathena': show_webathena,
|
||||||
'embedded': narrow_stream is not None,
|
'embedded': narrow_stream is not None,
|
||||||
|
|
|
@ -165,7 +165,8 @@ def update_display_settings_backend(
|
||||||
starred_message_counts: Optional[bool]=REQ(validator=check_bool, default=None),
|
starred_message_counts: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
fluid_layout_width: Optional[bool]=REQ(validator=check_bool, default=None),
|
fluid_layout_width: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
high_contrast_mode: Optional[bool]=REQ(validator=check_bool, default=None),
|
high_contrast_mode: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
night_mode: Optional[bool]=REQ(validator=check_bool, default=None),
|
color_scheme: Optional[int]=REQ(validator=check_int_in(
|
||||||
|
UserProfile.COLOR_SCHEME_CHOICES), default=None),
|
||||||
translate_emoticons: Optional[bool]=REQ(validator=check_bool, default=None),
|
translate_emoticons: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
default_language: Optional[str]=REQ(validator=check_string, default=None),
|
default_language: Optional[str]=REQ(validator=check_string, default=None),
|
||||||
left_side_userlist: Optional[bool]=REQ(validator=check_bool, default=None),
|
left_side_userlist: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
|
|
Loading…
Reference in New Issue