New room list and chat header design

This commit is contained in:
N Pex 2023-03-03 14:43:53 +00:00
parent 7396fbc959
commit baf0120eee
23 changed files with 651 additions and 287 deletions

View file

@ -6,7 +6,7 @@
v-on="$listeners"
>
<v-col cols="auto" class="me-2">
<v-icon size="22">{{ icon }}</v-icon>
<v-icon :size="iconSize">{{ icon }}</v-icon>
</v-col>
<v-col>{{ text }}</v-col>
</v-row>
@ -22,6 +22,12 @@ export default {
return null;
},
},
iconSize: {
type: Number,
default: function() {
return 22;
}
},
text: {
type: String,
default: function () {

View file

@ -24,6 +24,7 @@
color="black"
@click.stop="onBackgroundClick"
class="bottom-sheet-close"
v-if="showCloseButton"
>
<v-icon color="white" >cancel</v-icon>
</v-btn>
@ -40,6 +41,10 @@ import Hammer from "hammerjs";
export default {
props: {
showCloseButton: {
type: Boolean,
default: true,
},
openY: {
type: Number,
default: 0.1,

View file

@ -1,9 +1,6 @@
<template>
<div class="chat-root fill-height d-flex flex-column">
<div class="chat-room-invitations clickable" v-if="invitationCount > 0" @click.stop="onInvitationsClick">
{{ $tc("room.invitations", invitationCount) }}
</div>
<ChatHeader class="chat-header flex-grow-0 flex-shrink-0" v-on:header-click="onHeaderClick" />
<ChatHeader class="chat-header flex-grow-0 flex-shrink-0" v-on:header-click="onHeaderClick" v-on:view-room-details="viewRoomDetails" />
<AudioLayout ref="chatContainer" class="auto-audio-player-root" v-if="useVoiceMode" :room="room"
:events="events" :autoplay="!showRecorder"
:timelineSet="timelineSet"
@ -513,9 +510,6 @@ export default {
debugging() {
return false; //(window.location.host || "").startsWith("localhost");
},
invitationCount() {
return this.$matrix.invites.length;
},
canCreatePoll() {
// We say that if you can redact events, you are allowed to create polls.
const me = this.room && this.room.getMember(this.$matrix.currentUserId);
@ -1436,16 +1430,17 @@ export default {
},
onHeaderClick() {
const invitations = this.$matrix.invites.length;
const joinedRooms = this.$matrix.joinedRooms;
if (joinedRooms && joinedRooms.length == 1 && joinedRooms[0].roomId == this.room.roomId) {
if (invitations == 0 && joinedRooms && joinedRooms.length == 1 && joinedRooms[0].roomId == this.room.roomId) {
// Only joined to this room, go directly to room details!
this.$navigation.push({ name: "RoomInfo" });
return;
}
this.$refs.roomInfoSheet.open();
},
onInvitationsClick() {
this.$navigation.push({ name: "Home" }, -1);
viewRoomDetails() {
this.$navigation.push({ name: "RoomInfo" });
},
pollWasClosed(ignoredE) {
let div = document.createElement("div");

View file

@ -1,92 +1,73 @@
<template>
<v-container fluid v-if="room">
<v-row class="chat-header-row flex-nowrap">
<v-col
cols="auto"
class="chat-header-members text-start ma-0 pa-0"
@click.stop="onHeaderClicked"
>
<v-avatar size="40" class="me-2">
<v-img v-if="room.avatar || memberAvatar" :src="room.avatar || memberAvatar" />
</v-avatar>
</v-col>
<v-col
class="chat-header-name ma-0 pa-0 flex-shrink-1 flex-nowrap"
@click.stop="onHeaderClicked"
>
<div class="room-name-inline text-truncate" :title="room.name">
{{ room.name }}<v-icon class="icon-dropdown" size="11">$vuetify.icons.ic_dropdown</v-icon><div class="notification-alert" v-if="notifications"></div>
<!--<v-icon>expand_more</v-icon>-->
<v-col class="chat-header-name ma-0 pa-0 flex-shrink-1 flex-nowrap" @click.stop="onHeaderClicked">
<div class="room-title-row">
<div class="room-name-inline text-truncate" :title="room.name">
{{ room.name }}
</div>
<v-icon class="icon-dropdown" size="11">$vuetify.icons.ic_dropdown</v-icon>
<div :class="{ 'notification-alert': true, 'popup-open': showMissedItemsInfo }" v-if="notifications">
<!-- MISSED ITEMS POPUP -->
<div class="missed-items-popup-background" v-if="showMissedItemsInfo" @click.stop=""></div>
<div class="missed-items-popup" v-if="showMissedItemsInfo">
<div class="text">{{ notificationsText }}</div>
<div class="button clickable" @click.stop="$store.commit('setHasShownMissedItemsHint', true)">{{$t('menu.ok')}}</div>
</div>
</div>
</div>
<div class="num-members">{{ $tc("room.members", memberCount) }}</div>
</v-col>
<v-col cols="auto" class="text-end ma-0 pa-0">
<v-btn
id="btn-purge-room"
v-if="userCanPurgeRoom"
class="mx-2 box-shadow-none"
fab
dark
small
color="red"
@click.stop="showPurgeConfirmation = true"
>
<v-icon light>$vuetify.icons.ic_moderator-delete</v-icon>
</v-btn>
<v-btn
id="btn-leave-room"
class="mx-2 box-shadow-none"
fab
dark
small
color="red"
@click.stop="leaveRoom"
v-else
>
<v-icon>$vuetify.icons.ic_member-leave</v-icon>
</v-btn>
</v-col>
<v-col cols="auto" class="text-end ma-0 pa-0 ms-2">
<v-avatar
class="avatar-32 clickable"
size="40"
color="#e0e0e0"
@click.stop="showProfileInfo = true"
>
<v-col cols="auto" class="text-end ma-0 pa-0 ms-1">
<v-avatar :class="{ 'avatar-32': true, 'clickable': true, 'popup-open': showProfileInfo }" size="26"
color="#e0e0e0" @click.stop="showProfileInfo = true">
<img v-if="userAvatar" :src="userAvatar" />
<span v-else class="white--text">{{ userAvatarLetter }}</span>
</v-avatar>
</v-col>
<v-col cols="auto" class="text-end ma-0 pa-0 ms-1">
<v-btn id="btn-purge-room" v-if="userCanPurgeRoom" class="mx-2 box-shadow-none" fab dark small color="red"
@click.stop="showPurgeConfirmation = true">
<v-icon light>$vuetify.icons.ic_moderator-delete</v-icon>
</v-btn>
<v-btn id="btn-leave-room" class="mx-2 box-shadow-none" fab dark small color="red" @click.stop="leaveRoom" v-else>
<v-icon color="white">$vuetify.icons.ic_member-leave</v-icon>
</v-btn>
</v-col>
<v-col cols="auto" class="text-end ma-0 pa-0 ms-1 clickable">
<div :class="{ 'popup-open': showMoreMenu }">
<v-btn class="mx-2 box-shadow-none" fab dark small color="transparent" @click.stop="showMoreMenu = true">
<v-icon size="15" color="black">$vuetify.icons.ic_more</v-icon>
</v-btn>
</div>
</v-col>
</v-row>
<!-- "REALLY LEAVE?" dialog -->
<LeaveRoomDialog
:show="showLeaveConfirmation"
:room="room"
@close="showLeaveConfirmation = false"
/>
<LeaveRoomDialog :show="showLeaveConfirmation" :room="room" @close="showLeaveConfirmation = false" />
<!-- PROFILE INFO POPUP -->
<ProfileInfoPopup
:show="showProfileInfo"
@close="showProfileInfo = false"
/>
<ProfileInfoPopup :show="showProfileInfo" @close="showProfileInfo = false" />
<!-- MORE MENU POPUP -->
<MoreMenuPopup :show="showMoreMenu" :menuItems="moreMenuItems" @close="showMoreMenu = false"
v-on:leave="showLeaveConfirmation = true" />
<!-- PURGE ROOM POPUP -->
<PurgeRoomDialog
:show="showPurgeConfirmation"
:room="room"
@close="showPurgeConfirmation = false"
/>
<PurgeRoomDialog :show="showPurgeConfirmation" :room="room" @close="showPurgeConfirmation = false" />
<RoomExport :room="room" v-if="downloadingChat" v-on:close="downloadingChat = false" />
</v-container>
</template>
<script>
import LeaveRoomDialog from "../components/LeaveRoomDialog";
import ProfileInfoPopup from "../components/ProfileInfoPopup";
import MoreMenuPopup from "../components/MoreMenuPopup";
import profileInfoMixin from "../components/profileInfoMixin";
import PurgeRoomDialog from "../components/PurgeRoomDialog";
import RoomExport from "../components/RoomExport";
import roomInfoMixin from "./roomInfoMixin";
@ -96,14 +77,18 @@ export default {
components: {
LeaveRoomDialog,
ProfileInfoPopup,
PurgeRoomDialog
MoreMenuPopup,
PurgeRoomDialog,
RoomExport
},
data() {
return {
memberCount: null,
showLeaveConfirmation: false,
showProfileInfo: false,
showPurgeConfirmation: false
showPurgeConfirmation: false,
showMoreMenu: false,
downloadingChat: false,
};
},
mounted() {
@ -123,8 +108,8 @@ export default {
let roomMember;
if (this.room) {
this.room.getMembers().forEach(member => {
if(this.room.name === member.name) {
roomMember = member;
if (this.room.name === member.name) {
roomMember = member;
}
});
if (roomMember) {
@ -140,8 +125,57 @@ export default {
return null;
},
notifications() {
return this.$matrix.joinedRooms.some(room => room.roomId !== this.$matrix.currentRoomId && room.getUnreadNotificationCount("total") > 0);
}
return this.$matrix.joinedRooms.some(room => room.roomId !== this.$matrix.currentRoomId && room.getUnreadNotificationCount("total") > 0) ||
this.$matrix.invites.length > 0;
},
notificationsText() {
const invitationCount = this.$matrix.invites.length
if (invitationCount > 0) {
return this.$tc('room.invitations', invitationCount);
}
const missedMessagesCount = this.$matrix.joinedRooms.reduce((value, room) => (room.roomId !== this.$matrix.currentRoomId ? (value + room.getUnreadNotificationCount("total")) : value), 0);
if (missedMessagesCount > 0) {
return this.$tc('room.unseen_messages', missedMessagesCount);
}
return "";
},
moreMenuItems() {
let items = [];
const roomLink = this.publicRoomLink;
if (roomLink) {
items.push({
icon: '$vuetify.icons.ic_link', text: this.$t('room_info.copy_link'), handler: () => {
this.$copyText(this.publicRoomLink);
}
});
}
if (this.userCanExportChat) {
items.push({
icon: '$vuetify.icons.ic_download', text: this.$t('room_info.download_chat'), handler: () => {
this.downloadingChat = true;
}
});
}
items.push({
icon: '$vuetify.icons.ic_info', text: this.$t('room_info.title'), handler: () => {
this.$emit("view-room-details", { event: this.event });
}
});
items.push({
icon: '$vuetify.icons.ic_member-leave', text: this.$t('leave.leave'), handler: () => {
this.leaveRoom();
}
});
return items;
},
showMissedItemsInfo: {
get() {
return this.notifications && (this.$store.state.hasShownMissedItemsHint !== true);
},
set(newValue) {
console.log("Ignore", newValue);
}
},
},
watch: {
room: {
@ -186,4 +220,36 @@ export default {
<style lang="scss">
@import "@/assets/css/chat.scss";
.popup-open {
position: relative;
overflow: visible;
}
.popup-open::after {
position: absolute;
left: 50%;
content: "▲";
top: 30px;
margin-left: -10px;
font-size: 20px;
color: #ffffff;
z-index: 400;
pointer-events: none;
animation-duration: 0.3s;
animation-delay: 0.2s;
animation-fill-mode: both;
animation-name: fadein;
animation-iteration-count: 1;
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>

View file

@ -15,8 +15,6 @@
<RoomList
showInvites
showCreate
title=""
:invitesTitle="$t('room.room_list_invites')"
v-on:newroom="createRoom"
/>
</v-card-text>

View file

@ -0,0 +1,169 @@
<template>
<v-dialog v-model="showDialog" content-class="more-menu-popup" class="ma-0 pa-0">
<div class="popup-wrapper">
<v-card flat>
<v-card-text>
<v-container class="mt-0 pa-0 action-row-container-no-dividers">
<ActionRow v-for="item in menuItems" :key="item.name" :icon="item.icon" :iconSize="16" :text="item.text" @click="$emit('close');item.handler()" />
<v-row class="profile-row clickable" @click="viewProfile" no-gutters align-content="center">
<v-col cols="auto" class="me-2">
<v-avatar class="avatar-32" size="32" color="#e0e0e0" @click.stop="viewProfile">
<img v-if="userAvatar" :src="userAvatar" />
<span v-else class="white--text">{{ userAvatarLetter }}</span>
</v-avatar>
</v-col>
<v-col>
<div class="profile-label">{{ $t('profile.title') }}</div>
<div class="display-name">{{ displayName }}</div>
</v-col>
</v-row>
</v-container>
</v-card-text>
</v-card>
</div>
</v-dialog>
</template>
<script>
import profileInfoMixin from "./profileInfoMixin";
import ActionRow from "./ActionRow.vue";
export default {
name: "MoreMenuPopup",
mixins: [profileInfoMixin],
components: { ActionRow },
props: {
show: {
type: Boolean,
default: function () {
return false;
},
},
menuItems: {
type: Array,
default: function() {
return [];
}
}
},
data() {
return {
showDialog: false,
};
},
watch: {
show: {
immediate: true,
handler(newVal, ignoredOldVal) {
this.showDialog = newVal;
},
},
showDialog() {
if (!this.showDialog) {
this.$emit("close");
}
},
},
methods: {
viewProfile() {
this.showDialog = false;
this.$navigation.push({ name: "Profile" }, 1);
},
},
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@import '~vuetify/src/styles/settings/_variables.scss';
.popup-wrapper {
width: fit-content;
background: rgba(255, 255, 255, 0.98);
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.15);
border-radius: 18px;
pointer-events: initial;
overflow: hidden;
}
.more-menu-popup {
font-family: "Inter", sans-serif !important;
font-size: 16px;
line-height: 117%;
font-style: normal;
font-weight: 400;
letter-spacing: 0.4px;
position: fixed;
margin: 0px;
top: 70px;
right: 10px;
display: flex;
justify-content: flex-end;
box-shadow: none;
pointer-events: none;
.v-card__text {
padding: 0 !important;
}
.action-row {
height: 40px;
padding: 4px 20px !important;
font-size: 16px;
color: #000B16;
}
.profile-row {
border-top: 1px solid rgba(0, 0, 0, 0.1);
padding: 20px 20px !important;
}
.action-row:after {
display: none !important;
}
.profile-label {
letter-spacing: 0.4px;
color: #000B16;
}
.display-name {
font-size: 13px;
letter-spacing: 0.4px;
color: #000B16;
}
[dir="rtl"] & {
right: inherit;
left: 10px;
}
//border-radius: 40px;
width: 95%;
@media #{map-get($display-breakpoints, 'sm-and-up')} {
width: 70%;
}
@media #{map-get($display-breakpoints, 'lg-and-up')} {
overflow: unset;
width: $main-desktop-width;
;
position: absolute;
top: 70px;
right: unset;
width: $dialog-desktop-width;
&::before {
position: absolute;
top: -18px;
right: 40px;
}
// .v-card {
// border-radius: 20px;
// }
}
}
</style>

View file

@ -1,51 +1,38 @@
<template>
<v-dialog
v-model="showDialog"
content-class="profile-info-popup"
class="ma-0 pa-0"
>
<v-dialog v-model="showDialog" content-class="profile-info-popup" class="ma-0 pa-0">
<v-card flat>
<v-card-text>
<div class="you-are">{{ $t("profile_info_popup.you_are") }}</div>
<v-container fluid>
<v-row>
<v-col :class="['username',{'editable': editDisplayName }]" cols="pa-2" ref="username">
<v-col :class="['username', { 'editable': editDisplayName }]" cols="pa-2" ref="username">
<div v-if="$matrix.currentUser.is_guest">
<i18n path="profile_info_popup.identity_temporary" tag="span">
<template v-slot:displayName>
<input
v-model="displayName"
@blur="
updateDisplayName($event.target.value);
editDisplayName = !editDisplayName;
"
@focus="editDisplayName = !editDisplayName"
/>
<input v-model="displayName"
@keyup.enter="$event => $event.target.blur()"
@blur="
updateDisplayName($event.target.value);
editDisplayName = !editDisplayName;
" @focus="editDisplayName = !editDisplayName" />
</template>
</i18n>
</div>
<div v-else>
<i18n path="profile_info_popup.identity" tag="span">
<template v-slot:displayName>
<input
<input
v-model="displayName"
@blur="
updateDisplayName($event.target.value);
editDisplayName = !editDisplayName;
"
@focus="editDisplayName = !editDisplayName"
/>
@keyup.enter="$event => $event.target.blur()"
@blur="updateDisplayName($event.target.value);editDisplayName = !editDisplayName;"
@focus="editDisplayName = !editDisplayName"
/>
</template>
</i18n>
</div>
</v-col>
<v-col cols="auto" class="pa-2">
<v-avatar
class="avatar-32"
size="32"
color="#e0e0e0"
@click.stop="viewProfile"
>
<v-avatar class="avatar-32" size="32" color="#e0e0e0" @click.stop="viewProfile">
<img v-if="userAvatar" :src="userAvatar" />
<span v-else class="white--text">{{ userAvatarLetter }}</span>
</v-avatar>
@ -53,24 +40,6 @@
</v-row>
</v-container>
<v-container class="mt-4 pa-0">
<ActionRow
@click="viewProfile"
:icon="'account_circle'"
:text="$t('profile_info_popup.edit_profile')"
/>
<ActionRow
@click.stop="showLogoutPopup=true"
:icon="'logout'"
:text="$t('profile_info_popup.logout')"
/>
<LogoutRoomDialog
:showLogoutPopup="showLogoutPopup"
@onOutsideLogoutPopupClicked="showLogoutPopup=false"
@onCancelLogoutClicked="showLogoutPopup=false"
/>
</v-container>
<div class="more-container">
<div class="want_more">
🙌 {{ $t("profile_info_popup.want_more") }}
@ -78,7 +47,7 @@
<i18n path="profile_info_popup.powered_by" tag="div">
<template v-slot:product>{{ product }}</template>
<template v-slot:productLink>
<a :href="'//'+productLink">{{ productLink }}</a>
<a :href="'//' + productLink">{{ productLink }}</a>
</template>
</i18n>
<div class="text-end">
@ -93,16 +62,10 @@
</template>
<script>
import profileInfoMixin from "./profileInfoMixin";
import ActionRow from "./ActionRow.vue";
import LogoutRoomDialog from './LogoutRoomDialog.vue';
export default {
name: "ProfileInfoPopup",
mixins: [profileInfoMixin],
components: {
ActionRow,
LogoutRoomDialog
},
props: {
show: {
type: Boolean,
@ -115,7 +78,6 @@ export default {
return {
showDialog: false,
editDisplayName: false,
showLogoutPopup: false
};
},
computed: {
@ -163,28 +125,20 @@ export default {
margin: 0px;
top: 70px;
right: 10px;
[dir="rtl"] & {
right: inherit;
left: 10px;
}
border-radius: 40px;
width: 95%;
&::before {
content: "▲";
position: fixed;
top: 57px;
right: 22px;
[dir="rtl"] & {
left: 22px;
right: inherit;
}
color: white;
}
.you-are {
padding-top: 20px;
font-size: 12px;
}
.username {
border-radius: 4px;
background-color: #f5f5f5;
@ -202,19 +156,45 @@ export default {
}
}
}
.more-container {
border-radius: 10px;
background-color: #f5f5f5;
padding: 20px;
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-size: 14 * $chat-text-size;
line-height: 117%;
letter-spacing: 0.4px;
color: #000B16;
margin-top: 18px;
a {
color: #000B16 !important;
}
.want_more {
font-family: "Poppins", sans-serif;
font-weight: 700;
font-size: 13 * $chat-text-size;
color: #0e252d;
margin-bottom: 9px;
}
.new_room {
margin-top: 16px;
height: 27px;
border-radius: 13.5px;
border: 1px solid #000000;
}
.new_room .v-btn__content {
font-family: "Poppins", sans-serif !important;
font-weight: 700 !important;
font-size: 13 * $chat-text-size !important;
font-family: 'Inter', sans-serif;
font-style: normal;
font-weight: 700;
font-size: 10 * $chat-text-size !important;
line-height: 133%;
letter-spacing: 0.34px;
text-transform: uppercase;
color: #181719;
}
}
@ -224,7 +204,8 @@ export default {
@media #{map-get($display-breakpoints, 'lg-and-up')} {
overflow: unset;
width: $main-desktop-width;;
width: $main-desktop-width;
;
position: absolute;
top: 70px;
right: unset;

View file

@ -4,12 +4,6 @@
<!-- Header-->
<v-container fluid class="chat-header flex-grow-0 flex-shrink-0">
<v-row class="chat-header-row flex-nowrap">
<v-col cols="auto" class="chat-header-members text-start ma-0 pa-0" @click.stop="onHeaderClicked">
<v-avatar size="40" class="me-2">
<v-img v-if="room.avatar" :src="room.avatar" />
</v-avatar>
</v-col>
<v-col class="chat-header-name ma-0 pa-0 flex-shrink-1 flex-nowrap">
<div class="room-name-inline text-truncate" :title="room.name">
{{ room.name }}

View file

@ -3,52 +3,16 @@
class="room-info-bottom-sheet"
:halfY="0.12"
ref="sheet"
:showCloseButton="false"
>
<div class="room-info-sheet" ref="roomInfoSheetContent">
<div class="text-center current-room">
<room-avatar-picker />
<div class="h4">{{$t('room_info_sheet.this_room')}}</div>
<div
class="h2"
v-if="!isRoomNameEditMode"
@click="onRoomNameClicked()"
>
{{ roomName }}
</div>
<v-text-field
v-model="editedRoomName"
ref="editedRoomName"
:rules="[(v) => !!v || $t('room.room_name_required')]"
:error="roomNameErrorMessage != null"
:error-messages="roomNameErrorMessage"
required
color="black"
counter="50"
background-color="white"
autofocus
v-if="isRoomNameEditMode"
maxlength="50"
@blur="updateRoomName()"
@keyup.enter="updateRoomName()"
solo
></v-text-field>
<v-btn
id="btn-room-details"
height="20px"
color="black"
class="filled-button"
@click.stop="showDetails"
>{{$t('room_info_sheet.view_details')}}</v-btn
>
</div>
<room-list :title="'Other rooms'" v-on:close="close" v-on:newroom="createRoom" :showCreate="true" />
<room-list v-on:close="close" v-on:newroom="createRoom" :showCreate="true" />
</div>
</BottomSheet>
</template>
<script>
import BottomSheet from "./BottomSheet";
import RoomAvatarPicker from "./RoomAvatarPicker";
import RoomList from "./RoomList";
import roomInfoMixin from "./roomInfoMixin";
@ -58,7 +22,6 @@ export default {
components: {
BottomSheet,
RoomList,
RoomAvatarPicker,
},
methods: {
open() {

View file

@ -1,70 +1,59 @@
<template>
<v-list dense class="room-list">
<div class="h4">{{ title }}</div>
<v-list-item-group v-model="currentRoomId" color="primary">
<v-list-item v-if="showCreate" @click.stop="$emit('newroom')">
<v-list-item v-if="showCreate" @click.stop="$emit('newroom')" class="room-list-room">
<v-list-item-avatar class="round" size="42" color="#d9d9d9">
<v-icon size="11">$vuetify.icons.ic_new_room</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="new-room">{{
<v-list-item-title class="room-list-new-room">{{
$t("menu.new_room")
}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<!-- invites -->
<v-list-item
:disabled="roomsProcessing[room.roomId]"
v-for="room in invitedRooms"
:key="room.roomId"
:value="room.roomId"
>
<v-list-item-avatar size="40" color="#e0e0e0">
<v-img v-if="room.avatar" :src="room.avatar" />
<v-list-item :disabled="roomsProcessing[room.roomId]" v-for="room in invitedRooms" :key="room.roomId"
:value="room.roomId" class="room-list-room">
<v-list-item-avatar size="42" color="#d9d9d9">
<v-img v-if="roomAvatar(room)" :src="roomAvatar(room)" />
<span v-else class="white--text headline">{{
room.name.substring(0, 1).toUpperCase()
}}</span>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{ room.name }}</v-list-item-title>
<v-list-item-title class="room-list-name">{{ room.name }}</v-list-item-title>
<v-list-item-subtitle>{{ room.topic }}</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-btn
id="btn-accept"
class="filled-button"
depressed
color="black"
@click.stop="acceptInvitation(room)"
>{{ $t("menu.join") }}</v-btn
>
<v-btn
id="btn-reject"
class="filled-button"
color="black"
@click.stop="rejectInvitation(room)"
text
>{{ $t("menu.ignore") }}</v-btn
>
<v-btn id="btn-accept" class="filled-button" depressed color="black" @click.stop="acceptInvitation(room)">{{
$t("menu.join") }}</v-btn>
<v-btn id="btn-reject" class="filled-button" color="black" @click.stop="rejectInvitation(room)" text>{{
$t("menu.ignore") }}</v-btn>
</v-list-item-action>
</v-list-item>
<v-list-item
v-for="room in joinedRooms"
:key="room.roomId"
:value="room.roomId"
>
<v-list-item-avatar size="40" color="#e0e0e0">
<v-img v-if="room.avatar" :src="room.avatar" />
<v-list-item v-for="room in joinedRooms" :key="room.roomId" :value="room.roomId" class="room-list-room"
#default="{ active }">
<v-list-item-avatar size="42" color="#d9d9d9">
<v-img v-if="roomAvatar(room)" :src="roomAvatar(room)" />
<span v-else class="white--text headline">{{
room.name.substring(0, 1).toUpperCase()
}}</span>
</v-list-item-avatar>
<div class="room-list-notification-count" v-if="notificationCount(room) > 0">
{{ notificationCount(room) }}
</div>
<v-list-item-content>
<v-list-item-title>{{ room.name }}</v-list-item-title>
<v-list-item-subtitle>{{ room.topic }}</v-list-item-subtitle>
<v-list-item-title class="room-list-name">{{ room.name }}
<v-icon class="ml-2 mb-1" size="10" v-if="isPublic(room)">$vuetify.icons.ic_public</v-icon>
</v-list-item-title>
<v-list-item-subtitle class="room-list-new-messages" v-if="notificationCount(room) > 0">
{{ $t("room.room_list_new_messages", { count: notificationCount(room) }) }}
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-icon size="16" v-if="active">$vuetify.icons.ic_circle_filled</v-icon>
<v-icon size="16" v-else>$vuetify.icons.ic_circle</v-icon>
</v-list-item-action>
</v-list-item>
</v-list-item-group>
</v-list>
@ -108,6 +97,26 @@ export default {
},
methods: {
roomAvatar(room) {
if (this.isDirect(room)) {
if (room.avatar) {
return room.avatar;
}
const membersNotMe = room.getMembers().filter(m => m.userId != this.$matrix.currentUserId);
if (membersNotMe && membersNotMe.length == 1) {
return membersNotMe[0].getAvatarUrl(
this.$matrix.matrixClient.getHomeserverUrl(),
42,
42,
"scale",
true
);
}
} else {
return room.avatar;
}
},
sortItemsOnName(items) {
if (items == null) {
return [];
@ -166,6 +175,14 @@ export default {
Vue.delete(this.roomsProcessing, room.roomId);
});
},
isPublic(room) {
return this.$matrix.getRoomJoinRule(room) === "public"
},
isDirect(room) {
return this.$matrix.isDirectRoom(room);
}
},
watch: {