compose: Migrate upload banner to new banner styling.

Fixes #22524.

This affects both the banner in the main compose box and the banner
in the message edit compose box. The use of ProgressBar has been
replaced with a more simple CSS (with light Javascript) solution.

The classnames are changing because the upload banner is now a
template rendered and remove()-ed from a banner container
(#compose_banners in the composebox, and a new div for banners in the
message edit view). It used to be in the send_status container so
there are a lot of class renames across the codebase.
This commit is contained in:
evykassirer
2023-01-08 22:43:39 -08:00
committed by Tim Abbott
parent b01ac3623f
commit 97d355fa72
8 changed files with 232 additions and 84 deletions

View File

@@ -18,7 +18,6 @@ mock_esm("@uppy/core", {
},
});
mock_esm("@uppy/xhr-upload", {default: class XHRUpload {}});
mock_esm("@uppy/progress-bar", {default: class ProgressBar {}});
const compose_actions = mock_esm("../../static/js/compose_actions");
mock_esm("../../static/js/csrf", {csrf_token: "csrf_token"});
@@ -29,7 +28,7 @@ const upload = zrequire("upload");
function test(label, f) {
run_test(label, (helpers) => {
page_params.max_file_upload_size_mib = 25;
f(helpers);
return f(helpers);
});
}
@@ -48,7 +47,11 @@ test("get_item", () => {
assert.equal(upload.get_item("textarea", {mode: "compose"}), $("#compose-textarea"));
assert.equal(
upload.get_item("send_status_message", {mode: "compose"}),
$("#compose-error-msg"),
$("#compose_banners .upload_banner .upload_msg"),
);
assert.equal(
upload.get_item("send_status_close_button", {mode: "compose"}),
$("#compose_banners .upload_banner .compose_banner_close_button"),
);
assert.equal(
upload.get_item("file_input_identifier", {mode: "compose"}),
@@ -74,27 +77,30 @@ test("get_item", () => {
assert.equal(
upload.get_item("send_status_identifier", {mode: "edit", row: 11}),
`#message-edit-send-status-${CSS.escape(11)}`,
`#edit_form_${CSS.escape(11)} .upload_banner`,
);
assert.equal(
upload.get_item("send_status", {mode: "edit", row: 75}),
$(`#message-edit-send-status-${CSS.escape(75)}`),
$(`#edit_form_${CSS.escape(75)} .upload_banner`),
);
$(`#message-edit-send-status-${CSS.escape(2)}`).set_find_results(
".send-status-close",
$(".send-status-close"),
$(`#edit_form_${CSS.escape(2)} .upload_banner`).set_find_results(
".compose_banner_close_button",
$(".compose_banner_close_button"),
);
assert.equal(
upload.get_item("send_status_close_button", {mode: "edit", row: 2}),
$(".send-status-close"),
$(`#edit_form_${CSS.escape(2)} .upload_banner .compose_banner_close_button`),
);
$(`#message-edit-send-status-${CSS.escape(22)}`).set_find_results(
".error-msg",
$(".error-msg"),
$(`#edit_form_${CSS.escape(22)} .upload_banner`).set_find_results(
".upload_msg",
$(".upload_msg"),
);
assert.equal(
upload.get_item("send_status_message", {mode: "edit", row: 22}),
$(`#edit_form_${CSS.escape(22)} .upload_banner .upload_msg`),
);
assert.equal(upload.get_item("send_status_message", {mode: "edit", row: 22}), $(".error-msg"));
assert.equal(
upload.get_item("file_input_identifier", {mode: "edit", row: 123}),
@@ -158,34 +164,49 @@ test("get_item", () => {
});
test("hide_upload_status", () => {
let banner_removed = false;
$("#compose_banners .upload_banner").remove = () => {
banner_removed = true;
};
$("#compose-send-button").prop("disabled", true);
$("#compose-send-status").addClass("alert-info").show();
upload.hide_upload_status({mode: "compose"});
assert.ok(banner_removed);
assert.equal($("#compose-send-button").prop("disabled"), false);
assert.equal($("#compose-send-button").hasClass("alert-info"), false);
assert.equal($("#compose-send-button").visible(), false);
});
test("show_error_message", () => {
test("show_error_message", ({mock_template}) => {
$("#compose_banners .upload_banner .moving_bar").css = () => {};
$("#compose_banners .upload_banner").length = 0;
let banner_shown = false;
mock_template("compose_banner/upload_banner.hbs", false, (data) => {
assert.equal(data.banner_type, "error");
assert.equal(data.banner_text, "Error message");
banner_shown = true;
});
$("#compose-send-button").prop("disabled", true);
$("#compose-send-status").addClass("alert-info").removeClass("alert-error").hide();
$("#compose-error-msg").text("");
$("#compose-error-msg").hide();
upload.show_error_message({mode: "compose"}, "Error message");
assert.equal($("#compose-send-button").prop("disabled"), false);
assert.ok($("#compose-send-status").hasClass("alert-error"));
assert.equal($("#compose-send-status").hasClass("alert-info"), false);
assert.ok($("#compose-send-status").visible());
assert.equal($("#compose-error-msg").text(), "Error message");
assert.ok(banner_shown);
mock_template("compose_banner/upload_banner.hbs", false, (data) => {
assert.equal(data.banner_type, "error");
assert.equal(data.banner_text, "translated: An unknown error occurred.");
banner_shown = true;
});
upload.show_error_message({mode: "compose"});
assert.equal($("#compose-error-msg").text(), "translated: An unknown error occurred.");
});
test("upload_files", ({override_rewire}) => {
test("upload_files", async ({mock_template, override_rewire}) => {
$("#compose_banners .upload_banner").remove = () => {};
$("#compose_banners .upload_banner .moving_bar").css = () => {};
$("#compose_banners .upload_banner").length = 0;
let uppy_cancel_all_called = false;
let files = [
{
@@ -214,20 +235,26 @@ test("upload_files", ({override_rewire}) => {
});
const config = {mode: "compose"};
$("#compose-send-button").prop("disabled", false);
upload.upload_files(uppy, config, []);
await upload.upload_files(uppy, config, []);
assert.ok(!$("#compose-send-button").prop("disabled"));
page_params.max_file_upload_size_mib = 0;
$("#compose-error-msg").text("");
upload.upload_files(uppy, config, files);
let banner_shown = false;
mock_template("compose_banner/upload_banner.hbs", false, (data) => {
assert.equal(data.banner_type, "error");
assert.equal(
$("#compose-error-msg").text(),
data.banner_text,
"translated: File and image uploads have been disabled for this organization.",
);
banner_shown = true;
});
page_params.max_file_upload_size_mib = 0;
$("#compose_banners .upload_banner .upload_msg").text("");
await upload.upload_files(uppy, config, files);
assert.ok(banner_shown);
page_params.max_file_upload_size_mib = 25;
let on_click_close_button_callback;
$(".compose-send-status-close").one = (event, callback) => {
$("#compose_banners .upload_banner .compose_banner_close_button").one = (event, callback) => {
assert.equal(event, "click");
on_click_close_button_callback = callback;
};
@@ -246,18 +273,24 @@ test("upload_files", ({override_rewire}) => {
markdown_preview_hide_button_clicked = true;
});
$("#compose-send-button").prop("disabled", false);
$("#compose-send-status").removeClass("alert-info").hide();
$("#compose_banners .upload_banner").remove();
$("#compose .undo_markdown_preview").show();
upload.upload_files(uppy, config, files);
banner_shown = false;
mock_template("compose_banner/upload_banner.hbs", false, (data) => {
assert.equal(data.banner_type, "info");
assert.equal(data.banner_text, "translated: Uploading…");
banner_shown = true;
});
await upload.upload_files(uppy, config, files);
assert.ok($("#compose-send-button").prop("disabled"));
assert.ok($("#compose-send-status").hasClass("alert-info"));
assert.ok($("#compose-send-status").visible());
assert.equal($("<p>").text(), "translated: Uploading…");
assert.ok(banner_shown);
assert.ok(compose_ui_insert_syntax_and_focus_called);
assert.ok(compose_ui_autosize_textarea_called);
assert.ok(markdown_preview_hide_button_clicked);
assert.ok(uppy_add_file_called);
banner_shown = false;
files = [
{
name: "budapest.png",
@@ -274,7 +307,8 @@ test("upload_files", ({override_rewire}) => {
add_file_counter += 1;
throw new Error("some error");
};
upload.upload_files(uppy, config, files);
await upload.upload_files(uppy, config, files);
assert.ok(banner_shown);
assert.equal(add_file_counter, 1);
hide_upload_status_called = false;
@@ -311,7 +345,6 @@ test("uppy_config", () => {
let uppy_stub_called = false;
let uppy_set_meta_called = false;
let uppy_used_xhrupload = false;
let uppy_used_progressbar = false;
uppy_stub = function (config) {
uppy_stub_called = true;
@@ -336,10 +369,6 @@ test("uppy_config", () => {
assert.equal(params.limit, 5);
assert.equal(Object.keys(params.locale.strings).length, 1);
assert.ok("timedOut" in params.locale.strings);
} else if (func_name === "ProgressBar") {
uppy_used_progressbar = true;
assert.equal(params.target, "#compose-send-status");
assert.equal(params.hideAfterFinish, false);
} else {
/* istanbul ignore next */
assert.fail(`Missing tests for ${func_name}`);
@@ -353,7 +382,6 @@ test("uppy_config", () => {
assert.equal(uppy_stub_called, true);
assert.equal(uppy_set_meta_called, true);
assert.equal(uppy_used_xhrupload, true);
assert.equal(uppy_used_progressbar, true);
});
test("file_input", ({override_rewire}) => {
@@ -454,7 +482,10 @@ test("copy_paste", ({override_rewire}) => {
assert.equal(upload_files_called, false);
});
test("uppy_events", ({override, override_rewire}) => {
test("uppy_events", ({override, override_rewire, mock_template}) => {
$("#compose_banners .upload_banner .moving_bar").css = () => {};
$("#compose_banners .upload_banner").length = 0;
const callbacks = {};
let uppy_cancel_all_called = false;
let state = {};
@@ -486,7 +517,7 @@ test("uppy_events", ({override, override_rewire}) => {
};
};
upload.setup_upload({mode: "compose"});
assert.equal(Object.keys(callbacks).length, 5);
assert.equal(Object.keys(callbacks).length, 6);
const on_upload_success_callback = callbacks["upload-success"];
const file = {
@@ -541,7 +572,7 @@ test("uppy_events", ({override, override_rewire}) => {
override_rewire(upload, "hide_upload_status", () => {
hide_upload_status_called = true;
});
$("#compose-send-status").removeClass("alert-error");
$("#compose_banner .upload_banner").removeClass("error");
files = [
{
id: "uppy-zulip/jpeg-1e-image/jpeg-163515-1578367331279",
@@ -561,11 +592,11 @@ test("uppy_events", ({override, override_rewire}) => {
assert.equal(files.length, 0);
hide_upload_status_called = false;
$("#compose-send-status").addClass("alert-error");
$("#compose_banners .upload_banner").addClass("error");
on_complete_callback();
assert.ok(!hide_upload_status_called);
$("#compose-send-status").removeClass("alert-error");
$("#compose_banners .upload_banner").removeClass("error");
hide_upload_status_called = false;
files = [
{
@@ -585,19 +616,22 @@ test("uppy_events", ({override, override_rewire}) => {
assert.ok(!hide_upload_status_called);
assert.equal(files.length, 1);
mock_template("compose_banner/upload_banner.hbs", false, (data) => {
assert.equal(data.banner_type, "error");
assert.equal(data.banner_text, "Some error message");
});
state = {
type: "error",
details: "Some error",
message: "Some error message",
};
const on_info_visible_callback = callbacks["info-visible"];
$("#compose-error-msg").text("");
$("#compose_banners .upload_banner .upload_msg").text("");
uppy_cancel_all_called = false;
compose_ui_replace_syntax_called = false;
const on_restriction_failed_callback = callbacks["restriction-failed"];
on_info_visible_callback();
assert.ok(uppy_cancel_all_called);
assert.equal($("#compose-error-msg").text(), "Some error message");
override_rewire(compose_ui, "replace_syntax", (old_syntax, new_syntax, textarea) => {
compose_ui_replace_syntax_called = true;
assert.equal(old_syntax, "[translated: Uploading copenhagen.png…]()");
@@ -627,7 +661,11 @@ test("uppy_events", ({override, override_rewire}) => {
on_info_visible_callback();
const on_upload_error_callback = callbacks["upload-error"];
$("#compose-error-msg").text("");
$("#compose_banners .upload_banner .upload_msg").text("");
mock_template("compose_banner/upload_banner.hbs", false, (data) => {
assert.equal(data.banner_type, "error");
assert.equal(data.banner_text, "Response message");
});
compose_ui_replace_syntax_called = false;
response = {
body: {
@@ -637,21 +675,22 @@ test("uppy_events", ({override, override_rewire}) => {
uppy_cancel_all_called = false;
on_upload_error_callback(file, null, response);
assert.ok(uppy_cancel_all_called);
assert.equal($("#compose-error-msg").text(), "Response message");
assert.ok(compose_ui_replace_syntax_called);
mock_template("compose_banner/upload_banner.hbs", false, (data) => {
assert.equal(data.banner_type, "error");
assert.equal(data.banner_text, "translated: An unknown error occurred.");
});
compose_ui_replace_syntax_called = false;
uppy_cancel_all_called = false;
on_upload_error_callback(file, null, null);
assert.ok(uppy_cancel_all_called);
assert.equal($("#compose-error-msg").text(), "translated: An unknown error occurred.");
assert.ok(compose_ui_replace_syntax_called);
$("#compose-error-msg").text("");
$("#compose_banners .upload_banner .upload_msg").text("");
$("#compose-textarea").val("user modified text");
uppy_cancel_all_called = false;
on_upload_error_callback(file, null);
assert.ok(uppy_cancel_all_called);
assert.equal($("#compose-error-msg").text(), "translated: An unknown error occurred.");
assert.ok(compose_ui_replace_syntax_called);
assert.equal($("#compose-textarea").val(), "user modified text");
});

View File

@@ -229,6 +229,14 @@ function FakeElement(selector, opts) {
shown = show;
return $self;
},
toggleClass(class_name, add) {
if (add) {
classes.set(class_name, true);
} else {
classes.delete(class_name);
}
return $self;
},
trigger(ev) {
event_store.trigger($self, ev);
return $self;

View File

@@ -337,11 +337,6 @@ export function initialize() {
ui_util.blur_active_element();
}
});
$(".message_edit_form .send-status-close").on("click", function () {
const row_id = rows.id($(this).closest(".message_row"));
const $send_status = $(`#message-edit-send-status-${CSS.escape(row_id)}`);
$send_status.stop(true).fadeOut(200);
});
$("body").on("click", ".message_edit_form .compose_upload_file", function (e) {
e.preventDefault();

View File

@@ -1,8 +1,9 @@
import {Uppy} from "@uppy/core";
import ProgressBar from "@uppy/progress-bar";
import XHRUpload from "@uppy/xhr-upload";
import $ from "jquery";
import render_upload_banner from "../templates/compose_banner/upload_banner.hbs";
import * as compose_actions from "./compose_actions";
import * as compose_state from "./compose_state";
import * as compose_ui from "./compose_ui";
@@ -32,14 +33,16 @@ export function get_item(key, config) {
return $("#compose-textarea");
case "send_button":
return $("#compose-send-button");
case "banner_container":
return $("#compose_banners");
case "send_status_identifier":
return "#compose-send-status";
return "#compose_banners .upload_banner";
case "send_status":
return $("#compose-send-status");
return $("#compose_banners .upload_banner");
case "send_status_close_button":
return $(".compose-send-status-close");
return $("#compose_banners .upload_banner .compose_banner_close_button");
case "send_status_message":
return $("#compose-error-msg");
return $("#compose_banners .upload_banner .upload_msg");
case "file_input_identifier":
return "#compose .file_input";
case "source":
@@ -62,16 +65,20 @@ export function get_item(key, config) {
return $(`#edit_form_${CSS.escape(config.row)} .message_edit_content`)
.closest(".message_edit_form")
.find(".message_edit_save");
case "banner_container":
return $(`#edit_form_${CSS.escape(config.row)} .banners`);
case "send_status_identifier":
return `#message-edit-send-status-${CSS.escape(config.row)}`;
return `#edit_form_${CSS.escape(config.row)} .upload_banner`;
case "send_status":
return $(`#message-edit-send-status-${CSS.escape(config.row)}`);
return $(`#edit_form_${CSS.escape(config.row)} .upload_banner`);
case "send_status_close_button":
return $(`#message-edit-send-status-${CSS.escape(config.row)}`).find(
".send-status-close",
return $(
`#edit_form_${CSS.escape(
config.row,
)} .upload_banner .compose_banner_close_button`,
);
case "send_status_message":
return $(`#message-edit-send-status-${CSS.escape(config.row)}`).find(".error-msg");
return $(`#edit_form_${CSS.escape(config.row)} .upload_banner .upload_msg`);
case "file_input_identifier":
return `#edit_form_${CSS.escape(config.row)} .file_input`;
case "source":
@@ -90,7 +97,36 @@ export function get_item(key, config) {
export function hide_upload_status(config) {
get_item("send_button", config).prop("disabled", false);
get_item("send_status", config).removeClass("alert-info").hide();
get_item("send_status", config).remove();
}
function show_upload_banner(config, banner_type, banner_text) {
// We only show one upload banner at a time per compose box,
// and all uploads are combined into the same progress bar.
// TODO: It would be nice to separate the error banner into
// a different element, so that we can show it at the same
// time as the upload bar and other uploads can still continue
// when an error occurs.
const $upload_banner = get_item("send_status", config);
if ($upload_banner.length) {
if (banner_type === "error") {
// Hide moving bar so that it doesn't do the 1s transition to 0
const $moving_bar = $(`${get_item("send_status_identifier", config)} .moving_bar`);
$moving_bar.hide();
$upload_banner.removeClass("info").addClass("error");
// Show it again once the animation is complete.
setTimeout(() => $moving_bar.show(), 1000);
} else {
$upload_banner.removeClass("error").addClass("info");
}
get_item("send_status_message", config).text(banner_text);
return;
}
const $new_banner = render_upload_banner({
banner_type,
banner_text,
});
get_item("banner_container", config).append($new_banner);
}
export function show_error_message(
@@ -98,11 +134,10 @@ export function show_error_message(
message = $t({defaultMessage: "An unknown error occurred."}),
) {
get_item("send_button", config).prop("disabled", false);
get_item("send_status", config).addClass("alert-error").removeClass("alert-info").show();
get_item("send_status_message", config).text(message);
show_upload_banner(config, "error", message);
}
export function upload_files(uppy, config, files) {
export async function upload_files(uppy, config, files) {
if (files.length === 0) {
return;
}
@@ -128,8 +163,7 @@ export function upload_files(uppy, config, files) {
}
get_item("send_button", config).prop("disabled", true);
get_item("send_status", config).addClass("alert-info").removeClass("alert-error").show();
get_item("send_status_message", config).html($("<p>").text($t({defaultMessage: "Uploading…"})));
show_upload_banner(config, "info", $t({defaultMessage: "Uploading…"}));
get_item("send_status_close_button", config).one("click", () => {
for (const file of uppy.getFiles()) {
compose_ui.replace_syntax(
@@ -280,7 +314,7 @@ export function setup_upload(config) {
}
}
const has_errors = get_item("send_status", config).hasClass("alert-error");
const has_errors = get_item("send_status", config).hasClass("error");
if (!uploads_in_progress && !has_errors) {
// Hide upload status for 100ms after the 1s transition to 100%
// so that the user can see the progress bar at 100%.
@@ -314,6 +348,8 @@ export function setup_upload(config) {
if (info.type === "error") {
// The remaining errors are mostly frontend errors like file being too large
// for upload.
// TODO: It would be nice to keep the other uploads going if one fails,
// and show both an error message and the upload bar.
uppy.cancelAll();
show_error_message(config, info.message);
}

View File

@@ -399,10 +399,53 @@
}
}
}
&.info {
background-color: hsl(204, 58%, 92%);
border-color: hsla(204, 49%, 29%, 0.4);
position: relative;
color: hsl(204, 49%, 29%);
.compose_banner_close_button {
color: hsla(204, 49%, 29%, 0.5);
&:hover {
color: hsl(204, 49%, 29%);
}
&:active {
color: hsla(204, 49%, 29%, 0.75);
}
}
}
}
.upload_banner {
overflow: hidden;
&.hidden {
display: none;
}
.moving_bar {
position: absolute;
width: 0;
/* The progress updates seem to come every second or so,
so this is the smoothest it can probably get. */
transition: width 1s ease-in-out;
background: hsl(204, 63%, 85%);
top: 0;
bottom: 0;
}
.upload_msg,
.compose_banner_close_button {
z-index: 1;
position: relative;
}
}
/* Like .nav-tabs > li > a */
div[id^="message-edit-send-status"],
#compose-send-status {
padding: 8px 14px;
margin-bottom: 8px;

View File

@@ -251,6 +251,31 @@
}
}
}
&.info {
background-color: hsl(204, 100%, 12%);
border-color: hsla(205, 58%, 69%, 0.4);
position: relative;
color: hsl(205, 58%, 69%);
.compose_banner_close_button {
color: hsla(205, 58%, 69%, 0.5);
&:hover {
color: hsl(205, 58%, 69%);
}
&:active {
color: hsla(205, 58%, 69%, 0.75);
}
}
}
}
.upload_banner {
.moving_bar {
background: hsl(204, 63%, 18%);
}
}
.message_embed .data-container::after {

View File

@@ -0,0 +1,5 @@
<div class="upload_banner compose_banner {{banner_type}}">
<div class="moving_bar"></div>
<p class="upload_msg">{{banner_text}}</p>
<a role="button" class="zulip-icon zulip-icon-close compose_banner_close_button"></a>
</div>

View File

@@ -1,10 +1,7 @@
{{! Client-side Mustache template for rendering the message edit form. }}
<form id="edit_form_{{message_id}}" class="new-style">
<div class="alert" id="message-edit-send-status-{{message_id}}">
<span class="send-status-close">&times;</span>
<span class="error-msg"></span>
</div>
<div class="banners"></div>
<div class="edit-controls">
{{> copy_message_button message_id=this.message_id}}
<textarea class="message_edit_content" maxlength="{{ max_message_length }}">{{content}}</textarea>