From 4dad9fa1581afe7dfb0dc30c76da940b3f14378a Mon Sep 17 00:00:00 2001 From: Raghav Luthra Date: Sat, 13 Aug 2022 02:11:06 +0530 Subject: [PATCH] user_settings: Add user setting to control the user list style. Added a user_list_style personal user setting to the bottom of Settings > Display settings > Theme section which controls the look of the right sidebar user list. The radio button UI includes a preview of what the styles look like. The setting is intended to eventually have 3 possible values: COMPACT, WITH_STATUS and WITH_AVATAR; the final value is not yet implemented. Co-authored-by: Tim Abbott --- frontend_tests/node_tests/activity.js | 20 ++++++ frontend_tests/node_tests/buddy_data.js | 13 ++++ frontend_tests/node_tests/dispatch.js | 11 ++++ frontend_tests/node_tests/lib/events.js | 7 +++ static/js/admin.js | 2 + static/js/buddy_data.js | 10 +++ static/js/realm_user_settings_defaults.ts | 1 + static/js/server_events_dispatch.js | 7 +++ static/js/settings.js | 1 + static/js/settings_config.ts | 16 +++++ static/js/settings_display.js | 49 ++++++++++++++- static/js/settings_org.js | 20 +++++- .../settings_realm_user_settings_defaults.js | 4 +- static/js/user_settings.ts | 1 + static/styles/right_sidebar.css | 18 ++++++ static/styles/settings.css | 61 ++++++++++++++++--- static/templates/presence_row.hbs | 14 ++++- .../templates/settings/display_settings.hbs | 27 ++++++++ .../organization_user_settings_defaults.hbs | 2 +- templates/zerver/api/changelog.md | 8 +++ version.py | 2 +- .../0407_userprofile_user_list_style.py | 23 +++++++ zerver/models.py | 12 ++++ zerver/openapi/zulip.yaml | 54 ++++++++++++++++ zerver/tests/test_events.py | 2 + zerver/tests/test_realm.py | 1 + zerver/tests/test_settings.py | 4 +- zerver/views/realm.py | 3 + zerver/views/user_settings.py | 3 + 29 files changed, 374 insertions(+), 22 deletions(-) create mode 100644 zerver/migrations/0407_userprofile_user_list_style.py diff --git a/frontend_tests/node_tests/activity.js b/frontend_tests/node_tests/activity.js index d7be071c16..440ef050e9 100644 --- a/frontend_tests/node_tests/activity.js +++ b/frontend_tests/node_tests/activity.js @@ -376,6 +376,7 @@ test("handlers", ({override, mock_template}) => { test("first/prev/next", ({override, mock_template}) => { let rendered_alice; let rendered_fred; + user_settings.user_list_style = 2; mock_template("presence_row.hbs", false, (data) => { switch (data.user_id) { @@ -392,6 +393,12 @@ test("first/prev/next", ({override, mock_template}) => { user_circle_status: "translated: Active", user_id: alice.user_id, status_emoji_info: undefined, + status_text: undefined, + user_list_style: { + COMPACT: false, + WITH_STATUS: true, + WITH_AVATAR: false, + }, }); break; case fred.user_id: @@ -407,6 +414,12 @@ test("first/prev/next", ({override, mock_template}) => { user_circle_status: "translated: Active", faded: false, status_emoji_info: undefined, + status_text: undefined, + user_list_style: { + COMPACT: false, + WITH_STATUS: true, + WITH_AVATAR: false, + }, }); break; /* istanbul ignore next */ @@ -438,6 +451,7 @@ test("first/prev/next", ({override, mock_template}) => { }); test("insert_one_user_into_empty_list", ({override, mock_template}) => { + user_settings.user_list_style = 2; mock_template("presence_row.hbs", true, (data, html) => { assert.deepEqual(data, { href: "#narrow/pm-with/1-alice", @@ -450,6 +464,12 @@ test("insert_one_user_into_empty_list", ({override, mock_template}) => { user_circle_status: "translated: Active", faded: true, status_emoji_info: undefined, + status_text: undefined, + user_list_style: { + COMPACT: false, + WITH_STATUS: true, + WITH_AVATAR: false, + }, }); assert.ok(html.startsWith("
  • { people.add_active_user(fred); user_status.set_away(alice.user_id); user_settings.emojiset = "google"; + user_settings.user_list_style = 2; const status_emoji_info = { emoji_alt_code: false, emoji_name: "car", @@ -521,6 +522,12 @@ test("get_items_for_users", () => { user_status.set_status_emoji({user_id, ...status_emoji_info}); } + const user_list_style = { + COMPACT: false, + WITH_STATUS: true, + WITH_AVATAR: false, + }; + assert.deepEqual(buddy_data.get_items_for_users(user_ids), [ { faded: false, @@ -530,9 +537,11 @@ test("get_items_for_users", () => { name: "Human Myself", num_unread: 0, status_emoji_info, + status_text: undefined, user_circle_class: "user_circle_green", user_circle_status: "translated: Active", user_id: 1001, + user_list_style, }, { faded: false, @@ -542,9 +551,11 @@ test("get_items_for_users", () => { name: "Alice Smith", num_unread: 0, status_emoji_info, + status_text: undefined, user_circle_class: "user_circle_empty_line", user_circle_status: "translated: Unavailable", user_id: 1002, + user_list_style, }, { faded: false, @@ -554,9 +565,11 @@ test("get_items_for_users", () => { name: "Fred Flintstone", num_unread: 0, status_emoji_info, + status_text: undefined, user_circle_class: "user_circle_empty", user_circle_status: "translated: Offline", user_id: 1003, + user_list_style, }, ]); }); diff --git a/frontend_tests/node_tests/dispatch.js b/frontend_tests/node_tests/dispatch.js index 0b6a68b87c..53542e4135 100644 --- a/frontend_tests/node_tests/dispatch.js +++ b/frontend_tests/node_tests/dispatch.js @@ -885,6 +885,17 @@ run_test("user_settings", ({override}) => { assert_same(user_settings.demote_inactive_streams, 2); } + { + const stub = make_stub(); + event = event_fixtures.user_settings__user_list_style; + override(settings_display, "report_user_list_style_change", stub.f); + user_settings.user_list_style = 1; + override(activity, "build_user_sidebar", stub.f); + dispatch(event); + assert.equal(stub.num_calls, 2); + assert_same(user_settings.user_list_style, 2); + } + event = event_fixtures.user_settings__enter_sends; user_settings.enter_sends = false; dispatch(event); diff --git a/frontend_tests/node_tests/lib/events.js b/frontend_tests/node_tests/lib/events.js index 025b5509d6..17708a4e44 100644 --- a/frontend_tests/node_tests/lib/events.js +++ b/frontend_tests/node_tests/lib/events.js @@ -915,6 +915,13 @@ exports.fixtures = { value: true, }, + user_settings__user_list_style: { + type: "user_settings", + op: "update", + property: "user_list_style", + value: 2, + }, + user_status__revoke_away: { type: "user_status", user_id: 63, diff --git a/static/js/admin.js b/static/js/admin.js index 357fb4c943..b1e3bcf580 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -89,6 +89,7 @@ function get_realm_level_notification_settings(options) { export function build_page() { const options = { custom_profile_field_types: page_params.custom_profile_field_types, + full_name: page_params.full_name, realm_name: page_params.realm_name, realm_org_type: page_params.realm_org_type, realm_available_video_chat_providers: page_params.realm_available_video_chat_providers, @@ -158,6 +159,7 @@ export function build_page() { settings_config.common_message_policy_values.by_admins_only.code, ...settings_org.get_organization_settings_options(), demote_inactive_streams_values: settings_config.demote_inactive_streams_values, + user_list_style_values: settings_config.user_list_style_values, color_scheme_values: settings_config.color_scheme_values, default_view_values: settings_config.default_view_values, settings_object: realm_user_settings_defaults, diff --git a/static/js/buddy_data.js b/static/js/buddy_data.js index 682275c2fd..f440deb01a 100644 --- a/static/js/buddy_data.js +++ b/static/js/buddy_data.js @@ -8,6 +8,7 @@ import * as people from "./people"; import * as presence from "./presence"; import * as timerender from "./timerender"; import * as unread from "./unread"; +import {user_settings} from "./user_settings"; import * as user_status from "./user_status"; import * as util from "./util"; @@ -179,6 +180,13 @@ export function info_for(user_id) { const status_emoji_info = user_status.get_status_emoji(user_id); const user_circle_status = status_description(user_id); + const status_text = user_status.get_status_text(user_id); + const user_list_style_value = user_settings.user_list_style; + const user_list_style = { + COMPACT: user_list_style_value === 1, + WITH_STATUS: user_list_style_value === 2, + WITH_AVATAR: user_list_style_value === 3, + }; return { href: hash_util.pm_with_url(person.email), @@ -190,6 +198,8 @@ export function info_for(user_id) { num_unread: get_num_unread(user_id), user_circle_class, user_circle_status, + status_text, + user_list_style, }; } diff --git a/static/js/realm_user_settings_defaults.ts b/static/js/realm_user_settings_defaults.ts index e1a16a5fcd..52181eb16c 100644 --- a/static/js/realm_user_settings_defaults.ts +++ b/static/js/realm_user_settings_defaults.ts @@ -34,6 +34,7 @@ export type RealmDefaultSettings = { starred_message_counts: boolean; translate_emoticons: boolean; twenty_four_hour_time: boolean; + user_list_style: boolean; wildcard_mentions_notify: boolean; }; diff --git a/static/js/server_events_dispatch.js b/static/js/server_events_dispatch.js index 5eaa95ea13..f9289ff3f0 100644 --- a/static/js/server_events_dispatch.js +++ b/static/js/server_events_dispatch.js @@ -621,6 +621,7 @@ export function dispatch_normal_event(event) { "twenty_four_hour_time", "translate_emoticons", "display_emoji_reaction_users", + "user_list_style", "starred_message_counts", "send_stream_typing_notifications", "send_private_typing_notifications", @@ -652,6 +653,12 @@ export function dispatch_normal_event(event) { stream_list.update_streams_sidebar(); stream_data.set_filter_out_inactives(); } + if (event.property === "user_list_style") { + settings_display.report_user_list_style_change( + settings_display.user_settings_panel, + ); + activity.build_user_sidebar(); + } if (event.property === "dense_mode") { $("body").toggleClass("less_dense_mode"); $("body").toggleClass("more_dense_mode"); diff --git a/static/js/settings.js b/static/js/settings.js index 7d0b20cf7a..78eb1f60d2 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -83,6 +83,7 @@ export function build_page() { can_create_new_bots: settings_bots.can_create_new_bots(), settings_label, demote_inactive_streams_values: settings_config.demote_inactive_streams_values, + user_list_style_values: settings_config.user_list_style_values, color_scheme_values: settings_config.color_scheme_values, default_view_values: settings_config.default_view_values, twenty_four_hour_time_values: settings_config.twenty_four_hour_time_values, diff --git a/static/js/settings_config.ts b/static/js/settings_config.ts index 6f03f3f5db..b32b52c75f 100644 --- a/static/js/settings_config.ts +++ b/static/js/settings_config.ts @@ -39,6 +39,22 @@ export const demote_inactive_streams_values = { }, }; +export const user_list_style_values = { + compact: { + code: 1, + description: $t({defaultMessage: "Compact"}), + }, + with_status: { + code: 2, + description: $t({defaultMessage: "Show status text"}), + }, + // The `with_avatar` design in still in discussion. + // with_avatar: { + // code: 3, + // description: $t({defaultMessage: "Show status text and avatar"}), + // }, +}; + export const default_view_values = { recent_topics: { code: "recent_topics", diff --git a/static/js/settings_display.js b/static/js/settings_display.js index 50ee0dc6a5..37131c7d5f 100644 --- a/static/js/settings_display.js +++ b/static/js/settings_display.js @@ -166,6 +166,9 @@ export function set_up(settings_panel) { $container .find(`.setting_emojiset_choice[value="${CSS.escape(settings_object.emojiset)}"]`) .prop("checked", true); + $container + .find(`.setting_user_list_style_choice[value=${settings_object.user_list_style}]`) + .prop("checked", true); if (for_realm_settings) { // For the realm-level defaults page, we use the common @@ -224,6 +227,29 @@ export function set_up(settings_panel) { }, }); }); + + $container.find(".setting_user_list_style_choice").on("click", function () { + const data = {user_list_style: $(this).val()}; + const current_user_list_style = settings_object.user_list_style; + if (current_user_list_style === data.user_list_style) { + return; + } + const $spinner = $container.find(".theme-settings-status").expectOne(); + loading.make_indicator($spinner, {text: settings_ui.strings.saving}); + + channel.patch({ + url: "/json/settings", + data, + success() {}, + error(xhr) { + ui_report.error( + settings_ui.strings.failure_html, + xhr, + $container.find(".theme-settings-status").expectOne(), + ); + }, + }); + }); } export async function report_emojiset_change(settings_panel) { @@ -247,6 +273,25 @@ export async function report_emojiset_change(settings_panel) { } } +export async function report_user_list_style_change(settings_panel) { + // TODO: Clean up how this works so we can use + // change_display_setting. The challenge is that we don't want to + // report success before the server_events request returns that + // causes the actual sprite sheet to change. The current + // implementation is wrong, though, in that it displays the UI + // update in all active browser windows. + const $spinner = $(settings_panel.container).find(".theme-settings-status"); + if ($spinner.length) { + loading.destroy_indicator($spinner); + ui_report.success( + $t_html({defaultMessage: "User list style changed successfully!"}), + $spinner.expectOne(), + ); + $spinner.expectOne(); + settings_ui.display_checkmark($spinner); + } +} + export function update_page(property) { if (!overlays.settings_open()) { return; @@ -262,8 +307,8 @@ export function update_page(property) { } // settings_org.set_input_element_value doesn't support radio - // button widgets like this one. - if (property === "emojiset") { + // button widgets like these. + if (property === "emojiset" || property === "user_list_style") { $container.find(`input[value=${CSS.escape(value)}]`).prop("checked", true); return; } diff --git a/static/js/settings_org.js b/static/js/settings_org.js index f85d3e10d1..e6bc110846 100644 --- a/static/js/settings_org.js +++ b/static/js/settings_org.js @@ -244,8 +244,14 @@ function get_subsection_property_elements(element) { // structure, it needs custom code. const $color_scheme_elem = $subsection.find(".setting_color_scheme"); const $emojiset_elem = $subsection.find("input[name='emojiset']:checked"); + const $user_list_style_elem = $subsection.find("input[name='user_list_style']:checked"); const $translate_emoticons_elem = $subsection.find(".translate_emoticons"); - return [$color_scheme_elem, $emojiset_elem, $translate_emoticons_elem]; + return [ + $color_scheme_elem, + $emojiset_elem, + $user_list_style_elem, + $translate_emoticons_elem, + ]; } return Array.from($subsection.find(".prop-element")); } @@ -533,13 +539,21 @@ function discard_property_element_changes(elem, for_realm_default_settings) { ); break; case "emojiset": - // Because the emojiset widget has a unique radio button - // structure, it needs custom reset code. + // Because this widget has a radio button structure, it + // needs custom reset code. $elem .closest(".org-subsection-parent") .find(`.setting_emojiset_choice[value='${CSS.escape(property_value)}'`) .prop("checked", true); break; + case "user_list_style": + // Because this widget has a radio button structure, it + // needs custom reset code. + $elem + .closest(".org-subsection-parent") + .find(`.setting_user_list_style_choice[value='${CSS.escape(property_value)}'`) + .prop("checked", true); + break; case "email_notifications_batching_period_seconds": case "email_notification_batching_period_edit_minutes": settings_notifications.set_notification_batching_ui( diff --git a/static/js/settings_realm_user_settings_defaults.js b/static/js/settings_realm_user_settings_defaults.js index 8ab6471558..a24efb9265 100644 --- a/static/js/settings_realm_user_settings_defaults.js +++ b/static/js/settings_realm_user_settings_defaults.js @@ -29,8 +29,8 @@ export function update_page(property) { let value = realm_user_settings_defaults[property]; // settings_org.set_input_element_value doesn't support radio - // button widgets like this one. - if (property === "emojiset") { + // button widgets like these. + if (property === "emojiset" || property === "user_list_style") { $container.find(`input[value=${CSS.escape(value)}]`).prop("checked", true); return; } diff --git a/static/js/user_settings.ts b/static/js/user_settings.ts index 7a3d5f7e2c..c7fc42d3ae 100644 --- a/static/js/user_settings.ts +++ b/static/js/user_settings.ts @@ -37,6 +37,7 @@ export type UserSettings = (StreamNotificationSettings & PmNotificationSettings) pm_content_in_desktop_notifications: boolean; presence_enabled: boolean; realm_name_in_notifications: boolean; + user_list_style: number; starred_message_counts: boolean; translate_emoticons: boolean; display_emoji_reaction_users: boolean; diff --git a/static/styles/right_sidebar.css b/static/styles/right_sidebar.css index 46d658095f..8c9cd96a3c 100644 --- a/static/styles/right_sidebar.css +++ b/static/styles/right_sidebar.css @@ -130,6 +130,24 @@ align-items: flex-start; justify-content: space-between; + .user-name-and-status-emoji { + display: flex; + } + + .status-text { + display: block; + width: 170px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + opacity: 0.75; + font-size: 90%; + } + + span.status-text:not(:empty) { + margin-top: -3px; + } + .unread_count { display: none; margin-top: 2.5px; diff --git a/static/styles/settings.css b/static/styles/settings.css index 5454df1b06..0aaf7b5c7c 100644 --- a/static/styles/settings.css +++ b/static/styles/settings.css @@ -918,15 +918,10 @@ input[type="checkbox"] { } } -.emojiset_choices { - width: 250px; +.emojiset_choices, +.user_list_style_values { padding: 0 10px; - .emoji { - height: 22px; - width: 22px; - } - label { border-bottom: 1px solid hsla(0, 0%, 0%, 0.2); padding: 8px 0 10px; @@ -944,10 +939,56 @@ input[type="checkbox"] { font-weight: 600; } } - } - .right { - float: right; + .right { + float: right; + } + } +} + +.emojiset_choices { + width: 250px; + + .emoji { + height: 22px; + width: 22px; + } +} + +$right_sidebar_width: 170px; +$option_title_width: 180px; + +.user_list_style_values { + max-width: calc($right_sidebar_width + $option_title_width); + + .preview { + background-color: inherit !important; + /* Match the 170px width of the right sidebar region for the name/status, + doing something reasonable if the window shrinks. */ + width: calc(100% - $option_title_width); + text-align: left; + white-space: nowrap; + text-overflow: ellipsis; + overflow-x: hidden; + overflow-y: visible; + position: relative; + height: 36px; + + .user-name-and-status-text { + margin-top: -4px; + display: flex; + flex-direction: column; + } + + .status-text { + display: inline-block; + opacity: 0.75; + font-size: 90%; + + &:not(:empty) { + margin-top: -3px; + } + } } } diff --git a/static/templates/presence_row.hbs b/static/templates/presence_row.hbs index fc875bf72b..f76fa65b3f 100644 --- a/static/templates/presence_row.hbs +++ b/static/templates/presence_row.hbs @@ -5,8 +5,18 @@ href="{{href}}" data-user-id="{{user_id}}" data-name="{{name}}"> - {{name}} - {{> status_emoji status_emoji_info}} + {{#if user_list_style.WITH_STATUS}} +
    +
    + {{name}} + {{> status_emoji status_emoji_info}} +
    + {{status_text}} +
    + {{else}} + {{name}} + {{> status_emoji status_emoji_info}} + {{/if}} {{#if num_unread}}{{num_unread}}{{/if}} diff --git a/static/templates/settings/display_settings.hbs b/static/templates/settings/display_settings.hbs index 0429534312..e08f0ba70e 100644 --- a/static/templates/settings/display_settings.hbs +++ b/static/templates/settings/display_settings.hbs @@ -77,6 +77,33 @@ label=settings_label.display_emoji_reaction_users prefix=prefix}} {{/if}} + +
    + +
    + {{#each user_list_style_values}} + + {{/each}} +
    +
    diff --git a/static/templates/settings/organization_user_settings_defaults.hbs b/static/templates/settings/organization_user_settings_defaults.hbs index 0a240bf9be..ceb70a09a1 100644 --- a/static/templates/settings/organization_user_settings_defaults.hbs +++ b/static/templates/settings/organization_user_settings_defaults.hbs @@ -6,7 +6,7 @@ {{#*inline "z-link"}}{{> @partial-block }}{{/inline}} {{/tr}}
    - {{> display_settings prefix="realm_" for_realm_settings=true}} + {{> display_settings prefix="realm_" for_realm_settings=true full_name=full_name}} {{> notification_settings prefix="realm_" for_realm_settings=true}} diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md index 887fcd1884..23cadede50 100644 --- a/templates/zerver/api/changelog.md +++ b/templates/zerver/api/changelog.md @@ -20,6 +20,14 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 6.0 +**Feature level 141** + +* [`POST /register`](/api/register-queue), [`PATCH + /settings`](/api/update-settings), [`PATCH + /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults): + Added new `user_list_style` display setting, which controls the + layout of the right sidebar. + **Feature level 140** * [`POST /register`](/api/register-queue): Added string field `server_emoji_data_url` diff --git a/version.py b/version.py index 6cd9902549..d12d074ded 100644 --- a/version.py +++ b/version.py @@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3" # Changes should be accompanied by documentation explaining what the # new level means in templates/zerver/api/changelog.md, as well as # "**Changes**" entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 140 +API_FEATURE_LEVEL = 141 # 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 diff --git a/zerver/migrations/0407_userprofile_user_list_style.py b/zerver/migrations/0407_userprofile_user_list_style.py new file mode 100644 index 0000000000..03629f6b48 --- /dev/null +++ b/zerver/migrations/0407_userprofile_user_list_style.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.6 on 2022-08-14 18:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("zerver", "0406_alter_realm_message_content_edit_limit_seconds"), + ] + + operations = [ + migrations.AddField( + model_name="realmuserdefault", + name="user_list_style", + field=models.PositiveSmallIntegerField(default=2), + ), + migrations.AddField( + model_name="userprofile", + name="user_list_style", + field=models.PositiveSmallIntegerField(default=2), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 0abd389782..0f9b3aeed4 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -1523,6 +1523,17 @@ class UserBaseSettings(models.Model): default=GOOGLE_EMOJISET, choices=EMOJISET_CHOICES, max_length=20 ) + # User list style + USER_LIST_STYLE_COMPACT = 1 + USER_LIST_STYLE_WITH_STATUS = 2 + USER_LIST_STYLE_WITH_AVATAR = 3 + USER_LIST_STYLE_CHOICES = [ + USER_LIST_STYLE_COMPACT, + USER_LIST_STYLE_WITH_STATUS, + USER_LIST_STYLE_WITH_AVATAR, + ] + user_list_style: int = models.PositiveSmallIntegerField(default=USER_LIST_STYLE_WITH_STATUS) + ### Notifications settings. ### email_notifications_batching_period_seconds: int = models.IntegerField(default=120) @@ -1621,6 +1632,7 @@ class UserBaseSettings(models.Model): send_private_typing_notifications=bool, send_read_receipts=bool, send_stream_typing_notifications=bool, + user_list_style=int, ) modern_notification_settings: Dict[str, Any] = dict( diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 0334cf6463..3c9be156f9 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -8516,6 +8516,23 @@ paths: - 2 - 3 example: 1 + - name: user_list_style + in: query + description: | + The style selected by the user for the right sidebar user list. + + - 1 - Compact + - 2 - With status + - 3 - With avatar and status + + **Changes**: New in Zulip 6.0 (feature level 141). + schema: + type: integer + enum: + - 1 + - 2 + - 3 + example: 1 - name: enable_stream_desktop_notifications in: query description: | @@ -10431,6 +10448,16 @@ paths: - 1 - Automatic - 2 - Always - 3 - Never + user_list_style: + type: integer + description: | + The style selected by the user for the right sidebar user list. + + - 1 - Compact + - 2 - With status + - 3 - With avatar and status + + **Changes**: New in Zulip 6.0 (feature level 141). timezone: type: string description: | @@ -12344,6 +12371,16 @@ paths: - 1 - Automatic - 2 - Always - 3 - Never + user_list_style: + type: integer + description: | + The style selected by the user for the right sidebar user list. + + - 1 - Compact + - 2 - With status + - 3 - With avatar and status + + **Changes**: New in Zulip 6.0 (feature level 141). enable_stream_desktop_notifications: type: boolean description: | @@ -13350,6 +13387,23 @@ paths: - 2 - 3 example: 1 + - name: user_list_style + in: query + description: | + The style selected by the user for the right sidebar user list. + + - 1 - Compact + - 2 - With status + - 3 - With avatar and status + + **Changes**: New in Zulip 6.0 (feature level 141). + schema: + type: integer + enum: + - 1 + - 2 + - 3 + example: 1 - name: timezone in: query description: | diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 604d805dc4..adb5c3def3 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -2583,6 +2583,7 @@ class RealmPropertyActionTest(BaseAction): default_view=["recent_topics", "all_messages"], emojiset=[emojiset["key"] for emojiset in RealmUserDefault.emojiset_choices()], demote_inactive_streams=UserProfile.DEMOTE_STREAMS_CHOICES, + user_list_style=UserProfile.USER_LIST_STYLE_CHOICES, desktop_icon_count_display=[1, 2, 3], notification_sound=["zulip", "ding"], email_notifications_batching_period_seconds=[120, 300], @@ -2656,6 +2657,7 @@ class UserDisplayActionTest(BaseAction): default_language=["es", "de", "en"], default_view=["all_messages", "recent_topics"], demote_inactive_streams=[2, 3, 1], + user_list_style=[1, 2, 3], color_scheme=[2, 3, 1], ) diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py index 70585dd2a1..cd5468ebf7 100644 --- a/zerver/tests/test_realm.py +++ b/zerver/tests/test_realm.py @@ -1202,6 +1202,7 @@ class RealmAPITest(ZulipTestCase): default_view=["recent_topics", "all_messages"], emojiset=[emojiset["key"] for emojiset in RealmUserDefault.emojiset_choices()], demote_inactive_streams=UserProfile.DEMOTE_STREAMS_CHOICES, + user_list_style=UserProfile.USER_LIST_STYLE_CHOICES, desktop_icon_count_display=[1, 2, 3], notification_sound=["zulip", "ding"], email_notifications_batching_period_seconds=[120, 300], diff --git a/zerver/tests/test_settings.py b/zerver/tests/test_settings.py index cca3aeb802..e51e55f7fc 100644 --- a/zerver/tests/test_settings.py +++ b/zerver/tests/test_settings.py @@ -357,6 +357,7 @@ class ChangeSettingsTest(ZulipTestCase): emojiset="google", timezone="America/Denver", demote_inactive_streams=2, + user_list_style=2, color_scheme=2, email_notifications_batching_period_seconds=100, notification_sound="ding", @@ -369,7 +370,7 @@ class ChangeSettingsTest(ZulipTestCase): if test_value is None: raise AssertionError(f"No test created for {setting_name}") - if setting_name not in ["demote_inactive_streams", "color_scheme"]: + if setting_name not in ["demote_inactive_streams", "user_list_style", "color_scheme"]: data = {setting_name: test_value} else: data = {setting_name: orjson.dumps(test_value).decode()} @@ -395,6 +396,7 @@ class ChangeSettingsTest(ZulipTestCase): emojiset="apple", timezone="invalid_US/Mountain", demote_inactive_streams=10, + user_list_style=10, color_scheme=10, notification_sound="invalid_sound", desktop_icon_count_display=10, diff --git a/zerver/views/realm.py b/zerver/views/realm.py index a743a5e872..cbc93b2ff3 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -440,6 +440,9 @@ def update_realm_user_settings_defaults( json_validator=check_bool, default=None ), send_read_receipts: Optional[bool] = REQ(json_validator=check_bool, default=None), + user_list_style: Optional[int] = REQ( + json_validator=check_int_in(UserProfile.USER_LIST_STYLE_CHOICES), default=None + ), ) -> HttpResponse: if notification_sound is not None or email_notifications_batching_period_seconds is not None: check_settings_values(notification_sound, email_notifications_batching_period_seconds) diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index 8865f2d7f9..87ee69e12d 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -217,6 +217,9 @@ def json_change_settings( ), send_stream_typing_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None), send_read_receipts: Optional[bool] = REQ(json_validator=check_bool, default=None), + user_list_style: Optional[int] = REQ( + json_validator=check_int_in(UserProfile.USER_LIST_STYLE_CHOICES), default=None + ), ) -> HttpResponse: if ( default_language is not None