More poll styling
This commit is contained in:
parent
98ed17723d
commit
32ebd86c5d
12 changed files with 361 additions and 344 deletions
|
|
@ -15,5 +15,5 @@ $chat-button-height: 50px;
|
|||
$voice-recorder-color: #6f6f6f;
|
||||
$voice-recording-color: red;
|
||||
$voice-recorded-color: #3ae17d;
|
||||
|
||||
$poll-hilite-color: $very-very-purple;
|
||||
$poll-hilite-color: #6360f0;
|
||||
$poll-hilite-color-bg: #d6d5fc;
|
||||
|
|
@ -611,6 +611,10 @@ $admin-fg: white;
|
|||
box-shadow: 4px 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.send-options {
|
||||
z-index: 11; // Above mic button
|
||||
}
|
||||
|
||||
.message-operations-picker {
|
||||
background-color: white;
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -2,19 +2,57 @@
|
|||
width: 70%;
|
||||
}
|
||||
|
||||
.poll-bubble {
|
||||
color: black;
|
||||
padding: $chat-standard-padding-s !important;
|
||||
font-family: "Inter", sans-serif;
|
||||
font-size: 16 * $chat-text-size;
|
||||
line-height: 16 * $chat-text-size;
|
||||
}
|
||||
|
||||
.from-admin .poll-bubble {
|
||||
color: rgba(white, 0.9);
|
||||
}
|
||||
|
||||
.poll-icon {
|
||||
path {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
.poll-check-icon {
|
||||
width: 14.18px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.poll-question {
|
||||
font-weight: 700;
|
||||
margin-top: $chat-standard-padding-xs;
|
||||
margin-bottom: $chat-standard-padding-s;
|
||||
}
|
||||
|
||||
.poll-answer {
|
||||
border: 1px solid #666;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
border: 1px solid currentColor;
|
||||
border-radius: 4px;
|
||||
padding: 15px 14px;
|
||||
margin: 0px;
|
||||
&.winner {
|
||||
font-weight: 700;
|
||||
}
|
||||
&.selected {
|
||||
border: 1px solid $poll-hilite-color;
|
||||
background-color: $poll-hilite-color-bg;
|
||||
color: #1d1d1d;
|
||||
font-weight: 700;
|
||||
}
|
||||
&.result {
|
||||
border: none;
|
||||
padding: 15px 0px;
|
||||
}
|
||||
.poll-answer-title {
|
||||
color: #444;
|
||||
}
|
||||
.poll-answer-num-votes {
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
|
@ -23,32 +61,49 @@
|
|||
.poll-percent-indicator {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 2px;
|
||||
right: 2px;
|
||||
height: 4px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
height: 8px;
|
||||
margin-top: 4px;
|
||||
.bar {
|
||||
background-color: $poll-hilite-color;
|
||||
background-color: #7e7cf8;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
border-radius: 3px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.poll-status {
|
||||
margin: 10px;
|
||||
justify-content: space-between;
|
||||
font-size: 13px;
|
||||
line-height: 117%;
|
||||
margin: 0px;
|
||||
.poll-status-title {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.poll-status-close {
|
||||
font-size: 0.7rem;
|
||||
color: $poll-hilite-color;
|
||||
}
|
||||
}
|
||||
|
||||
.poll-submit {
|
||||
.v-btn {
|
||||
font-family: "Inter", sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 11 * $chat-text-size;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
background-color: $poll-hilite-color !important;
|
||||
border: 1px solid black;
|
||||
border-radius: 21px !important;
|
||||
height: 42px !important;
|
||||
margin-top: $chat-standard-padding-xs;
|
||||
margin-bottom: $chat-standard-padding-xs;
|
||||
}
|
||||
}
|
||||
|
||||
// Creation dialog
|
||||
//
|
||||
.poll-create-dialog-content {
|
||||
|
|
|
|||
3
src/assets/icons/ic_check.svg
Normal file
3
src/assets/icons/ic_check.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="15" height="12" viewBox="0 0 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.9265 0.278549C13.6041 -0.0706278 13.0413 -0.0953257 12.6944 0.225985L4.74201 7.59645C4.66894 7.6675 4.55947 7.67068 4.48341 7.6057L2.44807 5.93997C2.20767 5.74531 1.92476 5.64018 1.62665 5.64018C1.22503 5.64018 0.844759 5.83181 0.586146 6.15932L0.321439 6.49622C-0.153089 7.10185 -0.095373 7.992 0.449243 8.52339L3.64365 11.6229C3.89315 11.8671 4.2187 12 4.55335 12C4.93661 12 5.30168 11.827 5.5603 11.521L13.9508 1.5886C14.2672 1.21487 14.2581 0.64014 13.9265 0.278521L13.9265 0.278549Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 620 B |
6
src/assets/icons/ic_poll.svg
Normal file
6
src/assets/icons/ic_poll.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="17" height="19" viewBox="0 0 17 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.31462 16.4718C3.31462 16.9496 3.70026 17.3368 4.17609 17.3368L16.1385 17.3368C16.6144 17.3368 17 16.9496 17 16.4718L16.9998 13.6229C16.9998 13.1452 16.6142 12.7579 16.1383 12.7579L4.1764 12.7579C3.70056 12.7579 3.31492 13.1452 3.31492 13.6229L3.31512 16.4718L3.31462 16.4718Z" fill="white"/>
|
||||
<path d="M3.31462 10.4557C3.31462 10.9335 3.70026 11.3208 4.17609 11.3208L11.3428 11.3208C11.8186 11.3208 12.2043 10.9335 12.2043 10.4557L12.2043 7.60711C12.2043 7.12931 11.8186 6.74208 11.3428 6.74208L4.17609 6.74208C3.70026 6.74208 3.31462 7.12932 3.31462 7.60711L3.31462 10.4557Z" fill="white"/>
|
||||
<path d="M3.31451 1.59127L3.31451 4.44011C3.31451 4.91791 3.70016 5.30514 4.17598 5.30514L6.99509 5.30514C7.47093 5.30514 7.85657 4.91791 7.85657 4.44011L7.85637 1.59127C7.85637 1.11347 7.47073 0.726242 6.9949 0.726242L4.17599 0.726242C3.70035 0.726242 3.31452 1.11348 3.31452 1.59127L3.31451 1.59127Z" fill="white"/>
|
||||
<path d="M-2.00529e-05 0.587841L-2.0791e-05 17.4747C-2.08052e-05 17.7995 0.262306 18.0625 0.585404 18.0625L1.38198 18.0625C1.70528 18.0625 1.96741 17.7995 1.96741 17.4747L1.96741 0.587841C1.96741 0.263208 1.70508 -1.14667e-08 1.38198 -2.55897e-08L0.585405 -6.04092e-08C0.261911 -7.45496e-08 -2.00387e-05 0.263213 -2.00529e-05 0.587841Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -254,6 +254,8 @@
|
|||
"poll_status_disclosed": "Results will be shown when poll is closed.",
|
||||
"poll_status_open": "Poll is open",
|
||||
"poll_status_open_not_voted": "Poll is open - vote to see the results",
|
||||
"close_poll": "Close poll"
|
||||
"close_poll": "Close poll",
|
||||
"poll_submit": "Submit",
|
||||
"num_answered": "{count} have answered"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
<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)}}
|
||||
{{ $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" />
|
||||
<div
|
||||
class="chat-content flex-grow-1 flex-shrink-1"
|
||||
ref="chatContainer"
|
||||
|
|
@ -50,32 +47,15 @@
|
|||
</div>
|
||||
|
||||
<!-- Handle resizes, e.g. when soft keyboard is shown/hidden -->
|
||||
<resize-observer
|
||||
ref="chatContainerResizer"
|
||||
@notify="handleChatContainerResize"
|
||||
/>
|
||||
<resize-observer ref="chatContainerResizer" @notify="handleChatContainerResize" />
|
||||
|
||||
<CreatedRoomWelcomeHeader
|
||||
v-if="showCreatedRoomWelcomeHeader"
|
||||
v-on:close="closeCreateRoomWelcomeHeader"
|
||||
/>
|
||||
<CreatedRoomWelcomeHeader v-if="showCreatedRoomWelcomeHeader" v-on:close="closeCreateRoomWelcomeHeader" />
|
||||
|
||||
<div
|
||||
v-for="(event, index) in events"
|
||||
:key="event.getId()"
|
||||
:eventId="event.getId()"
|
||||
>
|
||||
<div v-for="(event, index) in events" :key="event.getId()" :eventId="event.getId()">
|
||||
<!-- DAY Marker, shown for every new day in the timeline -->
|
||||
<div
|
||||
v-if="showDayMarkerBeforeEvent(event)"
|
||||
class="day-marker"
|
||||
:title="dayForEvent(event)"
|
||||
/>
|
||||
<div v-if="showDayMarkerBeforeEvent(event)" class="day-marker" :title="dayForEvent(event)" />
|
||||
|
||||
<div
|
||||
v-if="!event.isRelation() && !event.isRedacted() && !event.isRedaction()"
|
||||
:ref="event.getId()"
|
||||
>
|
||||
<div v-if="!event.isRelation() && !event.isRedacted() && !event.isRedaction()" :ref="event.getId()">
|
||||
<div
|
||||
class="message-wrapper"
|
||||
v-on:touchstart="
|
||||
|
|
@ -92,13 +72,7 @@
|
|||
:room="room"
|
||||
:event="event"
|
||||
:nextEvent="events[index + 1]"
|
||||
:reactions="
|
||||
timelineSet.getRelationsForEvent(
|
||||
event.getId(),
|
||||
'm.annotation',
|
||||
'm.reaction'
|
||||
)
|
||||
"
|
||||
:reactions="timelineSet.getRelationsForEvent(event.getId(), 'm.annotation', 'm.reaction')"
|
||||
:timelineSet="timelineSet"
|
||||
v-on:send-quick-reaction="sendQuickReaction"
|
||||
v-on:context-menu="showContextMenuForEvent($event)"
|
||||
|
|
@ -119,8 +93,8 @@
|
|||
</div>
|
||||
|
||||
<!-- Input area -->
|
||||
<v-container v-if="room" fluid :class="['input-area-outer', replyToEvent ? 'reply-to':'']">
|
||||
<div :class="[replyToEvent ? 'iput-area-inner-box':'']">
|
||||
<v-container v-if="room" fluid :class="['input-area-outer', replyToEvent ? 'reply-to' : '']">
|
||||
<div :class="[replyToEvent ? 'iput-area-inner-box' : '']">
|
||||
<!-- "Scroll to end"-button -->
|
||||
<v-btn
|
||||
class="scroll-to-end"
|
||||
|
|
@ -138,35 +112,20 @@
|
|||
<div v-if="replyToEvent" class="row">
|
||||
<div class="col">
|
||||
<div class="font-weight-medium">{{ $t("message.replying_to", { user: replyToEvent.sender.name }) }}</div>
|
||||
<div v-if="replyToContentType === 'm.text'" class="reply-text" :title="replyToEvent.getContent().body"> {{ replyToEvent.getContent().body | latestReply }} </div>
|
||||
<div v-if="replyToContentType === 'm.image'"> {{ $t("message.reply_image") }} </div>
|
||||
<div v-if="replyToContentType === 'm.audio'"> {{ $t("message.reply_audio_message") }} </div>
|
||||
<div v-if="replyToContentType === 'm.video'"> {{ $t("message.reply_video") }} </div>
|
||||
<div v-if="replyToContentType === 'm.text'" class="reply-text" :title="replyToEvent.getContent().body">
|
||||
{{ replyToEvent.getContent().body | latestReply }}
|
||||
</div>
|
||||
<div v-if="replyToContentType === 'm.image'">{{ $t("message.reply_image") }}</div>
|
||||
<div v-if="replyToContentType === 'm.audio'">{{ $t("message.reply_audio_message") }}</div>
|
||||
<div v-if="replyToContentType === 'm.video'">{{ $t("message.reply_video") }}</div>
|
||||
</div>
|
||||
<div class="col col-auto" v-if="replyToContentType !== 'm.text'">
|
||||
<img
|
||||
v-if="replyToContentType === 'm.image'"
|
||||
width="150px"
|
||||
:src="replyToImg"
|
||||
class="rounded"
|
||||
/>
|
||||
<v-img
|
||||
v-if="replyToContentType === 'm.audio'"
|
||||
src="@/assets/icons/audio_message.svg"
|
||||
/>
|
||||
<v-img
|
||||
v-if="replyToContentType === 'm.video'"
|
||||
src="@/assets/icons/video_message.svg"
|
||||
/>
|
||||
<img v-if="replyToContentType === 'm.image'" width="150px" :src="replyToImg" class="rounded" />
|
||||
<v-img v-if="replyToContentType === 'm.audio'" src="@/assets/icons/audio_message.svg" />
|
||||
<v-img v-if="replyToContentType === 'm.video'" src="@/assets/icons/video_message.svg" />
|
||||
</div>
|
||||
<div class="col col-auto">
|
||||
<v-btn
|
||||
fab
|
||||
x-small
|
||||
elevation="0"
|
||||
color="black"
|
||||
@click.stop="cancelEditReply"
|
||||
>
|
||||
<v-btn fab x-small elevation="0" color="black" @click.stop="cancelEditReply">
|
||||
<v-icon color="white">cancel</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
|
@ -199,17 +158,8 @@
|
|||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||
v-if="editedEvent"
|
||||
>
|
||||
<v-btn
|
||||
fab
|
||||
small
|
||||
elevation="0"
|
||||
color="black"
|
||||
@click.stop="cancelEditReply"
|
||||
>
|
||||
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1" v-if="editedEvent">
|
||||
<v-btn fab small elevation="0" color="black" @click.stop="cancelEditReply">
|
||||
<v-icon color="white">cancel</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
|
@ -244,10 +194,7 @@
|
|||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||
v-else
|
||||
>
|
||||
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1" v-else>
|
||||
<v-btn
|
||||
fab
|
||||
small
|
||||
|
|
@ -256,16 +203,11 @@
|
|||
@click.stop="sendCurrentTextMessage"
|
||||
:disabled="sendButtonDisabled"
|
||||
>
|
||||
<v-icon color="white">{{
|
||||
editedEvent ? "save" : "arrow_upward"
|
||||
}}</v-icon>
|
||||
<v-icon color="white">{{ editedEvent ? "save" : "arrow_upward" }}</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
v-if="$config.shortCodeStickers"
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||
>
|
||||
<v-col v-if="$config.shortCodeStickers" class="input-area-button text-center flex-grow-0 flex-shrink-1">
|
||||
<v-btn
|
||||
v-if="!showRecorder"
|
||||
id="btn-attach"
|
||||
|
|
@ -305,7 +247,7 @@
|
|||
<v-col
|
||||
v-if="!showRecorder && canCreatePoll"
|
||||
ref="sendOptions"
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1 send-options"
|
||||
>
|
||||
<v-menu close-on-click>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
|
|
@ -314,7 +256,29 @@
|
|||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item @click="showCreatePollDialog = true"><v-list-item-title>{{ $t("poll_create.create_poll_menu_option") }}</v-list-item-title></v-list-item>
|
||||
<v-list-item @click="showCreatePollDialog = true">
|
||||
<v-list-item-icon>
|
||||
<svg width="17" height="19" viewBox="0 0 17 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M3.31462 16.4718C3.31462 16.9496 3.70026 17.3368 4.17609 17.3368L16.1385 17.3368C16.6144 17.3368 17 16.9496 17 16.4718L16.9998 13.6229C16.9998 13.1452 16.6142 12.7579 16.1383 12.7579L4.1764 12.7579C3.70056 12.7579 3.31492 13.1452 3.31492 13.6229L3.31512 16.4718L3.31462 16.4718Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.31462 10.4557C3.31462 10.9335 3.70026 11.3208 4.17609 11.3208L11.3428 11.3208C11.8186 11.3208 12.2043 10.9335 12.2043 10.4557L12.2043 7.60711C12.2043 7.12931 11.8186 6.74208 11.3428 6.74208L4.17609 6.74208C3.70026 6.74208 3.31462 7.12932 3.31462 7.60711L3.31462 10.4557Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.31451 1.59127L3.31451 4.44011C3.31451 4.91791 3.70016 5.30514 4.17598 5.30514L6.99509 5.30514C7.47093 5.30514 7.85657 4.91791 7.85657 4.44011L7.85637 1.59127C7.85637 1.11347 7.47073 0.726242 6.9949 0.726242L4.17599 0.726242C3.70035 0.726242 3.31452 1.11348 3.31452 1.59127L3.31451 1.59127Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M-2.00529e-05 0.587841L-2.0791e-05 17.4747C-2.08052e-05 17.7995 0.262306 18.0625 0.585404 18.0625L1.38198 18.0625C1.70528 18.0625 1.96741 17.7995 1.96741 17.4747L1.96741 0.587841C1.96741 0.263208 1.70508 -1.14667e-08 1.38198 -2.55897e-08L0.585405 -6.04092e-08C0.261911 -7.45496e-08 -2.00387e-05 0.263213 -2.00529e-05 0.587841Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title>{{ $t("poll_create.create_poll_menu_option") }}</v-list-item-title></v-list-item
|
||||
>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-col>
|
||||
|
|
@ -330,11 +294,7 @@
|
|||
</v-container>
|
||||
|
||||
<div v-if="currentImageInputPath">
|
||||
<v-dialog
|
||||
v-model="currentImageInputPath"
|
||||
class="ma-0 pa-0"
|
||||
:width="$vuetify.breakpoint.smAndUp ? '50%' : '85%'"
|
||||
>
|
||||
<v-dialog v-model="currentImageInputPath" class="ma-0 pa-0" :width="$vuetify.breakpoint.smAndUp ? '50%' : '85%'">
|
||||
<v-card class="ma-0 pa-0">
|
||||
<v-card-text class="ma-0 pa-2">
|
||||
<v-img
|
||||
|
|
@ -346,34 +306,16 @@
|
|||
/>
|
||||
<div>
|
||||
file: {{ currentImageInputPath.name }}
|
||||
<span
|
||||
v-if="
|
||||
currentImageInput &&
|
||||
currentImageInput.scaled &&
|
||||
currentImageInput.useScaled
|
||||
"
|
||||
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
|
||||
{{ currentImageInput.scaledDimensions.width }} x {{ currentImageInput.scaledDimensions.height }}</span
|
||||
>
|
||||
{{ currentImageInput.scaledDimensions.width }} x
|
||||
{{ currentImageInput.scaledDimensions.height }}</span
|
||||
>
|
||||
<span
|
||||
v-else-if="currentImageInput && currentImageInput.dimensions"
|
||||
>
|
||||
{{ currentImageInput.dimensions.width }} x
|
||||
{{ currentImageInput.dimensions.height }}</span
|
||||
>
|
||||
<span
|
||||
v-if="
|
||||
currentImageInput &&
|
||||
currentImageInput.scaled &&
|
||||
currentImageInput.useScaled
|
||||
"
|
||||
<span v-else-if="currentImageInput && currentImageInput.dimensions">
|
||||
{{ currentImageInput.dimensions.width }} x {{ currentImageInput.dimensions.height }}</span
|
||||
>
|
||||
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
|
||||
({{ formatBytes(currentImageInput.scaledSize) }})</span
|
||||
>
|
||||
<span v-else>
|
||||
({{ formatBytes(currentImageInputPath.size) }})</span
|
||||
>
|
||||
<span v-else> ({{ formatBytes(currentImageInputPath.size) }})</span>
|
||||
<v-switch
|
||||
v-if="currentImageInput && currentImageInput.scaled"
|
||||
:label="$t('message.scale_image')"
|
||||
|
|
@ -403,33 +345,17 @@
|
|||
</v-dialog>
|
||||
</div>
|
||||
|
||||
<MessageOperationsBottomSheet
|
||||
ref="messageOperationsSheet"
|
||||
>
|
||||
<VEmojiPicker
|
||||
ref="emojiPicker"
|
||||
@select="emojiSelected"
|
||||
/>
|
||||
<MessageOperationsBottomSheet ref="messageOperationsSheet">
|
||||
<VEmojiPicker ref="emojiPicker" @select="emojiSelected" />
|
||||
</MessageOperationsBottomSheet>
|
||||
|
||||
<StickerPickerBottomSheet
|
||||
ref="stickerPickerSheet"
|
||||
v-on:selectSticker="sendSticker"
|
||||
/>
|
||||
<StickerPickerBottomSheet ref="stickerPickerSheet" v-on:selectSticker="sendSticker" />
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<v-container
|
||||
fluid
|
||||
class="loading-indicator"
|
||||
fill-height
|
||||
v-if="!initialLoadDone || loading"
|
||||
>
|
||||
<v-container fluid class="loading-indicator" fill-height v-if="!initialLoadDone || loading">
|
||||
<v-row align="center" justify="center">
|
||||
<v-col class="text-center">
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
color="primary"
|
||||
></v-progress-circular>
|
||||
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
|
@ -437,36 +363,21 @@
|
|||
<RoomInfoBottomSheet ref="roomInfoSheet" />
|
||||
|
||||
<!-- Dialog for audio recording not supported! -->
|
||||
<v-dialog
|
||||
v-model="showNoRecordingAvailableDialog"
|
||||
class="ma-0 pa-0"
|
||||
width="80%"
|
||||
>
|
||||
<v-dialog v-model="showNoRecordingAvailableDialog" class="ma-0 pa-0" width="80%">
|
||||
<v-card>
|
||||
<v-card-title>{{
|
||||
$t("voice_recorder.not_supported_title")
|
||||
}}</v-card-title>
|
||||
<v-card-text
|
||||
>{{ $t("voice_recorder.not_supported_text") }}
|
||||
</v-card-text>
|
||||
<v-card-title>{{ $t("voice_recorder.not_supported_title") }}</v-card-title>
|
||||
<v-card-text>{{ $t("voice_recorder.not_supported_text") }} </v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
id="btn-ok"
|
||||
color="primary"
|
||||
text
|
||||
@click="showNoRecordingAvailableDialog = false"
|
||||
>{{ $t("menu.ok") }}</v-btn
|
||||
>
|
||||
<v-btn id="btn-ok" color="primary" text @click="showNoRecordingAvailableDialog = false">{{
|
||||
$t("menu.ok")
|
||||
}}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<CreatePollDialog
|
||||
:show="showCreatePollDialog"
|
||||
@close="showCreatePollDialog = false"
|
||||
/>
|
||||
<CreatePollDialog :show="showCreatePollDialog" @close="showCreatePollDialog = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -532,20 +443,18 @@ function ScrollPosition(node) {
|
|||
this.readyFor = "up";
|
||||
}
|
||||
|
||||
ScrollPosition.prototype.restore = function () {
|
||||
ScrollPosition.prototype.restore = function() {
|
||||
if (this.readyFor === "up") {
|
||||
this.node.scrollTop =
|
||||
this.node.scrollHeight - this.previousScrollHeightMinusTop;
|
||||
this.node.scrollTop = this.node.scrollHeight - this.previousScrollHeightMinusTop;
|
||||
} else {
|
||||
this.node.scrollTop = this.previousScrollTop;
|
||||
}
|
||||
};
|
||||
|
||||
ScrollPosition.prototype.prepareFor = function (direction) {
|
||||
ScrollPosition.prototype.prepareFor = function(direction) {
|
||||
this.readyFor = direction || "up";
|
||||
if (this.readyFor === "up") {
|
||||
this.previousScrollHeightMinusTop =
|
||||
this.node.scrollHeight - this.node.scrollTop;
|
||||
this.previousScrollHeightMinusTop = this.node.scrollHeight - this.node.scrollTop;
|
||||
} else {
|
||||
this.previousScrollTop = this.node.scrollTop;
|
||||
}
|
||||
|
|
@ -592,7 +501,7 @@ export default {
|
|||
StickerPickerBottomSheet,
|
||||
BottomSheet,
|
||||
AvatarOperations,
|
||||
CreatePollDialog
|
||||
CreatePollDialog,
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -662,12 +571,12 @@ export default {
|
|||
|
||||
filters: {
|
||||
latestReply(contents) {
|
||||
const contentArr = contents.split('\n').reverse();
|
||||
if (contentArr[0] === '') {
|
||||
const contentArr = contents.split("\n").reverse();
|
||||
if (contentArr[0] === "") {
|
||||
contentArr.shift();
|
||||
}
|
||||
return contentArr[0].replace(/^> (<.*> )?/g, '');
|
||||
}
|
||||
return contentArr[0].replace(/^> (<.*> )?/g, "");
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
|
@ -714,10 +623,7 @@ export default {
|
|||
// If we have sent a RR, use that as read marker (so we don't have to wait for server round trip)
|
||||
return this.lastRR.getId();
|
||||
}
|
||||
return (
|
||||
this.fullyReadMarker ||
|
||||
this.room.getEventReadUpTo(this.$matrix.currentUserId, false)
|
||||
);
|
||||
return this.fullyReadMarker || this.room.getEventReadUpTo(this.$matrix.currentUserId, false);
|
||||
},
|
||||
fullyReadMarker() {
|
||||
const readEvent = this.room.getAccountData("m.fully_read");
|
||||
|
|
@ -727,11 +633,7 @@ export default {
|
|||
return null;
|
||||
},
|
||||
attachButtonDisabled() {
|
||||
return (
|
||||
this.editedEvent != null ||
|
||||
this.replyToEvent != null ||
|
||||
this.currentInput.length > 0
|
||||
);
|
||||
return this.editedEvent != null || this.replyToEvent != null || this.currentInput.length > 0;
|
||||
},
|
||||
sendButtonDisabled() {
|
||||
return this.currentInput.length == 0;
|
||||
|
|
@ -760,8 +662,7 @@ export default {
|
|||
if (ref && ref[0]) {
|
||||
if (this.showAvatarMenuAnchor) {
|
||||
var rectAnchor = this.showAvatarMenuAnchor.getBoundingClientRect();
|
||||
var rectChat =
|
||||
this.$refs.avatarOperationsStrut.getBoundingClientRect();
|
||||
var rectChat = this.$refs.avatarOperationsStrut.getBoundingClientRect();
|
||||
top = rectAnchor.top - rectChat.top;
|
||||
left = rectAnchor.left - rectChat.left;
|
||||
// if (left + 250 > rectChat.right) {
|
||||
|
|
@ -783,9 +684,10 @@ export default {
|
|||
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);
|
||||
let isAdmin = me && this.room.currentState && this.room.currentState.hasSufficientPowerLevelFor("redact", me.powerLevel);
|
||||
let isAdmin =
|
||||
me && this.room.currentState && this.room.currentState.hasSufficientPowerLevelFor("redact", me.powerLevel);
|
||||
return isAdmin;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
|
@ -795,9 +697,7 @@ export default {
|
|||
if (value && value == oldValue) {
|
||||
return; // No change.
|
||||
}
|
||||
console.log(
|
||||
"Chat: Current room changed to " + (value ? value : "null")
|
||||
);
|
||||
console.log("Chat: Current room changed to " + (value ? value : "null"));
|
||||
|
||||
// Clear old events
|
||||
this.$matrix.off("Room.timeline", this.onEvent);
|
||||
|
|
@ -839,18 +739,14 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
// Calculate where to show the context menu.
|
||||
//
|
||||
const ref =
|
||||
this.selectedEvent && this.$refs[this.selectedEvent.getId()];
|
||||
const ref = this.selectedEvent && this.$refs[this.selectedEvent.getId()];
|
||||
var top = 0;
|
||||
var left = 0;
|
||||
if (ref && ref[0]) {
|
||||
if (this.showContextMenuAnchor) {
|
||||
var rectAnchor =
|
||||
this.showContextMenuAnchor.getBoundingClientRect();
|
||||
var rectChat =
|
||||
this.$refs.messageOperationsStrut.getBoundingClientRect();
|
||||
var rectOps =
|
||||
this.$refs.messageOperations.$el.getBoundingClientRect();
|
||||
var rectAnchor = this.showContextMenuAnchor.getBoundingClientRect();
|
||||
var rectChat = this.$refs.messageOperationsStrut.getBoundingClientRect();
|
||||
var rectOps = this.$refs.messageOperations.$el.getBoundingClientRect();
|
||||
top = rectAnchor.top - rectChat.top - 50;
|
||||
left = rectAnchor.left - rectChat.left - 50;
|
||||
if (left + rectOps.width >= rectChat.right) {
|
||||
|
|
@ -868,16 +764,10 @@ export default {
|
|||
onRoomJoined(initialEventId) {
|
||||
// Was this room just created (by you)? Show a small info header in
|
||||
// that case!
|
||||
const createEvent = this.room.currentState.getStateEvents(
|
||||
"m.room.create",
|
||||
""
|
||||
);
|
||||
const createEvent = this.room.currentState.getStateEvents("m.room.create", "");
|
||||
if (createEvent) {
|
||||
const creatorId = createEvent.getContent().creator;
|
||||
if (
|
||||
creatorId == this.$matrix.currentUserId &&
|
||||
createEvent.getLocalAge() < 5 * 60000 /* 5 minutes */
|
||||
) {
|
||||
if (creatorId == this.$matrix.currentUserId && createEvent.getLocalAge() < 5 * 60000 /* 5 minutes */) {
|
||||
this.showCreatedRoomWelcomeHeader = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -890,11 +780,7 @@ export default {
|
|||
|
||||
//initialEventId = null;
|
||||
this.timelineSet = this.room.getUnfilteredTimelineSet();
|
||||
this.timelineWindow = new TimelineWindow(
|
||||
this.$matrix.matrixClient,
|
||||
this.timelineSet,
|
||||
{}
|
||||
);
|
||||
this.timelineWindow = new TimelineWindow(this.$matrix.matrixClient, this.timelineSet, {});
|
||||
const self = this;
|
||||
this.timelineWindow
|
||||
.load(initialEventId, 20)
|
||||
|
|
@ -904,21 +790,18 @@ export default {
|
|||
const getMoreIfNeeded = function _getMoreIfNeeded() {
|
||||
const container = self.$refs.chatContainer;
|
||||
if (
|
||||
container.scrollHeight <=
|
||||
(1 + 2 * WINDOW_BUFFER_SIZE) * container.clientHeight &&
|
||||
container.scrollHeight <= (1 + 2 * WINDOW_BUFFER_SIZE) * container.clientHeight &&
|
||||
self.timelineWindow &&
|
||||
self.timelineWindow.canPaginate(EventTimeline.BACKWARDS)
|
||||
) {
|
||||
return self.timelineWindow
|
||||
.paginate(EventTimeline.BACKWARDS, 10, true, 5)
|
||||
.then((success) => {
|
||||
self.events = self.timelineWindow.getEvents();
|
||||
if (success) {
|
||||
return _getMoreIfNeeded.call(self);
|
||||
} else {
|
||||
return Promise.reject("Failed to paginate");
|
||||
}
|
||||
});
|
||||
return self.timelineWindow.paginate(EventTimeline.BACKWARDS, 10, true, 5).then((success) => {
|
||||
self.events = self.timelineWindow.getEvents();
|
||||
if (success) {
|
||||
return _getMoreIfNeeded.call(self);
|
||||
} else {
|
||||
return Promise.reject("Failed to paginate");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve("Done");
|
||||
}
|
||||
|
|
@ -967,18 +850,11 @@ export default {
|
|||
},
|
||||
|
||||
scrollToEndOfTimeline() {
|
||||
if (
|
||||
this.timelineWindow &&
|
||||
this.timelineWindow.canPaginate(EventTimeline.FORWARDS)
|
||||
) {
|
||||
if (this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
|
||||
this.loading = true;
|
||||
// Instead of paging though ALL history, just reload a timeline at the live marker...
|
||||
var timelineSet = this.room.getUnfilteredTimelineSet();
|
||||
var timelineWindow = new TimelineWindow(
|
||||
this.$matrix.matrixClient,
|
||||
timelineSet,
|
||||
{}
|
||||
);
|
||||
var timelineWindow = new TimelineWindow(this.$matrix.matrixClient, timelineSet, {});
|
||||
const self = this;
|
||||
timelineWindow
|
||||
.load(null, 20)
|
||||
|
|
@ -1062,10 +938,7 @@ export default {
|
|||
switch (event.getType()) {
|
||||
case "m.room.member":
|
||||
if (event.getContent().membership == "join") {
|
||||
if (
|
||||
event.getPrevContent() &&
|
||||
event.getPrevContent().membership == "join"
|
||||
) {
|
||||
if (event.getPrevContent() && event.getPrevContent().membership == "join") {
|
||||
// We we already joined, so this must be a display name and/or avatar update!
|
||||
return ContactChanged;
|
||||
} else {
|
||||
|
|
@ -1163,14 +1036,8 @@ export default {
|
|||
|
||||
case "im.keanu.room_deletion_notice": {
|
||||
// Custom event for notice 30 seconds before a room is deleted/purged.
|
||||
const deletionNotices = this.room.currentState.getStateEvents(
|
||||
"im.keanu.room_deletion_notice"
|
||||
);
|
||||
if (
|
||||
deletionNotices &&
|
||||
deletionNotices.length > 0 &&
|
||||
deletionNotices[deletionNotices.length - 1] == event
|
||||
) {
|
||||
const deletionNotices = this.room.currentState.getStateEvents("im.keanu.room_deletion_notice");
|
||||
if (deletionNotices && deletionNotices.length > 0 && deletionNotices[deletionNotices.length - 1] == event) {
|
||||
// This is the latest/last one. Look at the status flag. Show nothing if it is "cancel".
|
||||
if (event.getContent().status != "cancel") {
|
||||
return RoomDeletionNotice;
|
||||
|
|
@ -1198,19 +1065,12 @@ export default {
|
|||
if (container.scrollTop <= bufferHeight) {
|
||||
// Scrolled to top
|
||||
this.handleScrolledToTop();
|
||||
} else if (
|
||||
container.scrollHeight -
|
||||
container.scrollTop.toFixed(0) -
|
||||
container.clientHeight <=
|
||||
bufferHeight
|
||||
) {
|
||||
} else if (container.scrollHeight - container.scrollTop.toFixed(0) - container.clientHeight <= bufferHeight) {
|
||||
this.handleScrolledToBottom(false);
|
||||
}
|
||||
this.showScrollToEnd =
|
||||
container.scrollHeight - container.scrollTop.toFixed(0) >
|
||||
container.clientHeight ||
|
||||
(this.timelineWindow &&
|
||||
this.timelineWindow.canPaginate(EventTimeline.FORWARDS));
|
||||
container.scrollHeight - container.scrollTop.toFixed(0) > container.clientHeight ||
|
||||
(this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS));
|
||||
|
||||
this.restartRRTimer();
|
||||
},
|
||||
|
|
@ -1229,10 +1089,7 @@ export default {
|
|||
// If we are at bottom, scroll to see new events...
|
||||
const container = this.$refs.chatContainer;
|
||||
var scrollToSeeNew = event.getSender() == this.$matrix.currentUserId; // When we sent, scroll
|
||||
if (
|
||||
container.scrollHeight - container.scrollTop.toFixed(0) ==
|
||||
container.clientHeight
|
||||
) {
|
||||
if (container.scrollHeight - container.scrollTop.toFixed(0) == container.clientHeight) {
|
||||
scrollToSeeNew = true;
|
||||
}
|
||||
if (this.initialLoadDone && event.forwardLooking && !event.isRelation()) {
|
||||
|
|
@ -1270,13 +1127,7 @@ export default {
|
|||
sendMessage(text) {
|
||||
if (text && text.length > 0) {
|
||||
util
|
||||
.sendTextMessage(
|
||||
this.$matrix.matrixClient,
|
||||
this.roomId,
|
||||
text,
|
||||
this.editedEvent,
|
||||
this.replyToEvent
|
||||
)
|
||||
.sendTextMessage(this.$matrix.matrixClient, this.roomId, text, this.editedEvent, this.replyToEvent)
|
||||
.then(() => {
|
||||
console.log("Sent message");
|
||||
})
|
||||
|
|
@ -1308,9 +1159,7 @@ export default {
|
|||
dimensions: null,
|
||||
};
|
||||
try {
|
||||
this.currentImageInput.dimensions = sizeOf(
|
||||
dataUriToBuffer(e.target.result)
|
||||
);
|
||||
this.currentImageInput.dimensions = sizeOf(dataUriToBuffer(e.target.result));
|
||||
|
||||
// Need to resize?
|
||||
const w = this.currentImageInput.dimensions.width;
|
||||
|
|
@ -1318,9 +1167,7 @@ export default {
|
|||
if (w > 640 || h > 640) {
|
||||
var aspect = w / h;
|
||||
var newWidth = parseInt((w > h ? 640 : 640 * aspect).toFixed());
|
||||
var newHeight = parseInt(
|
||||
(w > h ? 640 / aspect : 640).toFixed()
|
||||
);
|
||||
var newHeight = parseInt((w > h ? 640 / aspect : 640).toFixed());
|
||||
var imageResize = new ImageResize({
|
||||
format: "png",
|
||||
width: newWidth,
|
||||
|
|
@ -1366,10 +1213,10 @@ export default {
|
|||
|
||||
onUploadProgress(p) {
|
||||
if (p.total) {
|
||||
this.currentSendProgress = this.$t(
|
||||
"message.upload_progress_with_total",
|
||||
{ count: p.loaded || 0, total: p.total }
|
||||
);
|
||||
this.currentSendProgress = this.$t("message.upload_progress_with_total", {
|
||||
count: p.loaded || 0,
|
||||
total: p.total,
|
||||
});
|
||||
} else {
|
||||
this.currentSendProgress = this.$t("message.upload_progress", {
|
||||
count: p.loaded || 0,
|
||||
|
|
@ -1381,11 +1228,7 @@ export default {
|
|||
this.$refs.attachment.value = null;
|
||||
if (this.currentImageInputPath) {
|
||||
var inputFile = this.currentImageInputPath;
|
||||
if (
|
||||
this.currentImageInput &&
|
||||
this.currentImageInput.scaled &&
|
||||
this.currentImageInput.useScaled
|
||||
) {
|
||||
if (this.currentImageInput && this.currentImageInput.scaled && this.currentImageInput.useScaled) {
|
||||
// Send scaled version of image instead!
|
||||
inputFile = this.currentImageInput.scaled;
|
||||
}
|
||||
|
|
@ -1497,7 +1340,7 @@ export default {
|
|||
},
|
||||
|
||||
smoothScrollToEnd() {
|
||||
this.$nextTick(function () {
|
||||
this.$nextTick(function() {
|
||||
const container = this.$refs.chatContainer;
|
||||
if (container.children.length > 0) {
|
||||
const lastChild = container.children[container.children.length - 1];
|
||||
|
|
@ -1532,19 +1375,19 @@ export default {
|
|||
|
||||
setReplyToImage(event) {
|
||||
util
|
||||
.getThumbnail(this.$matrix.matrixClient, event)
|
||||
.then((url) => {
|
||||
this.replyToImg = url;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Failed to fetch thumbnail: ", err);
|
||||
});
|
||||
.getThumbnail(this.$matrix.matrixClient, event)
|
||||
.then((url) => {
|
||||
this.replyToImg = url;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Failed to fetch thumbnail: ", err);
|
||||
});
|
||||
},
|
||||
|
||||
addReply(event) {
|
||||
this.replyToEvent = event;
|
||||
this.$refs.messageInput.focus();
|
||||
this.replyToContentType= event.getContent().msgtype;
|
||||
this.replyToContentType = event.getContent().msgtype;
|
||||
this.setReplyToImage(event);
|
||||
},
|
||||
|
||||
|
|
@ -1576,10 +1419,10 @@ export default {
|
|||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
setTimeout(function(){
|
||||
setTimeout(function() {
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}, 200)
|
||||
}, 200);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Failed to fetch attachment: ", err);
|
||||
|
|
@ -1604,12 +1447,7 @@ export default {
|
|||
|
||||
sendQuickReaction(e) {
|
||||
util
|
||||
.sendQuickReaction(
|
||||
this.$matrix.matrixClient,
|
||||
this.roomId,
|
||||
e.reaction,
|
||||
e.event
|
||||
)
|
||||
.sendQuickReaction(this.$matrix.matrixClient, this.roomId, e.reaction, e.event)
|
||||
.then(() => {
|
||||
console.log("Quick reaction message");
|
||||
})
|
||||
|
|
@ -1651,9 +1489,7 @@ export default {
|
|||
{
|
||||
name: "Chat",
|
||||
params: {
|
||||
roomId: util.sanitizeRoomId(
|
||||
room.getCanonicalAlias() || room.roomId
|
||||
),
|
||||
roomId: util.sanitizeRoomId(room.getCanonicalAlias() || room.roomId),
|
||||
},
|
||||
},
|
||||
-1
|
||||
|
|
@ -1729,10 +1565,7 @@ export default {
|
|||
this.$matrix.matrixClient
|
||||
.sendReadReceipt(event)
|
||||
.then(() => {
|
||||
this.$matrix.matrixClient.setRoomReadMarkers(
|
||||
this.room.roomId,
|
||||
event.getId()
|
||||
);
|
||||
this.$matrix.matrixClient.setRoomReadMarkers(this.room.roomId, event.getId());
|
||||
})
|
||||
.then(() => {
|
||||
console.log("RR sent for event: " + event.getId());
|
||||
|
|
@ -1830,11 +1663,7 @@ export default {
|
|||
|
||||
onHeaderClick() {
|
||||
const joinedRooms = this.$matrix.joinedRooms;
|
||||
if (
|
||||
joinedRooms &&
|
||||
joinedRooms.length == 1 &&
|
||||
joinedRooms[0].roomId == this.room.roomId
|
||||
) {
|
||||
if (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;
|
||||
|
|
@ -1842,7 +1671,7 @@ export default {
|
|||
this.$refs.roomInfoSheet.open();
|
||||
},
|
||||
onInvitationsClick() {
|
||||
this.$navigation.push({ name: "Home" }, -1);
|
||||
this.$navigation.push({ name: "Home" }, -1);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -1850,4 +1679,4 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
@click.stop="onHeaderClicked"
|
||||
>
|
||||
<v-avatar size="40" class="me-2">
|
||||
<v-img :src="room.avatar || memberAvatar" />
|
||||
<v-img v-if="room.avatar || memberAvatar" :src="room.avatar || memberAvatar" />
|
||||
</v-avatar>
|
||||
</v-col>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@
|
|||
:value="room.roomId"
|
||||
>
|
||||
<v-list-item-avatar size="40" color="#e0e0e0">
|
||||
<v-img :src="room.avatar" />
|
||||
<v-img v-if="room.avatar" :src="room.avatar" />
|
||||
<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>
|
||||
|
|
@ -50,7 +53,10 @@
|
|||
:value="room.roomId"
|
||||
>
|
||||
<v-list-item-avatar size="40" color="#e0e0e0">
|
||||
<v-img :src="room.avatar" />
|
||||
<v-img v-if="room.avatar" :src="room.avatar" />
|
||||
<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) }}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,60 @@
|
|||
<template>
|
||||
<message-incoming v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
|
||||
<div class="bubble poll-bubble">
|
||||
{{ pollQuestion }}
|
||||
<v-container fluid>
|
||||
<div class="poll-icon">
|
||||
<svg width="17" height="19" viewBox="0 0 17 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M3.31462 16.4718C3.31462 16.9496 3.70026 17.3368 4.17609 17.3368L16.1385 17.3368C16.6144 17.3368 17 16.9496 17 16.4718L16.9998 13.6229C16.9998 13.1452 16.6142 12.7579 16.1383 12.7579L4.1764 12.7579C3.70056 12.7579 3.31492 13.1452 3.31492 13.6229L3.31512 16.4718L3.31462 16.4718Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.31462 10.4557C3.31462 10.9335 3.70026 11.3208 4.17609 11.3208L11.3428 11.3208C11.8186 11.3208 12.2043 10.9335 12.2043 10.4557L12.2043 7.60711C12.2043 7.12931 11.8186 6.74208 11.3428 6.74208L4.17609 6.74208C3.70026 6.74208 3.31462 7.12932 3.31462 7.60711L3.31462 10.4557Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.31451 1.59127L3.31451 4.44011C3.31451 4.91791 3.70016 5.30514 4.17598 5.30514L6.99509 5.30514C7.47093 5.30514 7.85657 4.91791 7.85657 4.44011L7.85637 1.59127C7.85637 1.11347 7.47073 0.726242 6.9949 0.726242L4.17599 0.726242C3.70035 0.726242 3.31452 1.11348 3.31452 1.59127L3.31451 1.59127Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M-2.00529e-05 0.587841L-2.0791e-05 17.4747C-2.08052e-05 17.7995 0.262306 18.0625 0.585404 18.0625L1.38198 18.0625C1.70528 18.0625 1.96741 17.7995 1.96741 17.4747L1.96741 0.587841C1.96741 0.263208 1.70508 -1.14667e-08 1.38198 -2.55897e-08L0.585405 -6.04092e-08C0.261911 -7.45496e-08 -2.00387e-05 0.263213 -2.00529e-05 0.587841Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="poll-question">{{ pollQuestion }}</div>
|
||||
<v-container fluid ma-0 pa-0>
|
||||
<v-row
|
||||
v-for="answer in pollAnswers"
|
||||
:key="answer.id"
|
||||
@click="pollAnswer(answer.id)"
|
||||
:class="{ 'poll-answer': true, selected: answer.hasMyVote }"
|
||||
:class="{
|
||||
'poll-answer': true,
|
||||
selected: !userHasVoted && answer.id == pollTentativeAnswer,
|
||||
result: userHasVoted || pollIsClosed,
|
||||
winner: answer.winner,
|
||||
}"
|
||||
ma-0
|
||||
pa-0
|
||||
>
|
||||
<v-col cols="auto" class="ma-0 pa-0 poll-answer-title">{{ answer.text }}</v-col>
|
||||
<v-col cols="auto" class="ma-0 pa-0 poll-answer-title">{{ answer.text }} {{ answer.max }}</v-col>
|
||||
<v-col v-if="answer.id == pollTentativeAnswer" cols="auto" class="ma-0 pa-0 poll-answer-title"
|
||||
><v-img class="poll-check-icon" src="@/assets/icons/ic_check.svg"
|
||||
/></v-col>
|
||||
<v-col
|
||||
v-if="pollIsClosed || (pollIsDisclosed && userHasVoted) || pollIsAdmin"
|
||||
cols="auto"
|
||||
class="ma-0 pa-0 poll-answer-num-votes"
|
||||
>{{ answer.numVotes }}</v-col
|
||||
>{{ answer.percentage }}%</v-col
|
||||
>
|
||||
<div v-if="pollIsClosed || (pollIsDisclosed && userHasVoted) || pollIsAdmin" class="poll-percent-indicator">
|
||||
<div class="bar" :style="{ width: `${answer.percentage}%` }"></div>
|
||||
</div>
|
||||
</v-row>
|
||||
<v-row class="poll-status">
|
||||
<v-col cols="auto" class="ma-0 pa-0 poll-status-title">{{
|
||||
<v-col cols="auto" class="ma-0 pa-0 poll-status-title">
|
||||
{{ $t("poll_create.num_answered", { count: pollNumAnswers }) }}
|
||||
</v-col>
|
||||
<!-- <v-col cols="auto" class="ma-0 pa-0 poll-status-title">{{
|
||||
pollIsClosed
|
||||
? $t("poll_create.poll_status_closed")
|
||||
: pollIsDisclosed
|
||||
|
|
@ -29,7 +62,7 @@
|
|||
? $t("poll_create.poll_status_open")
|
||||
: $t("poll_create.poll_status_open_not_voted")
|
||||
: $t("poll_create.poll_status_disclosed")
|
||||
}}</v-col>
|
||||
}}</v-col> -->
|
||||
<v-col
|
||||
cols="auto"
|
||||
class="ma-0 pa-0 poll-status-close clickable"
|
||||
|
|
@ -38,6 +71,13 @@
|
|||
>{{ $t("poll_create.close_poll") }}</v-col
|
||||
>
|
||||
</v-row>
|
||||
<v-row v-if="pollTentativeAnswer" justify="center">
|
||||
<v-col cols="auto" class="ma-0 pa-0 poll-submit">
|
||||
<v-btn @click.stop="pollAnswerSubmit">
|
||||
{{ $t("poll_create.poll_submit") }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</message-incoming>
|
||||
|
|
|
|||
|
|
@ -1,27 +1,61 @@
|
|||
<template>
|
||||
<message-outgoing v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
|
||||
<div class="bubble poll-bubble">
|
||||
{{ pollQuestion }}
|
||||
<v-container fluid>
|
||||
<div class="poll-icon">
|
||||
<svg width="17" height="19" viewBox="0 0 17 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M3.31462 16.4718C3.31462 16.9496 3.70026 17.3368 4.17609 17.3368L16.1385 17.3368C16.6144 17.3368 17 16.9496 17 16.4718L16.9998 13.6229C16.9998 13.1452 16.6142 12.7579 16.1383 12.7579L4.1764 12.7579C3.70056 12.7579 3.31492 13.1452 3.31492 13.6229L3.31512 16.4718L3.31462 16.4718Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.31462 10.4557C3.31462 10.9335 3.70026 11.3208 4.17609 11.3208L11.3428 11.3208C11.8186 11.3208 12.2043 10.9335 12.2043 10.4557L12.2043 7.60711C12.2043 7.12931 11.8186 6.74208 11.3428 6.74208L4.17609 6.74208C3.70026 6.74208 3.31462 7.12932 3.31462 7.60711L3.31462 10.4557Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.31451 1.59127L3.31451 4.44011C3.31451 4.91791 3.70016 5.30514 4.17598 5.30514L6.99509 5.30514C7.47093 5.30514 7.85657 4.91791 7.85657 4.44011L7.85637 1.59127C7.85637 1.11347 7.47073 0.726242 6.9949 0.726242L4.17599 0.726242C3.70035 0.726242 3.31452 1.11348 3.31452 1.59127L3.31451 1.59127Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M-2.00529e-05 0.587841L-2.0791e-05 17.4747C-2.08052e-05 17.7995 0.262306 18.0625 0.585404 18.0625L1.38198 18.0625C1.70528 18.0625 1.96741 17.7995 1.96741 17.4747L1.96741 0.587841C1.96741 0.263208 1.70508 -1.14667e-08 1.38198 -2.55897e-08L0.585405 -6.04092e-08C0.261911 -7.45496e-08 -2.00387e-05 0.263213 -2.00529e-05 0.587841Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="poll-question">{{ pollQuestion }}</div>
|
||||
<v-container fluid ma-0 pa-0>
|
||||
<v-row
|
||||
v-for="answer in pollAnswers"
|
||||
:key="answer.id"
|
||||
@click="pollAnswer(answer.id)"
|
||||
:class="{ 'poll-answer': true, selected: answer.hasMyVote }"
|
||||
:class="{
|
||||
'poll-answer': true,
|
||||
selected: !userHasVoted && answer.id == pollTentativeAnswer,
|
||||
result: userHasVoted || pollIsClosed,
|
||||
winner: answer.winner,
|
||||
}"
|
||||
ma-0
|
||||
pa-0
|
||||
>
|
||||
<v-col cols="auto" class="ma-0 pa-0 poll-answer-title">{{ answer.text }}</v-col>
|
||||
<v-col cols="auto" class="ma-0 pa-0 poll-answer-title">{{ answer.text }} {{ answer.max }}</v-col>
|
||||
<v-col v-if="answer.id == pollTentativeAnswer" cols="auto" class="ma-0 pa-0 poll-answer-title"
|
||||
><v-img class="poll-check-icon" src="@/assets/icons/ic_check.svg"
|
||||
/></v-col>
|
||||
<v-col
|
||||
v-if="pollIsClosed || (pollIsDisclosed && userHasVoted) || pollIsAdmin"
|
||||
cols="auto"
|
||||
class="ma-0 pa-0 poll-answer-num-votes"
|
||||
>{{ answer.numVotes }}</v-col
|
||||
>{{ answer.percentage }}%</v-col
|
||||
>
|
||||
<div v-if="pollIsClosed || (pollIsDisclosed && userHasVoted) || pollIsAdmin" class="poll-percent-indicator">
|
||||
<div class="bar" :style="{ width: `${answer.percentage}%` }"></div>
|
||||
</div>
|
||||
</v-row>
|
||||
<v-row class="poll-status">
|
||||
<v-col cols="auto" class="ma-0 pa-0 poll-status-title">{{
|
||||
<v-col cols="auto" class="ma-0 pa-0 poll-status-title">
|
||||
{{ $t("poll_create.num_answered", { count: pollNumAnswers }) }}
|
||||
</v-col>
|
||||
<!-- <v-col cols="auto" class="ma-0 pa-0 poll-status-title">{{
|
||||
pollIsClosed
|
||||
? $t("poll_create.poll_status_closed")
|
||||
: pollIsDisclosed
|
||||
|
|
@ -29,7 +63,7 @@
|
|||
? $t("poll_create.poll_status_open")
|
||||
: $t("poll_create.poll_status_open_not_voted")
|
||||
: $t("poll_create.poll_status_disclosed")
|
||||
}}</v-col>
|
||||
}}</v-col> -->
|
||||
<v-col
|
||||
cols="auto"
|
||||
class="ma-0 pa-0 poll-status-close clickable"
|
||||
|
|
@ -38,6 +72,13 @@
|
|||
>{{ $t("poll_create.close_poll") }}</v-col
|
||||
>
|
||||
</v-row>
|
||||
<v-row v-if="pollTentativeAnswer" justify="center">
|
||||
<v-col cols="auto" class="ma-0 pa-0 poll-submit">
|
||||
<v-btn @click.stop="pollAnswerSubmit">
|
||||
{{ $t("poll_create.poll_submit") }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</message-outgoing>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ export default {
|
|||
return {
|
||||
pollQuestion: "",
|
||||
pollAnswers: [],
|
||||
pollTotalVotes: 0,
|
||||
pollResponseRelations: null,
|
||||
pollEndRelations: null,
|
||||
pollEndTs: null,
|
||||
pollIsDisclosed: true,
|
||||
pollTentativeAnswer: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -93,25 +95,51 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
// Update percentage
|
||||
answerArray.forEach(a => {
|
||||
a.percentage = parseInt(((100 * a.numVotes) / totalVotes).toFixed(0));
|
||||
// Update percentages. For algorithm, see here: https://revs.runtime-revolution.com/getting-100-with-rounded-percentages-273ffa70252b
|
||||
// (need to add up to 100%)
|
||||
let a = answerArray.map(a => {
|
||||
let votes = (100 * a.numVotes) / totalVotes;
|
||||
return {
|
||||
answer: a,
|
||||
unrounded: votes,
|
||||
floor: Math.floor(votes)
|
||||
}
|
||||
});
|
||||
a.sort((item1,item2) => {
|
||||
Math.abs(item2.floor) - Math.abs(item1.floor);
|
||||
});
|
||||
var diff = 100 - a.reduce((previousValue, currentValue) => {
|
||||
return previousValue + currentValue.floor;
|
||||
}, 0);
|
||||
const maxVotes = Math.max(...a.map(item => item.unrounded));
|
||||
a.map((answer, index) => {
|
||||
answer.answer.percentage = (index < diff) ? (answer.floor + 1) : answer.floor;
|
||||
answer.answer.winner = (answer.unrounded == maxVotes);
|
||||
});
|
||||
this.pollAnswers = answerArray;
|
||||
this.pollTotalVotes = totalVotes;
|
||||
},
|
||||
pollAnswer(id) {
|
||||
if (this.pollIsClosed) {
|
||||
if (!this.userHasVoted) {
|
||||
this.pollTentativeAnswer = id;
|
||||
}
|
||||
},
|
||||
pollAnswerSubmit() {
|
||||
if (!this.pollTentativeAnswer || this.pollIsClosed) {
|
||||
return;
|
||||
}
|
||||
util
|
||||
.sendPollAnswer(
|
||||
this.$matrix.matrixClient,
|
||||
this.room.roomId,
|
||||
[id],
|
||||
[this.pollTentativeAnswer],
|
||||
this.event
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log("Failed to send:", err);
|
||||
})
|
||||
.finally(() => {
|
||||
this.pollTentativeAnswer = null;
|
||||
});
|
||||
},
|
||||
pollClose() {
|
||||
|
|
@ -148,6 +176,9 @@ export default {
|
|||
userHasVoted() {
|
||||
return this.pollAnswers.some(a => a.hasMyVote);
|
||||
},
|
||||
pollNumAnswers() {
|
||||
return this.pollTotalVotes;
|
||||
},
|
||||
pollIsAdmin() {
|
||||
// Admins can view results of not-yet-closed undisclosed polls.
|
||||
const me = this.room && this.room.getMember(this.$matrix.currentUserId);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue