Merge branch '584-update-profile-view-feedback-changes' into 'dev'

Improvement based on  "Update profile view" feedback

See merge request keanuapp/keanuapp-weblite!287
This commit is contained in:
N Pex 2024-03-25 13:57:56 +00:00
commit 7b434d2b3f
9 changed files with 104 additions and 180 deletions

View file

@ -4,6 +4,7 @@ $app-background: #f6f6f6;
$main-desktop-width: 900px;
$dialog-desktop-width: 940px;
$very-very-purple: #536dfe;
$lighter-gray: #FDFBF9;
$chat-background: $background;
$chat-standard-padding: 32px;

View file

@ -740,8 +740,7 @@ body {
}
}
.message-operations-strut,
.avatar-operations-strut {
.message-operations-strut {
position: relative;
height: 0px;
z-index: 10;
@ -757,16 +756,6 @@ body {
white-space: nowrap;
}
.avatar-operations {
position: absolute;
width: fit-content;
background-color: white;
height: 40px;
border-radius: 20px;
padding: 0px 20px;
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.15);
}
.send-options {
z-index: 11; // Above mic button
}
@ -922,7 +911,7 @@ body {
}
.room-info {
background-color: #FDFBF9;
background-color: $lighter-gray;
height: 100%;
.chat-header {
@ -1004,6 +993,19 @@ body {
}
}
.user-icon-with-badge {
display: inline;
.user-badge {
border: 0.5px solid rgba(0, 0, 0, 0.1);
position: absolute;
left: 35px;
top: 30px;
background: #FFFFFF;
padding: 10px;
}
}
.show-all {
color: black;
font-size: 14 * $chat-text-size;
@ -1088,7 +1090,7 @@ body {
}
.profile {
background-color: #e8e8e8;
background-color: $lighter-gray;
height: 100%;
.chat-header {
background-color: transparent;
@ -1098,6 +1100,7 @@ body {
.v-card {
background-color: white;
border-radius: 20px;
box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.05) !important;
}
.user-info {

View file

@ -46,14 +46,6 @@
/>
</div>
<div ref="avatarOperationsStrut" class="avatar-operations-strut">
<avatar-operations ref="avatarOperations" :style="avatarOpStyle" v-on:close="
showAvatarMenu = false;
showAvatarMenuAnchor = null;
" v-on:start-private-chat="startPrivateChat($event)" v-if="selectedEvent && showAvatarMenu" :room="room"
:originalEvent="selectedEvent" />
</div>
<!-- Handle resizes, e.g. when soft keyboard is shown/hidden -->
<resize-observer ref="chatContainerResizer" @notify="handleChatContainerResize" />
@ -324,6 +316,12 @@
<CreatePollDialog :show="showCreatePollDialog" @close="showCreatePollDialog = false" />
<UserProfileDialog
:show="showProfileDialog"
:activeMember="compActiveMember"
:room="room"
@close="showProfileDialog = false"
/>
</div>
</template>
@ -332,7 +330,6 @@ import Vue from "vue";
import { TimelineWindow, EventTimeline } from "matrix-js-sdk";
import util, { ROOM_TYPE_VOICE_MODE, ROOM_TYPE_FILE_MODE } from "../plugins/utils";
import MessageOperations from "./messages/MessageOperations.vue";
import AvatarOperations from "./messages/AvatarOperations.vue";
import ChatHeader from "./ChatHeader";
import ChatHeaderPrivate from "./ChatHeaderPrivate.vue";
import VoiceRecorder from "./VoiceRecorder";
@ -342,6 +339,7 @@ import DirectChatWelcomeHeader from "./DirectChatWelcomeHeader";
import NoHistoryRoomWelcomeHeader from "./NoHistoryRoomWelcomeHeader.vue";
import MessageOperationsBottomSheet from "./MessageOperationsBottomSheet";
import StickerPickerBottomSheet from "./StickerPickerBottomSheet";
import UserProfileDialog from "./UserProfileDialog.vue"
import BottomSheet from "./BottomSheet.vue";
import ImageResize from "image-resize";
import CreatePollDialog from "./CreatePollDialog.vue";
@ -399,10 +397,10 @@ export default {
MessageOperationsBottomSheet,
StickerPickerBottomSheet,
BottomSheet,
AvatarOperations,
CreatePollDialog,
AudioLayout,
FileDropLayout
FileDropLayout,
UserProfileDialog
},
data() {
@ -432,8 +430,6 @@ export default {
showNoRecordingAvailableDialog: false,
showContextMenu: false,
showContextMenuAnchor: null,
showAvatarMenu: false,
showAvatarMenuAnchor: null,
initialLoadDone: false,
loading: false, // Set this to true during long operations to show a "spinner" overlay
showRecorder: false,
@ -489,7 +485,8 @@ export default {
/**
* A timer to handle message retention/auto deletion
*/
retentionTimer: null
retentionTimer: null,
showProfileDialog: false
};
},
@ -521,7 +518,7 @@ export default {
if (this.retentionTimer) {
clearInterval(this.retentionTimer);
this.retentionTimer = null;
}
}
},
destroyed() {
@ -530,6 +527,10 @@ export default {
},
computed: {
compActiveMember() {
const currentUserId= this.selectedEvent?.sender.userId || this.$matrix.currentUserId
return this.joinedAndInvitedMembers.find(({userId}) => userId === currentUserId)
},
nonImageFiles() {
return this.isCurrentFileInputsAnArray && this.currentFileInputs.filter(file => !file?.type.includes("image/"))
},
@ -615,30 +616,6 @@ export default {
showMessageOperations() {
return this.selectedEvent && this.showContextMenu;
},
avatarOpStyle() {
// Calculate where to show the context menu.
//
const ref = this.selectedEvent && this.$refs[this.selectedEvent.getId()];
var top = 0;
var left = "unset";
var right = "unset";
if (ref && ref[0]) {
if (this.showAvatarMenuAnchor) {
var rectAnchor = this.showAvatarMenuAnchor.getBoundingClientRect();
var rectChat = this.$refs.avatarOperationsStrut.getBoundingClientRect();
top = rectAnchor.top - rectChat.top;
if (this.$vuetify.rtl) {
right = (rectAnchor.right - rectChat.right)+ "px";
} else {
left = (rectAnchor.left - rectChat.left) + "px";
}
// if (left + 250 > rectChat.right) {
// left = rectChat.right - 250; // Pretty ugly, but we want to make sure it does not escape the screen, and we don't have the exakt width of it (yet)!
// }
}
}
return "top:" + top + "px;left:" + left + ";right:" + right;
},
canRecordAudio() {
return util.browserCanRecordAudio();
},
@ -910,7 +887,7 @@ export default {
if (this.retentionTimer) {
clearInterval(this.retentionTimer);
this.retentionTimer = null;
}
}
},
removeTimedOutEvents(events) {
@ -921,7 +898,7 @@ export default {
}
return events.filter((e) => {
if (maxLifetime > 0 && !e.isState()) { // Keep all state events
return e.getLocalAge() < maxLifetime;
return e.getLocalAge() < maxLifetime;
}
return true;
});
@ -1700,50 +1677,20 @@ export default {
showAvatarMenuForEvent(e) {
const event = e.event;
this.selectedEvent = event;
this.showAvatarMenu = true;
this.showAvatarMenuAnchor = e.anchor;
this.showProfileDialog = true
},
viewProfile() {
this.$navigation.push({ name: "Profile" }, 1);
},
startPrivateChat(e) {
this.loading = true;
this.$matrix
.getOrCreatePrivateChat(e.event.getSender())
.then((room) => {
this.$nextTick(() => {
this.$navigation.push(
{
name: "Chat",
params: {
roomId: util.sanitizeRoomId(room.getCanonicalAlias() || room.roomId),
},
},
-1
);
});
})
.catch((err) => {
console.error(err);
})
.finally(() => {
this.loading = false;
});
},
closeContextMenusIfOpen(e) {
if (this.showContextMenu) {
this.showContextMenu = false;
this.showContextMenuAnchor = null;
e.preventDefault();
}
if (this.showAvatarMenu) {
this.showAvatarMenu = false;
this.showAvatarMenuAnchor = null;
e.preventDefault();
}
},
/** Stop Read Receipt timer */

View file

@ -120,26 +120,6 @@ export default {
room() {
return this.$matrix.currentRoom;
},
memberAvatar() {
let roomMember;
if (this.room) {
this.room.getMembers().forEach(member => {
if (this.room.name === member.name) {
roomMember = member;
}
});
if (roomMember) {
return roomMember.getAvatarUrl(
this.$matrix.matrixClient.getHomeserverUrl(),
40,
40,
"scale",
true
);
}
}
return null;
},
notifications() {
return this.$matrix.joinedRooms.some(r => (r.roomId !== this.$matrix.currentRoomId && r.getCanonicalAlias() !== this.$matrix.currentRoomId) && r.getUnreadNotificationCount("total") > 0) ||
this.$matrix.invites.length > 0;

View file

@ -84,7 +84,6 @@ import RoomEncrypted from "./messages/RoomEncrypted.vue";
import RoomDeletionNotice from "./messages/RoomDeletionNotice.vue";
import DebugEvent from "./messages/DebugEvent.vue";
import MessageOperations from "./messages/MessageOperations.vue";
import AvatarOperations from "./messages/AvatarOperations.vue";
import ChatHeader from "./ChatHeader.vue";
import VoiceRecorder from "./VoiceRecorder.vue";
import RoomInfoBottomSheet from "./RoomInfoBottomSheet.vue";
@ -141,7 +140,6 @@ export default {
MessageOperationsBottomSheet,
StickerPickerBottomSheet,
BottomSheet,
AvatarOperations,
CreatePollDialog,
},
props: {
@ -264,7 +262,7 @@ export default {
if (parentEvent) {
Vue.set(parentEvent, "isMxThread", true);
Vue.set(event, "parentThread", parentEvent);
}
}
});
this.events.filter(event => (event.replyEventId && !event.replyEvent)).forEach(event => {
const parentEvent = this.timelineSet.findEventById(event.replyEventId) || this.room.findEventById(event.replyEventId);

View file

@ -214,12 +214,17 @@
@click="onListItemClick(member)"
>
<div>
<v-avatar class="avatar" size="32" color="grey">
<img v-if="memberAvatar(member)" :src="memberAvatar(member)" />
<span v-else class="white--text headline">{{
member.name.substring(0, 1).toUpperCase()
}}</span>
</v-avatar>
<div class="user-icon-with-badge">
<v-avatar class="avatar" size="32" color="grey">
<img v-if="memberAvatar(member)" :src="memberAvatar(member)" />
<span v-else class="white--text headline">{{
member.name.substring(0, 1).toUpperCase()
}}</span>
</v-avatar>
<v-avatar circle class="user-badge" size="15" v-if="isAdmin(member) || isModerator(member)">
<v-icon>{{ `$vuetify.icons.${isAdmin(member)? 'make_admin' : 'make_moderator'}` }}</v-icon>
</v-avatar>
</div>
<span class="user-name">
{{
member.userId == $matrix.currentUserId
@ -266,7 +271,7 @@
{{ $t("room_info.version_info", { version: buildVersion }) }}
</div>
<MemberActionDialog
<UserProfileDialog
:show="showMemberActionConfirmation"
:activeMember="activeMember || members[0]"
:room="room"
@ -304,7 +309,7 @@ import RoomExport from "../components/RoomExport";
import RoomAvatarPicker from "../components/RoomAvatarPicker";
import CopyLink from "../components/CopyLink.vue"
import RoomTypeSelector from "./RoomTypeSelector.vue";
import MemberActionDialog from "./MemberActionDialog.vue"
import UserProfileDialog from "./UserProfileDialog.vue"
import roomInfoMixin from "./roomInfoMixin";
import roomTypeMixin from "./roomTypeMixin";
import util, { STATE_EVENT_ROOM_TYPE } from "../plugins/utils";
@ -316,7 +321,7 @@ export default {
LeaveRoomDialog,
PurgeRoomDialog,
MessageRetentionDialog,
MemberActionDialog,
UserProfileDialog,
RoomExport,
RoomAvatarPicker,
RoomTypeSelector,

View file

@ -1,40 +1,39 @@
<template>
<v-dialog
v-if="activeMember"
v-model="showDialog"
v-show="room"
class="ma-0 pa-0"
:width="$vuetify.breakpoint.smAndUp ? '688px' : '95%'"
>
<div class="dialog-content text-center member-action-dialog">
<div>
<div class="pt-4">
<v-avatar class="avatar" size="56" color="grey">
<img v-if="memberAvatar(activeMember)" :src="memberAvatar(activeMember)" />
<span v-else class="white--text headline">{{
activeMember.name.substring(0, 1).toUpperCase()
}}</span>
<img v-if="memberAvatarComp" :src="memberAvatarComp" />
<span v-else class="white--text headline">{{ firstLetterUserName }}</span>
</v-avatar>
<div>
<span class="user-name">
{{
activeMember.userId == $matrix.currentUserId
isCurrentUser
? $t("room_info.user_you", {
user: activeMemberName(activeMember)
user: activeMemberNameComp
})
: $t("room_info.user", {
user: activeMemberName(activeMember)
user: activeMemberNameComp
})
}}
</span>
<span v-if="isAdmin(activeMember)" class="user-power">
<span v-if="isAdminComp" class="user-power">
{{ $t("room_info.user_admin") }}
</span>
<span v-else-if="isModerator(activeMember)" class="user-power">
<span v-else-if="isModeratorComp" class="user-power">
{{ $t("room_info.user_moderator") }}
</span>
</div>
<template v-if="activeMember.userId != $matrix.currentUserId && sharedRooms.length">
<span v-if="sharedRooms.length < 3">{{ $t("room_info.shared_room_number", {count: sharedRooms.length, name: activeMemberName(activeMember)}) }}</span>
<span v-else>{{ $t("room_info.shared_room_number_more", {count: sharedRooms.length, name: activeMemberName(activeMember)}) }}</span>
<span v-if="sharedRooms.length < 3">{{ $t("room_info.shared_room_number", {count: sharedRooms.length, name: activeMemberNameComp}) }}</span>
<span v-else>{{ $t("room_info.shared_room_number_more", {count: sharedRooms.length, name: activeMemberNameComp}) }}</span>
<p class="font-weight-light">{{ sharedRooms.slice(0, 3).join(", ") }}</p>
</template>
</div>
@ -43,18 +42,18 @@
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && !$matrix.isDirectRoomWith(room, activeMember.userId)" class="start-private-chat clickable d-block text-none justify-start" @click="startPrivateChat(activeMember.userId)">
<v-icon left>$vuetify.icons.direct_chat</v-icon> {{ $t("menu.direct_chat") }}
</v-btn>
<div v-if="canBanUser(activeMember)">
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && canBanUser(activeMember)" class="start-private-chat clickable d-block text-none justify-start" @click="banUser(activeMember)">
<div v-if="canBanUserComp">
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && canBanUserComp" class="start-private-chat clickable d-block text-none justify-start" @click="banUser(activeMember)">
<v-icon left>$vuetify.icons.kickout</v-icon> {{ $t("menu.user_kick_and_ban") }}
</v-btn>
</div>
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && !isAdmin(activeMember) && canMakeAdmin(activeMember)" class="start-private-chat clickable d-block text-none justify-start" @click="makeAdmin(activeMember)">
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && !isAdminComp && canMakeAdminComp" class="start-private-chat clickable d-block text-none justify-start" @click="makeAdmin(activeMember)">
<v-icon left>$vuetify.icons.make_admin</v-icon> {{ $t("menu.user_make_admin") }}
</v-btn>
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && !isModerator(activeMember) && !isAdmin(activeMember) && canMakeModerator(activeMember)" class="start-private-chat clickable d-block text-none justify-start" @click="makeModerator(activeMember)">
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && !isModeratorComp && !isAdminComp && canMakeModeratorComp" class="start-private-chat clickable d-block text-none justify-start" @click="makeModerator(activeMember)">
<v-icon left>$vuetify.icons.make_moderator</v-icon> {{ $t("menu.user_make_moderator") }}
</v-btn>
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && isModerator(activeMember) && canRevokeModerator(activeMember)" class="start-private-chat clickable d-block text-none justify-start" @click="revokeModerator(activeMember)">
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && isModeratorComp && canRevokeModeratorComp" class="start-private-chat clickable d-block text-none justify-start" @click="revokeModerator(activeMember)">
<v-icon left>$vuetify.icons.revoke</v-icon> {{ $t("menu.user_revoke_moderator") }}
</v-btn>
</div>
@ -67,7 +66,7 @@ import DeviceList from "../components/DeviceList";
import util from "../plugins/utils";
export default {
name: "MemberActionDialog",
name: "UserProfileDialog",
mixins: [roomInfoMixin],
components: {
DeviceList
@ -80,7 +79,10 @@ export default {
},
},
activeMember: {
type: Object
type: Object,
default: function () {
return null;
}
}
},
data() {
@ -89,6 +91,36 @@ export default {
};
},
computed: {
canRevokeModeratorComp () {
return this.canRevokeModerator(this.activeMember)
},
isModeratorComp() {
return this.isModerator(this.activeMember)
},
canMakeModeratorComp() {
return this.canMakeModerator(this.activeMember)
},
canMakeAdminComp() {
return this.canMakeAdmin(this.activeMember)
},
canBanUserComp() {
return this.canBanUser(this.activeMember)
},
isAdminComp() {
return this.isAdmin(this.activeMember)
},
activeMemberNameComp() {
return this.activeMember.user ? this.activeMember.user.displayName : this.activeMember.name
},
isCurrentUser() {
return this.activeMember.userId == this.$matrix.currentUserId
},
firstLetterUserName() {
return this.activeMember.name.substring(0, 1).toUpperCase()
},
memberAvatarComp() {
return this.memberAvatar(this.activeMember)
},
joinedMembersByRoomId() {
const joinedRooms = this.$matrix.joinedRooms.filter(room => !this.$matrix.isDirectRoom(room)) || [];
return joinedRooms.map(room => ({
@ -120,9 +152,6 @@ export default {
},
methods: {
activeMemberName(activeMember) {
return activeMember.user ? activeMember.user.displayName : activeMember.name
},
startPrivateChat(userId) {
this.$matrix
.getOrCreatePrivateChat(userId)

View file

@ -38,7 +38,6 @@ import RoomTopicChanged from "./messages/RoomTopicChanged.vue";
import RoomAvatarChanged from "./messages/RoomAvatarChanged.vue";
import RoomHistoryVisibility from "./messages/RoomHistoryVisibility.vue";
import MessageOperations from "./messages/MessageOperations.vue";
import AvatarOperations from "./messages/AvatarOperations.vue";
import ChatHeader from "./ChatHeader";
import VoiceRecorder from "./VoiceRecorder";
import RoomInfoBottomSheet from "./RoomInfoBottomSheet";
@ -100,7 +99,6 @@ export default {
MessageOperationsBottomSheet,
StickerPickerBottomSheet,
BottomSheet,
AvatarOperations,
CreatePollDialog,
},
methods: {

View file

@ -1,37 +0,0 @@
<template>
<div
:class="{
'avatar-operations': true,
incoming: incoming,
outgoing: !incoming,
}"
>
<v-btn id="btn-private-chat" v-if="incoming" text @click.stop="startPrivateChat" class="ma-0 pa-0"
>{{ $t("menu.start_private_chat") }}</v-btn
>
</div>
</template>
<script>
import messageMixin from "./messageMixin";
export default {
mixins: [messageMixin],
mounted() {
// Any items to show?
if (this.room && this.event && this.$matrix.isDirectRoomWith(this.room, this.event.getSender())) {
this.$emit("close");
}
},
methods: {
startPrivateChat() {
this.$emit("close");
this.$emit("start-private-chat", { event: this.event });
},
},
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
</style>