Experimental "read only" room support

This commit is contained in:
N-Pex 2023-03-16 15:23:26 +01:00 committed by n8fr8
parent f34721c930
commit 76ca3f8e70
8 changed files with 169 additions and 17 deletions

View file

@ -40,5 +40,6 @@
}
}
],
"experimental_voice_mode": true
"experimental_voice_mode": true,
"experimental_read_only_room": true
}

View file

@ -295,6 +295,19 @@ body {
}
}
.input-area-read-only {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(white, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 40;
}
@media #{map-get($display-breakpoints, 'sm-and-down')} {
position: fixed;
bottom: 0px;
@ -1466,6 +1479,21 @@ body {
.mic-button {
z-index: 0;
}
.mic-button.dimmed {
opacity: 0.5;
}
.toast-read-only {
position: fixed;
left: 10px;
right: 10px;
bottom: 10px;
background-color: rgba(black, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 40;
color: white;
}
}
.audio-layout.voice-recorder {

View file

@ -82,7 +82,8 @@
"reply_poll": "Poll",
"time_ago": "Today | Yesterday | {count} days ago",
"outgoing_message_deleted_text": "You deleted this message.",
"incoming_message_deleted_text": "This message was deleted."
"incoming_message_deleted_text": "This message was deleted.",
"not_allowed_to_send": "Only admins and moderators are allowed to send to the room"
},
"room": {
"invitations": "You have no invitations | You have 1 invitation | You have {count} invitations",
@ -249,7 +250,9 @@
"experimental_features": "Experimental Features",
"voice_mode": "Voice mode",
"voice_mode_info": "Switches the chat interface to a 'listen and record' mode",
"download_chat": "Download chat"
"download_chat": "Download chat",
"read_only_room": "Read only room",
"read_only_room_info": "Only admins and moderators are allowed to send to the room"
},
"room_info_sheet": {
"this_room": "This room",

View file

@ -56,12 +56,14 @@
</div>
<div class="load-later">
<v-btn v-if="canRecordAudio" class="mic-button" ref="mic_button" fab small elevation="0" v-blur
@click.stop="$emit('start-recording')">
<v-btn :class="{'mic-button': true, 'dimmed': !canRecordAudio}" ref="mic_button" fab small elevation="0" v-blur
@click.stop="micButtonClicked()">
<v-icon color="white">mic</v-icon>
</v-btn>
<v-icon class="clickable" @click="loadNext" color="white" size="28">expand_more</v-icon>
</div>
<div v-if="showReadOnlyToast" class="toast-read-only">{{ $t("message.not_allowed_to_send") }}</div>
</div>
</template>
@ -111,6 +113,7 @@ export default {
playing: false,
analyzer: null,
analyzerDataArray: null,
showReadOnlyToast: false,
};
},
mounted() {
@ -162,12 +165,7 @@ export default {
},
computed: {
canRecordAudio() {
if (this.room) {
const myUserId = this.$matrix.currentUserId;
const me = this.room.getMember(myUserId);
return me && me.powerLevelNorm > 0 && util.browserCanRecordAudio();
}
return false;
return !this.$matrix.currentRoomIsReadOnlyForUser && util.browserCanRecordAudio();
},
currentTime() {
return util.formatDuration(this.playTime);
@ -448,6 +446,17 @@ export default {
}
return null;
},
micButtonClicked() {
if (this.$matrix.currentRoomIsReadOnlyForUser) {
this.showReadOnlyToast = true;
setTimeout(() => {
this.showReadOnlyToast = false;
}, 3000);
} else {
this.$emit('start-recording');
}
}
}
};
</script>

View file

@ -6,7 +6,7 @@
:timelineSet="timelineSet"
:readMarker="readMarker"
:recordingMembers="typingMembers"
v-on:start-recording="showRecorder = true"
v-on:start-recording="setShowRecorder()"
v-on:loadnext="handleScrolledToBottom(false)"
v-on:loadprevious="handleScrolledToTop()"
v-on:mark-read="sendRR"
@ -177,6 +177,7 @@
<VoiceRecorder :micButtonRef="$refs.mic_button" :ptt="showRecorderPTT" :show="showRecorder"
v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
</div>
<div v-if="!useVoiceMode && room && $matrix.currentRoomIsReadOnlyForUser" class="input-area-read-only">{{ $t("message.not_allowed_to_send") }}</div>
</v-container>
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
@ -1450,7 +1451,15 @@ export default {
setTimeout(() => {
this.chatContainer.parentElement.removeChild(div);
}, 3000);
},
setShowRecorder() {
if (this.canRecordAudio) {
this.showRecorder = true;
} else {
this.showNoRecordingAvailableDialog = true;
}
}
},
};
</script>

View file

