Add AttachmentBatch for future removal of sendAttachmentsMixin
This commit is contained in:
parent
1d30d0633d
commit
fd82fd8840
5 changed files with 377 additions and 231 deletions
|
|
@ -45,9 +45,11 @@ $hiliteColor: #4642f1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50%;
|
height: 50%;
|
||||||
background-color: $background;
|
background-color: $background;
|
||||||
|
|
||||||
&.drop-target {
|
&.drop-target {
|
||||||
background-color: $backgroundHilite;
|
background-color: $backgroundHilite;
|
||||||
}
|
}
|
||||||
|
|
||||||
border-radius: 19px;
|
border-radius: 19px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -79,6 +81,7 @@ $hiliteColor: #4642f1;
|
||||||
height: $small-button-height !important;
|
height: $small-button-height !important;
|
||||||
margin-top: $chat-standard-padding-xs;
|
margin-top: $chat-standard-padding-xs;
|
||||||
margin-bottom: $chat-standard-padding-xs;
|
margin-bottom: $chat-standard-padding-xs;
|
||||||
|
|
||||||
&.large {
|
&.large {
|
||||||
padding: 16px 23px;
|
padding: 16px 23px;
|
||||||
height: $large-button-height;
|
height: $large-button-height;
|
||||||
|
|
@ -89,6 +92,7 @@ $hiliteColor: #4642f1;
|
||||||
textarea {
|
textarea {
|
||||||
color: rgba($text, 80%) !important;
|
color: rgba($text, 80%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea::placeholder {
|
textarea::placeholder {
|
||||||
color: rgba($text, 80%) !important;
|
color: rgba($text, 80%) !important;
|
||||||
}
|
}
|
||||||
|
|
@ -98,22 +102,28 @@ $hiliteColor: #4642f1;
|
||||||
flex: 0 0 100%;
|
flex: 0 0 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-drop-current-item {
|
.file-drop-current-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
background-color: $backgroundSection;
|
background-color: $backgroundSection;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
&.drop-target {
|
&.drop-target {
|
||||||
background-color: $backgroundHilite;
|
background-color: $backgroundHilite;
|
||||||
}
|
}
|
||||||
|
|
||||||
border-radius: 19px;
|
border-radius: 19px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.v-img {
|
.v-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filename {
|
.filename {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -158,9 +168,12 @@ $hiliteColor: #4642f1;
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide scrollbar for IE, Edge and Firefox */
|
/* Hide scrollbar for IE, Edge and Firefox */
|
||||||
-ms-overflow-style: none; /* IE and Edge */
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none; /* Firefox */
|
/* IE and Edge */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* Firefox */
|
||||||
|
|
||||||
.file-drop-thumbnail {
|
.file-drop-thumbnail {
|
||||||
width: 46px;
|
width: 46px;
|
||||||
|
|
@ -171,17 +184,21 @@ $hiliteColor: #4642f1;
|
||||||
border: 2px solid white;
|
border: 2px solid white;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.current {
|
&.current {
|
||||||
border: 2px solid #4642f1;
|
border: 2px solid #4642f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.noborder {
|
&.noborder {
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-img {
|
.v-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
|
||||||
.add,
|
.add,
|
||||||
|
|
@ -195,11 +212,13 @@ $hiliteColor: #4642f1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.v-icon {
|
.v-icon {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 15.75px;
|
height: 15.75px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove {
|
.remove {
|
||||||
// Slight background to make visible
|
// Slight background to make visible
|
||||||
background-color: rgba(black, 0.2);
|
background-color: rgba(black, 0.2);
|
||||||
|
|
@ -228,31 +247,40 @@ $hiliteColor: #4642f1;
|
||||||
border-radius: 19px;
|
border-radius: 19px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.input-area-text {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 50px;
|
|
||||||
padding: 16px 18px;
|
|
||||||
font-family: "Inter", sans-serif;
|
|
||||||
font-weight: 300;
|
|
||||||
.v-field {
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.input-container__buttons {
|
.input-container__buttons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
& > *:not(:first-child) {
|
|
||||||
|
&>*:not(:first-child) {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-area-text {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
|
||||||
|
.v-field {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fadeInStackItem {
|
@keyframes fadeInStackItem {
|
||||||
from {opacity: 0;}
|
from {
|
||||||
to {opacity: 1;}
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sending
|
// Sending
|
||||||
|
|
@ -263,49 +291,63 @@ $hiliteColor: #4642f1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.no-items {
|
.no-items {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-drop-stack-item {
|
.file-drop-stack-item {
|
||||||
transform: rotate(-4.4deg);
|
transform: rotate(-4.4deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 21 * $chat-text-size;
|
font-size: 21 * $chat-text-size;
|
||||||
font-family: "Poppins", sans-serif;
|
font-family: "Poppins",
|
||||||
|
sans-serif;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.34px;
|
letter-spacing: 0.34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.items-sent {
|
.items-sent {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
div, .v-icon {
|
|
||||||
|
div,
|
||||||
|
.v-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
.v-icon, .v-icon__component {
|
|
||||||
|
.v-icon,
|
||||||
|
.v-icon__component {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
height: 30%;
|
height: 30%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-drop-stack-item {
|
.file-drop-stack-item {
|
||||||
background: #3a3a3c;
|
background: #3a3a3c;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
.v-img {
|
.v-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.direct {
|
&.direct {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.animated {
|
&.animated {
|
||||||
animation-name: fadeInStackItem;
|
animation-name: fadeInStackItem;
|
||||||
animation-fill-mode: both;
|
animation-fill-mode: both;
|
||||||
|
|
@ -327,9 +369,12 @@ $hiliteColor: #4642f1;
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide scrollbar for IE, Edge and Firefox */
|
/* Hide scrollbar for IE, Edge and Firefox */
|
||||||
-ms-overflow-style: none; /* IE and Edge */
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none; /* Firefox */
|
/* IE and Edge */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* Firefox */
|
||||||
|
|
||||||
.file-drop-sending-item {
|
.file-drop-sending-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -339,6 +384,7 @@ $hiliteColor: #4642f1;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
|
||||||
.v-img {
|
.v-img {
|
||||||
width: $min-touch-target;
|
width: $min-touch-target;
|
||||||
height: $min-touch-target;
|
height: $min-touch-target;
|
||||||
|
|
@ -347,18 +393,22 @@ $hiliteColor: #4642f1;
|
||||||
flex: 0 0 $min-touch-target;
|
flex: 0 0 $min-touch-target;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.filename {
|
.filename {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 18px;
|
top: 18px;
|
||||||
left: 8px;
|
left: 8px;
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-progress-linear {
|
.v-progress-linear {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-drop-cancel {
|
.file-drop-cancel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
|
|
@ -379,7 +429,9 @@ $hiliteColor: #4642f1;
|
||||||
.v-progress-circular {
|
.v-progress-circular {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
background: linear-gradient(0deg, #000 0%, #000 100%), #4642f1;
|
|
||||||
|
background: linear-gradient(0deg, #000 0%, #000 100%),
|
||||||
|
#4642f1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,10 +448,12 @@ $hiliteColor: #4642f1;
|
||||||
|
|
||||||
.file-drop-sent-input-container {
|
.file-drop-sent-input-container {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
.v-btn {
|
.v-btn {
|
||||||
right: unset;
|
right: unset;
|
||||||
left: 8px;
|
left: 8px;
|
||||||
background: linear-gradient(0deg, #000 0%, #000 100%), #4642f1;
|
background: linear-gradient(0deg, #000 0%, #000 100%), #4642f1;
|
||||||
|
|
||||||
&.close {
|
&.close {
|
||||||
right: 8px;
|
right: 8px;
|
||||||
left: unset;
|
left: unset;
|
||||||
|
|
|
||||||
|
|
@ -228,90 +228,13 @@
|
||||||
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
|
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
|
||||||
accept="image/*,audio/*,video/*,.mp3,.mp4,.wav,.m4a,.pdf,application/pdf,.apk,application/vnd.android.package-archive,.ipa,.zip,application/zip,application/x-zip-compressed,multipart/x-zip" class="d-none" multiple/>
|
accept="image/*,audio/*,video/*,.mp3,.mp4,.wav,.m4a,.pdf,application/pdf,.apk,application/vnd.android.package-archive,.ipa,.zip,application/zip,application/x-zip-compressed,multipart/x-zip" class="d-none" multiple/>
|
||||||
|
|
||||||
<!-- <div v-if="currentFileInputsDialog && !useFileModeNonAdmin">
|
|
||||||
<v-dialog v-model="currentFileInputsDialog" class="ma-0 pa-0" :width="$vuetify.display.smAndUp ? '50%' : '85%'" persistent scrollable>
|
|
||||||
<v-card class="ma-0 pa-0">
|
|
||||||
<v-card-text v-if="!currentFileInputs.length">
|
|
||||||
{{ this.$t("message.preparing_to_upload")}}
|
|
||||||
<v-progress-linear
|
|
||||||
indeterminate
|
|
||||||
class="mb-0"
|
|
||||||
></v-progress-linear>
|
|
||||||
</v-card-text>
|
|
||||||
<template v-else>
|
|
||||||
<v-card-title>
|
|
||||||
<div v-if="currentSendErrorExceededFile" class="text-red">{{ currentSendErrorExceededFile }}</div>
|
|
||||||
<span v-else> {{ $t('message.send_attachements_dialog_title') }} </span>
|
|
||||||
</v-card-title>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<template v-if="imageFiles && imageFiles.length">
|
|
||||||
<v-card-title v-if="imageFiles.length > 1"> {{ $t('message.images') }} </v-card-title>
|
|
||||||
<v-card-text :class="{'ma-0 pa-2' : true, 'd-flex flex-wrap justify-center': imageFiles.length > 1}">
|
|
||||||
<div :class="{'col-4': imageFiles.length > 1}" v-for="(currentImageInput, id) in imageFiles" :key="id">
|
|
||||||
<div style="position: relative">
|
|
||||||
<v-img v-if="currentImageInput && currentImageInput.image" :aspect-ratio="1" :src="currentImageInput.image"
|
|
||||||
contain class="current-image-input-path" />
|
|
||||||
<v-progress-linear :style="{ position: 'absolute', left: '0', right: '0', bottom: '0', opacity: currentImageInput.sendInfo ? '1' : '0' }" :value="currentImageInput.sendInfo ? currentImageInput.sendInfo.progress : 0"></v-progress-linear>
|
|
||||||
<C2PABadge :file="currentImageInput.actualFile" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
|
|
||||||
{{ 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">
|
|
||||||
({{ formatBytes(currentImageInput.scaledSize) }})
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
({{ formatBytes(currentImageInput.actualSize) }})
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<v-switch v-if="currentImageInput && currentImageInput.scaled" :label="$t('message.scale_image')"
|
|
||||||
v-model="currentImageInput.useScaled" :disabled="currentImageInput && currentImageInput.sendInfo !== undefined" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</v-card-text>
|
|
||||||
</template>
|
|
||||||
<template v-if="Array.isArray(currentFileInputs) && currentFileInputs.length">
|
|
||||||
<v-card-title v-if="nonImageFiles.length > 1">{{ $t('message.files') }}</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<div v-for="(currentImageInputPath, id) in currentFileInputs" :key="id">
|
|
||||||
<div v-if="!currentImageInputPath.type.includes('image/')">
|
|
||||||
<span> {{ $t('message.file') }}: {{ currentImageInputPath.name }}</span>
|
|
||||||
<span> ({{ formatBytes(currentImageInputPath.size) }})</span>
|
|
||||||
<v-progress-linear :style="{ opacity: currentImageInputPath.sendInfo ? '1' : '0' }" :value="currentImageInputPath.sendInfo ? currentImageInputPath.sendInfo.progress : 0"></v-progress-linear>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</v-card-text>
|
|
||||||
</template>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-textarea v-if="showAttachmentCaptionInput" v-model="attachmentCaption" ref="attachmentCaption" color="black" background-color="transparent"
|
|
||||||
variant="solo" full-width auto-grow rows="1" no-resize hide-details :placeholder="$t('file_mode.add_a_message')"></v-textarea>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer>
|
|
||||||
<div v-if="currentSendError">{{ currentSendError }}</div>
|
|
||||||
</v-spacer>
|
|
||||||
<v-btn color="primary" variant="text" @click="cancelSendAttachment" id="btn-attachment-cancel" :disabled="sendingStatus != sendStatuses.SENDING && sendingStatus != sendStatuses.INITIAL">
|
|
||||||
{{ $t("menu.cancel") }}
|
|
||||||
</v-btn>
|
|
||||||
<v-btn id="btn-attachment-send" color="primary" variant="text" @click="sendAttachment(attachmentCaption)"
|
|
||||||
v-if="currentSendShowSendButton" :disabled="currentSendShowSendButton && sendingStatus != sendStatuses.INITIAL">{{ $t("menu.send") }}</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</template>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<SendAttachmentsLayout
|
<SendAttachmentsLayout
|
||||||
v-if="currentFileInputs && currentFileInputs.length > 0 && !useFileModeNonAdmin"
|
v-if="uploadBatch && uploadBatch.attachments.length > 0 && !useFileModeNonAdmin"
|
||||||
:room="room"
|
:room="room"
|
||||||
v-on:pick-file="showAttachmentPicker(false)"
|
v-on:pick-file="showAttachmentPicker(false)"
|
||||||
v-on:add-files="(files) => addAttachments(files)"
|
v-on:add-files="(files) => addAttachments(files)"
|
||||||
v-on:remove-file="(index) => removeAttachment(index)"
|
:batch="uploadBatch"
|
||||||
:attachments="currentFileInputs"
|
v-on:close="() => { uploadBatch = undefined }"
|
||||||
v-on:close="resetAttachments"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MessageOperationsBottomSheet ref="messageOperationsSheet">
|
<MessageOperationsBottomSheet ref="messageOperationsSheet">
|
||||||
|
|
@ -490,10 +413,7 @@ export default {
|
||||||
|
|
||||||
scrollPosition: null,
|
scrollPosition: null,
|
||||||
currentFileInputs: [],
|
currentFileInputs: [],
|
||||||
currentSendShowSendButton: true,
|
uploadBatch: undefined,
|
||||||
currentSendError: null,
|
|
||||||
currentSendErrorExceededFile: null,
|
|
||||||
attachmentCaption: undefined,
|
|
||||||
showEmojiPicker: false,
|
showEmojiPicker: false,
|
||||||
selectedEvent: null,
|
selectedEvent: null,
|
||||||
editedEvent: null,
|
editedEvent: null,
|
||||||
|
|
@ -615,29 +535,6 @@ export default {
|
||||||
const currentUserId= this.selectedEvent?.sender.userId || this.$matrix.currentUserId
|
const currentUserId= this.selectedEvent?.sender.userId || this.$matrix.currentUserId
|
||||||
return this.joinedAndInvitedMembers.find(({userId}) => userId === currentUserId)
|
return this.joinedAndInvitedMembers.find(({userId}) => userId === currentUserId)
|
||||||
},
|
},
|
||||||
nonImageFiles() {
|
|
||||||
return this.isCurrentFileInputsAnArray && this.currentFileInputs.filter(file => !file?.type.includes("image/"))
|
|
||||||
},
|
|
||||||
imageFiles() {
|
|
||||||
return this.isCurrentFileInputsAnArray && this.currentFileInputs.filter(file => file?.type.includes("image/"))
|
|
||||||
},
|
|
||||||
isCurrentFileInputsAnArray() {
|
|
||||||
return Array.isArray(this.currentFileInputs)
|
|
||||||
},
|
|
||||||
showAttachmentCaptionInput() {
|
|
||||||
// IFF we are sending one PDF, add option to set caption.
|
|
||||||
const imageFiles = this.imageFiles || [];
|
|
||||||
const otherFiles = this.nonImageFiles || [];
|
|
||||||
return imageFiles.length == 0 && otherFiles.length == 1 && (otherFiles[0].type === "application/pdf" || (otherFiles[0].name || "").endsWith(".pdf"));
|
|
||||||
},
|
|
||||||
currentFileInputsDialog: {
|
|
||||||
get() {
|
|
||||||
return this.isCurrentFileInputsAnArray
|
|
||||||
},
|
|
||||||
set() {
|
|
||||||
this.currentFileInputs = [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chatContainer() {
|
chatContainer() {
|
||||||
const container = this.$refs.chatContainer;
|
const container = this.$refs.chatContainer;
|
||||||
if (this.useVoiceMode) {
|
if (this.useVoiceMode) {
|
||||||
|
|
@ -1493,32 +1390,15 @@ export default {
|
||||||
this.$refs.attachment.click();
|
this.$refs.attachment.click();
|
||||||
},
|
},
|
||||||
|
|
||||||
async addAttachment(file) {
|
addAttachment(file) {
|
||||||
if (file) {
|
if (file) {
|
||||||
this.currentFileInputs = [... this.currentFileInputs, this.$matrix.attachmentManager.createAttachment(file)];
|
if (!this.uploadBatch) {
|
||||||
// let optimizedFileObj;
|
this.uploadBatch = this.$matrix.attachmentManager.createUpload(this.room);
|
||||||
// if (file.type.startsWith("image/")) {
|
}
|
||||||
// const f = await proofmode.proofCheckFile(file);
|
this.uploadBatch?.addAttachment(this.$matrix.attachmentManager.createAttachment(file));
|
||||||
|
|
||||||
// var reader = new FileReader();
|
|
||||||
// optimizedFileObj = await new Promise(resolve => {
|
|
||||||
// reader.onload = evt => {
|
|
||||||
// resolve(this.optimizeImage(evt, file));
|
|
||||||
// }
|
|
||||||
// reader.readAsDataURL(f);
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
// optimizedFileObj = file;
|
|
||||||
// }
|
|
||||||
// console.error("OPTIMIZED", optimizedFileObj);
|
|
||||||
// this.currentFileInputs = Array.isArray(this.currentFileInputs) ? [...this.currentFileInputs, optimizedFileObj] : [optimizedFileObj];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeAttachment(index) {
|
|
||||||
this.currentFileInputs = this.currentFileInputs.toSpliced(index, 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle picked attachment
|
* Handle picked attachment
|
||||||
*/
|
*/
|
||||||
|
|
@ -1527,24 +1407,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
addAttachments(files) {
|
addAttachments(files) {
|
||||||
// TODO - refactor
|
files.forEach(file => this.addAttachment(file));
|
||||||
this.$matrix.matrixClient.getMediaConfig(this.$matrix.useAuthedMedia).then((config) => {
|
|
||||||
const configUploadSize = config["m.upload.size"];
|
|
||||||
const configFormattedUploadSize = this.formatBytes(configUploadSize);
|
|
||||||
|
|
||||||
files.every(file => {
|
|
||||||
if (configUploadSize && file.size > configUploadSize) {
|
|
||||||
this.currentSendError = this.$t("message.upload_file_too_large");
|
|
||||||
this.currentSendErrorExceededFile = this.$t("message.upload_exceeded_file_limit", { configFormattedUploadSize });
|
|
||||||
this.currentSendShowSendButton = false;
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
this.currentSendShowSendButton = true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
files.forEach(file => this.addAttachment(file));
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
showStickerPicker() {
|
showStickerPicker() {
|
||||||
|
|
@ -1559,7 +1422,6 @@ export default {
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
this.sendingAttachments = [];
|
this.sendingAttachments = [];
|
||||||
this.currentFileInputs = [];
|
this.currentFileInputs = [];
|
||||||
this.attachmentCaption = undefined;
|
|
||||||
this.sendingStatus = "initial"
|
this.sendingStatus = "initial"
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
@ -1580,7 +1442,6 @@ export default {
|
||||||
this.cancelSendAttachments();
|
this.cancelSendAttachments();
|
||||||
}
|
}
|
||||||
this.currentFileInputs = [];
|
this.currentFileInputs = [];
|
||||||
this.attachmentCaption = undefined;
|
|
||||||
this.currentSendError = null;
|
this.currentSendError = null;
|
||||||
this.currentSendErrorExceededFile = null;
|
this.currentSendErrorExceededFile = null;
|
||||||
this.sendingStatus = "initial";
|
this.sendingStatus = "initial";
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="send-attachments__title">{{ $t("message.send_attachements_dialog_title") }}</div>
|
<div class="send-attachments__title">{{ $t("message.send_attachements_dialog_title") }}</div>
|
||||||
|
|
||||||
<!-- ATTACHMENT SELECTION MODE -->
|
<!-- ATTACHMENT SELECTION MODE -->
|
||||||
<template v-if="attachments && attachments.length > 0 && status == mainStatuses.SELECTING">
|
<template v-if="batch.attachments.length > 0 && status == mainStatuses.SELECTING">
|
||||||
<div class="attachment-wrapper" ref="attachmentWrapper" v-if="currentAttachment">
|
<div class="attachment-wrapper" ref="attachmentWrapper" v-if="currentAttachment">
|
||||||
<div
|
<div
|
||||||
:class="{ 'file-drop-current-item': true, 'drop-target': dropTarget }"
|
:class="{ 'file-drop-current-item': true, 'drop-target': dropTarget }"
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="send-attachments__current-item__info">
|
<!-- <div class="send-attachments__current-item__info">
|
||||||
<div class="send-attachments__current-item__info__size">
|
<div class="send-attachments__current-item__info__size">
|
||||||
<span
|
<span
|
||||||
v-if="currentAttachment.scaledFile && currentAttachment.useScaled && currentAttachment.scaledDimensions"
|
v-if="currentAttachment.scaledFile && currentAttachment.useScaled && currentAttachment.scaledDimensions"
|
||||||
|
|
@ -38,7 +38,10 @@
|
||||||
</span>
|
</span>
|
||||||
<span v-else> ({{ formatBytes(currentAttachment.file.size) }}) </span>
|
<span v-else> ({{ formatBytes(currentAttachment.file.size) }}) </span>
|
||||||
|
|
||||||
<span class="send-attachments__current-item__info__size__filename" v-if="currentAttachment.src && currentAttachment.file.name">
|
<span
|
||||||
|
class="send-attachments__current-item__info__size__filename"
|
||||||
|
v-if="currentAttachment.src && currentAttachment.file.name"
|
||||||
|
>
|
||||||
- {{ currentAttachment.file.name }}
|
- {{ currentAttachment.file.name }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -51,17 +54,21 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<C2PABadge :proof="currentAttachment.proof" />
|
<C2PABadge :proof="currentAttachment.proof" />
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<div class="file-drop-thumbnail-container">
|
<div class="file-drop-thumbnail-container">
|
||||||
<div
|
<div
|
||||||
:class="{ 'file-drop-thumbnail': true, clickable: true, current: id == currentItemIndex }"
|
:class="{ 'file-drop-thumbnail': true, clickable: true, current: id == currentItemIndex }"
|
||||||
@click="currentItemIndex = id"
|
@click="currentItemIndex = id"
|
||||||
v-for="(currentImageInput, id) in attachments"
|
v-for="(currentImageInput, id) in batch.attachments"
|
||||||
:key="id"
|
:key="id"
|
||||||
>
|
>
|
||||||
<v-img v-if="currentImageInput && currentImageInput.src" :src="currentImageInput.src" />
|
<v-img v-if="currentImageInput && currentImageInput.src" :src="currentImageInput.src" />
|
||||||
<div v-if="currentItemIndex == id" class="remove clickable" @click.stop="$emit('remove-file', id)">
|
<div
|
||||||
|
v-if="currentItemIndex == id"
|
||||||
|
class="remove clickable"
|
||||||
|
@click.stop="batch.removeAttachment(currentImageInput)"
|
||||||
|
>
|
||||||
<v-icon>$vuetify.icons.ic_trash</v-icon>
|
<v-icon>$vuetify.icons.ic_trash</v-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -91,25 +98,23 @@
|
||||||
/>
|
/>
|
||||||
<div class="input-container__buttons">
|
<div class="input-container__buttons">
|
||||||
<v-btn @click="close">{{ $t("menu.cancel") }}</v-btn>
|
<v-btn @click="close">{{ $t("menu.cancel") }}</v-btn>
|
||||||
<v-btn @click="sendAll" :disabled="!attachments || attachments.length == 0">{{ $t("menu.send") }}</v-btn>
|
<v-btn @click="sendAll" :disabled="batch.attachments.length == 0">{{ $t("menu.send") }}</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- ATTACHMENT SENDING/SENT MODE -->
|
<!-- ATTACHMENT SENDING/SENT MODE -->
|
||||||
<template
|
<template v-if="batch.attachments.length > 0 && (status == mainStatuses.SENDING || status == mainStatuses.SENT)">
|
||||||
v-if="attachments && attachments.length > 0 && (status == mainStatuses.SENDING || status == mainStatuses.SENT)"
|
|
||||||
>
|
|
||||||
<div class="attachment-wrapper">
|
<div class="attachment-wrapper">
|
||||||
<div class="file-drop-sent-stack" ref="stackContainer">
|
<div class="file-drop-sent-stack" ref="stackContainer">
|
||||||
<div v-if="status == mainStatuses.SENDING && attachmentsSentCount == 0" class="no-items">
|
<div v-if="status == mainStatuses.SENDING && batch.attachmentsSentCount == 0" class="no-items">
|
||||||
<div class="file-drop-stack-item direct" :style="stackItemTransform(null, -1)"></div>
|
<div class="file-drop-stack-item direct" :style="stackItemTransform(null, -1)"></div>
|
||||||
<div>{{ $t("file_mode.sending_progress") }}</div>
|
<div>{{ $t("file_mode.sending_progress") }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
v-for="(info, index) in attachmentsSent"
|
v-for="(info, index) in batch.attachmentsSent"
|
||||||
:key="info.file.name"
|
:key="info.file.name"
|
||||||
class="file-drop-stack-item animated"
|
class="file-drop-stack-item animated"
|
||||||
:style="stackItemTransform(info, index)"
|
:style="stackItemTransform(info, index)"
|
||||||
|
|
@ -123,11 +128,11 @@
|
||||||
|
|
||||||
<!-- Middle section -->
|
<!-- Middle section -->
|
||||||
<div v-if="status == mainStatuses.SENDING" class="file-drop-sending-container">
|
<div v-if="status == mainStatuses.SENDING" class="file-drop-sending-container">
|
||||||
<div class="file-drop-sending-item" v-for="(attachment) in attachmentsSending" :key="attachment.file.name">
|
<div class="file-drop-sending-item" v-for="attachment in batch.attachmentsSending" :key="attachment.file.name">
|
||||||
<v-img v-if="attachment.src" :src="attachment.src" />
|
<v-img v-if="attachment.src" :src="attachment.src" />
|
||||||
<div v-else class="filename">{{ attachment.file.name }}</div>
|
<div v-else class="filename">{{ attachment.file.name }}</div>
|
||||||
<v-progress-linear :model-value="attachment.sendInfo?.progress ?? 0"></v-progress-linear>
|
<v-progress-linear :model-value="attachment.sendInfo?.progress ?? 0"></v-progress-linear>
|
||||||
<div class="file-drop-cancel clickable" @click.stop="cancelSendAttachmentItem(attachment)">
|
<div class="file-drop-cancel clickable" @click.stop="batch.cancelSendAttachment(attachment)">
|
||||||
<v-icon size="14" color="white">close</v-icon>
|
<v-icon size="14" color="white">close</v-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -136,10 +141,8 @@
|
||||||
<div class="file-drop-files-sent">
|
<div class="file-drop-files-sent">
|
||||||
{{
|
{{
|
||||||
$t(
|
$t(
|
||||||
messageInput && messageInput.length > 0
|
messageInput && messageInput.length > 0 ? "file_mode.files_sent_with_note" : "file_mode.files_sent",
|
||||||
? "file_mode.files_sent_with_note"
|
batch.attachmentsSent.length
|
||||||
: "file_mode.files_sent",
|
|
||||||
attachmentsSent.length
|
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -192,21 +195,19 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import messageMixin from "../messages/messageMixin";
|
import messageMixin from "../messages/messageMixin";
|
||||||
import sendAttachmentsMixin from "../sendAttachmentsMixin";
|
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
import type { PropType } from "vue";
|
|
||||||
import { Attachment } from "../../models/attachment";
|
import { Attachment } from "../../models/attachment";
|
||||||
import C2PABadge from "../c2pa/C2PABadge.vue";
|
import C2PABadge from "../c2pa/C2PABadge.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [messageMixin, sendAttachmentsMixin],
|
mixins: [messageMixin],
|
||||||
components: { C2PABadge },
|
components: { C2PABadge },
|
||||||
emits: ["add-files", "remove-file", "pick-file", "close"],
|
emits: ["add-files", "pick-file", "close"],
|
||||||
props: {
|
props: {
|
||||||
attachments: {
|
batch: {
|
||||||
type: Array as PropType<Attachment[]>,
|
type: Object,
|
||||||
default: function () {
|
default: function () {
|
||||||
return [] as Attachment[];
|
return {}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -227,25 +228,31 @@ export default defineComponent({
|
||||||
this.$audioPlayer.setAutoplay(false);
|
this.$audioPlayer.setAutoplay(false);
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentAttachment(): Attachment {
|
currentAttachment(): Attachment | undefined {
|
||||||
return this.attachments[this.currentItemIndex];
|
if (this.currentItemIndex >= 0 && this.currentItemIndex < this.batch.attachments.length) {
|
||||||
|
return this.batch.attachments[this.currentItemIndex];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
},
|
},
|
||||||
currentItemHasImagePreview() {
|
currentItemHasImagePreview() {
|
||||||
return (
|
return (
|
||||||
this.currentItemIndex >= 0 &&
|
this.currentItemIndex >= 0 &&
|
||||||
this.currentItemIndex < this.attachments.length &&
|
this.currentItemIndex < this.batch.attachments.length &&
|
||||||
this.attachments[this.currentItemIndex].src
|
this.batch.attachments[this.currentItemIndex].src
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
attachments(newValue, oldValue) {
|
"batch.attachments": {
|
||||||
// Added or removed?
|
handler(newValue, oldValue) {
|
||||||
if (newValue && oldValue && newValue.length > oldValue.length) {
|
// Added or removed?
|
||||||
this.currentItemIndex = oldValue.length;
|
if (newValue && oldValue && newValue.length > oldValue.length) {
|
||||||
} else if (newValue) {
|
this.currentItemIndex = oldValue.length;
|
||||||
this.currentItemIndex = newValue.length - 1;
|
} else if (newValue) {
|
||||||
}
|
this.currentItemIndex = newValue.length - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
},
|
},
|
||||||
messageInput() {
|
messageInput() {
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
|
|
@ -256,7 +263,7 @@ export default defineComponent({
|
||||||
this.dropTarget = false;
|
this.dropTarget = false;
|
||||||
let droppedFiles: FileList | undefined = e.dataTransfer?.files;
|
let droppedFiles: FileList | undefined = e.dataTransfer?.files;
|
||||||
if (!droppedFiles) return;
|
if (!droppedFiles) return;
|
||||||
this.$emit("add-files", [... droppedFiles]);
|
this.$emit("add-files", [...droppedFiles]);
|
||||||
},
|
},
|
||||||
scrollToBottom() {
|
scrollToBottom() {
|
||||||
const el = this.$refs.attachmentWrapper;
|
const el = this.$refs.attachmentWrapper;
|
||||||
|
|
@ -276,7 +283,7 @@ export default defineComponent({
|
||||||
return prettyBytes(bytes);
|
return prettyBytes(bytes);
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.sendingAttachments = [];
|
this.batch.cancel();
|
||||||
this.status = this.mainStatuses.SELECTING;
|
this.status = this.mainStatuses.SELECTING;
|
||||||
this.messageInput = "";
|
this.messageInput = "";
|
||||||
this.currentItemIndex = 0;
|
this.currentItemIndex = 0;
|
||||||
|
|
@ -284,28 +291,27 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
sendAll() {
|
sendAll() {
|
||||||
this.status = this.mainStatuses.SENDING;
|
this.status = this.mainStatuses.SENDING;
|
||||||
this.sendAttachments(
|
this.batch.send(
|
||||||
this.messageInput && this.messageInput.length > 0 ? this.messageInput : this.$t("file_mode.files"),
|
this.messageInput && this.messageInput.length > 0 ? this.messageInput : this.$t("file_mode.files")
|
||||||
this.attachments
|
|
||||||
).then(() => {
|
).then(() => {
|
||||||
this.status = this.mainStatuses.SENT;
|
this.status = this.mainStatuses.SENT;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
stackItemTransform(item, index) {
|
stackItemTransform(attachment: Attachment, index: number) {
|
||||||
const size =
|
const size =
|
||||||
0.6 *
|
0.6 *
|
||||||
(this.$refs.stackContainer
|
(this.$refs.stackContainer
|
||||||
? Math.min(this.$refs.stackContainer.clientWidth, this.$refs.stackContainer.clientHeight)
|
? Math.min(this.$refs.stackContainer.clientWidth, this.$refs.stackContainer.clientHeight)
|
||||||
: 176);
|
: 176);
|
||||||
let transform = "";
|
let transform = "";
|
||||||
if (item != null && index != -1) {
|
if (attachment != null && attachment.sendInfo && index != -1) {
|
||||||
transform =
|
transform =
|
||||||
"transform: rotate(" +
|
"transform: rotate(" +
|
||||||
item.randomRotation +
|
attachment.sendInfo.randomRotation +
|
||||||
"deg) translate(" +
|
"deg) translate(" +
|
||||||
item.randomTranslationX +
|
attachment.sendInfo.randomTranslationX +
|
||||||
"px," +
|
"px," +
|
||||||
item.randomTranslationY +
|
attachment.sendInfo.randomTranslationY +
|
||||||
"px); z-index:" +
|
"px); z-index:" +
|
||||||
(index + 2) +
|
(index + 2) +
|
||||||
";";
|
";";
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { ComputedRef, Ref } from "vue";
|
||||||
|
|
||||||
export class UploadPromise<Type> {
|
export class UploadPromise<Type> {
|
||||||
wrappedPromise: Promise<Type>;
|
wrappedPromise: Promise<Type>;
|
||||||
aborted: boolean = false;
|
aborted: boolean = false;
|
||||||
|
|
@ -49,3 +51,19 @@ export type Attachment = {
|
||||||
proof?: any;
|
proof?: any;
|
||||||
sendInfo?: AttachmentSendInfo;
|
sendInfo?: AttachmentSendInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AttachmentBatch = {
|
||||||
|
sendingStatus: Ref<"initial" | "sending" | "sent" | "canceled" | "failed">;
|
||||||
|
sendingRootEventId: Ref<string | undefined>;
|
||||||
|
sendingPromise: Ref<Promise<any> | undefined>;
|
||||||
|
attachments: Ref<Attachment[]>;
|
||||||
|
attachmentsSentCount: ComputedRef<number>;
|
||||||
|
attachmentsSending: ComputedRef<Attachment[]>;
|
||||||
|
attachmentsSent: ComputedRef<Attachment[]>;
|
||||||
|
addAttachment: (attachment: Attachment) => void;
|
||||||
|
removeAttachment: (attachment: Attachment) => void;
|
||||||
|
send: (message: string) => Promise<any>;
|
||||||
|
cancel: () => void;
|
||||||
|
cancelSendAttachment: (attachment: Attachment) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk";
|
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk";
|
||||||
import { EventAttachment, KeanuEventExtension } from "./eventAttachment";
|
import { EventAttachment, KeanuEventExtension } from "./eventAttachment";
|
||||||
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||||
import { Counter, ModeOfOperation } from "aes-js";
|
import { Counter, ModeOfOperation } from "aes-js";
|
||||||
import { Attachment } from "./attachment";
|
import { Attachment, AttachmentBatch, AttachmentSendInfo } from "./attachment";
|
||||||
import proofmode from "../plugins/proofmode";
|
import proofmode from "../plugins/proofmode";
|
||||||
import imageSize from "image-size";
|
import imageSize from "image-size";
|
||||||
import imageResize from "image-resize";
|
import imageResize from "image-resize";
|
||||||
import { Reactive, reactive } from "vue";
|
import { computed, isRef, Reactive, reactive, ref, Ref } from "vue";
|
||||||
|
import utils from "@/plugins/utils";
|
||||||
|
|
||||||
export class AttachmentManager {
|
export class AttachmentManager {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
|
@ -33,6 +34,10 @@ export class AttachmentManager {
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public createUpload(room: Room) {
|
||||||
|
return createUploadBatch(this.matrixClient, room);
|
||||||
|
}
|
||||||
|
|
||||||
public createAttachment(file: File): Attachment {
|
public createAttachment(file: File): Attachment {
|
||||||
let a: Attachment = {
|
let a: Attachment = {
|
||||||
status: "loading",
|
status: "loading",
|
||||||
|
|
@ -133,7 +138,7 @@ export class AttachmentManager {
|
||||||
attachment.src = src;
|
attachment.src = src;
|
||||||
return src;
|
return src;
|
||||||
});
|
});
|
||||||
return attachment.srcPromise;
|
return attachment.srcPromise;
|
||||||
};
|
};
|
||||||
attachment.loadThumbnail = () => {
|
attachment.loadThumbnail = () => {
|
||||||
if (attachment.thumbnail) {
|
if (attachment.thumbnail) {
|
||||||
|
|
@ -141,13 +146,13 @@ export class AttachmentManager {
|
||||||
} else if (attachment.thumbnailPromise) {
|
} else if (attachment.thumbnailPromise) {
|
||||||
return attachment.thumbnailPromise;
|
return attachment.thumbnailPromise;
|
||||||
}
|
}
|
||||||
attachment.thumbnailPromise = this._loadEventAttachmentOrThumbnail(event, true, (percent) => {
|
attachment.thumbnailPromise = this._loadEventAttachmentOrThumbnail(event, true, (percent) => {
|
||||||
attachment.thumbnailProgress = percent;
|
attachment.thumbnailProgress = percent;
|
||||||
}).then((thummbnail) => {
|
}).then((thummbnail) => {
|
||||||
attachment.thumbnail = thummbnail;
|
attachment.thumbnail = thummbnail;
|
||||||
return thummbnail;
|
return thummbnail;
|
||||||
});
|
});
|
||||||
return attachment.thumbnailPromise;
|
return attachment.thumbnailPromise;
|
||||||
};
|
};
|
||||||
attachment.release = (src: boolean, thumbnail: boolean) => {
|
attachment.release = (src: boolean, thumbnail: boolean) => {
|
||||||
// TODO - figure out logic
|
// TODO - figure out logic
|
||||||
|
|
@ -161,7 +166,7 @@ export class AttachmentManager {
|
||||||
URL.revokeObjectURL(attachment.thumbnail);
|
URL.revokeObjectURL(attachment.thumbnail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
this.cache.set(event.getId(), attachment!);
|
this.cache.set(event.getId(), attachment!);
|
||||||
return attachment;
|
return attachment;
|
||||||
}
|
}
|
||||||
|
|
@ -292,3 +297,205 @@ export class AttachmentManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createUploadBatch = (matrixClient: MatrixClient, room: Room): AttachmentBatch => {
|
||||||
|
const sendingStatus: Ref<"initial" | "sending" | "sent" | "canceled" | "failed"> = ref("initial");
|
||||||
|
const sendingRootEventId: Ref<string | undefined> = ref(undefined);
|
||||||
|
const sendingPromise: Ref<Promise<any> | undefined> = ref(undefined);
|
||||||
|
const attachments: Ref<Attachment[]> = ref([]);
|
||||||
|
|
||||||
|
const attachmentsSentCount = computed(() => {
|
||||||
|
return attachments.value.reduce((a, elem) => (elem.sendInfo?.status == "sent" ? a + 1 : a), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const attachmentsSending = computed(() => {
|
||||||
|
return attachments.value.filter((elem) => elem.sendInfo?.status == "initial" || elem.sendInfo?.status == "sending");
|
||||||
|
});
|
||||||
|
|
||||||
|
const attachmentsSent = computed(() => {
|
||||||
|
sortSendingAttachments();
|
||||||
|
return attachments.value.filter((elem) => elem.sendInfo?.status == "sent");
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortSendingAttachments = () => {
|
||||||
|
attachments.value.sort((a, b) => (b.sendInfo?.statusDate ?? 0) - (a.sendInfo?.statusDate ?? 0));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addAttachment = (attachment: Attachment) => {
|
||||||
|
if (sendingStatus.value == "initial") {
|
||||||
|
attachments.value.push(attachment);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeAttachment = (attachment: Attachment) => {
|
||||||
|
if (sendingStatus.value == "initial") {
|
||||||
|
attachments.value = attachments.value.filter((a) => a !== attachment);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
if (sendingStatus.value !== "initial") {
|
||||||
|
attachments.value.toReversed().forEach((attachment) => {
|
||||||
|
cancelSendAttachment(attachment);
|
||||||
|
});
|
||||||
|
sendingStatus.value = "canceled";
|
||||||
|
if (sendingRootEventId.value) {
|
||||||
|
// Redact all media we already sent, plus the root event
|
||||||
|
let promises = attachments.value.reduce((val: Promise<any>[], attachment: Attachment) => {
|
||||||
|
if (attachment.sendInfo?.mediaEventId) {
|
||||||
|
val.push(
|
||||||
|
matrixClient.redactEvent(room.roomId, attachment.sendInfo!.mediaEventId, undefined, {
|
||||||
|
reason: "cancel",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}, [] as Promise<any>[]);
|
||||||
|
if (sendingRootEventId.value) {
|
||||||
|
promises.push(
|
||||||
|
matrixClient.redactEvent(room.roomId, sendingRootEventId.value, undefined, {
|
||||||
|
reason: "cancel",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Promise.allSettled(promises)
|
||||||
|
.then(() => {
|
||||||
|
console.log("Message redacted");
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log("Redaction failed: ", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelSendAttachment = (attachment: Attachment) => {
|
||||||
|
if (attachment.sendInfo) {
|
||||||
|
if (attachment.sendInfo.promise && attachment.sendInfo.status != "initial") {
|
||||||
|
attachment.sendInfo.promise.abort();
|
||||||
|
}
|
||||||
|
attachment.sendInfo.status = "canceled";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const send = (message: string): Promise<any> => {
|
||||||
|
sendingStatus.value = "sending";
|
||||||
|
attachments.value.forEach((attachment) => {
|
||||||
|
let sendInfo: AttachmentSendInfo = {
|
||||||
|
status: "initial",
|
||||||
|
statusDate: Date.now(),
|
||||||
|
mediaEventId: undefined,
|
||||||
|
progress: 0,
|
||||||
|
randomRotation: 0,
|
||||||
|
randomTranslationX: 0,
|
||||||
|
randomTranslationY: 0,
|
||||||
|
promise: undefined,
|
||||||
|
};
|
||||||
|
attachment.sendInfo = reactive(sendInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendingPromise = utils
|
||||||
|
.sendTextMessage(matrixClient, room.roomId, message)
|
||||||
|
.then((eventId: string) => {
|
||||||
|
sendingRootEventId.value = eventId;
|
||||||
|
|
||||||
|
// Use the eventId as a thread root for all the media
|
||||||
|
let promiseChain = Promise.resolve();
|
||||||
|
const getItemPromise = (index: number) => {
|
||||||
|
if (index < attachments.value.length) {
|
||||||
|
const attachment = attachments.value[index];
|
||||||
|
const item = attachment.sendInfo!;
|
||||||
|
if (item.status !== "initial") {
|
||||||
|
return getItemPromise(++index);
|
||||||
|
}
|
||||||
|
item.status = "sending";
|
||||||
|
|
||||||
|
let file = (() => {
|
||||||
|
if (attachment.scaledFile && attachment.useScaled) {
|
||||||
|
// Send scaled version of image instead!
|
||||||
|
return attachment.scaledFile;
|
||||||
|
} else {
|
||||||
|
// Send actual file image when not scaled!
|
||||||
|
return attachment.file;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const itemPromise = utils
|
||||||
|
.sendFile(
|
||||||
|
matrixClient,
|
||||||
|
room.roomId,
|
||||||
|
file,
|
||||||
|
({ loaded, total }: { loaded: number; total: number }) => {
|
||||||
|
if (loaded == total) {
|
||||||
|
item.progress = 100;
|
||||||
|
} else if (total > 0) {
|
||||||
|
item.progress = (100 * loaded) / total;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
eventId,
|
||||||
|
attachment.dimensions
|
||||||
|
)
|
||||||
|
.then((mediaEventId: string) => {
|
||||||
|
// Look at last item rotation, flipping the sign on this, so looks more like a true stack
|
||||||
|
let signR = 1;
|
||||||
|
let signX = 1;
|
||||||
|
let signY = 1;
|
||||||
|
if (attachmentsSent.value.length > 0) {
|
||||||
|
if (attachmentsSent.value[0].sendInfo!.randomRotation >= 0) {
|
||||||
|
signR = -1;
|
||||||
|
}
|
||||||
|
if (attachmentsSent.value[0].sendInfo!.randomTranslationX >= 0) {
|
||||||
|
signX = -1;
|
||||||
|
}
|
||||||
|
if (attachmentsSent.value[0].sendInfo!.randomTranslationY >= 0) {
|
||||||
|
signY = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.randomRotation = signR * (2 + Math.random() * 10);
|
||||||
|
item.randomTranslationX = signX * Math.random() * 20;
|
||||||
|
item.randomTranslationY = signY * Math.random() * 20;
|
||||||
|
item.mediaEventId = mediaEventId;
|
||||||
|
item.status = "sent";
|
||||||
|
item.statusDate = Date.now();
|
||||||
|
})
|
||||||
|
.catch((ignorederr: any) => {
|
||||||
|
if (item.promise?.aborted) {
|
||||||
|
item.status = "canceled";
|
||||||
|
} else {
|
||||||
|
console.error("ERROR", ignorederr);
|
||||||
|
item.status = "failed";
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
item.promise = itemPromise;
|
||||||
|
return itemPromise.then(() => getItemPromise(++index));
|
||||||
|
} else return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
return promiseChain.then(() => getItemPromise(0));
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
sendingStatus.value = "sent";
|
||||||
|
sendingRootEventId.value = undefined;
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.error("ERROR", err);
|
||||||
|
});
|
||||||
|
return sendingPromise;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
sendingStatus,
|
||||||
|
sendingRootEventId,
|
||||||
|
sendingPromise,
|
||||||
|
attachments,
|
||||||
|
attachmentsSentCount,
|
||||||
|
attachmentsSending,
|
||||||
|
attachmentsSent,
|
||||||
|
addAttachment,
|
||||||
|
removeAttachment,
|
||||||
|
send,
|
||||||
|
cancel,
|
||||||
|
cancelSendAttachment,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue