436 lines
No EOL
13 KiB
Vue
436 lines
No EOL
13 KiB
Vue
<template>
|
|
<div v-if="user" class="profile">
|
|
<div class="chat-header">
|
|
<v-container fluid>
|
|
<div class="room-name no-upper">{{ $t("profile.title") }}</div>
|
|
<v-btn
|
|
id="btn-close"
|
|
variant="text"
|
|
class="header-button-right"
|
|
v-show="$navigation && $navigation.canPop()"
|
|
@click.stop="$navigation.pop"
|
|
>
|
|
<v-icon>close</v-icon>
|
|
</v-btn>
|
|
</v-container>
|
|
</div>
|
|
|
|
<v-container class="user-info">
|
|
<v-row>
|
|
<v-col class="flex-grow-0 flex-shrink-0">
|
|
<v-avatar
|
|
class="avatar-48 clickable"
|
|
size="48"
|
|
color="#e0e0e0"
|
|
@click="showAvatarPicker"
|
|
v-if="isAvatarLoaded"
|
|
>
|
|
<AuthedImage v-if="userAvatar" :src="userAvatar" />
|
|
<span v-else class="text-white">{{ userAvatarLetter }}</span>
|
|
<input
|
|
id="avatar-picker"
|
|
ref="avatar"
|
|
type="file"
|
|
name="avatar"
|
|
@change="handlePickedAvatar($event)"
|
|
:accept="supportedAvatarImageTypes"
|
|
class="d-none"
|
|
/>
|
|
</v-avatar>
|
|
<v-progress-circular
|
|
:rotate="360"
|
|
v-else
|
|
:width="3"
|
|
:model-value="loadValue"
|
|
color="primary"
|
|
>
|
|
{{ loadValue }}
|
|
</v-progress-circular>
|
|
</v-col>
|
|
<v-col class="flex-shrink-1 flex-grow-1">
|
|
<div class="h1">{{ displayName }}</div>
|
|
<div class="text-center">{{ currentUserId }}</div>
|
|
<!-- <div v-if="$matrix.currentUser.is_guest">
|
|
{{ $t("profile.temporary_identity") }}
|
|
</div> -->
|
|
<v-btn id="btn-logout" variant="flat" block class="outlined-button" @click.stop="showLogoutPopup=true">
|
|
{{ $t("menu.logout") }}
|
|
</v-btn>
|
|
<LogoutRoomDialog
|
|
:showLogoutPopup="showLogoutPopup"
|
|
@onOutsideLogoutPopupClicked="showLogoutPopup=false"
|
|
@onCancelLogoutClicked="showLogoutPopup=false"
|
|
/>
|
|
</v-col>
|
|
</v-row>
|
|
</v-container>
|
|
|
|
<copy-link :locationLink="directMessageLink" :allowMirrors="true" @mirror-change="(mirror) => currentMirror = mirror">
|
|
<v-card-title class="h2">{{ $t("room_info.direct_link") }}</v-card-title>
|
|
<v-card-text>{{ $t("room_info.direct_link_desc") }}</v-card-text>
|
|
</copy-link>
|
|
|
|
<v-container class="mt-2 pa-5">
|
|
<ActionRow
|
|
@click="showEditPasswordDialog = true"
|
|
:icon="'$vuetify.icons.password'"
|
|
:text="$matrix.currentUser.is_guest ? $t('profile.set_password') : $t('profile.change_password')"
|
|
/>
|
|
<ActionRow
|
|
@click="
|
|
editValue = displayName;
|
|
showEditDisplaynameDialog = true;
|
|
"
|
|
:icon="'$vuetify.icons.edit'"
|
|
:text="$t('profile.change_name')"
|
|
/>
|
|
<ActionRow
|
|
@click="goHome"
|
|
:icon="'$vuetify.icons.ic_rooms'"
|
|
:text="$t('profile.my_rooms')"
|
|
/>
|
|
|
|
<ActionRow
|
|
@click="showSelectLanguageDialog = true"
|
|
:icon="'$vuetify.icons.globe'"
|
|
:text="$t('profile.select_language')"
|
|
/>
|
|
<ActionRow
|
|
@click="onUpdateGlobalNotification"
|
|
:icon="notificationIcon"
|
|
:text="$t('profile.notification_label')"
|
|
>
|
|
<v-switch
|
|
v-model="globalNotification"
|
|
readonly
|
|
></v-switch>
|
|
</ActionRow>
|
|
</v-container>
|
|
|
|
<!-- edit password dialog -->
|
|
<v-dialog
|
|
v-model="showEditPasswordDialog"
|
|
class="ma-0 pa-0"
|
|
:width="$vuetify.display.smAndUp ? '940px' : '80%'"
|
|
scroll-strategy="none"
|
|
>
|
|
<v-card :disabled="settingPassword" class="dialog-content">
|
|
<h2 class="dialog-title">{{ $matrix.currentUser.is_guest ? $t("profile.set_password") : $t("profile.change_password") }}</h2>
|
|
<div class="dialog-text">
|
|
<v-text-field
|
|
v-if="!$matrix.currentUser.is_guest"
|
|
v-model="password"
|
|
:label="$t('profile.password_old')"
|
|
type="password"
|
|
/>
|
|
<v-text-field
|
|
v-model="newPassword1"
|
|
:append-icon="showPassword1 ? 'visibility' : 'visibility_off'"
|
|
:hint="$t('global.password_hint')"
|
|
:rules="[(v) => v ? !!v.match(passwordValidation) || $t('global.password_hint'):true]"
|
|
:label="$t('profile.password_new')"
|
|
counter="20"
|
|
maxlength="20"
|
|
:type="showPassword1 ? 'text' : 'password'"
|
|
@click:append="showPassword1 = !showPassword1"
|
|
@update:error="updateErrorState"
|
|
/>
|
|
<v-text-field
|
|
v-model="newPassword2"
|
|
:append-icon="showPassword2 ? 'visibility' : 'visibility_off'"
|
|
:rules="[(v) => v===newPassword1 || $t('global.password_didnot_match')]"
|
|
:label="$t('profile.password_repeat')"
|
|
counter="20"
|
|
maxlength="20"
|
|
:type="showPassword2 ? 'text' : 'password'"
|
|
@click:append="showPassword2 = !showPassword2"
|
|
/>
|
|
<div class="text-red" v-if="passwordErrorMessage">
|
|
{{ passwordErrorMessage }}
|
|
</div>
|
|
</div>
|
|
<v-divider></v-divider>
|
|
<v-card-actions>
|
|
<v-spacer></v-spacer>
|
|
<v-btn id="btn-password-cancel" variant="text" @click="closeEditPasswordDialog">{{
|
|
$t("menu.cancel")
|
|
}}</v-btn>
|
|
<v-btn
|
|
id="btn-password-set"
|
|
:disabled="!passwordsMatch"
|
|
color="primary"
|
|
variant="text"
|
|
@click="
|
|
setPassword(
|
|
$matrix.currentUser.is_guest
|
|
? $matrix.currentUser.password
|
|
: password,
|
|
newPassword1
|
|
)
|
|
"
|
|
>{{ $t("global.save") }}</v-btn
|
|
>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
|
|
<!-- edit display name dialog -->
|
|
<RoundedDialog
|
|
v-model="showEditDisplaynameDialog"
|
|
class="ma-0 pa-0"
|
|
:width="$vuetify.display.smAndUp ? '940px' : '80%'"
|
|
>
|
|
<v-card>
|
|
<v-card-title>{{ $t("profile.display_name") }}</v-card-title>
|
|
<v-card-text>
|
|
<v-text-field
|
|
v-model="editValue"
|
|
:rules="[(v) => !!v || $t('profile.display_name_required')]"
|
|
required
|
|
/>
|
|
</v-card-text>
|
|
<v-divider></v-divider>
|
|
<v-card-actions>
|
|
<v-spacer></v-spacer>
|
|
<v-btn id="btn-displayname-cancel" variant="text" @click="showEditDisplaynameDialog = false">{{
|
|
$t("menu.cancel")
|
|
}}</v-btn>
|
|
<v-btn
|
|
id="btn-displayname-set"
|
|
color="primary"
|
|
variant="text"
|
|
@click="
|
|
updateDisplayName(editValue);
|
|
showEditDisplaynameDialog = false;
|
|
"
|
|
:disabled="!editValue"
|
|
>{{ $t("global.save") }}</v-btn
|
|
>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</RoundedDialog>
|
|
|
|
<SelectLanguageDialog
|
|
v-model="showSelectLanguageDialog"
|
|
/>
|
|
|
|
<!-- Dialog for request Notification -->
|
|
<v-dialog
|
|
v-model="notificationDialog"
|
|
persistent
|
|
class="ma-0 pa-0"
|
|
:width="$vuetify.display.smAndUp ? '688px' : '95%'"
|
|
scroll-strategy="none"
|
|
>
|
|
<div class="dialog-content text-center">
|
|
<v-icon size="30">notifications_active</v-icon>
|
|
<h2 class="dialog-title">
|
|
{{ $t("notification.dialog.title") }}
|
|
</h2>
|
|
<div class="dialog-text">{{ $t("notification.dialog.body") }}</div>
|
|
<v-container fluid>
|
|
<v-row cols="12">
|
|
<v-col cols="6">
|
|
<v-btn
|
|
variant="flat"
|
|
block
|
|
class="text-button"
|
|
@click="onNotifyDialogClosed"
|
|
>{{ $t("global.close") }}</v-btn
|
|
>
|
|
</v-col>
|
|
<v-col cols="6" align="center">
|
|
<v-btn
|
|
color="primary"
|
|
variant="flat"
|
|
block
|
|
class="filled-button"
|
|
@click.stop="onNotifyDialog"
|
|
>{{ $t("notification.dialog.enable") }}</v-btn
|
|
>
|
|
</v-col>
|
|
</v-row>
|
|
</v-container>
|
|
</div>
|
|
</v-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import SelectLanguageDialog from "./SelectLanguageDialog.vue";
|
|
import ActionRow from "./ActionRow.vue";
|
|
import util from "../plugins/utils";
|
|
import profileInfoMixin from "./profileInfoMixin";
|
|
import LogoutRoomDialog from './LogoutRoomDialog.vue';
|
|
import AuthedImage from "./AuthedImage.vue";
|
|
import CopyLink from "./CopyLink.vue"
|
|
import { requestNotificationPermission, windowNotificationPermission } from "../plugins/notificationAndServiceWorker.js"
|
|
import { mapState } from 'vuex'
|
|
import RoundedDialog from "./RoundedDialog.vue";
|
|
|
|
export default {
|
|
name: "Profile",
|
|
mixins: [profileInfoMixin],
|
|
components: {
|
|
ActionRow,
|
|
SelectLanguageDialog,
|
|
LogoutRoomDialog,
|
|
CopyLink,
|
|
AuthedImage,
|
|
RoundedDialog
|
|
},
|
|
data() {
|
|
return {
|
|
showEditPasswordDialog: false,
|
|
showEditDisplaynameDialog: false,
|
|
showSelectLanguageDialog: false,
|
|
showLogoutPopup: false,
|
|
editValue: null,
|
|
password: null,
|
|
showPassword1: false,
|
|
showPassword2: false,
|
|
passwordValidation: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{12,20}$/,
|
|
newPassword1: null,
|
|
newPassword2: null,
|
|
settingPassword: false,
|
|
passwordErrorMessage: null,
|
|
isAvatarLoaded: true,
|
|
loadValue: 0,
|
|
newPasswordHasError: false,
|
|
notificationDialog: false,
|
|
currentMirror: null
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
currentUserId() {
|
|
return this.$matrix.currentUser.user_id
|
|
},
|
|
directMessageLink() {
|
|
return this.$router.getDMLink(this.$matrix.currentUser, this.$config, this.currentMirror);
|
|
},
|
|
passwordsMatch() {
|
|
return (
|
|
!this.newPasswordHasError &&
|
|
this.newPassword1 &&
|
|
this.newPassword2 &&
|
|
this.newPassword1 == this.newPassword2
|
|
);
|
|
},
|
|
notificationIcon() {
|
|
return this.globalNotification ? 'notifications_active' : 'notifications_off';
|
|
},
|
|
...mapState([
|
|
'globalNotification'
|
|
])
|
|
},
|
|
|
|
methods: {
|
|
updateErrorState(errorState) {
|
|
this.newPasswordHasError = errorState
|
|
},
|
|
setPassword(oldPassword, newPassword) {
|
|
this.settingPassword = true;
|
|
this.passwordErrorMessage = null;
|
|
this.$matrix
|
|
.setPassword(oldPassword, newPassword)
|
|
.then((success) => {
|
|
console.log(
|
|
success ? "Password changed" : "Failed to change password"
|
|
);
|
|
this.closeEditPasswordDialog();
|
|
})
|
|
.catch((error) => {
|
|
this.passwordErrorMessage = error.message;
|
|
})
|
|
.finally(() => {
|
|
this.settingPassword = false;
|
|
});
|
|
},
|
|
|
|
closeEditPasswordDialog() {
|
|
this.passwordErrorMessage = null;
|
|
this.password = null;
|
|
this.newPassword1 = null;
|
|
this.newPassword2 = null;
|
|
this.showEditPasswordDialog = false;
|
|
},
|
|
|
|
showAvatarPicker() {
|
|
this.$refs.avatar.click();
|
|
},
|
|
|
|
handlePickedAvatar(event) {
|
|
util.loadAvatarFromFile(event, (image) => {
|
|
this.setAvatar(image);
|
|
});
|
|
},
|
|
setAvatar(image) {
|
|
const self = this;
|
|
this.isAvatarLoaded = false;
|
|
return util.setAvatar(this.$matrix, image, function (progress) {
|
|
self.loadValue = Math.round(progress.loaded/progress.total * 100);
|
|
if(progress.loaded === progress.total) {
|
|
self.isAvatarLoaded = true;
|
|
}
|
|
console.log("Progress: " + JSON.stringify(progress));
|
|
});
|
|
},
|
|
updateGlobalNotificationStore(flag) {
|
|
this.$store.commit('setGlobalNotification', flag);
|
|
},
|
|
windowNotificationPermission,
|
|
onUpdateGlobalNotification(showAlertOrDialog = true) {
|
|
const permission = this.windowNotificationPermission();
|
|
|
|
switch (permission) {
|
|
case 'denied':
|
|
this.updateGlobalNotificationStore(false);
|
|
if (showAlertOrDialog) {
|
|
alert(this.$t("notification.blocked_message"));
|
|
}
|
|
break;
|
|
case 'granted':
|
|
this.updateGlobalNotificationStore(!this.globalNotification);
|
|
break;
|
|
case 'default':
|
|
if (showAlertOrDialog) {
|
|
this.notificationDialog = true;
|
|
}
|
|
this.updateGlobalNotificationStore(!this.globalNotification);
|
|
break;
|
|
default:
|
|
alert(this.$t("notification.not_supported"));
|
|
}
|
|
},
|
|
async onNotifyDialog() {
|
|
const permission = await requestNotificationPermission()
|
|
if(permission === 'denied') {
|
|
this.updateGlobalNotificationStore(false);
|
|
alert(this.$t("notification.blocked_message"));
|
|
} else {
|
|
this.updateGlobalNotificationStore(true);
|
|
}
|
|
this.notificationDialog = false;
|
|
},
|
|
onNotifyDialogClosed() {
|
|
this.updateGlobalNotificationStore(false);
|
|
this.notificationDialog = false;
|
|
},
|
|
|
|
goHome() {
|
|
this.$navigation.push({ name: "Home" }, -1);
|
|
}
|
|
},
|
|
mounted() {
|
|
if(this.globalNotification && this.windowNotificationPermission() !== 'granted') {
|
|
this.onUpdateGlobalNotification(false);
|
|
}
|
|
this.currentMirror = null;
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@use "@/assets/css/chat.scss" as *;
|
|
</style> |