diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 5b625fe6..86edf027 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -87,13 +87,24 @@ export class RoomViewModel extends ViewModel { return ""; } - get avatarInitials() { + get avatarLetter() { return avatarInitials(this._room.name); } get avatarColorNumber() { return getIdentifierColorNumber(this._room.id) } + + get avatarUrl() { + if (this._room.avatarUrl) { + return this._room.mediaRepository.mxcUrlThumbnail(this._room.avatarUrl, 32, 32, "crop"); + } + return null; + } + + get avatarTitle() { + return this.name; + } async _sendMessage(message) { if (message) { diff --git a/src/domain/session/room/timeline/tiles/MessageTile.js b/src/domain/session/room/timeline/tiles/MessageTile.js index 794ba1a9..5b3bb603 100644 --- a/src/domain/session/room/timeline/tiles/MessageTile.js +++ b/src/domain/session/room/timeline/tiles/MessageTile.js @@ -35,6 +35,7 @@ export class MessageTile extends SimpleTile { return this._entry.displayName || this._entry.sender; } + // Avatar view model contract get avatarColorNumber() { return getIdentifierColorNumber(this._entry.sender); } @@ -50,6 +51,10 @@ export class MessageTile extends SimpleTile { return avatarInitials(this.sender); } + get avatarTitle() { + return this.sender; + } + get date() { return this._date && this._date.toLocaleDateString({}, {month: "numeric", day: "numeric"}); } diff --git a/src/domain/session/roomlist/RoomTileViewModel.js b/src/domain/session/roomlist/RoomTileViewModel.js index 9f527f65..56d0345f 100644 --- a/src/domain/session/roomlist/RoomTileViewModel.js +++ b/src/domain/session/roomlist/RoomTileViewModel.js @@ -61,11 +61,23 @@ export class RoomTileViewModel extends ViewModel { return this._room.name; } - get avatarInitials() { + // Avatar view model contract + get avatarLetter() { return avatarInitials(this._room.name); } get avatarColorNumber() { return getIdentifierColorNumber(this._room.id) } + + get avatarUrl() { + if (this._room.avatarUrl) { + return this._room.mediaRepository.mxcUrlThumbnail(this._room.avatarUrl, 32, 32, "crop"); + } + return null; + } + + get avatarTitle() { + return this.name; + } } diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index 7bea8362..5ac77009 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -180,6 +180,10 @@ export class Room extends EventEmitter { return this._roomId; } + get avatarUrl() { + return this._summary.avatarUrl; + } + /** @public */ async openTimeline() { if (this._timeline) { diff --git a/src/matrix/room/RoomSummary.js b/src/matrix/room/RoomSummary.js index dd443be3..bef9424c 100644 --- a/src/matrix/room/RoomSummary.js +++ b/src/matrix/room/RoomSummary.js @@ -34,6 +34,12 @@ function applySyncResponse(data, roomResponse, membership) { } data = timeline.events.reduce(processEvent, data); } + const unreadNotifications = roomResponse.unread_notifications; + if (unreadNotifications) { + data = data.cloneIfNeeded(); + data.highlightCount = unreadNotifications.highlight_count; + data.notificationCount = unreadNotifications.notification_count; + } return data; } @@ -46,15 +52,21 @@ function processEvent(data, event) { } } if (event.type === "m.room.name") { - const newName = event.content && event.content.name; + const newName = event.content?.name; if (newName !== data.name) { data = data.cloneIfNeeded(); data.name = newName; } + } if (event.type === "m.room.avatar") { + const newUrl = event.content?.url; + if (newUrl !== data.avatarUrl) { + data = data.cloneIfNeeded(); + data.avatarUrl = newUrl; + } } else if (event.type === "m.room.message") { - const content = event.content; - const body = content && content.body; - const msgtype = content && content.msgtype; + const {content} = event; + const body = content?.body; + const msgtype = content?.msgtype; if (msgtype === "m.text") { data = data.cloneIfNeeded(); data.lastMessageBody = body; @@ -105,6 +117,9 @@ class SummaryData { this.altAliases = copy ? copy.altAliases : null; this.hasFetchedMembers = copy ? copy.hasFetchedMembers : false; this.lastPaginationToken = copy ? copy.lastPaginationToken : null; + this.avatarUrl = copy ? copy.avatarUrl : null; + this.notificationCount = copy ? copy.notificationCount : 0; + this.highlightCount = copy ? copy.highlightCount : 0; this.cloned = copy ? true : false; } @@ -155,6 +170,10 @@ export class RoomSummary { return this._data.joinCount; } + get avatarUrl() { + return this._data.avatarUrl; + } + get hasFetchedMembers() { return this._data.hasFetchedMembers; } diff --git a/src/ui/web/common.js b/src/ui/web/common.js index d7ae198b..2883652e 100644 --- a/src/ui/web/common.js +++ b/src/ui/web/common.js @@ -19,3 +19,23 @@ export function spinner(t, extraClasses = undefined) { t.circle({cx:"50%", cy:"50%", r:"45%", pathLength:"100"}) ); } + +/** + * @param {TemplateBuilder} t + * @param {Object} vm view model with {avatarUrl, avatarColorNumber, avatarTitle, avatarLetter} + * @param {Number} size + * @return {Element} + */ +export function renderAvatar(t, vm, size) { + const hasAvatar = !!vm.avatarUrl; + const avatarClasses = { + avatar: true, + [`usercolor${vm.avatarColorNumber}`]: !hasAvatar, + }; + // TODO: handle updates from default to img or reverse + const sizeStr = size.toString(); + const avatarContent = hasAvatar ? + t.img({src: vm => vm.avatarUrl, width: sizeStr, height: sizeStr, title: vm => vm.avatarTitle}) : + vm => vm.avatarLetter; + return t.div({className: avatarClasses}, [avatarContent]); +} diff --git a/src/ui/web/session/RoomTile.js b/src/ui/web/session/RoomTile.js index 9268bfe5..cde53b81 100644 --- a/src/ui/web/session/RoomTile.js +++ b/src/ui/web/session/RoomTile.js @@ -15,11 +15,12 @@ limitations under the License. */ import {TemplateView} from "../general/TemplateView.js"; +import {renderAvatar} from "../common.js"; export class RoomTile extends TemplateView { render(t, vm) { return t.li({"className": {"active": vm => vm.isOpen}}, [ - t.div({className: `avatar medium usercolor${vm.avatarColorNumber}`}, vm => vm.avatarInitials), + renderAvatar(t, vm, 32), t.div({className: "description"}, t.div({className: "name"}, vm => vm.name)) ]); } diff --git a/src/ui/web/session/room/RoomView.js b/src/ui/web/session/room/RoomView.js index 7a4abd45..3f9b9bce 100644 --- a/src/ui/web/session/room/RoomView.js +++ b/src/ui/web/session/room/RoomView.js @@ -19,6 +19,7 @@ import {TemplateView} from "../../general/TemplateView.js"; import {TimelineList} from "./TimelineList.js"; import {TimelineLoadingView} from "./TimelineLoadingView.js"; import {MessageComposer} from "./MessageComposer.js"; +import {renderAvatar} from "../../common.js"; export class RoomView extends TemplateView { render(t, vm) { @@ -26,7 +27,7 @@ export class RoomView extends TemplateView { t.div({className: "TimelinePanel"}, [ t.div({className: "RoomHeader"}, [ t.button({className: "back", onClick: () => vm.close()}), - t.div({className: `avatar usercolor${vm.avatarColorNumber}`}, vm => vm.avatarInitials), + renderAvatar(t, vm, 32), t.div({className: "room-description"}, [ t.h2(vm => vm.name), ]), diff --git a/src/ui/web/session/room/timeline/common.js b/src/ui/web/session/room/timeline/common.js index b7965905..36ccb624 100644 --- a/src/ui/web/session/room/timeline/common.js +++ b/src/ui/web/session/room/timeline/common.js @@ -15,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import {renderAvatar} from "../../../common.js"; + export function renderMessage(t, vm, children) { const classes = { "TextMessageView": true, @@ -23,16 +25,8 @@ export function renderMessage(t, vm, children) { continuation: vm => vm.isContinuation, }; - const hasAvatar = !!vm.avatarUrl; - const avatarClasses = { - avatar: true, - [`usercolor${vm.avatarColorNumber}`]: !hasAvatar, - }; - const avatarContent = hasAvatar ? - t.img({src: vm.avatarUrl, width: "30", height: "30", title: vm.sender}) : - vm.avatarLetter; const profile = t.div({className: "profile"}, [ - t.div({className: avatarClasses}, [avatarContent]), + renderAvatar(t, vm, 30), t.div({className: `sender usercolor${vm.avatarColorNumber}`}, vm.sender) ]); children = [profile].concat(children);