diff --git a/res/css/structures/_RoomStatusBar.scss b/res/css/structures/_RoomStatusBar.scss
index 5bf2aee3ae..db8e5a10ba 100644
--- a/res/css/structures/_RoomStatusBar.scss
+++ b/res/css/structures/_RoomStatusBar.scss
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_RoomStatusBar {
+.mx_RoomStatusBar:not(.mx_RoomStatusBar_unsentMessages) {
margin-left: 65px;
min-height: 50px;
}
@@ -68,6 +68,99 @@ limitations under the License.
min-height: 58px;
}
+.mx_RoomStatusBar_unsentMessages {
+ > div[role="alert"] {
+ // cheat some basic alignment
+ display: flex;
+ align-items: center;
+ min-height: 70px;
+ margin: 12px;
+ padding-left: 16px;
+ background-color: $header-panel-bg-color;
+ border-radius: 4px;
+ }
+
+ .mx_RoomStatusBar_unsentBadge {
+ margin-right: 12px;
+
+ .mx_NotificationBadge {
+ // Override sizing from the default badge
+ width: 24px !important;
+ height: 24px !important;
+ border-radius: 24px !important;
+
+ .mx_NotificationBadge_count {
+ font-size: $font-16px !important; // override default
+ }
+ }
+ }
+
+ .mx_RoomStatusBar_unsentTitle {
+ color: $warning-color;
+ font-size: $font-15px;
+ }
+
+ .mx_RoomStatusBar_unsentDescription {
+ font-size: $font-12px;
+ }
+
+ .mx_RoomStatusBar_unsentButtonBar {
+ flex-grow: 1;
+ text-align: right;
+ margin-right: 22px;
+ color: $muted-fg-color;
+
+ .mx_AccessibleButton {
+ padding: 5px 10px;
+ padding-left: 28px; // 16px for the icon, 2px margin to text, 10px regular padding
+ display: inline-block;
+ position: relative;
+
+ &:nth-child(2) {
+ border-left: 1px solid $input-darker-bg-color;
+ }
+
+ &::before {
+ content: '';
+ position: absolute;
+ left: 10px; // inset for regular button padding
+ background-color: $muted-fg-color;
+ mask-repeat: no-repeat;
+ mask-position: center;
+ mask-size: contain;
+ }
+
+ &.mx_RoomStatusBar_unsentCancelAllBtn::before {
+ mask-image: url('$(res)/img/element-icons/trashcan.svg');
+ width: 12px;
+ height: 16px;
+ top: calc(50% - 8px); // text sizes are dynamic
+ }
+
+ &.mx_RoomStatusBar_unsentResendAllBtn {
+ padding-left: 34px; // 28px from above, but +6px to account for the wider icon
+
+ &::before {
+ mask-image: url('$(res)/img/element-icons/retry.svg');
+ width: 18px;
+ height: 18px;
+ top: calc(50% - 9px); // text sizes are dynamic
+ }
+ }
+ }
+
+ .mx_InlineSpinner {
+ vertical-align: middle;
+ margin-right: 5px;
+ top: 1px; // just to help the vertical alignment be slightly better
+
+ & + span {
+ margin-right: 10px; // same margin/padding as the rightmost button
+ }
+ }
+ }
+}
+
.mx_RoomStatusBar_connectionLostBar img {
padding-left: 10px;
padding-right: 10px;
@@ -103,7 +196,7 @@ limitations under the License.
}
.mx_MatrixChat_useCompactLayout {
- .mx_RoomStatusBar {
+ .mx_RoomStatusBar:not(.mx_RoomStatusBar_unsentMessages) {
min-height: 40px;
}
diff --git a/res/img/element-icons/retry.svg b/res/img/element-icons/retry.svg
new file mode 100644
index 0000000000..09448d6458
--- /dev/null
+++ b/res/img/element-icons/retry.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/img/element-icons/trashcan.svg b/res/img/element-icons/trashcan.svg
new file mode 100644
index 0000000000..f8fb8b5c46
--- /dev/null
+++ b/res/img/element-icons/trashcan.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/Resend.js b/src/Resend.js
index bf69e59c1a..f1e5fb38f5 100644
--- a/src/Resend.js
+++ b/src/Resend.js
@@ -21,11 +21,11 @@ import { EventStatus } from 'matrix-js-sdk/src/models/event';
export default class Resend {
static resendUnsentEvents(room) {
- room.getPendingEvents().filter(function(ev) {
+ return Promise.all(room.getPendingEvents().filter(function(ev) {
return ev.status === EventStatus.NOT_SENT;
- }).forEach(function(event) {
- Resend.resend(event);
- });
+ }).map(function(event) {
+ return Resend.resend(event);
+ }));
}
static cancelUnsentEvents(room) {
@@ -38,7 +38,7 @@ export default class Resend {
static resend(event) {
const room = MatrixClientPeg.get().getRoom(event.getRoomId());
- MatrixClientPeg.get().resendEvent(event, room).then(function(res) {
+ return MatrixClientPeg.get().resendEvent(event, room).then(function(res) {
dis.dispatch({
action: 'message_sent',
event: event,
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index 54b6fee233..deffd6b95b 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -1,5 +1,5 @@
/*
-Copyright 2015-2020 The Matrix.org Foundation C.I.C.
+Copyright 2015-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -20,10 +20,15 @@ import { _t, _td } from '../../languageHandler';
import {MatrixClientPeg} from '../../MatrixClientPeg';
import Resend from '../../Resend';
import dis from '../../dispatcher/dispatcher';
-import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
+import {messageForResourceLimitError} from '../../utils/ErrorUtils';
import {Action} from "../../dispatcher/actions";
import {replaceableComponent} from "../../utils/replaceableComponent";
import {EventStatus} from "matrix-js-sdk/src/models/event";
+import NotificationBadge from "../views/rooms/NotificationBadge";
+import {NotificationColor} from "../../stores/notifications/NotificationColor";
+import {StaticNotificationState} from "../../stores/notifications/StaticNotificationState";
+import AccessibleButton from "../views/elements/AccessibleButton";
+import InlineSpinner from "../views/elements/InlineSpinner";
const STATUS_BAR_HIDDEN = 0;
const STATUS_BAR_EXPANDED = 1;
@@ -76,6 +81,7 @@ export default class RoomStatusBar extends React.Component {
syncState: MatrixClientPeg.get().getSyncState(),
syncStateData: MatrixClientPeg.get().getSyncStateData(),
unsentMessages: getUnsentMessages(this.props.room),
+ isResending: false,
};
componentDidMount() {
@@ -109,7 +115,10 @@ export default class RoomStatusBar extends React.Component {
};
_onResendAllClick = () => {
- Resend.resendUnsentEvents(this.props.room);
+ Resend.resendUnsentEvents(this.props.room).then(() => {
+ this.setState({isResending: false});
+ });
+ this.setState({isResending: true});
dis.fire(Action.FocusComposer);
};
@@ -120,10 +129,7 @@ export default class RoomStatusBar extends React.Component {
_onRoomLocalEchoUpdated = (event, room, oldEventId, oldStatus) => {
if (room.roomId !== this.props.room.roomId) return;
-
- this.setState({
- unsentMessages: getUnsentMessages(this.props.room),
- });
+ this.setState({unsentMessages: getUnsentMessages(this.props.room)});
};
// Check whether current size is greater than 0, if yes call props.onVisible
@@ -141,7 +147,7 @@ export default class RoomStatusBar extends React.Component {
_getSize() {
if (this._shouldShowConnectionError()) {
return STATUS_BAR_EXPANDED;
- } else if (this.state.unsentMessages.length > 0) {
+ } else if (this.state.unsentMessages.length > 0 || this.state.isResending) {
return STATUS_BAR_EXPANDED_LARGE;
}
return STATUS_BAR_HIDDEN;
@@ -162,7 +168,6 @@ export default class RoomStatusBar extends React.Component {
_getUnsentMessageContent() {
const unsentMessages = this.state.unsentMessages;
- if (!unsentMessages.length) return null;
let title;
@@ -206,75 +211,76 @@ export default class RoomStatusBar extends React.Component {
"Please contact your service administrator to continue using the service.",
),
});
- } else if (
- unsentMessages.length === 1 &&
- unsentMessages[0].error &&
- unsentMessages[0].error.data &&
- unsentMessages[0].error.data.error
- ) {
- title = messageForSendError(unsentMessages[0].error.data) || unsentMessages[0].error.data.error;
} else {
- title = _t('%(count)s of your messages have not been sent.', { count: unsentMessages.length });
+ title = _t('Some of your messages have not been sent');
}
- const content = _t("%(count)s