Finish unification of attachment sending

This commit is contained in:
N-Pex 2025-06-11 18:04:56 +02:00
parent fd82fd8840
commit a92d767fc2
11 changed files with 246 additions and 1100 deletions

View file

@ -1,170 +1,139 @@
<template>
<div v-bind="{ ...$attrs }" class="send-attachments">
<div class="send-attachments__title">{{ $t("message.send_attachements_dialog_title") }}</div>
<v-btn
v-if="showBackButton"
class="back-button clickable"
icon="arrow_back"
size="default"
elevation="0"
color="black"
@click.stop="close"
:disabled="backButtonDisabled"
></v-btn>
<!-- ATTACHMENT SELECTION MODE -->
<template v-if="batch.attachments.length > 0 && status == mainStatuses.SELECTING">
<div class="attachment-wrapper" ref="attachmentWrapper" v-if="currentAttachment">
<div
:class="{ 'file-drop-current-item': true, 'drop-target': dropTarget }"
@drop.prevent="filesDropped"
@dragover.prevent="dropTarget = true"
@dragleave.prevent="dropTarget = false"
@dragenter.prevent="dropTarget = true"
>
<v-img v-if="currentAttachment.src && currentAttachment.status === 'loaded'" :src="currentAttachment.src" />
<div v-else class="filename">
<div>{{ currentAttachment.file.name }}</div>
<div v-if="currentAttachment.status === 'loading'" style="font-size: 0.7em; opacity: 0.7">
{{ $t("message.preparing_to_upload") }}
<v-progress-linear indeterminate class="mb-0"></v-progress-linear>
</div>
<template v-if="status == mainStatuses.SELECTING">
<div
:class="{ 'send-attachments__selecting__current-item': true, 'drop-target': dropTarget }"
@drop.prevent="filesDropped"
@dragover.prevent="dropTarget = true"
@dragleave.prevent="dropTarget = false"
@dragenter.prevent="dropTarget = true"
>
<v-img
v-if="currentAttachment && currentAttachment.src && currentAttachment.status === 'loaded'"
:src="currentAttachment.src"
/>
<div v-else-if="currentAttachment" class="filename">
<div>{{ currentAttachment.file.name }}</div>
<div v-if="currentAttachment.status === 'loading'" style="font-size: 0.7em; opacity: 0.7">
{{ $t("message.preparing_to_upload") }}
<v-progress-linear indeterminate class="mb-0"></v-progress-linear>
</div>
</div>
<!-- <div class="send-attachments__current-item__info">
<div class="send-attachments__current-item__info__size">
<span
v-if="currentAttachment.scaledFile && currentAttachment.useScaled && currentAttachment.scaledDimensions"
>
{{ currentAttachment.scaledDimensions.width }} x {{ currentAttachment.scaledDimensions.height }}</span
>
<span v-else-if="currentAttachment.dimensions">
{{ currentAttachment.dimensions.width }} x {{ currentAttachment.dimensions.height }}
</span>
<span v-if="currentAttachment.scaledFile && currentAttachment.useScaled">
({{ formatBytes(currentAttachment.scaledFile.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"
>
- {{ currentAttachment.file.name }}
</span>
</div>
<v-switch
v-if="currentAttachment.scaledFile"
:label="$t('message.scale_image')"
v-model="currentAttachment.useScaled"
:disabled="currentAttachment.sendInfo !== undefined"
/>
<C2PABadge :proof="currentAttachment.proof" />
</div> -->
<div class="file-drop-thumbnail-container">
<div
:class="{ 'file-drop-thumbnail': true, clickable: true, current: id == currentItemIndex }"
@click="currentItemIndex = id"
v-for="(currentImageInput, id) in batch.attachments"
:key="id"
>
<v-img v-if="currentImageInput && currentImageInput.src" :src="currentImageInput.src" />
<div
v-if="currentItemIndex == id"
class="remove clickable"
@click.stop="batch.removeAttachment(currentImageInput)"
>
<v-icon>$vuetify.icons.ic_trash</v-icon>
</div>
</div>
<div class="file-drop-thumbnail noborder">
<div class="add clickable" @click.stop="$emit('pick-file')">+</div>
</div>
</div>
<div class="file-drop-input-container">
<v-textarea
ref="input"
full-width
variant="solo"
flat
auto-grow
v-model="messageInput"
no-resize
class="input-area-text"
rows="1"
:placeholder="$t('file_mode.add_a_message')"
hide-details
background-color="transparent"
v-on:keydown.enter.prevent="
() => {
sendAll();
}
"
/>
<div class="input-container__buttons">
<v-btn @click="close">{{ $t("menu.cancel") }}</v-btn>
<v-btn @click="sendAll" :disabled="batch.attachments.length == 0">{{ $t("menu.send") }}</v-btn>
</div>
</div>
<div class="file-drop-thumbnail-container">
<v-tooltip location="top" v-for="(attachment, index) in batch.attachments" :key="index">
<template v-slot:activator="{ props }">
<v-badge :model-value="batch.isTooLarge(attachment)" color="error">
<template v-slot:badge
><span v-bind="props">&nbsp;</span></template
>
<div
:class="{ 'file-drop-thumbnail': true, clickable: true, current: index == currentItemIndex }"
@click="currentItemIndex = index"
>
<v-img v-if="attachment && attachment.src" :src="attachment.src" />
<div
v-if="currentItemIndex == index"
class="remove clickable"
@click.stop="batch.removeAttachment(attachment)"
>
<v-icon>$vuetify.icons.ic_trash</v-icon>
</div>
</div>
</v-badge>
</template>
<span>{{ $t("message.upload_file_too_large") }}</span>
</v-tooltip>
<div class="file-drop-thumbnail noborder">
<div class="add clickable" @click.stop="$emit('pick-file')">+</div>
</div>
</div>
<div class="file-drop-input-container">
<v-textarea
ref="input"
full-width
variant="solo"
flat
auto-grow
v-model="messageInput"
no-resize
class="input-area-text"
rows="1"
:placeholder="$t('file_mode.add_a_message')"
hide-details
background-color="transparent"
v-on:keydown.enter.prevent="
() => {
sendAll();
}
"
/>
<v-btn
class="send-button clickable"
icon="arrow_upward"
size="default"
elevation="0"
color="black"
@click.stop="sendAll"
:disabled="sendButtonDisabled"
></v-btn>
</div>
</template>
<!-- ATTACHMENT SENDING/SENT MODE -->
<template v-if="batch.attachments.length > 0 && (status == mainStatuses.SENDING || status == mainStatuses.SENT)">
<div class="attachment-wrapper">
<div class="file-drop-sent-stack" ref="stackContainer">
<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>{{ $t("file_mode.sending_progress") }}</div>
</div>
<div
v-else
v-for="(info, index) in batch.attachmentsSent"
:key="info.file.name"
class="file-drop-stack-item animated"
:style="stackItemTransform(info, index)"
>
<v-img v-if="info.src" :src="info.src" />
</div>
<div v-if="status == mainStatuses.SENT" class="items-sent" :style="stackItemTransform(null, -1)">
<v-icon>$vuetify.icons.ic_check_circle</v-icon>
</div>
<div class="file-drop-sent-stack" ref="stackContainer">
<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>{{ $t("file_mode.sending_progress") }}</div>
</div>
<div
v-else
v-for="(info, index) in batch.attachmentsSent"
:key="info.file.name"
class="file-drop-stack-item animated"
:style="stackItemTransform(info, index)"
>
<v-img v-if="info.src" :src="info.src" />
</div>
<div v-if="status == mainStatuses.SENT" class="items-sent" :style="stackItemTransform(null, -1)">
<v-icon>$vuetify.icons.ic_check_circle</v-icon>
</div>
</div>
<!-- Middle section -->
<div v-if="status == mainStatuses.SENDING" class="file-drop-sending-container">
<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" />
<div v-else class="filename">{{ attachment.file.name }}</div>
<v-progress-linear :model-value="attachment.sendInfo?.progress ?? 0"></v-progress-linear>
<div class="file-drop-cancel clickable" @click.stop="batch.cancelSendAttachment(attachment)">
<v-icon size="14" color="white">close</v-icon>
</div>
<!-- Middle section -->
<div v-if="status == mainStatuses.SENDING" class="file-drop-sending-container">
<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" />
<div v-else class="filename">{{ attachment.file.name }}</div>
<v-progress-linear :model-value="attachment.sendInfo?.progress ?? 0"></v-progress-linear>
<div class="file-drop-cancel clickable" @click.stop="batch.cancelSendAttachment(attachment)">
<v-icon size="14" color="white">close</v-icon>
</div>
</div>
<div v-else-if="status == mainStatuses.SENT" class="file-drop-sending-container">
<div class="file-drop-files-sent">
{{
$t(
messageInput && messageInput.length > 0 ? "file_mode.files_sent_with_note" : "file_mode.files_sent",
batch.attachmentsSent.length
)
}}
</div>
<div class="file-drop-section">
<v-textarea
disabled
full-width
variant="solo"
flat
auto-grow
v-model="messageInput"
no-resize
class="input-area-text"
rows="1"
hide-details
background-color="transparent"
/>
</div>
</div>
<div v-else-if="status == mainStatuses.SENT" class="file-drop-sending-container">
<div class="file-drop-files-sent">
{{
$t(
messageInput && messageInput.length > 0 ? "file_mode.files_sent_with_note" : "file_mode.files_sent",
batch.attachmentsSent.length
)
}}
</div>
<!-- Bottom section -->
<div v-if="status == mainStatuses.SENDING" class="file-drop-sending-input-container">
<div class="file-drop-section">
<v-textarea
disabled
full-width
@ -175,39 +144,63 @@
no-resize
class="input-area-text"
rows="1"
:placeholder="$t('file_mode.add_a_message')"
hide-details
background-color="transparent"
/>
<v-btn
>{{ $t("file_mode.sending")
}}<v-progress-circular indeterminate size="18" width="2" color="#4642F1"></v-progress-circular
></v-btn>
</div>
<div v-else-if="status == mainStatuses.SENT" class="file-drop-sent-input-container">
<v-btn class="close" @click.stop="close">{{ $t("file_mode.close") }}</v-btn>
</div>
</div>
<!-- Bottom section -->
<div v-if="status == mainStatuses.SENDING" class="file-drop-sending-input-container">
<v-textarea
disabled
full-width
variant="solo"
flat
auto-grow
v-model="messageInput"
no-resize
class="input-area-text"
rows="1"
:placeholder="$t('file_mode.add_a_message')"
hide-details
background-color="transparent"
/>
<v-btn
>{{ $t("file_mode.sending")
}}<v-progress-circular indeterminate size="18" width="2" color="#4642F1"></v-progress-circular
></v-btn>
</div>
<div v-else-if="status == mainStatuses.SENT" class="file-drop-sent-input-container">
<v-btn class="close" @click.stop="close">{{ $t("file_mode.close") }}</v-btn>
</div>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { defineComponent, reactive } from "vue";
import messageMixin from "../messages/messageMixin";
import prettyBytes from "pretty-bytes";
import { Attachment } from "../../models/attachment";
import C2PABadge from "../c2pa/C2PABadge.vue";
import { createUploadBatch } from "../../models/attachmentManager";
export default defineComponent({
mixins: [messageMixin],
components: { C2PABadge },
emits: ["add-files", "pick-file", "close"],
emits: ["pick-file", "close"],
props: {
showBackButton: {
type: Boolean,
default: function () {
return true;
},
},
batch: {
type: Object,
default: function () {
return {}
return reactive(createUploadBatch(null, null, 0))
},
},
},
@ -228,6 +221,12 @@ export default defineComponent({
this.$audioPlayer.setAutoplay(false);
},
computed: {
backButtonDisabled() {
return this.status == this.mainStatuses.SENDING;
},
sendButtonDisabled() {
return !this.batch.canSend;
},
currentAttachment(): Attachment | undefined {
if (this.currentItemIndex >= 0 && this.currentItemIndex < this.batch.attachments.length) {
return this.batch.attachments[this.currentItemIndex];
@ -254,29 +253,15 @@ export default defineComponent({
},
deep: true,
},
messageInput() {
this.scrollToBottom();
},
},
methods: {
filesDropped(e: DragEvent) {
this.dropTarget = false;
let droppedFiles: FileList | undefined = e.dataTransfer?.files;
if (!droppedFiles) return;
this.$emit("add-files", [...droppedFiles]);
},
scrollToBottom() {
const el = this.$refs.attachmentWrapper;
if (el) {
// Ugly - need to wait until input is auto-sized, THEN scroll to bottom.
//
this.$nextTick(() => {
this.$nextTick(() => {
this.$nextTick(() => {
el.scrollTop = el.scrollHeight;
});
});
});
for (let i = 0; i < droppedFiles.length; i++) {
const file = droppedFiles.item(i);
this.batch.addAttachment(this.$matrix.attachmentManager.createAttachment(file));
}
},
formatBytes(bytes: number) {
@ -291,11 +276,11 @@ export default defineComponent({
},
sendAll() {
this.status = this.mainStatuses.SENDING;
this.batch.send(
this.messageInput && this.messageInput.length > 0 ? this.messageInput : this.$t("file_mode.files")
).then(() => {
this.status = this.mainStatuses.SENT;
});
this.batch
.send(this.messageInput && this.messageInput.length > 0 ? this.messageInput : this.$t("file_mode.files"))
.then(() => {
this.status = this.mainStatuses.SENT;
});
},
stackItemTransform(attachment: Attachment, index: number) {
const size =