Send progress in main view
This commit is contained in:
parent
41232fbd90
commit
e9accdebf1
11 changed files with 465 additions and 86 deletions
|
|
@ -52,6 +52,7 @@ import ReadMarker from "./messages/ReadMarker.vue";
|
||||||
import RoomTombstone from "./messages/composition/RoomTombstone.vue";
|
import RoomTombstone from "./messages/composition/RoomTombstone.vue";
|
||||||
import roomDisplayOptionsMixin from "./roomDisplayOptionsMixin";
|
import roomDisplayOptionsMixin from "./roomDisplayOptionsMixin";
|
||||||
import roomTypeMixin from "./roomTypeMixin";
|
import roomTypeMixin from "./roomTypeMixin";
|
||||||
|
import MessageThreadSending from "./messages/composition/MessageThreadSending.vue";
|
||||||
|
|
||||||
export const ROOM_READ_MARKER_EVENT_PLACEHOLDER = { getId: () => "ROOM_READ_MARKER", getTs: () => Date.now() };
|
export const ROOM_READ_MARKER_EVENT_PLACEHOLDER = { getId: () => "ROOM_READ_MARKER", getTs: () => Date.now() };
|
||||||
|
|
||||||
|
|
@ -225,6 +226,9 @@ export default {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (!isForExport && event.uploadBatch) {
|
||||||
|
return MessageThreadSending;
|
||||||
|
}
|
||||||
if (event.isMxThread) {
|
if (event.isMxThread) {
|
||||||
// Outgoing thread
|
// Outgoing thread
|
||||||
return isForExport ? MessageThreadExport : MessageThread;
|
return isForExport ? MessageThreadExport : MessageThread;
|
||||||
|
|
|
||||||
|
|
@ -245,8 +245,8 @@ export default defineComponent({
|
||||||
|
|
||||||
batch: {
|
batch: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: function () {
|
default: () => {
|
||||||
return reactive(createUploadBatch(null, null, 0));
|
return createUploadBatch(null, null);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -294,7 +294,7 @@ export default defineComponent({
|
||||||
this.currentItemIndex = newValue.length - 1;
|
this.currentItemIndex = newValue.length - 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -323,6 +323,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
sendAll() {
|
sendAll() {
|
||||||
this.status = this.mainStatuses.SENDING;
|
this.status = this.mainStatuses.SENDING;
|
||||||
|
this.$emit("close");
|
||||||
this.batch
|
this.batch
|
||||||
.send(this.messageInput && this.messageInput.length > 0 ? this.messageInput : this.defaultRootMessageText)
|
.send(this.messageInput && this.messageInput.length > 0 ? this.messageInput : this.defaultRootMessageText)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import utils from "../../plugins/utils";
|
import { singleOrDoubleTapRecognizer } from "../../plugins/touch";
|
||||||
import { computed, inject, onBeforeUnmount, onMounted, Ref, ref, useTemplateRef, watch } from "vue";
|
import { computed, inject, onBeforeUnmount, onMounted, Ref, ref, useTemplateRef, watch } from "vue";
|
||||||
import { EventAttachment } from "../../models/eventAttachment";
|
import { EventAttachment } from "../../models/eventAttachment";
|
||||||
import { useThumbnail } from "../messages/composition/useThumbnail";
|
import { useThumbnail } from "../messages/composition/useThumbnail";
|
||||||
|
|
@ -119,7 +119,7 @@ const loadingProgress = computed(() => {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (thumbnailRef.value && (item as EventAttachment)) {
|
if (thumbnailRef.value && (item as EventAttachment)) {
|
||||||
const hammerInstance = utils.singleOrDoubleTabRecognizer(thumbnailRef.value);
|
const hammerInstance = singleOrDoubleTapRecognizer(thumbnailRef.value);
|
||||||
hammerInstance.on("singletap doubletap", (ev: any) => {
|
hammerInstance.on("singletap doubletap", (ev: any) => {
|
||||||
if (ev.type === "singletap") {
|
if (ev.type === "singletap") {
|
||||||
emits("itemclick", { item: item as EventAttachment });
|
emits("itemclick", { item: item as EventAttachment });
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { singleOrDoubleTapRecognizer } from "@/plugins/touch";
|
||||||
import { computed, inject, onMounted, ref, useTemplateRef, watch } from "vue";
|
import { computed, inject, onMounted, ref, useTemplateRef, watch } from "vue";
|
||||||
import MessageIncoming from "./MessageIncoming.vue";
|
import MessageIncoming from "./MessageIncoming.vue";
|
||||||
import MessageOutgoing from "./MessageOutgoing.vue";
|
import MessageOutgoing from "./MessageOutgoing.vue";
|
||||||
|
|
@ -30,7 +31,6 @@ import { useI18n } from "vue-i18n";
|
||||||
import { MessageProps, useMessage } from "./useMessage";
|
import { MessageProps, useMessage } from "./useMessage";
|
||||||
import { EventAttachment } from "../../../models/eventAttachment";
|
import { EventAttachment } from "../../../models/eventAttachment";
|
||||||
import { useDisplay } from "vuetify";
|
import { useDisplay } from "vuetify";
|
||||||
import utils from "@/plugins/utils";
|
|
||||||
import Hammer from "hammerjs";
|
import Hammer from "hammerjs";
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
@ -80,7 +80,7 @@ onMounted(() => {
|
||||||
contain.value = true;
|
contain.value = true;
|
||||||
}
|
}
|
||||||
if (imageRef.value) {
|
if (imageRef.value) {
|
||||||
const hammerInstance = utils.singleOrDoubleTabRecognizer(imageRef.value);
|
const hammerInstance = singleOrDoubleTapRecognizer(imageRef.value);
|
||||||
hammerInstance.on("singletap doubletap", (ev: Hammer.HammerInput) => {
|
hammerInstance.on("singletap doubletap", (ev: Hammer.HammerInput) => {
|
||||||
if (ev.type === "singletap") {
|
if (ev.type === "singletap") {
|
||||||
attachment.value?.loadSrc();
|
attachment.value?.loadSrc();
|
||||||
|
|
|
||||||
194
src/components/messages/composition/MessageThreadSending.vue
Normal file
194
src/components/messages/composition/MessageThreadSending.vue
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
<template>
|
||||||
|
<MessageOutgoing :is="rootComponent" ref="root" v-bind="{ ...$props, ...$attrs }">
|
||||||
|
<div class="bubble">
|
||||||
|
<div class="original-message" v-if="inReplyToText">
|
||||||
|
<div class="original-message-sender">{{ inReplyToSender }}</div>
|
||||||
|
<div class="original-message-text" v-html="linkify($$sanitize(inReplyToText))" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-upload-progress" v-if="unref(uploadBatch?.sendingStatus) === 'sending'">
|
||||||
|
<v-progress-circular
|
||||||
|
class="message-upload-progress__progress clickable"
|
||||||
|
:model-value="unref(uploadBatch?.progressPercent ?? 0)"
|
||||||
|
color="white"
|
||||||
|
bg-color="rgba(255,255,255,0.5)"
|
||||||
|
width="2"
|
||||||
|
size="16"
|
||||||
|
@click.stop="cancelUpload"
|
||||||
|
>
|
||||||
|
<v-icon size="8">close</v-icon> </v-progress-circular
|
||||||
|
>{{ uploadBatch?.progressPercent }}%
|
||||||
|
</div>
|
||||||
|
<SwipeableThumbnailsView :items="items" v-if="room.displayType == ROOM_TYPE_CHANNEL" v-bind="$attrs" />
|
||||||
|
<v-container v-else fluid class="imageCollection">
|
||||||
|
<v-row wrap>
|
||||||
|
<v-col v-for="{ size, item } in layoutedItems" :key="item.file.name + item.file.size" :cols="size">
|
||||||
|
<ThumbnailView :file="item" v-on:itemclick="onItemClick($event)" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
<span v-html="linkify($$sanitize(messageText))" v-if="messageText" />
|
||||||
|
<span class="edit-marker" v-if="event && event.replacingEventId() && !event.isRedacted()">
|
||||||
|
{{ t("message.edited") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<GalleryItemsView
|
||||||
|
:originalEvent="originalEvent"
|
||||||
|
:items="items"
|
||||||
|
:initialItem="showItem"
|
||||||
|
v-if="!!showItem"
|
||||||
|
v-on:close="showItem = undefined"
|
||||||
|
/>
|
||||||
|
</MessageOutgoing>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MessageOutgoing from "./MessageOutgoing.vue";
|
||||||
|
import { MessageProps, useMessage } from "./useMessage";
|
||||||
|
import { ROOM_TYPE_CHANNEL } from "@/plugins/utils";
|
||||||
|
import GalleryItemsView from "../../file_mode/GalleryItemsView.vue";
|
||||||
|
import ThumbnailView from "../../file_mode/ThumbnailView.vue";
|
||||||
|
import SwipeableThumbnailsView from "../channel/SwipeableThumbnailsView.vue";
|
||||||
|
import { inject, ref, Ref, unref, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { Attachment, AttachmentBatch } from "../../../models/attachment";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const $matrix: any = inject("globalMatrix");
|
||||||
|
const $$sanitize: any = inject("globalSanitize");
|
||||||
|
|
||||||
|
let items: Ref<Attachment[]> = ref([]);
|
||||||
|
const layoutedItems: Ref<{ size: number; item: Attachment }[]> = ref([]);
|
||||||
|
const showItem: Ref<Attachment | undefined> = ref(undefined);
|
||||||
|
|
||||||
|
const uploadBatch: Ref<AttachmentBatch | undefined> = ref(undefined);
|
||||||
|
|
||||||
|
const props = defineProps<MessageProps>();
|
||||||
|
|
||||||
|
const { room } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
event,
|
||||||
|
inReplyToSender,
|
||||||
|
inReplyToText,
|
||||||
|
messageText,
|
||||||
|
linkify,
|
||||||
|
} = useMessage($matrix, t, props, undefined, undefined);
|
||||||
|
|
||||||
|
const cancelUpload = () => {
|
||||||
|
if (uploadBatch.value) {
|
||||||
|
uploadBatch.value.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onItemClick = (event: any) => {
|
||||||
|
showItem.value = event.item;
|
||||||
|
};
|
||||||
|
|
||||||
|
const layout = () => {
|
||||||
|
if (!items.value || items.value.length == 0) {
|
||||||
|
layoutedItems.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let array = items.value.slice(0);
|
||||||
|
let rows = [];
|
||||||
|
while (array.length > 0) {
|
||||||
|
if (array.length >= 7) {
|
||||||
|
rows.push({ size: 6, item: array[0] });
|
||||||
|
rows.push({ size: 6, item: array[1] });
|
||||||
|
rows.push({ size: 12, item: array[2] });
|
||||||
|
rows.push({ size: 3, item: array[3] });
|
||||||
|
rows.push({ size: 3, item: array[4] });
|
||||||
|
rows.push({ size: 3, item: array[5] });
|
||||||
|
rows.push({ size: 3, item: array[6] });
|
||||||
|
array = array.slice(7);
|
||||||
|
} else if (array.length >= 3) {
|
||||||
|
rows.push({ size: 6, item: array[0] });
|
||||||
|
rows.push({ size: 6, item: array[1] });
|
||||||
|
rows.push({ size: 12, item: array[2] });
|
||||||
|
array = array.slice(3);
|
||||||
|
} else if (array.length >= 2) {
|
||||||
|
rows.push({ size: 6, item: array[0] });
|
||||||
|
rows.push({ size: 6, item: array[1] });
|
||||||
|
array = array.slice(2);
|
||||||
|
} else {
|
||||||
|
rows.push({ size: 12, item: array[0] });
|
||||||
|
array = array.slice(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layoutedItems.value = rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
event,
|
||||||
|
(updated) => {
|
||||||
|
const batch = updated?.uploadBatch;
|
||||||
|
if (batch) {
|
||||||
|
uploadBatch.value = updated?.uploadBatch;
|
||||||
|
items = ref(batch.attachments);
|
||||||
|
layout();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use "@/assets/css/chat.scss" as *;
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.bubble {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageCollection {
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin: -4px; // Compensate for column padding, so the border-radius above looks round!
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-upload-progress {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: 4px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
font-family: "Inter";
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 125%;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 2px;
|
||||||
|
padding-right: 4px;
|
||||||
|
.message-upload-progress__progress {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ComputedRef, Ref } from "vue";
|
import { ComputedRef, Ref } from "vue";
|
||||||
import { Proof } from "./proof";
|
import { Proof, ProofHintFlags } from "./proof";
|
||||||
|
|
||||||
export class UploadPromise<Type> {
|
export class UploadPromise<Type> {
|
||||||
wrappedPromise: Promise<Type>;
|
wrappedPromise: Promise<Type>;
|
||||||
|
|
@ -39,6 +39,7 @@ export type AttachmentSendInfo = {
|
||||||
randomRotation: number; // For UI effects
|
randomRotation: number; // For UI effects
|
||||||
randomTranslationX: number; // For UI effects
|
randomTranslationX: number; // For UI effects
|
||||||
randomTranslationY: number; // For UI effects
|
randomTranslationY: number; // For UI effects
|
||||||
|
proofHintFlags?: ProofHintFlags;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AttachmentThumbnail = {
|
export type AttachmentThumbnail = {
|
||||||
|
|
@ -58,15 +59,15 @@ export type Attachment = {
|
||||||
useScaled: boolean;
|
useScaled: boolean;
|
||||||
src?: string;
|
src?: string;
|
||||||
proof?: Proof;
|
proof?: Proof;
|
||||||
sendInfo?: AttachmentSendInfo;
|
|
||||||
thumbnail?: AttachmentThumbnail;
|
thumbnail?: AttachmentThumbnail;
|
||||||
|
sendInfo: AttachmentSendInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AttachmentBatch = {
|
export type AttachmentBatch = {
|
||||||
sendingStatus: Ref<"initial" | "sending" | "sent" | "canceled" | "failed">;
|
sendingStatus: Ref<AttachmentSendStatus>;
|
||||||
sendingRootEventId: Ref<string | undefined>;
|
sendingRootEventId: Ref<string | undefined>;
|
||||||
sendingPromise: Ref<Promise<any> | undefined>;
|
progressPercent: Ref<number>;
|
||||||
attachments: Ref<Attachment[]>;
|
attachments: Attachment[];
|
||||||
attachmentsSentCount: ComputedRef<number>;
|
attachmentsSentCount: ComputedRef<number>;
|
||||||
attachmentsSending: ComputedRef<Attachment[]>;
|
attachmentsSending: ComputedRef<Attachment[]>;
|
||||||
attachmentsSent: ComputedRef<Attachment[]>;
|
attachmentsSent: ComputedRef<Attachment[]>;
|
||||||
|
|
@ -78,4 +79,3 @@ export type AttachmentBatch = {
|
||||||
cancel: () => void;
|
cancel: () => void;
|
||||||
cancelSendAttachment: (attachment: Attachment) => void;
|
cancelSendAttachment: (attachment: Attachment) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk";
|
import { MatrixClient, MatrixEvent, Room, RoomEvent } from "matrix-js-sdk";
|
||||||
import {
|
import {
|
||||||
EventAttachment,
|
EventAttachment,
|
||||||
EventAttachmentUrlData,
|
EventAttachmentUrlData,
|
||||||
|
|
@ -8,11 +8,18 @@ import {
|
||||||
} from "./eventAttachment";
|
} 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, AttachmentBatch, AttachmentSendInfo, AttachmentThumbnail } from "./attachment";
|
import {
|
||||||
|
Attachment,
|
||||||
|
AttachmentBatch,
|
||||||
|
AttachmentSendInfo,
|
||||||
|
AttachmentSendStatus,
|
||||||
|
AttachmentThumbnail,
|
||||||
|
} from "./attachment";
|
||||||
import proofmode from "../plugins/proofmode";
|
import proofmode from "../plugins/proofmode";
|
||||||
import imageResize from "image-resize";
|
import imageResize from "image-resize";
|
||||||
import { computed, Reactive, reactive, ref, Ref, shallowReactive } from "vue";
|
import { computed, ref, Ref, shallowReactive, unref } from "vue";
|
||||||
import utils, { THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT } from "@/plugins/utils";
|
import utils, { THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT } from "@/plugins/utils";
|
||||||
|
import { extractProofHintFlags } from "./proof";
|
||||||
|
|
||||||
export class AttachmentManager {
|
export class AttachmentManager {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
|
@ -20,7 +27,8 @@ export class AttachmentManager {
|
||||||
maxSizeUploads: number;
|
maxSizeUploads: number;
|
||||||
maxSizeAutoDownloads: number;
|
maxSizeAutoDownloads: number;
|
||||||
|
|
||||||
cache: Map<string | undefined, Reactive<EventAttachment>>;
|
cache: Map<string, EventAttachment>;
|
||||||
|
cacheUploads: Map<string, AttachmentBatch>;
|
||||||
|
|
||||||
constructor(matrixClient: MatrixClient, useAuthedMedia: boolean, maxSizeAutoDownloads: number) {
|
constructor(matrixClient: MatrixClient, useAuthedMedia: boolean, maxSizeAutoDownloads: number) {
|
||||||
this.matrixClient = matrixClient;
|
this.matrixClient = matrixClient;
|
||||||
|
|
@ -29,6 +37,7 @@ export class AttachmentManager {
|
||||||
this.maxSizeAutoDownloads = maxSizeAutoDownloads;
|
this.maxSizeAutoDownloads = maxSizeAutoDownloads;
|
||||||
|
|
||||||
this.cache = new Map();
|
this.cache = new Map();
|
||||||
|
this.cacheUploads = new Map();
|
||||||
|
|
||||||
// Get max upload size
|
// Get max upload size
|
||||||
this.matrixClient
|
this.matrixClient
|
||||||
|
|
@ -40,7 +49,7 @@ export class AttachmentManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public createUpload(room: Room) {
|
public createUpload(room: Room) {
|
||||||
return createUploadBatch(this.matrixClient, room, this.maxSizeUploads);
|
return createUploadBatch(this, room);
|
||||||
}
|
}
|
||||||
|
|
||||||
public createAttachment(file: File): Attachment {
|
public createAttachment(file: File): Attachment {
|
||||||
|
|
@ -48,6 +57,16 @@ export class AttachmentManager {
|
||||||
status: "loading",
|
status: "loading",
|
||||||
file: file,
|
file: file,
|
||||||
useScaled: false,
|
useScaled: false,
|
||||||
|
sendInfo: {
|
||||||
|
status: "initial",
|
||||||
|
statusDate: 0,
|
||||||
|
mediaEventId: undefined,
|
||||||
|
progress: 0,
|
||||||
|
promise: undefined,
|
||||||
|
randomRotation: 0,
|
||||||
|
randomTranslationX: 0,
|
||||||
|
randomTranslationY: 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const ra = shallowReactive(a);
|
const ra = shallowReactive(a);
|
||||||
this.prepareUpload(ra);
|
this.prepareUpload(ra);
|
||||||
|
|
@ -182,15 +201,15 @@ export class AttachmentManager {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEventAttachment(event: KeanuEvent): Reactive<EventAttachment> {
|
public getEventAttachment(event: KeanuEvent): EventAttachment {
|
||||||
let entry = this.cache.get(event.getId());
|
let entry = this.cache.get(event.getId() ?? "invalid");
|
||||||
if (entry !== undefined) {
|
if (entry !== undefined) {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileSize = this.getSrcFileSize(event);
|
const fileSize = this.getSrcFileSize(event);
|
||||||
|
|
||||||
const attachment: Reactive<EventAttachment> = shallowReactive({
|
const attachment: EventAttachment = {
|
||||||
event: event,
|
event: event,
|
||||||
name: this.getFileName(event),
|
name: this.getFileName(event),
|
||||||
srcSize: fileSize,
|
srcSize: fileSize,
|
||||||
|
|
@ -201,7 +220,7 @@ export class AttachmentManager {
|
||||||
loadThumbnail: () => Promise.reject("Not implemented"),
|
loadThumbnail: () => Promise.reject("Not implemented"),
|
||||||
loadBlob: () => Promise.reject("Not implemented"),
|
loadBlob: () => Promise.reject("Not implemented"),
|
||||||
release: () => Promise.reject("Not implemented"),
|
release: () => Promise.reject("Not implemented"),
|
||||||
});
|
};
|
||||||
attachment.loadSrc = () => {
|
attachment.loadSrc = () => {
|
||||||
if (attachment.src) {
|
if (attachment.src) {
|
||||||
return Promise.resolve({ data: attachment.src, type: "src" });
|
return Promise.resolve({ data: attachment.src, type: "src" });
|
||||||
|
|
@ -246,7 +265,7 @@ export class AttachmentManager {
|
||||||
// TODO - figure out logic
|
// TODO - figure out logic
|
||||||
if (entry) {
|
if (entry) {
|
||||||
// TODO - abortable promises
|
// TODO - abortable promises
|
||||||
this.cache.delete(event.getId());
|
this.cache.delete(event.getId() ?? "invalid");
|
||||||
if (attachment.src) {
|
if (attachment.src) {
|
||||||
URL.revokeObjectURL(attachment.src);
|
URL.revokeObjectURL(attachment.src);
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +274,9 @@ export class AttachmentManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.cache.set(event.getId(), attachment!);
|
if (event.getId()) {
|
||||||
|
this.cache.set(event.getId()!, attachment!);
|
||||||
|
}
|
||||||
return attachment;
|
return attachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -391,16 +412,35 @@ export class AttachmentManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createUploadBatch = (
|
export const createUploadBatch = (manager: AttachmentManager | null, room: Room | null): AttachmentBatch => {
|
||||||
matrixClient: MatrixClient | null,
|
const matrixClient = manager?.matrixClient;
|
||||||
room: Room | null,
|
const maxSizeUploads = manager?.maxSizeUploads ?? 0;
|
||||||
maxSizeUploads: number
|
|
||||||
): AttachmentBatch => {
|
const txnId = utils.randomPass();
|
||||||
const sendingStatus: Ref<"initial" | "sending" | "sent" | "canceled" | "failed"> = ref("initial");
|
console.error("Created txnId", txnId);
|
||||||
|
|
||||||
|
const sendingStatus: Ref<AttachmentSendStatus> = ref("initial");
|
||||||
const sendingRootEventId: Ref<string | undefined> = ref(undefined);
|
const sendingRootEventId: Ref<string | undefined> = ref(undefined);
|
||||||
const sendingPromise: Ref<Promise<any> | undefined> = ref(undefined);
|
const sendingPromise: Ref<Promise<any> | undefined> = ref(undefined);
|
||||||
const attachments: Ref<Attachment[]> = ref([]);
|
const attachments: Ref<Attachment[]> = ref([]);
|
||||||
|
|
||||||
|
const totalOriginalFileSize: Ref<number> = ref(0);
|
||||||
|
const progressPercent: Ref<number> = ref(0);
|
||||||
|
|
||||||
|
const updateProgress = () => {
|
||||||
|
// Use relative sizes of the original files to determine how many percent
|
||||||
|
// the individual files contribute to the total progress.
|
||||||
|
const progress = attachments.value.reduce((cb, item) => {
|
||||||
|
const info = item.sendInfo;
|
||||||
|
const thisFileCurrent = item.file.size;
|
||||||
|
const max = totalOriginalFileSize.value > 0 ? thisFileCurrent / totalOriginalFileSize.value : 0;
|
||||||
|
const q = (info.progress * max) / 100;
|
||||||
|
return cb + q;
|
||||||
|
}, 0);
|
||||||
|
const percent = parseInt(Math.floor(progress * 100).toFixed());
|
||||||
|
progressPercent.value = percent;
|
||||||
|
};
|
||||||
|
|
||||||
const attachmentsSentCount = computed(() => {
|
const attachmentsSentCount = computed(() => {
|
||||||
return attachments.value.reduce((a, elem) => (elem.sendInfo?.status == "sent" ? a + 1 : a), 0);
|
return attachments.value.reduce((a, elem) => (elem.sendInfo?.status == "sent" ? a + 1 : a), 0);
|
||||||
});
|
});
|
||||||
|
|
@ -486,9 +526,10 @@ export const createUploadBatch = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const send = (message: string): Promise<any> => {
|
const send = async (message: string): Promise<any> => {
|
||||||
if (!matrixClient || !room) return Promise.reject("Not configured");
|
if (!matrixClient || !room) return Promise.reject("Not configured");
|
||||||
sendingStatus.value = "sending";
|
sendingStatus.value = "sending";
|
||||||
|
|
||||||
attachments.value.forEach((attachment) => {
|
attachments.value.forEach((attachment) => {
|
||||||
let sendInfo: AttachmentSendInfo = {
|
let sendInfo: AttachmentSendInfo = {
|
||||||
status: "initial",
|
status: "initial",
|
||||||
|
|
@ -499,25 +540,54 @@ export const createUploadBatch = (
|
||||||
randomTranslationX: 0,
|
randomTranslationX: 0,
|
||||||
randomTranslationY: 0,
|
randomTranslationY: 0,
|
||||||
promise: undefined,
|
promise: undefined,
|
||||||
|
proofHintFlags: extractProofHintFlags(attachment.proof),
|
||||||
};
|
};
|
||||||
attachment.sendInfo = shallowReactive(sendInfo);
|
attachment.sendInfo = shallowReactive(sendInfo);
|
||||||
|
attachment.proof = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
const sendingPromise = utils
|
totalOriginalFileSize.value = attachments.value.reduce((cb, item) => {
|
||||||
.sendTextMessage(matrixClient, room.roomId, message)
|
const thisFileCurrent = item.scaledFile && item.useScaled ? item.scaledFile.size : item.file.size;
|
||||||
|
return cb + thisFileCurrent;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const onLocalEchoUpdated = (event: KeanuEvent) => {
|
||||||
|
console.error("Local echo updated", event.getTxnId(), event.uploadBatch);
|
||||||
|
if (!event.uploadBatch && event.getTxnId() === txnId) {
|
||||||
|
event.uploadBatch = {
|
||||||
|
sendingStatus,
|
||||||
|
sendingRootEventId,
|
||||||
|
progressPercent,
|
||||||
|
attachments: unref(attachments),
|
||||||
|
attachmentsSentCount,
|
||||||
|
attachmentsSending,
|
||||||
|
attachmentsSent,
|
||||||
|
addAttachments,
|
||||||
|
removeAttachment,
|
||||||
|
isTooLarge,
|
||||||
|
canSend,
|
||||||
|
send,
|
||||||
|
cancel,
|
||||||
|
cancelSendAttachment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
room.on(RoomEvent.LocalEchoUpdated, onLocalEchoUpdated);
|
||||||
|
|
||||||
|
const promise = utils
|
||||||
|
.sendTextMessage(matrixClient, room.roomId, message, undefined, undefined, txnId)
|
||||||
.then((eventId: string) => {
|
.then((eventId: string) => {
|
||||||
sendingRootEventId.value = eventId;
|
sendingRootEventId.value = eventId;
|
||||||
|
|
||||||
// Use the eventId as a thread root for all the media
|
|
||||||
let promiseChain = Promise.resolve();
|
let promiseChain = Promise.resolve();
|
||||||
const getItemPromise = (index: number) => {
|
const getItemPromise = (index: number) => {
|
||||||
if (index < attachments.value.length) {
|
if (index < attachments.value.length) {
|
||||||
const attachment = attachments.value[index];
|
const attachment = attachments.value[index];
|
||||||
const item = attachment.sendInfo!;
|
const item = attachment;
|
||||||
if (item.status !== "initial") {
|
if (item.sendInfo.status !== "initial") {
|
||||||
return getItemPromise(++index);
|
return getItemPromise(++index);
|
||||||
}
|
}
|
||||||
item.status = "sending";
|
item.sendInfo.status = "sending";
|
||||||
|
|
||||||
let file = (() => {
|
let file = (() => {
|
||||||
if (attachment.scaledFile && attachment.useScaled) {
|
if (attachment.scaledFile && attachment.useScaled) {
|
||||||
|
|
@ -536,10 +606,11 @@ export const createUploadBatch = (
|
||||||
file,
|
file,
|
||||||
({ loaded, total }: { loaded: number; total: number }) => {
|
({ loaded, total }: { loaded: number; total: number }) => {
|
||||||
if (loaded == total) {
|
if (loaded == total) {
|
||||||
item.progress = 100;
|
item.sendInfo.progress = 100;
|
||||||
} else if (total > 0) {
|
} else if (total > 0) {
|
||||||
item.progress = (100 * loaded) / total;
|
item.sendInfo.progress = (100 * loaded) / total;
|
||||||
}
|
}
|
||||||
|
updateProgress();
|
||||||
},
|
},
|
||||||
eventId,
|
eventId,
|
||||||
attachment.dimensions,
|
attachment.dimensions,
|
||||||
|
|
@ -561,23 +632,23 @@ export const createUploadBatch = (
|
||||||
signY = -1;
|
signY = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item.randomRotation = signR * (2 + Math.random() * 10);
|
item.sendInfo.randomRotation = signR * (2 + Math.random() * 10);
|
||||||
item.randomTranslationX = signX * Math.random() * 20;
|
item.sendInfo.randomTranslationX = signX * Math.random() * 20;
|
||||||
item.randomTranslationY = signY * Math.random() * 20;
|
item.sendInfo.randomTranslationY = signY * Math.random() * 20;
|
||||||
item.mediaEventId = mediaEventId;
|
item.sendInfo.mediaEventId = mediaEventId;
|
||||||
item.status = "sent";
|
item.sendInfo.status = "sent";
|
||||||
item.statusDate = Date.now();
|
item.sendInfo.statusDate = Date.now();
|
||||||
})
|
})
|
||||||
.catch((ignorederr: any) => {
|
.catch((ignorederr: any) => {
|
||||||
if (item.promise?.aborted) {
|
if (item.sendInfo.promise?.aborted) {
|
||||||
item.status = "canceled";
|
item.sendInfo.status = "canceled";
|
||||||
} else {
|
} else {
|
||||||
console.error("ERROR", ignorederr);
|
console.error("ERROR", ignorederr);
|
||||||
item.status = "failed";
|
item.sendInfo.status = "failed";
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
item.promise = itemPromise;
|
item.sendInfo.promise = itemPromise;
|
||||||
return itemPromise.then(() => getItemPromise(++index));
|
return itemPromise.then(() => getItemPromise(++index));
|
||||||
} else return Promise.resolve();
|
} else return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
@ -589,16 +660,20 @@ export const createUploadBatch = (
|
||||||
sendingRootEventId.value = undefined;
|
sendingRootEventId.value = undefined;
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
console.error("ERROR", err);
|
console.error("Upload error", err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
room.off(RoomEvent.LocalEchoUpdated, onLocalEchoUpdated);
|
||||||
});
|
});
|
||||||
return sendingPromise;
|
sendingPromise.value = promise;
|
||||||
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sendingStatus,
|
sendingStatus,
|
||||||
sendingRootEventId,
|
sendingRootEventId,
|
||||||
sendingPromise,
|
progressPercent,
|
||||||
attachments,
|
attachments: unref(attachments),
|
||||||
attachmentsSentCount,
|
attachmentsSentCount,
|
||||||
attachmentsSending,
|
attachmentsSending,
|
||||||
attachmentsSent,
|
attachmentsSent,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { MatrixEvent, Room } from "matrix-js-sdk";
|
import { MatrixEvent, Room } from "matrix-js-sdk";
|
||||||
|
import { AttachmentBatch } from "./attachment";
|
||||||
|
|
||||||
export type KeanuEventExtension = {
|
export type KeanuEventExtension = {
|
||||||
isMxThread?: boolean;
|
isMxThread?: boolean;
|
||||||
|
|
@ -6,6 +7,7 @@ export type KeanuEventExtension = {
|
||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
parentThread?: MatrixEvent & KeanuEventExtension;
|
parentThread?: MatrixEvent & KeanuEventExtension;
|
||||||
replyEvent?: MatrixEvent & KeanuEventExtension;
|
replyEvent?: MatrixEvent & KeanuEventExtension;
|
||||||
|
uploadBatch?: AttachmentBatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type KeanuEvent = MatrixEvent & KeanuEventExtension;
|
export type KeanuEvent = MatrixEvent & KeanuEventExtension;
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,72 @@ export type Proof = {
|
||||||
data?: any;
|
data?: any;
|
||||||
name?: string;
|
name?: string;
|
||||||
json?: string;
|
json?: string;
|
||||||
integrity?: { pgp?: any; c2pa?: any; exif?: {[key: string]: string | Object}; opentimestamps?: any };
|
integrity?: { pgp?: any; c2pa?: C2PAData; exif?: {[key: string]: string | Object}; opentimestamps?: any };
|
||||||
ai?: { inferenceResult?: AIInferenceResult};
|
ai?: { inferenceResult?: AIInferenceResult};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ProofHintFlags = {
|
||||||
|
aiGenerated?: boolean;
|
||||||
|
aiEdited?: boolean;
|
||||||
|
screenshot?: boolean;
|
||||||
|
camera?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extractProofHintFlags = (proof?: Proof): (ProofHintFlags | undefined) => {
|
||||||
|
if (!proof) return undefined;
|
||||||
|
|
||||||
|
let screenshot = false;
|
||||||
|
let camera = false;
|
||||||
|
let aiGenerated = false;
|
||||||
|
let aiEdited = false;
|
||||||
|
let valid = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let results = proof.integrity?.c2pa?.manifest_info.validation_results?.activeManifest;
|
||||||
|
if (results) {
|
||||||
|
valid = results.failure.length == 0 && results.success.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifests = Object.values(proof.integrity?.c2pa?.manifest_info.manifests ?? {});
|
||||||
|
for (const manifest of manifests) {
|
||||||
|
for (const assertion of manifest.assertions) {
|
||||||
|
if (assertion.label === "c2pa.actions") {
|
||||||
|
const actions = (assertion.data as C2PAActionsAssertion)?.actions ?? [];
|
||||||
|
const a = actions.find((a) => a.action === "c2pa.created");
|
||||||
|
if (a) {
|
||||||
|
// creator.value = a.softwareAgent;
|
||||||
|
// dateCreated.value = dayjs(Date.parse(manifest.signature_info.time));
|
||||||
|
if (a.digitalSourceType === C2PASourceTypeScreenCapture) {
|
||||||
|
screenshot = true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
a.digitalSourceType === C2PASourceTypeDigitalCapture ||
|
||||||
|
a.digitalSourceType === C2PASourceTypeComputationalCapture ||
|
||||||
|
a.digitalSourceType === C2PASourceTypeCompositeCapture
|
||||||
|
) {
|
||||||
|
camera = true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
a.digitalSourceType === C2PASourceTypeTrainedAlgorithmicMedia ||
|
||||||
|
a.digitalSourceType === C2PASourceTypeCompositeWithTrainedAlgorithmicMedia
|
||||||
|
) {
|
||||||
|
aiGenerated = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (valid) {
|
||||||
|
const flags: ProofHintFlags = {
|
||||||
|
aiGenerated,
|
||||||
|
aiEdited,
|
||||||
|
screenshot,
|
||||||
|
camera
|
||||||
|
};
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
13
src/plugins/touch.js
Normal file
13
src/plugins/touch.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Hammer from "hammerjs";
|
||||||
|
|
||||||
|
export function singleOrDoubleTapRecognizer(element) {
|
||||||
|
// reference: https://codepen.io/jtangelder/pen/xxYyJQ
|
||||||
|
const hm = new Hammer.Manager(element);
|
||||||
|
|
||||||
|
hm.add(new Hammer.Tap({ event: "doubletap", taps: 2 }));
|
||||||
|
hm.add(new Hammer.Tap({ event: "singletap" }));
|
||||||
|
|
||||||
|
hm.get("doubletap").recognizeWith("singletap");
|
||||||
|
hm.get("singletap").requireFailure("doubletap");
|
||||||
|
return hm;
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ import imageResize from "image-resize";
|
||||||
import { AutoDiscovery, Method } from "matrix-js-sdk";
|
import { AutoDiscovery, Method } from "matrix-js-sdk";
|
||||||
import User from "../models/user";
|
import User from "../models/user";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
import Hammer from "hammerjs";
|
|
||||||
import { Thread } from "matrix-js-sdk/lib/models/thread";
|
import { Thread } from "matrix-js-sdk/lib/models/thread";
|
||||||
import { imageSize } from "image-size";
|
import { imageSize } from "image-size";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
@ -144,13 +143,29 @@ class Util {
|
||||||
var file = null;
|
var file = null;
|
||||||
let decrypt = true;
|
let decrypt = true;
|
||||||
if (!!content.info && !!content.info.thumbnail_url) {
|
if (!!content.info && !!content.info.thumbnail_url) {
|
||||||
url = matrixClient.mxcUrlToHttp(content.info.thumbnail_url, undefined, undefined, undefined, undefined, undefined, useAuthedMedia);
|
url = matrixClient.mxcUrlToHttp(
|
||||||
|
content.info.thumbnail_url,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthedMedia
|
||||||
|
);
|
||||||
decrypt = false;
|
decrypt = false;
|
||||||
if (content.info.thumbnail_info) {
|
if (content.info.thumbnail_info) {
|
||||||
mime = content.info.thumbnail_info.mimetype;
|
mime = content.info.thumbnail_info.mimetype;
|
||||||
}
|
}
|
||||||
} else if (content.url != null) {
|
} else if (content.url != null) {
|
||||||
url = matrixClient.mxcUrlToHttp(content.url, undefined, undefined, undefined, undefined, undefined, useAuthedMedia);
|
url = matrixClient.mxcUrlToHttp(
|
||||||
|
content.url,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthedMedia
|
||||||
|
);
|
||||||
decrypt = false;
|
decrypt = false;
|
||||||
if (content.info) {
|
if (content.info) {
|
||||||
mime = content.info.mimetype;
|
mime = content.info.mimetype;
|
||||||
|
|
@ -175,13 +190,17 @@ class Util {
|
||||||
throw new Error("No url found!");
|
throw new Error("No url found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios
|
const response = await axios.get(
|
||||||
.get(url, useAuthedMedia ? {
|
url,
|
||||||
responseType: "arraybuffer",
|
useAuthedMedia
|
||||||
headers: {
|
? {
|
||||||
Authorization: `Bearer ${matrixClient.getAccessToken()}`,
|
responseType: "arraybuffer",
|
||||||
},
|
headers: {
|
||||||
} : { responseType: "arraybuffer" });
|
Authorization: `Bearer ${matrixClient.getAccessToken()}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: { responseType: "arraybuffer" }
|
||||||
|
);
|
||||||
const bytes = decrypt ? await this.decryptIfNeeded(file, response) : { buffer: response.data };
|
const bytes = decrypt ? await this.decryptIfNeeded(file, response) : { buffer: response.data };
|
||||||
return URL.createObjectURL(new Blob([bytes.buffer], { type: mime }));
|
return URL.createObjectURL(new Blob([bytes.buffer], { type: mime }));
|
||||||
}
|
}
|
||||||
|
|
@ -217,7 +236,7 @@ class Util {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTextMessage(matrixClient, roomId, text, editedEvent, replyToEvent) {
|
sendTextMessage(matrixClient, roomId, text, editedEvent, replyToEvent, txnId) {
|
||||||
var content = ContentHelpers.makeTextMessage(text);
|
var content = ContentHelpers.makeTextMessage(text);
|
||||||
if (editedEvent) {
|
if (editedEvent) {
|
||||||
content["m.relates_to"] = {
|
content["m.relates_to"] = {
|
||||||
|
|
@ -248,7 +267,7 @@ class Util {
|
||||||
.join("\n");
|
.join("\n");
|
||||||
content.body = prefix + "\n\n" + content.body;
|
content.body = prefix + "\n\n" + content.body;
|
||||||
}
|
}
|
||||||
return this.sendMessage(matrixClient, roomId, "m.room.message", content);
|
return this.sendMessage(matrixClient, roomId, "m.room.message", content, txnId);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendQuickReaction(matrixClient, roomId, emoji, event, extraData = {}) {
|
sendQuickReaction(matrixClient, roomId, emoji, event, extraData = {}) {
|
||||||
|
|
@ -306,10 +325,10 @@ class Util {
|
||||||
return this.sendMessage(matrixClient, roomId, "org.matrix.msc3381.poll.response", content);
|
return this.sendMessage(matrixClient, roomId, "org.matrix.msc3381.poll.response", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(matrixClient, roomId, eventType, content) {
|
sendMessage(matrixClient, roomId, eventType, content, txnId) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
matrixClient
|
matrixClient
|
||||||
.sendEvent(roomId, eventType, content, undefined, undefined)
|
.sendEvent(roomId, eventType, content, txnId, undefined)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
console.log("Message sent: ", result);
|
console.log("Message sent: ", result);
|
||||||
resolve(result.event_id);
|
resolve(result.event_id);
|
||||||
|
|
@ -419,7 +438,7 @@ class Util {
|
||||||
|
|
||||||
var description = file.name;
|
var description = file.name;
|
||||||
var msgtype = "m.file";
|
var msgtype = "m.file";
|
||||||
|
|
||||||
if (thumbnail) {
|
if (thumbnail) {
|
||||||
thumbnailData = thumbnail.data;
|
thumbnailData = thumbnail.data;
|
||||||
thumbnailInfo = {
|
thumbnailInfo = {
|
||||||
|
|
@ -428,8 +447,8 @@ class Util {
|
||||||
w: thumbnail.w,
|
w: thumbnail.w,
|
||||||
h: thumbnail.h,
|
h: thumbnail.h,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.type.startsWith("image/")) {
|
if (file.type.startsWith("image/")) {
|
||||||
msgtype = "m.image";
|
msgtype = "m.image";
|
||||||
|
|
||||||
|
|
@ -446,7 +465,9 @@ class Util {
|
||||||
width: newWidth,
|
width: newWidth,
|
||||||
height: newHeight,
|
height: newHeight,
|
||||||
outputType: "blob",
|
outputType: "blob",
|
||||||
}).catch(() => {return Promise.resolve(undefined)});
|
}).catch(() => {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
});
|
||||||
if (scaled && file.size > scaled.size) {
|
if (scaled && file.size > scaled.size) {
|
||||||
thumbnailData = new Uint8Array(await scaled.arrayBuffer());
|
thumbnailData = new Uint8Array(await scaled.arrayBuffer());
|
||||||
thumbnailInfo = {
|
thumbnailInfo = {
|
||||||
|
|
@ -483,12 +504,19 @@ class Util {
|
||||||
messageContent.filename = file.name;
|
messageContent.filename = file.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let totalBytes = 0;
|
||||||
|
let thumbBytes = 0;
|
||||||
|
|
||||||
const useEncryption = matrixClient.isRoomEncrypted(roomId);
|
const useEncryption = matrixClient.isRoomEncrypted(roomId);
|
||||||
|
|
||||||
const dataUploadOpts = {
|
const dataUploadOpts = {
|
||||||
type: useEncryption ? "application/octet-stream" : file.type,
|
type: useEncryption ? "application/octet-stream" : file.type,
|
||||||
name: description,
|
name: description,
|
||||||
progressHandler: onUploadProgress,
|
progressHandler: ({ loaded, total }) => {
|
||||||
|
if (onUploadProgress) {
|
||||||
|
onUploadProgress({ loaded: loaded + thumbBytes, total: totalBytes });
|
||||||
|
}
|
||||||
|
},
|
||||||
onlyContentUri: false,
|
onlyContentUri: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -498,6 +526,8 @@ class Util {
|
||||||
data = encryptedBytes;
|
data = encryptedBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
totalBytes = data.length;
|
||||||
|
|
||||||
if (thumbnailData) {
|
if (thumbnailData) {
|
||||||
messageContent.thumbnail_info = thumbnailInfo;
|
messageContent.thumbnail_info = thumbnailInfo;
|
||||||
if (useEncryption) {
|
if (useEncryption) {
|
||||||
|
|
@ -505,10 +535,16 @@ class Util {
|
||||||
messageContent.info.thumbnail_file = encryptedFile;
|
messageContent.info.thumbnail_file = encryptedFile;
|
||||||
thumbnailData = encryptedBytes;
|
thumbnailData = encryptedBytes;
|
||||||
}
|
}
|
||||||
|
thumbBytes = thumbnailData.length;
|
||||||
|
totalBytes += thumbnailData.length;
|
||||||
const thumbnailUploadOpts = {
|
const thumbnailUploadOpts = {
|
||||||
type: useEncryption ? "application/octet-stream" : file.type,
|
type: useEncryption ? "application/octet-stream" : file.type,
|
||||||
name: "thumb:" + description,
|
name: "thumb:" + description,
|
||||||
progressHandler: onUploadProgress,
|
progressHandler: ({ loaded, total }) => {
|
||||||
|
if (onUploadProgress) {
|
||||||
|
onUploadProgress({ loaded: loaded, total: totalBytes });
|
||||||
|
}
|
||||||
|
},
|
||||||
onlyContentUri: false,
|
onlyContentUri: false,
|
||||||
};
|
};
|
||||||
const thumbUploadPromise = matrixClient.uploadContent(thumbnailData, thumbnailUploadOpts);
|
const thumbUploadPromise = matrixClient.uploadContent(thumbnailData, thumbnailUploadOpts);
|
||||||
|
|
@ -1162,18 +1198,6 @@ class Util {
|
||||||
return mobileTabletPattern.test(userAgent);
|
return mobileTabletPattern.test(userAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
singleOrDoubleTabRecognizer(element) {
|
|
||||||
// reference: https://codepen.io/jtangelder/pen/xxYyJQ
|
|
||||||
const hm = new Hammer.Manager(element);
|
|
||||||
|
|
||||||
hm.add(new Hammer.Tap({ event: "doubletap", taps: 2 }));
|
|
||||||
hm.add(new Hammer.Tap({ event: "singletap" }));
|
|
||||||
|
|
||||||
hm.get("doubletap").recognizeWith("singletap");
|
|
||||||
hm.get("singletap").requireFailure("doubletap");
|
|
||||||
return hm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Possibly convert numerals to local representation (currently only for "bo" locale)
|
* Possibly convert numerals to local representation (currently only for "bo" locale)
|
||||||
* @param str String in which to convert numerals [0-9]
|
* @param str String in which to convert numerals [0-9]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue