diff --git a/.eslintrc.json b/.eslintrc.json index 0e81209754..6f9f13e849 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -117,6 +117,7 @@ "pygments_data": false, "reactions": false, "realm_icon": false, + "realm_logo": false, "recent_senders": false, "reload": false, "reload_state": false, diff --git a/frontend_tests/node_tests/settings_org.js b/frontend_tests/node_tests/settings_org.js index 1cf0ee43b2..bcc31aa6c4 100644 --- a/frontend_tests/node_tests/settings_org.js +++ b/frontend_tests/node_tests/settings_org.js @@ -55,6 +55,10 @@ const _ui_report = { }, }; +const _realm_logo = { + build_realm_logo_widget: noop, +}; + set_global('channel', _channel); set_global('csrf_token', 'token-stub'); set_global('FormData', _FormData); @@ -63,6 +67,7 @@ set_global('loading', _loading); set_global('overlays', _overlays); set_global('page_params', _page_params); set_global('realm_icon', _realm_icon); +set_global('realm_logo', _realm_logo); set_global('templates', _templates); set_global('ui_report', _ui_report); diff --git a/static/js/admin.js b/static/js/admin.js index 0804e2a081..56f4d89aa3 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -49,6 +49,8 @@ exports.build_page = function () { is_guest: page_params.is_guest, realm_icon_source: page_params.realm_icon_source, realm_icon_url: page_params.realm_icon_url, + realm_logo_source: page_params.realm_logo_source, + realm_logo_url: page_params.realm_logo_url, realm_mandatory_topics: page_params.realm_mandatory_topics, realm_send_welcome_emails: page_params.realm_send_welcome_emails, realm_default_twenty_four_hour_time: page_params.realm_default_twenty_four_hour_time, diff --git a/static/js/bundles/app.js b/static/js/bundles/app.js index 0eac14b8c5..5bce15910f 100644 --- a/static/js/bundles/app.js +++ b/static/js/bundles/app.js @@ -166,6 +166,7 @@ import "js/templates.js"; import "js/upload_widget.js"; import "js/avatar.js"; import "js/realm_icon.js"; +import "js/realm_logo.js"; import 'js/reminder.js'; import 'js/confirm_dialog.js'; import "js/settings_account.js"; diff --git a/static/js/realm_logo.js b/static/js/realm_logo.js new file mode 100644 index 0000000000..906e4e83b0 --- /dev/null +++ b/static/js/realm_logo.js @@ -0,0 +1,53 @@ +/* eslint indent: "off" */ +var realm_logo = (function () { + + var exports = {}; + + exports.build_realm_logo_widget = function (upload_function) { + var get_file_input = function () { + return $('#realm_logo_file_input').expectOne(); + }; + + if (page_params.realm_logo_source === 'D') { + $("#realm_logo_delete_button").hide(); + } else { + $("#realm_logo_delete_button").show(); + } + $("#realm_logo_delete_button").on('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + channel.del({ + url: '/json/realm/logo', + }); + }); + + return upload_widget.build_direct_upload_widget( + get_file_input, + $("#realm_logo_file_input_error").expectOne(), + $("#realm_logo_upload_button").expectOne(), + upload_function, + page_params.max_logo_file_size + ); + }; + + exports.rerender = function () { + $("#realm-settings-logo").attr("src", page_params.realm_logo_url); + $("#realm-logo").attr("src", page_params.realm_logo_url); + if (page_params.realm_logo_source === 'U') { + $("#realm_logo_delete_button").show(); + } else { + $("#realm_logo_delete_button").hide(); + // Need to clear input because of a small edge case + // where you try to upload the same image you just deleted. + var file_input = $("#realm_logo_file_input"); + file_input.val(''); + } + }; + + return exports; +}()); + +if (typeof module !== 'undefined') { + module.exports = realm_logo; +} +window.realm_logo = realm_logo; diff --git a/static/js/server_events_dispatch.js b/static/js/server_events_dispatch.js index c239828a79..de466d3eae 100644 --- a/static/js/server_events_dispatch.js +++ b/static/js/server_events_dispatch.js @@ -160,6 +160,10 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) { if (electron_bridge !== undefined) { electron_bridge.send_event('realm_icon_url', event.data.icon_url); } + } else if (event.op === 'update_dict' && event.property === 'logo') { + page_params.realm_logo_url = event.data.logo_url; + page_params.realm_logo_source = event.data.logo_source; + realm_logo.rerender(); } else if (event.op === 'deactivated') { window.location.href = "/accounts/deactivated/"; } diff --git a/static/js/settings_org.js b/static/js/settings_org.js index af3b439108..09a158b75f 100644 --- a/static/js/settings_org.js +++ b/static/js/settings_org.js @@ -1054,8 +1054,11 @@ exports.build_page = function () { form_data.append('file-' + i, file); }); + var error_field = $("#realm_icon_file_input_error"); + error_field.hide(); var spinner = $("#upload_icon_spinner").expectOne(); loading.make_indicator(spinner, {text: i18n.t("Uploading icon.")}); + $("#upload_icon_button_text").expectOne().hide(); channel.post({ url: '/json/realm/icon', @@ -1065,12 +1068,52 @@ exports.build_page = function () { contentType: false, success: function () { loading.destroy_indicator($("#upload_icon_spinner")); + $("#upload_icon_button_text").expectOne().show(); + }, + error: function (xhr) { + loading.destroy_indicator($("#upload_logo_spinner")); + $("#upload_logo_button_text").expectOne().show(); + ui_report.error("", xhr, error_field); }, }); } realm_icon.build_realm_icon_widget(upload_realm_icon); + function upload_realm_logo(file_input) { + var form_data = new FormData(); + + form_data.append('csrfmiddlewaretoken', csrf_token); + jQuery.each(file_input[0].files, function (i, file) { + form_data.append('file-' + i, file); + }); + + var error_field = $("#realm_logo_file_input_error"); + error_field.hide(); + var spinner = $("#upload_logo_spinner").expectOne(); + loading.make_indicator(spinner, {text: i18n.t("Uploading logo.")}); + $("#upload_logo_button_text").expectOne().hide(); + + channel.post({ + url: '/json/realm/logo', + data: form_data, + cache: false, + processData: false, + contentType: false, + success: function () { + loading.destroy_indicator($("#upload_logo_spinner")); + $("#upload_logo_button_text").expectOne().show(); + }, + error: function (xhr) { + loading.destroy_indicator($("#upload_logo_spinner")); + $("#upload_logo_button_text").expectOne().show(); + ui_report.error("", xhr, error_field); + }, + }); + + } + realm_logo.build_realm_logo_widget(upload_realm_logo); + $('#deactivate_realm_button').on('click', function (e) { if (!overlays.is_modal_open()) { e.preventDefault(); diff --git a/static/styles/settings.scss b/static/styles/settings.scss index 6801c83f90..25c45d087b 100644 --- a/static/styles/settings.scss +++ b/static/styles/settings.scss @@ -81,10 +81,15 @@ label { } .user-avatar-section, +.realm-logo-section, .realm-icon-section { position: relative; } +.realm-logo-block { + margin-bottom: 10px; +} + .user-avatar-section { float: right; } @@ -101,8 +106,9 @@ label { } .user-avatar-section .inline-block, +.realm-logo-section .inline-block, .realm-icon-section .inline-block { - margin: 10px 20px 0px 0px; + margin: 0px 20px 0px 0px; vertical-align: top; border-radius: 4px; @@ -458,6 +464,11 @@ input[type=checkbox] + .inline-block { margin-top: 0px; } +.realm-logo-section { + margin-top: 10px; + margin-bottom: 20px; +} + .realm-icon-section { margin-bottom: 20px; } @@ -937,6 +948,7 @@ input[type=checkbox].inline-block { } #upload_avatar_spinner, +#upload_logo_spinner, #upload_icon_spinner { font-size: 14px; margin: auto; @@ -1071,6 +1083,15 @@ input[type=checkbox].inline-block { height: 100px; } +#realm-settings-logo { + border-radius: 5px; + box-shadow: 0px 0px 10px hsla(0, 0%, 0%, 0.2); + /* We allow actual images up to 800x100 in the main display, but the + settings UI looks bad beyond ~730px, so we limit the width here */ + height: 100px; + max-width: 720px; +} + .invite-user-link i { text-decoration: none; margin-right: 5px; @@ -1636,6 +1657,13 @@ input[type=text]#settings_search { margin: 0 0 0 5px; } +@media (max-width: 1023px) { + #realm-settings-logo { + max-width: 600px; + height: 75px; + } +} + @media (max-width: 953px) { .user-avatar-section, .realm-icon-section { @@ -1655,6 +1683,11 @@ input[type=text]#settings_search { .subsection-failed-status p { margin: 5px 0 0 0; } + + #realm-settings-logo { + max-width: 400px; + height: 50px; + } } @media (max-width: 786px) { diff --git a/static/styles/zulip.scss b/static/styles/zulip.scss index d3e8662352..84c61b0a3a 100644 --- a/static/styles/zulip.scss +++ b/static/styles/zulip.scss @@ -2157,6 +2157,27 @@ div.floating_recipient { display: inline-block; } +.settings-section .realm-icon-section .loading_indicator_text, +.settings-section .realm-logo-section .loading_indicator_text { + font-size: 14px; + font-weight: 400; + vertical-align: middle; + line-height: 20px; + display: inline-block; + float: none; + margin-top: 0px; + margin-left: -12px; +} + +.settings-section .realm-logo-section .loading_indicator_spinner, +.settings-section .realm-icon-section .loading_indicator_spinner { + width: 10%; + height: 16px; + margin-top: 2px; + vertical-align: middle; + display: inline-block; +} + .settings-section #default_language { text-decoration: none; } diff --git a/static/templates/settings/organization-profile-admin.handlebars b/static/templates/settings/organization-profile-admin.handlebars index bd22ff67d4..0f2d4228d2 100644 --- a/static/templates/settings/organization-profile-admin.handlebars +++ b/static/templates/settings/organization-profile-admin.handlebars @@ -23,23 +23,47 @@

{{t "Organization avatar" }}

+

{{t "A square logo used to brand your Zulip organization." }}

-
+
-
+ id="realm_icon_upload_button"> + {{t 'Upload new icon' }} + +
+

{{t "Organization logo" }}

+

{{t "A wide image, replacing the Zulip logo in the upper left corner of the Zulip apps." }}

+ +
+
+ + +
+
+
+ + +
+
+

{{t "Deactivate organization" }}

diff --git a/templates/zerver/api/server-settings.md b/templates/zerver/api/server-settings.md index 0912c79a81..2a2f986999 100644 --- a/templates/zerver/api/server-settings.md +++ b/templates/zerver/api/server-settings.md @@ -50,7 +50,12 @@ curl {{ api_url }}/v1/server_settings \ enabled with a username and password combination. * `realm_uri`: the organization's canonical URI. * `realm_name`: the organization's name (for display purposes). -* `realm_icon`: the URI of the organization's icon (usually a logo). +* `realm_icon`: the URI of the organization's logo as a square image, + used for identifying the organization in small locations in the + mobile and desktop apps. +* `realm_logo`: the URI of the organization's logo as a horizontal + format image (displayed in the top-left corner of the logged-in + webapp). * `realm_description`: HTML description of the organization, as configured by the [organization profile](/help/create-your-organization-profile). diff --git a/templates/zerver/app/navbar.html b/templates/zerver/app/navbar.html index fe03922a17..160ac2dd0e 100644 --- a/templates/zerver/app/navbar.html +++ b/templates/zerver/app/navbar.html @@ -31,7 +31,7 @@