@ -43,13 +43,13 @@
v-on:keyup.enter="$refs.create.focus()" :disabled="step > steps.INITIAL" solo></v-text-field>
<!-- Our only option right now is voice mode, so if not enabled, hide the 'options' drop down as well -->
<template v-if="$config.experimental_voice_mode">
<template v-if="$config.experimental_voice_mode || $config.experimental_read_only_room">
<div @click.stop="showOptions = !showOptions" v-show="roomName.length > 0" class="options clickable">
<div>{{ $t("new_room.options") }}</div>
<v-icon v-if="!showOptions">expand_more</v-icon>
<v-icon v-else>expand_less</v-icon>
</div>
<v-card v-show="showOptions" class="account ma-3" flat>
<v-card v-if="$config.experimental_voice_mode" v-show="showOptions" class="account ma-3" flat>
<v-card-text class="with-right-label">
<div>
<div class="option-title">{{ $t('room_info.voice_mode') }}</div>
@ -58,6 +58,15 @@
<v-switch v-model="useVoiceMode"></v-switch>
</v-card-text>
</v-card>
<v-card v-if="$config.experimental_read_only_room" v-show="showOptions" class="account ma-3" flat>
<v-card-text class="with-right-label">
<div>
<div class="option-title">{{ $t('room_info.read_only_room') }}</div>
<div class="option-text">{{ $t('room_info.read_only_room_info') }}</div>
</div>
<v-switch v-model="readOnlyRoom"></v-switch>
</v-card-text>
</v-card>
</template>
<div class="error--text" v-if="roomCreationErrorMsg"> {{ roomCreationErrorMsg }}</div>
@ -259,6 +268,7 @@ export default {
roomCreationErrorMsg: "",
showOptions: false,
useVoiceMode: false,
readOnlyRoom: false,
};
},
@ -401,7 +411,7 @@ export default {
content: {
history_visibility: "joined"
}
}
},
],
};
} else {
@ -516,6 +526,20 @@ export default {
}
})
.then(() => {
// Set power level event. Need to do that here, because we might not have the userId when the options object is created.
const powerLevels = {};
powerLevels[this.$matrix.currentUserId] = 100;
createRoomOptions.initial_state.push(
{
type: "m.room.power_levels",
state_key: "",
content: {
users: powerLevels,
events_default: this.readOnlyRoom ? 50 : 0
}
});
return this.$matrix.matrixClient
.createRoom(createRoomOptions)
.then(({ room_id, room_alias }) => {

View file

@ -165,9 +165,9 @@
</v-card-text>
</v-card>
<v-card class="account ma-3" flat v-if="$config.experimental_voice_mode">
<v-card class="account ma-3" flat v-if="$config.experimental_voice_mode || canChangeReadOnly()">
<v-card-title class="h2 with-right-label"><div>{{ $t("room_info.experimental_features") }}</div><div></div></v-card-title>
<v-card-text class="with-right-label">
<v-card-text class="with-right-label" v-if="$config.experimental_voice_mode">
<div>
<div class="option-title">{{ $t('room_info.voice_mode') }}</div>
<div class="option-text">{{ $t('room_info.voice_mode_info') }}</div>
@ -176,6 +176,15 @@
v-model="useVoiceMode"
></v-switch>
</v-card-text>
<v-card-text class="with-right-label" v-if="canChangeReadOnly()">
<div>
<div class="option-title">{{ $t('room_info.read_only_room') }}</div>
<div class="option-text">{{ $t('room_info.read_only_room_info') }}</div>
</div>
<v-switch
v-model="readOnlyRoom"
></v-switch>
</v-card-text>
</v-card>
<v-card class="members ma-3" flat>
@ -389,6 +398,14 @@ export default {
this.$matrix.matrixClient.setRoomTag(this.room.roomId, "ui_options", options);
}
},
},
readOnlyRoom: {
get: function () {
return this.$matrix.isReadOnlyRoom(this.room.roomId);
},
set: function (readOnly) {
this.$matrix.setReadOnlyRoom(this.room.roomId, readOnly);
},
}
},
@ -591,6 +608,16 @@ export default {
}
return false;
},
/**
* Return true if we can change power levels in the room, i.e. make read only room
*/
canChangeReadOnly() {
if (!this.$config.experimental_read_only_room) { return false; }
if (this.room) {
return this.room.currentState && this.room.currentState.maySendStateEvent("m.room.power_levels", this.$matrix.currentUserId);
}
return false;
},
// TODO - following power level comparisons assume that default power levels are used in the room!
isAdmin(member) {
return member.powerLevelNorm > 50;

View file

@ -37,6 +37,7 @@ export default {
userDisplayName: null,
userAvatar: null,
currentRoom: null,
currentRoomIsReadOnlyForUser: false,
currentRoomBeingPurged: false,
notificationCount: 0,
};
@ -93,6 +94,7 @@ export default {
immediate: true,
handler(roomId) {
this.currentRoom = this.getRoom(roomId);
this.currentRoomIsReadOnlyForUser = this.isReadOnlyRoomForUser(roomId, this.currentUserId);
},
},
},
@ -355,6 +357,14 @@ export default {
}
}
break;
case "m.room.power_levels":
{
if (this.currentRoom && event.getRoomId() == this.currentRoom.roomId) {
this.currentRoomIsReadOnlyForUser = this.isReadOnlyRoomForUser(event.getRoomId(), this.currentUserId);
}
}
break;
}
this.updateNotificationCount();
},
@ -550,6 +560,47 @@ export default {
}
},
isReadOnlyRoom(roomId) {
if (this.matrixClient && roomId) {
const room = this.getRoom(roomId);
if (room && room.currentState) {
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (powerLevelEvent) {
return powerLevelEvent.getContent().events_default > 0
}
}
}
return false;
},
isReadOnlyRoomForUser(roomId, userId) {
if (this.matrixClient && roomId && userId) {
const room = this.getRoom(roomId);
if (room && room.currentState) {
return !room.currentState.maySendMessage(userId)
}
}
return false;
},
setReadOnlyRoom(roomId, readOnly) {
if (this.matrixClient && roomId) {
const room = this.getRoom(roomId);
if (room && room.currentState) {
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (powerLevelEvent) {
let content = powerLevelEvent.getContent();
content.events_default = readOnly ? 50 : 0;
this.matrixClient.sendStateEvent(
room.roomId,
"m.room.power_levels",
content
);
}
}
}
},
/**
* Purge the room with the given id! This means:
* - Make room invite only