Merge pull request #60 from vector-im/bwindels/room-avatars

Render room avatar images
This commit is contained in:
Bruno Windels 2020-08-20 15:34:11 +00:00 committed by GitHub
commit 7ea10ea2ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 84 additions and 17 deletions

View File

@ -87,7 +87,7 @@ export class RoomViewModel extends ViewModel {
return ""; return "";
} }
get avatarInitials() { get avatarLetter() {
return avatarInitials(this._room.name); return avatarInitials(this._room.name);
} }
@ -95,6 +95,17 @@ export class RoomViewModel extends ViewModel {
return getIdentifierColorNumber(this._room.id) 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) { async _sendMessage(message) {
if (message) { if (message) {
try { try {

View File

@ -35,6 +35,7 @@ export class MessageTile extends SimpleTile {
return this._entry.displayName || this._entry.sender; return this._entry.displayName || this._entry.sender;
} }
// Avatar view model contract
get avatarColorNumber() { get avatarColorNumber() {
return getIdentifierColorNumber(this._entry.sender); return getIdentifierColorNumber(this._entry.sender);
} }
@ -50,6 +51,10 @@ export class MessageTile extends SimpleTile {
return avatarInitials(this.sender); return avatarInitials(this.sender);
} }
get avatarTitle() {
return this.sender;
}
get date() { get date() {
return this._date && this._date.toLocaleDateString({}, {month: "numeric", day: "numeric"}); return this._date && this._date.toLocaleDateString({}, {month: "numeric", day: "numeric"});
} }

View File

@ -61,11 +61,23 @@ export class RoomTileViewModel extends ViewModel {
return this._room.name; return this._room.name;
} }
get avatarInitials() { // Avatar view model contract
get avatarLetter() {
return avatarInitials(this._room.name); return avatarInitials(this._room.name);
} }
get avatarColorNumber() { get avatarColorNumber() {
return getIdentifierColorNumber(this._room.id) 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;
}
} }

View File

@ -180,6 +180,10 @@ export class Room extends EventEmitter {
return this._roomId; return this._roomId;
} }
get avatarUrl() {
return this._summary.avatarUrl;
}
/** @public */ /** @public */
async openTimeline() { async openTimeline() {
if (this._timeline) { if (this._timeline) {

View File

@ -34,6 +34,12 @@ function applySyncResponse(data, roomResponse, membership) {
} }
data = timeline.events.reduce(processEvent, data); 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; return data;
} }
@ -46,15 +52,21 @@ function processEvent(data, event) {
} }
} }
if (event.type === "m.room.name") { if (event.type === "m.room.name") {
const newName = event.content && event.content.name; const newName = event.content?.name;
if (newName !== data.name) { if (newName !== data.name) {
data = data.cloneIfNeeded(); data = data.cloneIfNeeded();
data.name = newName; 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") { } else if (event.type === "m.room.message") {
const content = event.content; const {content} = event;
const body = content && content.body; const body = content?.body;
const msgtype = content && content.msgtype; const msgtype = content?.msgtype;
if (msgtype === "m.text") { if (msgtype === "m.text") {
data = data.cloneIfNeeded(); data = data.cloneIfNeeded();
data.lastMessageBody = body; data.lastMessageBody = body;
@ -105,6 +117,9 @@ class SummaryData {
this.altAliases = copy ? copy.altAliases : null; this.altAliases = copy ? copy.altAliases : null;
this.hasFetchedMembers = copy ? copy.hasFetchedMembers : false; this.hasFetchedMembers = copy ? copy.hasFetchedMembers : false;
this.lastPaginationToken = copy ? copy.lastPaginationToken : null; 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; this.cloned = copy ? true : false;
} }
@ -155,6 +170,10 @@ export class RoomSummary {
return this._data.joinCount; return this._data.joinCount;
} }
get avatarUrl() {
return this._data.avatarUrl;
}
get hasFetchedMembers() { get hasFetchedMembers() {
return this._data.hasFetchedMembers; return this._data.hasFetchedMembers;
} }

View File

@ -19,3 +19,23 @@ export function spinner(t, extraClasses = undefined) {
t.circle({cx:"50%", cy:"50%", r:"45%", pathLength:"100"}) 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]);
}

View File

@ -15,11 +15,12 @@ limitations under the License.
*/ */
import {TemplateView} from "../general/TemplateView.js"; import {TemplateView} from "../general/TemplateView.js";
import {renderAvatar} from "../common.js";
export class RoomTile extends TemplateView { export class RoomTile extends TemplateView {
render(t, vm) { render(t, vm) {
return t.li({"className": {"active": vm => vm.isOpen}}, [ 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)) t.div({className: "description"}, t.div({className: "name"}, vm => vm.name))
]); ]);
} }

View File

@ -19,6 +19,7 @@ import {TemplateView} from "../../general/TemplateView.js";
import {TimelineList} from "./TimelineList.js"; import {TimelineList} from "./TimelineList.js";
import {TimelineLoadingView} from "./TimelineLoadingView.js"; import {TimelineLoadingView} from "./TimelineLoadingView.js";
import {MessageComposer} from "./MessageComposer.js"; import {MessageComposer} from "./MessageComposer.js";
import {renderAvatar} from "../../common.js";
export class RoomView extends TemplateView { export class RoomView extends TemplateView {
render(t, vm) { render(t, vm) {
@ -26,7 +27,7 @@ export class RoomView extends TemplateView {
t.div({className: "TimelinePanel"}, [ t.div({className: "TimelinePanel"}, [
t.div({className: "RoomHeader"}, [ t.div({className: "RoomHeader"}, [
t.button({className: "back", onClick: () => vm.close()}), 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.div({className: "room-description"}, [
t.h2(vm => vm.name), t.h2(vm => vm.name),
]), ]),

View File

@ -15,6 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {renderAvatar} from "../../../common.js";
export function renderMessage(t, vm, children) { export function renderMessage(t, vm, children) {
const classes = { const classes = {
"TextMessageView": true, "TextMessageView": true,
@ -23,16 +25,8 @@ export function renderMessage(t, vm, children) {
continuation: vm => vm.isContinuation, 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"}, [ const profile = t.div({className: "profile"}, [
t.div({className: avatarClasses}, [avatarContent]), renderAvatar(t, vm, 30),
t.div({className: `sender usercolor${vm.avatarColorNumber}`}, vm.sender) t.div({className: `sender usercolor${vm.avatarColorNumber}`}, vm.sender)
]); ]);
children = [profile].concat(children); children = [profile].concat(children);