diff --git a/src/assets/icons/ic_arrow_narrow.vue b/src/assets/icons/ic_arrow_narrow.vue
new file mode 100644
index 0000000..99bdca5
--- /dev/null
+++ b/src/assets/icons/ic_arrow_narrow.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/assets/translations/en.json b/src/assets/translations/en.json
index c0cde6d..ab3763e 100644
--- a/src/assets/translations/en.json
+++ b/src/assets/translations/en.json
@@ -21,7 +21,8 @@
"close": "close",
"notify": "Notify",
"different_browser_title": "Try different browser",
- "different_browser_content": "Some features may break. Copy and open link in a different browser."
+ "different_browser_content": "Some features may break. Copy and open link in a different browser.",
+ "retry": "Retry"
},
"menu": {
"start_private_chat": "Direct Message with this user",
diff --git a/src/components/file_mode/SendAttachmentsLayout.vue b/src/components/file_mode/SendAttachmentsLayout.vue
index 668545c..db446fd 100644
--- a/src/components/file_mode/SendAttachmentsLayout.vue
+++ b/src/components/file_mode/SendAttachmentsLayout.vue
@@ -22,7 +22,7 @@
@dragleave.prevent="dropTarget = false"
@dragenter.prevent="dropTarget = true"
>
-
+
diff --git a/src/components/file_mode/ThumbnailView.vue b/src/components/file_mode/ThumbnailView.vue
index f6e83f2..93d03c6 100644
--- a/src/components/file_mode/ThumbnailView.vue
+++ b/src/components/file_mode/ThumbnailView.vue
@@ -29,48 +29,58 @@ import { EventAttachment } from "../../models/eventAttachment";
import { useThumbnail } from "../messages/composition/useThumbnail";
import { Attachment } from "../../models/attachment";
import ImageWithProgress from "../ImageWithProgress.vue";
+
+function isEventAttachment(source: EventAttachment | Attachment | undefined): source is EventAttachment {
+ return (source as EventAttachment).event !== undefined;
+}
+
+function isAttachment(source: EventAttachment | Attachment | undefined): source is Attachment {
+ return (source as Attachment).file !== undefined;
+}
const $$sanitize: any = inject("globalSanitize");
const thumbnailRef = useTemplateRef("thumbnailRef");
interface ThumbnailProps {
- item?: EventAttachment;
- file?: Attachment;
+ item?: EventAttachment | Attachment;
previewOnly?: boolean;
}
type ThumbnailEmits = {
- (event: "itemclick", value: { item: EventAttachment }): void;
+ (event: "itemclick", value: { item: EventAttachment | Attachment }): void;
};
const props = defineProps
();
-const { item, file, previewOnly = false } = props;
+const { item, previewOnly = false } = props;
const emits = defineEmits();
-let { isVideo, isImage, fileTypeIcon, fileTypeIconClass, fileName, fileSize } = useThumbnail(file?.file ?? item?.event);
+let { isVideo, isImage, fileTypeIcon, fileTypeIconClass, fileName, fileSize } = useThumbnail(isEventAttachment(props.item) ? props.item.event : isAttachment(props.item) ? props.item.file : undefined);
+
const fileURL: Ref = ref(undefined);
const source: Ref = ref(undefined);
const poster: Ref = ref(undefined);
const updateSource = () => {
- if (props.item) {
- if (isVideo.value || props.item.src) {
- source.value = props.item.src;
+ if (isEventAttachment(props.item)) {
+ const eventAttachment = props.item;
+ if (isVideo.value || eventAttachment.src) {
+ source.value = eventAttachment.src;
} else if (previewOnly) {
- props.item.loadThumbnail().then((url) => {
+ eventAttachment.loadThumbnail().then((url) => {
source.value = url.data;
})
} else if (isImage.value) {
- props.item.loadSrc().then((url) => {
+ eventAttachment.loadSrc().then((url) => {
source.value = url.data;
})
}
- } else if (props.file) {
+ } else if (isAttachment(props.item)) {
+ const attachment = props.item;
if (fileURL.value) {
URL.revokeObjectURL(fileURL.value);
}
- fileURL.value = URL.createObjectURL(props.file.file);
+ fileURL.value = URL.createObjectURL(attachment.file);
source.value = fileURL.value;
} else {
source.value = undefined;
@@ -78,11 +88,12 @@ const updateSource = () => {
};
const updatePoster = () => {
- if (props.item && isVideo.value) {
- if (props.item.thumbnail) {
- poster.value = props.item.thumbnail;
+ if (isEventAttachment(props.item) && isVideo.value) {
+ const eventAttachment = props.item;
+ if (eventAttachment.thumbnail) {
+ poster.value = eventAttachment.thumbnail;
} else {
- props.item
+ eventAttachment
.loadThumbnail()
.then((url) => {
poster.value = url.data;
@@ -99,7 +110,7 @@ updatePoster();
watch(props, (props: ThumbnailProps) => {
- const updates = useThumbnail(props.file?.file ?? props.item?.event);
+ const updates = useThumbnail(isEventAttachment(props.item) ? props.item.event : isAttachment(props.item) ? props.item.file : undefined);
isVideo.value = updates.isVideo.value;
isImage.value = updates.isImage.value;
fileTypeIcon = updates.fileTypeIcon;
@@ -111,23 +122,24 @@ watch(props, (props: ThumbnailProps) => {
});
const loadingProgress = computed(() => {
- if (item) {
- return previewOnly ? item.thumbnailProgress : item.srcProgress;
+ if (isEventAttachment(item)) {
+ const eventAttachment = item;
+ return previewOnly ? eventAttachment.thumbnailProgress : eventAttachment.srcProgress;
}
return -1;
});
onMounted(() => {
- if (thumbnailRef.value && (item as EventAttachment)) {
+ if (thumbnailRef.value && item) {
const hammerInstance = singleOrDoubleTapRecognizer(thumbnailRef.value);
hammerInstance.on("singletap doubletap", (ev: any) => {
if (ev.type === "singletap") {
- emits("itemclick", { item: item as EventAttachment });
+ emits("itemclick", { item: item });
}
});
}
- if (!previewOnly && item && (item as EventAttachment)) {
- (item as EventAttachment).loadSrc();
+ if (!previewOnly && item && isEventAttachment(item)) {
+ item.loadSrc();
}
});
diff --git a/src/components/messages/composition/MessageThreadSending.vue b/src/components/messages/composition/MessageThreadSending.vue
index c73154c..39463b1 100644
--- a/src/components/messages/composition/MessageThreadSending.vue
+++ b/src/components/messages/composition/MessageThreadSending.vue
@@ -1,5 +1,5 @@
-
+
{{ inReplyToSender }}
@@ -20,11 +20,15 @@
close {{ uploadBatch?.progressPercent }}%
+
+
$vuetify.icons.ic_arrow_narrow
+
{{ t("global.retry") }}
+
-
+
@@ -83,6 +87,12 @@ const cancelUpload = () => {
}
}
+const retryUpload = () => {
+ if (uploadBatch.value) {
+ uploadBatch.value.send(uploadBatch.value.sendingRootMessage.value ?? "");
+ }
+}
+
const onItemClick = (event: any) => {
showItem.value = event.item;
};
diff --git a/src/models/attachment.ts b/src/models/attachment.ts
index e80b2c4..1513c22 100644
--- a/src/models/attachment.ts
+++ b/src/models/attachment.ts
@@ -39,7 +39,6 @@ export type AttachmentSendInfo = {
randomRotation: number; // For UI effects
randomTranslationX: number; // For UI effects
randomTranslationY: number; // For UI effects
- proofHintFlags?: ProofHintFlags;
};
export type AttachmentThumbnail = {
@@ -59,6 +58,7 @@ export type Attachment = {
useScaled: boolean;
src?: string;
proof?: Proof;
+ proofHintFlags?: ProofHintFlags;
thumbnail?: AttachmentThumbnail;
sendInfo: AttachmentSendInfo;
};
@@ -66,6 +66,7 @@ export type Attachment = {
export type AttachmentBatch = {
sendingStatus: Ref
;
sendingRootEventId: Ref;
+ sendingRootMessage: Ref;
progressPercent: Ref;
attachments: Attachment[];
attachmentsSentCount: ComputedRef;
diff --git a/src/models/attachmentManager.ts b/src/models/attachmentManager.ts
index 752f9b5..1324846 100644
--- a/src/models/attachmentManager.ts
+++ b/src/models/attachmentManager.ts
@@ -119,6 +119,7 @@ export class AttachmentManager {
}
try {
attachment.proof = await proofmode.proofCheckFile(file);
+ attachment.proofHintFlags = extractProofHintFlags(attachment.proof);
// Default to scaled version if the image does not contain Content Credentials
//
@@ -420,6 +421,7 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room
const sendingStatus: Ref = ref("initial");
const sendingRootEventId: Ref = ref(undefined);
+ const sendingRootMessage: Ref = ref(undefined);
const sendingPromise: Ref | undefined> = ref(undefined);
const attachments: Ref = ref([]);
@@ -528,6 +530,8 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room
const send = async (message: string): Promise => {
if (!matrixClient || !room) return Promise.reject("Not configured");
sendingStatus.value = "sending";
+ progressPercent.value = 0;
+ sendingRootMessage.value = message;
attachments.value.forEach((attachment) => {
let sendInfo: AttachmentSendInfo = {
@@ -539,7 +543,6 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room
randomTranslationX: 0,
randomTranslationY: 0,
promise: undefined,
- proofHintFlags: extractProofHintFlags(attachment.proof),
};
attachment.sendInfo = shallowReactive(sendInfo);
attachment.proof = undefined;
@@ -555,6 +558,7 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room
event.uploadBatch = {
sendingStatus,
sendingRootEventId,
+ sendingRootMessage,
progressPercent,
attachments: unref(attachments),
attachmentsSentCount,
@@ -572,8 +576,7 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room
};
room.on(RoomEvent.LocalEchoUpdated, onLocalEchoUpdated);
- const promise = utils
- .sendTextMessage(matrixClient, room.roomId, message, undefined, undefined, txnId)
+ const promise = (sendingRootEventId.value ? Promise.resolve(sendingRootEventId.value) : utils.sendTextMessage(matrixClient, room.roomId, message, undefined, undefined, txnId))
.then((eventId: string) => {
sendingRootEventId.value = eventId;
@@ -582,7 +585,7 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room
if (index < attachments.value.length) {
const attachment = attachments.value[index];
const item = attachment;
- if (item.sendInfo.status !== "initial") {
+ if (item.sendInfo.status !== "initial" && item.sendInfo.status !== "failed") {
return getItemPromise(++index);
}
item.sendInfo.status = "sending";
@@ -637,12 +640,13 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room
item.sendInfo.status = "sent";
item.sendInfo.statusDate = Date.now();
})
- .catch((ignorederr: any) => {
+ .catch((error: any) => {
if (item.sendInfo.promise?.aborted) {
item.sendInfo.status = "canceled";
} else {
- console.error("ERROR", ignorederr);
+ console.error("ERROR", error);
item.sendInfo.status = "failed";
+ return Promise.reject(error)
}
return Promise.resolve();
});
@@ -659,6 +663,7 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room
})
.catch((err: any) => {
console.error("Upload error", err);
+ sendingStatus.value = "failed";
})
.finally(() => {
room.off(RoomEvent.LocalEchoUpdated, onLocalEchoUpdated);
@@ -670,6 +675,7 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room
return {
sendingStatus,
sendingRootEventId,
+ sendingRootMessage,
progressPercent,
attachments: unref(attachments),
attachmentsSentCount,