Merge branch 'more-audio-layout' into 'dev'
Fix playback bug on first load See merge request keanuapp/keanuapp-weblite!140
This commit is contained in:
commit
b7d3a1dc95
11 changed files with 424 additions and 252 deletions
|
|
@ -1,8 +1,21 @@
|
|||
<template>
|
||||
<div v-bind="{...$props, ...$attrs}" v-on="$listeners" class="messageIn">
|
||||
<div v-bind="{ ...$props, ...$attrs }" v-on="$listeners" class="messageIn">
|
||||
<div class="load-earlier clickable" @click="loadPrevious">
|
||||
<v-icon color="white" size="28">expand_less</v-icon>
|
||||
</div>
|
||||
|
||||
<!-- Currently recording users -->
|
||||
<div class="typing-users">
|
||||
<transition-group name="list" tag="div">
|
||||
<v-avatar v-for="(member) in recordingMembersExceptMe" :key="member.userId" class="typing-user" 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>
|
||||
</transition-group>
|
||||
</div>
|
||||
|
||||
<div class="sound-wave-view">
|
||||
<div class="volume-container">
|
||||
<div ref="volume"></div>
|
||||
|
|
@ -11,7 +24,7 @@
|
|||
@click.stop="otherAvatarClicked($refs.avatar.$el)">
|
||||
<img v-if="messageEventAvatar(currentAudioEvent)" :src="messageEventAvatar(currentAudioEvent)" />
|
||||
<span v-else class="white--text headline">{{
|
||||
eventSenderDisplayName(currentAudioEvent).substring(0, 1).toUpperCase()
|
||||
eventSenderDisplayName(currentAudioEvent).substring(0, 1).toUpperCase()
|
||||
}}</span>
|
||||
</v-avatar>
|
||||
</div>
|
||||
|
|
@ -25,7 +38,7 @@
|
|||
{{ currentTime }} / {{ totalTime }}
|
||||
</div>
|
||||
<audio ref="player" :src="src" @durationchange="updateDuration">
|
||||
{{ $t('fallbacks.audio_file')}}
|
||||
{{ $t('fallbacks.audio_file') }}
|
||||
</audio>
|
||||
<div v-if="currentAudioEvent" class="auto-audio-player">
|
||||
<v-btn id="btn-rewind" @click.stop="rewind" icon>
|
||||
|
|
@ -77,7 +90,13 @@ export default {
|
|||
default: function () {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
recordingMembers: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -98,8 +117,26 @@ export default {
|
|||
document.body.classList.add("dark");
|
||||
this.$root.$on('playback-start', this.onPlaybackStart);
|
||||
this.player = this.$refs.player;
|
||||
this.player.autoplay = false;
|
||||
this.player.addEventListener("timeupdate", this.updateProgressBar);
|
||||
this.player.addEventListener("play", () => {
|
||||
if (!this.analyser) {
|
||||
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
let audioSource = null;
|
||||
if (audioCtx) {
|
||||
audioSource = audioCtx.createMediaElementSource(this.player);
|
||||
this.analyser = audioCtx.createAnalyser();
|
||||
audioSource.connect(this.analyser);
|
||||
this.analyser.connect(audioCtx.destination);
|
||||
|
||||
this.analyser.fftSize = 128;
|
||||
const bufferLength = this.analyser.frequencyBinCount;
|
||||
this.analyzerDataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.playing = true;
|
||||
this.updateVisualization();
|
||||
if (this.currentAudioEvent) {
|
||||
|
|
@ -116,19 +153,6 @@ export default {
|
|||
this.clearVisualization();
|
||||
this.onPlaybackEnd();
|
||||
});
|
||||
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
let audioSource = null;
|
||||
if (audioCtx) {
|
||||
audioSource = audioCtx.createMediaElementSource(this.player);
|
||||
this.analyser = audioCtx.createAnalyser();
|
||||
audioSource.connect(this.analyser);
|
||||
this.analyser.connect(audioCtx.destination);
|
||||
|
||||
this.analyser.fftSize = 128;
|
||||
const bufferLength = this.analyser.frequencyBinCount;
|
||||
this.analyzerDataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.body.classList.remove("dark");
|
||||
|
|
@ -162,6 +186,11 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
recordingMembersExceptMe() {
|
||||
return this.recordingMembers.filter((member) => {
|
||||
return member.userId !== this.$matrix.currentUserId;
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
autoplay: {
|
||||
|
|
@ -204,7 +233,17 @@ export default {
|
|||
return;
|
||||
}
|
||||
this.src = null;
|
||||
const autoPlayWasSet = this.autoPlayNextEvent;
|
||||
this.autoPlayNextEvent = false;
|
||||
|
||||
if (value.getSender() == this.$matrix.currentUserId) {
|
||||
// Sent by us. Don't autoplay if we just sent this (i.e. it is ahead of our read marker)
|
||||
if (this.room && !this.room.getReceiptsForEvent(value).includes(value.getSender())) {
|
||||
this.player.autoplay = false;
|
||||
this.autoPlayNextEvent = autoPlayWasSet;
|
||||
}
|
||||
}
|
||||
|
||||
this.loadAudioAttachmentSource();
|
||||
}
|
||||
},
|
||||
|
|
@ -397,6 +436,18 @@ export default {
|
|||
});
|
||||
}
|
||||
},
|
||||
memberAvatar(member) {
|
||||
if (member) {
|
||||
return member.getAvatarUrl(
|
||||
this.$matrix.matrixClient.getHomeserverUrl(),
|
||||
40,
|
||||
40,
|
||||
"scale",
|
||||
true
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -4,20 +4,21 @@
|
|||
{{ $tc("room.invitations", invitationCount) }}
|
||||
</div>
|
||||
<ChatHeader class="chat-header flex-grow-0 flex-shrink-0" v-on:header-click="onHeaderClick" />
|
||||
<AudioLayout ref="chatContainer" class="auto-audio-player-root" v-if="useAudioLayout" :room="room"
|
||||
<AudioLayout ref="chatContainer" class="auto-audio-player-root" v-if="useVoiceMode" :room="room"
|
||||
:events="events" :autoplay="!showRecorder"
|
||||
:timelineSet="timelineSet"
|
||||
:readMarker="readMarker"
|
||||
:recordingMembers="typingMembers"
|
||||
v-on:start-recording="showRecorder = true"
|
||||
v-on:loadnext="handleScrolledToBottom(false)"
|
||||
v-on:loadprevious="handleScrolledToTop()"
|
||||
v-on:mark-read="sendRR"
|
||||
/>
|
||||
<VoiceRecorder class="audio-layout" v-if="useAudioLayout" :micButtonRef="$refs.mic_button" :ptt="showRecorderPTT" :show="showRecorder"
|
||||
v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
|
||||
<VoiceRecorder class="audio-layout" v-if="useVoiceMode" :micButtonRef="$refs.mic_button" :ptt="showRecorderPTT" :show="showRecorder"
|
||||
v-on:close="showRecorder = false" v-on:file="onVoiceRecording" :sendTypingIndicators="useVoiceMode" />
|
||||
|
||||
|
||||
<div v-if="!useAudioLayout" class="chat-content flex-grow-1 flex-shrink-1" ref="chatContainer"
|
||||
<div v-if="!useVoiceMode" class="chat-content flex-grow-1 flex-shrink-1" ref="chatContainer"
|
||||
v-on:scroll="onScroll" @click="closeContextMenusIfOpen">
|
||||
<div ref="messageOperationsStrut" class="message-operations-strut">
|
||||
<message-operations ref="messageOperations" :style="opStyle" :emojis="recentEmojis" v-on:close="
|
||||
|
|
@ -69,10 +70,10 @@
|
|||
</div>
|
||||
|
||||
<!-- Input area -->
|
||||
<v-container v-if="!useAudioLayout && room" fluid :class="['input-area-outer', replyToEvent ? 'reply-to' : '']">
|
||||
<v-container v-if="!useVoiceMode && room" fluid :class="['input-area-outer', replyToEvent ? 'reply-to' : '']">
|
||||
<div :class="[replyToEvent ? 'iput-area-inner-box' : '']">
|
||||
<!-- "Scroll to end"-button -->
|
||||
<v-btn v-if="!useAudioLayout" class="scroll-to-end" v-show="showScrollToEnd" fab x-small elevation="0" color="black"
|
||||
<v-btn v-if="!useVoiceMode" class="scroll-to-end" v-show="showScrollToEnd" fab x-small elevation="0" color="black"
|
||||
@click.stop="scrollToEndOfTimeline">
|
||||
<v-icon color="white">arrow_downward</v-icon>
|
||||
</v-btn>
|
||||
|
|
@ -421,7 +422,7 @@ export default {
|
|||
chatContainer() {
|
||||
const container = this.$refs.chatContainer;
|
||||
console.log("GOT CONTAINER", container);
|
||||
if (this.useAudioLayout) {
|
||||
if (this.useVoiceMode) {
|
||||
return container.$el;
|
||||
}
|
||||
return container;
|
||||
|
|
@ -522,15 +523,10 @@ export default {
|
|||
me && this.room.currentState && this.room.currentState.hasSufficientPowerLevelFor("redact", me.powerLevel);
|
||||
return isAdmin;
|
||||
},
|
||||
useAudioLayout: {
|
||||
useVoiceMode: {
|
||||
get: function () {
|
||||
if (this.room) {
|
||||
const tags = this.room.tags;
|
||||
if (tags && tags["ui_options"]) {
|
||||
return tags["ui_options"]["audio_layout"] === 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (!this.$config.experimental_voice_mode) return false;
|
||||
return util.useVoiceMode(this.room);
|
||||
},
|
||||
}
|
||||
},
|
||||
|
|
@ -611,6 +607,12 @@ export default {
|
|||
});
|
||||
}
|
||||
},
|
||||
showRecorder(show) {
|
||||
if (this.useVoiceMode) {
|
||||
// Send typing indicators when recorder UI is opened/closed
|
||||
this.$matrix.matrixClient.sendTyping(this.roomId, show, 10 * 60 * 1000);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -826,7 +828,7 @@ export default {
|
|||
const loadingDone = this.initialLoadDone;
|
||||
this.$matrix.matrixClient.decryptEventIfNeeded(event, {});
|
||||
|
||||
if (this.initialLoadDone && !this.useAudioLayout) {
|
||||
if (this.initialLoadDone && !this.useVoiceMode) {
|
||||
this.paginateBackIfNeeded();
|
||||
}
|
||||
|
||||
|
|
@ -1055,7 +1057,7 @@ export default {
|
|||
.then((success) => {
|
||||
if (success) {
|
||||
this.events = this.timelineWindow.getEvents();
|
||||
if (!this.useAudioLayout) {
|
||||
if (!this.useVoiceMode) {
|
||||
this.scrollPosition.prepareFor("down");
|
||||
this.$nextTick(() => {
|
||||
// restore scroll position!
|
||||
|
|
@ -1312,7 +1314,7 @@ export default {
|
|||
|
||||
let eventIdFirst = null;
|
||||
let eventIdLast = null;
|
||||
if (!this.useAudioLayout) {
|
||||
if (!this.useVoiceMode) {
|
||||
const container = this.chatContainer;
|
||||
const elFirst = util.getFirstVisibleElement(container);
|
||||
const elLast = util.getLastVisibleElement(container);
|
||||
|
|
|
|||
|
|
@ -3,29 +3,23 @@
|
|||
<div>
|
||||
<v-container fluid>
|
||||
<div class="room-name no-upper">{{ $t("new_room.new_room") }}</div>
|
||||
<v-btn
|
||||
id="btn-back"
|
||||
text
|
||||
class="header-button-left"
|
||||
v-show="$navigation && $navigation.canPop()"
|
||||
@click.stop="goBack"
|
||||
:disabled="step > steps.NAME_SET"
|
||||
>
|
||||
<v-btn id="btn-back" text class="header-button-left" v-show="$navigation && $navigation.canPop()"
|
||||
@click.stop="goBack" :disabled="step > steps.NAME_SET">
|
||||
<v-icon>arrow_back</v-icon>
|
||||
<span class="d-none d-sm-block">{{ $t("menu.back") }}</span>
|
||||
</v-btn>
|
||||
<!-- <v-btn
|
||||
text
|
||||
:disabled="
|
||||
!roomName || (step != steps.INITIAL && step != steps.CREATED)
|
||||
"
|
||||
class="header-button-right"
|
||||
@click.stop="onCreate"
|
||||
>
|
||||
<span>{{
|
||||
step == steps.CREATED ? $t("new_room.done") : $t("new_room.next")
|
||||
}}</span>
|
||||
</v-btn> -->
|
||||
text
|
||||
:disabled="
|
||||
!roomName || (step != steps.INITIAL && step != steps.CREATED)
|
||||
"
|
||||
class="header-button-right"
|
||||
@click.stop="onCreate"
|
||||
>
|
||||
<span>{{
|
||||
step == steps.CREATED ? $t("new_room.done") : $t("new_room.next")
|
||||
}}</span>
|
||||
</v-btn> -->
|
||||
</v-container>
|
||||
</div>
|
||||
|
||||
|
|
@ -39,51 +33,43 @@
|
|||
</v-col>
|
||||
</v-row>
|
||||
<v-row cols="12" align="center" justify="center">
|
||||
<v-col sm="8" align="center">
|
||||
<v-col sm="8" align="center">
|
||||
<div class="text-left font-weight-light">{{ $t("new_room.name_room") }}</div>
|
||||
<v-text-field
|
||||
v-model="roomName"
|
||||
color="black"
|
||||
:rules="roomNamerules"
|
||||
counter="50"
|
||||
maxlength="50"
|
||||
background-color="white"
|
||||
v-on:keyup.enter="$refs.topic.focus()"
|
||||
:disabled="step > steps.INITIAL"
|
||||
autofocus
|
||||
solo
|
||||
@update:error="updateErrorState"
|
||||
></v-text-field>
|
||||
<div class="text-left font-weight-light" v-show="roomName.length> 0">{{ $t("new_room.room_topic") }}</div>
|
||||
<v-text-field
|
||||
v-model="roomTopic"
|
||||
v-show="roomName.length > 0"
|
||||
color="black"
|
||||
background-color="white"
|
||||
v-on:keyup.enter="$refs.create.focus()"
|
||||
:disabled="step > steps.INITIAL"
|
||||
solo
|
||||
></v-text-field>
|
||||
<div class="error--text" v-if="roomCreationErrorMsg"> {{roomCreationErrorMsg}}</div>
|
||||
<v-btn
|
||||
id="btn-room-create"
|
||||
color="black"
|
||||
depressed
|
||||
class="filled-button"
|
||||
@click.stop="onCreate"
|
||||
:disabled="isDisabled"
|
||||
>
|
||||
<div v-if="status && !enterRoomDialog" class="text-center">
|
||||
{{ status }}
|
||||
<v-progress-circular
|
||||
v-if="step == steps.CREATING"
|
||||
indeterminate
|
||||
color="primary"
|
||||
size="20"
|
||||
></v-progress-circular>
|
||||
</div>
|
||||
<span v-else>{{ $t("new_room.create") }}</span>
|
||||
</v-btn>
|
||||
<v-text-field v-model="roomName" color="black" :rules="roomNamerules" counter="50" maxlength="50"
|
||||
background-color="white" v-on:keyup.enter="$refs.topic.focus()" :disabled="step > steps.INITIAL" autofocus
|
||||
solo @update:error="updateErrorState"></v-text-field>
|
||||
<div class="text-left font-weight-light" v-show="roomName.length > 0">{{ $t("new_room.room_topic") }}</div>
|
||||
<v-text-field v-model="roomTopic" v-show="roomName.length > 0" color="black" background-color="white"
|
||||
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">
|
||||
<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-text class="with-right-label">
|
||||
<div>
|
||||
<div class="option-title">{{ $t('room_info.voice_mode') }}</div>
|
||||
<div class="option-text">{{ $t('room_info.voice_mode_info') }}</div>
|
||||
</div>
|
||||
<v-switch v-model="useVoiceMode"></v-switch>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<div class="error--text" v-if="roomCreationErrorMsg"> {{ roomCreationErrorMsg }}</div>
|
||||
<v-btn id="btn-room-create" color="black" depressed class="filled-button" @click.stop="onCreate"
|
||||
:disabled="isDisabled">
|
||||
<div v-if="status && !enterRoomDialog" class="text-center">
|
||||
{{ status }}
|
||||
<v-progress-circular v-if="step == steps.CREATING" indeterminate color="primary"
|
||||
size="20"></v-progress-circular>
|
||||
</div>
|
||||
<span v-else>{{ $t("new_room.create") }}</span>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
|
@ -91,126 +77,101 @@
|
|||
<v-fade-transition>
|
||||
<!-- <div class="section ma-3" flat v-if="step > steps.INITIAL"> -->
|
||||
<!-- <div class="h4 text-left">{{ $t("new_room.join_permissions") }}</div>
|
||||
<div class="h2 text-left">
|
||||
{{ $t("new_room.set_join_permissions") }}
|
||||
</div>
|
||||
<div>{{ $t("new_room.join_permissions_info") }}</div>
|
||||
<v-select
|
||||
:disabled="step >= steps.CREATING"
|
||||
:items="joinRules"
|
||||
class="mt-4"
|
||||
v-model="joinRule"
|
||||
item-value="id"
|
||||
>
|
||||
<template v-slot:selection="{ item }">
|
||||
{{ item.text }}
|
||||
</template>
|
||||
<template v-slot:item="{ item, attrs, on }">
|
||||
<v-list-item v-on="on" v-bind="attrs" #default="{ active }">
|
||||
<v-list-item-avatar>
|
||||
<v-icon class="grey lighten-1" dark>{{ item.icon }}</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="item.text"></v-list-item-title>
|
||||
<v-list-item-subtitle
|
||||
v-text="item.descr"
|
||||
></v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-btn icon v-if="active">
|
||||
<v-icon color="grey lighten-1">check</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-select>
|
||||
<div class="h2 text-left">
|
||||
{{ $t("new_room.set_join_permissions") }}
|
||||
</div>
|
||||
<div>{{ $t("new_room.join_permissions_info") }}</div>
|
||||
<v-select
|
||||
:disabled="step >= steps.CREATING"
|
||||
:items="joinRules"
|
||||
class="mt-4"
|
||||
v-model="joinRule"
|
||||
item-value="id"
|
||||
>
|
||||
<template v-slot:selection="{ item }">
|
||||
{{ item.text }}
|
||||
</template>
|
||||
<template v-slot:item="{ item, attrs, on }">
|
||||
<v-list-item v-on="on" v-bind="attrs" #default="{ active }">
|
||||
<v-list-item-avatar>
|
||||
<v-icon class="grey lighten-1" dark>{{ item.icon }}</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="item.text"></v-list-item-title>
|
||||
<v-list-item-subtitle
|
||||
v-text="item.descr"
|
||||
></v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-btn icon v-if="active">
|
||||
<v-icon color="grey lighten-1">check</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-select>
|
||||
|
||||
<v-divider style="margin-bottom: 20px" />
|
||||
<v-divider style="margin-bottom: 20px" />
|
||||
|
||||
<v-text-field
|
||||
v-if="publicRoomLink"
|
||||
:value="publicRoomLink"
|
||||
class="room-link"
|
||||
readonly
|
||||
filled
|
||||
background-color="transparent"
|
||||
append-icon="content_copy"
|
||||
type="text"
|
||||
@click:append.stop="copyRoomLink"
|
||||
></v-text-field>
|
||||
<v-btn
|
||||
v-else-if="joinRule == 'public'"
|
||||
:loading="step == steps.CREATING"
|
||||
block
|
||||
depressed
|
||||
class="outlined-button"
|
||||
@click.stop="getPublicLink"
|
||||
><v-icon class="me-2">link</v-icon
|
||||
>{{ $t("new_room.get_link") }}</v-btn
|
||||
>
|
||||
<v-btn
|
||||
v-else-if="joinRule == 'invite'"
|
||||
block
|
||||
depressed
|
||||
class="outlined-button"
|
||||
@click.stop="addPeople"
|
||||
><v-icon class="me-2">person_add</v-icon
|
||||
>{{ $t("new_room.add_people") }}</v-btn
|
||||
>
|
||||
<v-text-field
|
||||
v-if="publicRoomLink"
|
||||
:value="publicRoomLink"
|
||||
class="room-link"
|
||||
readonly
|
||||
filled
|
||||
background-color="transparent"
|
||||
append-icon="content_copy"
|
||||
type="text"
|
||||
@click:append.stop="copyRoomLink"
|
||||
></v-text-field>
|
||||
<v-btn
|
||||
v-else-if="joinRule == 'public'"
|
||||
:loading="step == steps.CREATING"
|
||||
block
|
||||
depressed
|
||||
class="outlined-button"
|
||||
@click.stop="getPublicLink"
|
||||
><v-icon class="me-2">link</v-icon
|
||||
>{{ $t("new_room.get_link") }}</v-btn
|
||||
>
|
||||
<v-btn
|
||||
v-else-if="joinRule == 'invite'"
|
||||
block
|
||||
depressed
|
||||
class="outlined-button"
|
||||
@click.stop="addPeople"
|
||||
><v-icon class="me-2">person_add</v-icon
|
||||
>{{ $t("new_room.add_people") }}</v-btn
|
||||
>
|
||||
|
||||
<div v-if="publicRoomLinkCopied" class="link-copied">
|
||||
{{ $t("new_room.link_copied") }}
|
||||
</div>
|
||||
-->
|
||||
<div v-if="publicRoomLinkCopied" class="link-copied">
|
||||
{{ $t("new_room.link_copied") }}
|
||||
</div>
|
||||
-->
|
||||
<!-- <div v-if="status && !enterRoomDialog" class="text-center">
|
||||
<v-progress-circular
|
||||
v-if="step == steps.CREATING"
|
||||
indeterminate
|
||||
color="primary"
|
||||
size="20"
|
||||
></v-progress-circular>
|
||||
{{ status }}
|
||||
</div> -->
|
||||
<v-progress-circular
|
||||
v-if="step == steps.CREATING"
|
||||
indeterminate
|
||||
color="primary"
|
||||
size="20"
|
||||
></v-progress-circular>
|
||||
{{ status }}
|
||||
</div> -->
|
||||
<!-- </div> -->
|
||||
</v-fade-transition>
|
||||
<input
|
||||
id="room-avatar-picker"
|
||||
ref="avatar"
|
||||
type="file"
|
||||
name="avatar"
|
||||
@change="handlePickedAvatar($event)"
|
||||
accept="image/*"
|
||||
class="d-none"
|
||||
/>
|
||||
<v-dialog
|
||||
v-model="enterRoomDialog"
|
||||
:width="$vuetify.breakpoint.smAndUp ? '50%' : '90%'"
|
||||
>
|
||||
<input id="room-avatar-picker" ref="avatar" type="file" name="avatar" @change="handlePickedAvatar($event)"
|
||||
accept="image/*" class="d-none" />
|
||||
<v-dialog v-model="enterRoomDialog" :width="$vuetify.breakpoint.smAndUp ? '50%' : '90%'">
|
||||
<v-card>
|
||||
<v-container v-if="canEditProfile" class="pa-10">
|
||||
<v-row class="align-center">
|
||||
<v-col class="py-0">
|
||||
<div class="text-left font-weight-bold">{{ $t("join.choose_name") }}</div>
|
||||
<v-select
|
||||
ref="avatar"
|
||||
:items="availableAvatars"
|
||||
cache-items
|
||||
outlined
|
||||
dense
|
||||
@change="selectAvatar"
|
||||
:value="availableAvatars[0]"
|
||||
single-line
|
||||
autofocus
|
||||
>
|
||||
<v-select ref="avatar" :items="availableAvatars" cache-items outlined dense @change="selectAvatar"
|
||||
:value="availableAvatars[0]" single-line autofocus>
|
||||
<template v-slot:selection>
|
||||
<v-text-field
|
||||
background-color="transparent"
|
||||
solo
|
||||
flat
|
||||
hide-details
|
||||
@click.native.stop="{}"
|
||||
v-model="selectedProfile.name"
|
||||
></v-text-field>
|
||||
<v-text-field background-color="transparent" solo flat hide-details @click.native.stop="{}"
|
||||
v-model="selectedProfile.name"></v-text-field>
|
||||
</template>
|
||||
<template v-slot:item="data">
|
||||
<v-avatar size="32">
|
||||
|
|
@ -219,33 +180,23 @@
|
|||
<div class="ms-2">{{ data.item.name }}</div>
|
||||
</template>
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="2" class="py-0">
|
||||
<v-avatar @click="showAvatarPickerList">
|
||||
</v-col>
|
||||
<v-col cols="2" class="py-0">
|
||||
<v-avatar @click="showAvatarPickerList">
|
||||
<v-img v-if="selectedProfile" :src="selectedProfile.image" />
|
||||
</v-avatar>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row class="mt-0">
|
||||
<v-col class="py-0">
|
||||
<v-checkbox
|
||||
id="chk-remember-me"
|
||||
class="mt-0"
|
||||
v-model="rememberMe"
|
||||
@change="onRememberMe"
|
||||
:label="$t('join.remember_me')"
|
||||
/>
|
||||
<v-checkbox id="chk-remember-me" class="mt-0" v-model="rememberMe" @change="onRememberMe"
|
||||
:label="$t('join.remember_me')" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-btn
|
||||
color="black"
|
||||
depressed
|
||||
class="filled-button"
|
||||
@click.stop="onEnterRoom"
|
||||
:disabled="!selectedProfile.name"
|
||||
>
|
||||
{{ $t("join.enter_room") }}
|
||||
</v-btn>
|
||||
<v-btn color="black" depressed class="filled-button" @click.stop="onEnterRoom"
|
||||
:disabled="!selectedProfile.name">
|
||||
{{ $t("join.enter_room") }}
|
||||
</v-btn>
|
||||
</v-container>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
|
@ -253,7 +204,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import util from "../plugins/utils";
|
||||
import util, { ROOM_TYPE_VOICE_MODE } from "../plugins/utils";
|
||||
import rememberMeMixin from "./rememberMeMixin";
|
||||
|
||||
const steps = Object.freeze({
|
||||
|
|
@ -265,7 +216,7 @@ const steps = Object.freeze({
|
|||
|
||||
export default {
|
||||
name: "CreateRoom",
|
||||
mixins:[rememberMeMixin],
|
||||
mixins: [rememberMeMixin],
|
||||
data() {
|
||||
return {
|
||||
steps,
|
||||
|
|
@ -305,7 +256,9 @@ export default {
|
|||
v => !v.includes(':') || this.$t("new_room.colon_not_allowed")
|
||||
],
|
||||
roomNameHasError: false,
|
||||
roomCreationErrorMsg: ""
|
||||
roomCreationErrorMsg: "",
|
||||
showOptions: false,
|
||||
useVoiceMode: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -314,7 +267,7 @@ export default {
|
|||
this.availableAvatars = util.getDefaultAvatars();
|
||||
this.selectAvatar(
|
||||
this.availableAvatars[
|
||||
Math.floor(Math.random() * this.availableAvatars.length)
|
||||
Math.floor(Math.random() * this.availableAvatars.length)
|
||||
]
|
||||
);
|
||||
},
|
||||
|
|
@ -359,7 +312,7 @@ export default {
|
|||
},
|
||||
|
||||
onCreate() {
|
||||
if(this.currentUser) {
|
||||
if (this.currentUser) {
|
||||
this.onEnterRoom();
|
||||
} else {
|
||||
this.enterRoomDialog = true;
|
||||
|
|
@ -486,6 +439,11 @@ export default {
|
|||
// Add topic
|
||||
createRoomOptions.topic = this.roomTopic;
|
||||
}
|
||||
if (this.useVoiceMode) {
|
||||
createRoomOptions.creation_content = {
|
||||
type: ROOM_TYPE_VOICE_MODE
|
||||
}
|
||||
}
|
||||
|
||||
return this.$matrix
|
||||
.getLoginPromise()
|
||||
|
|
@ -592,7 +550,7 @@ export default {
|
|||
})
|
||||
.catch((error) => {
|
||||
this.status = ""
|
||||
this.roomCreationErrorMsg = (error.data && error.data.error) || error.message || error.toString();
|
||||
this.roomCreationErrorMsg = (error.data && error.data.error) || error.message || error.toString();
|
||||
this.step = steps.INITIAL; // revert
|
||||
return null;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -165,12 +165,15 @@
|
|||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card class="account ma-3" flat>
|
||||
<v-card-title class="h2">{{ $t("room_info.ui_options") }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-card class="account ma-3" flat v-if="$config.experimental_voice_mode">
|
||||
<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">
|
||||
<div>
|
||||
<div class="option-title">{{ $t('room_info.voice_mode') }}</div>
|
||||
<div class="option-text">{{ $t('room_info.voice_mode_info') }}</div>
|
||||
</div>
|
||||
<v-switch
|
||||
v-model="useAudioLayout"
|
||||
:label="$t('room_info.audio_layout')"
|
||||
v-model="useVoiceMode"
|
||||
></v-switch>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
|
@ -373,20 +376,14 @@ export default {
|
|||
return "";
|
||||
},
|
||||
|
||||
useAudioLayout: {
|
||||
useVoiceMode: {
|
||||
get: function () {
|
||||
if (this.room) {
|
||||
const tags = this.room.tags;
|
||||
if (tags && tags["ui_options"]) {
|
||||
return tags["ui_options"]["audio_layout"] === 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return util.useVoiceMode(this.room);
|
||||
},
|
||||
set: function (audioLayout) {
|
||||
if (this.room && this.room.tags) {
|
||||
let options = this.room.tags["ui_options"] || {}
|
||||
options["audio_layout"] = (audioLayout ? 1 : 0);
|
||||
options["voice_mode"] = (audioLayout ? 1 : 0);
|
||||
this.room.tags["ui_options"] = options;
|
||||
this.$matrix.matrixClient.setRoomTag(this.room.roomId, "ui_options", options);
|
||||
}
|
||||
|
|
@ -668,4 +665,5 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
|
||||
</style>
|
||||
|
|
@ -11,6 +11,20 @@
|
|||
<v-btn v-show="state == states.RECORDED" icon @click.stop="redo">
|
||||
<v-icon color="white">undo</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-show="state == states.INITIAL" icon @click.stop="importAudio">
|
||||
<v-icon color="white">$vuetify.icons.audio_import</v-icon>
|
||||
<input
|
||||
ref="audio_import"
|
||||
type="file"
|
||||
name="audio_import"
|
||||
@change="handleAudioImport($event)"
|
||||
accept="audio/*"
|
||||
class="d-none"
|
||||
/>
|
||||
</v-btn>
|
||||
<v-btn v-show="state == states.IMPORTED" icon @click.stop="previewAudio">
|
||||
<v-icon color="white">$vuetify.icons.audio_import_play</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="4" align="center">
|
||||
<v-btn
|
||||
|
|
@ -23,7 +37,7 @@
|
|||
<v-icon color="white">stop</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else-if="state == states.RECORDED"
|
||||
v-else-if="state == states.RECORDED || state == states.IMPORTED"
|
||||
id="btn-send"
|
||||
class="voice-recorder-btn recorded"
|
||||
icon
|
||||
|
|
@ -145,6 +159,7 @@ const State = {
|
|||
RECORDING: "recording",
|
||||
RECORDED: "recorded",
|
||||
ERROR: "error",
|
||||
IMPORTED: "imported"
|
||||
};
|
||||
import util from "../plugins/utils";
|
||||
import VoiceRecorderLock from "./VoiceRecorderLock";
|
||||
|
|
@ -192,7 +207,8 @@ export default {
|
|||
recordingLocked: false,
|
||||
recordedFile: null,
|
||||
errorMessage: null,
|
||||
recorder: null
|
||||
recorder: null,
|
||||
previewPlayer: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -242,8 +258,10 @@ export default {
|
|||
this.startRecording();
|
||||
} else {
|
||||
console.log("Not PTT");
|
||||
if (this.micButtonRef) {
|
||||
//eslint-disable-next-line
|
||||
this.micButtonRef.$el.style.display = "none";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove listeners
|
||||
|
|
@ -257,10 +275,17 @@ export default {
|
|||
this.startCoordinateX = null;
|
||||
this.startCoordinateY = null;
|
||||
this.recordingLocked = false;
|
||||
if (this.micButtonRef) {
|
||||
//eslint-disable-next-line
|
||||
this.micButtonRef.$el.style.display = "block";
|
||||
}
|
||||
}
|
||||
},
|
||||
state() {
|
||||
if (this.state != State.IMPORTED && this.previewPlayer) {
|
||||
this.previewPlayer.pause();
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lockButtonStyle() {
|
||||
|
|
@ -351,10 +376,10 @@ export default {
|
|||
},
|
||||
cancelRecording() {
|
||||
if(this.recorder) {
|
||||
this.state = State.INITIAL;
|
||||
this.recorder.stop();
|
||||
this.recorder = null;
|
||||
}
|
||||
this.state = State.INITIAL;
|
||||
this.close();
|
||||
},
|
||||
pauseRecording() {
|
||||
|
|
@ -433,6 +458,37 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show import picker to select file
|
||||
*/
|
||||
importAudio() {
|
||||
this.$refs.audio_import.click();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle picked audio file
|
||||
*/
|
||||
handleAudioImport(event) {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
this.recordedFile = event.target.files[0];
|
||||
this.state = State.IMPORTED;
|
||||
}
|
||||
},
|
||||
|
||||
previewAudio() {
|
||||
if (this.recordedFile) {
|
||||
if (!this.previewPlayer) {
|
||||
this.previewPlayer = new Audio();
|
||||
}
|
||||
var reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
this.previewPlayer.src = e.target.result;
|
||||
this.previewPlayer.play();
|
||||
};
|
||||
reader.readAsDataURL(this.recordedFile);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue