Support for running proof check on client side
This commit is contained in:
parent
66eef037e0
commit
46479d4c37
6 changed files with 126 additions and 29 deletions
61
src/components/file_mode/EventAttachmentInfo.vue
Normal file
61
src/components/file_mode/EventAttachmentInfo.vue
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div class="attachment-info">
|
||||||
|
<div v-if="loadingProof">
|
||||||
|
<div style="font-size: 0.7em; opacity: 0.7">
|
||||||
|
<v-progress-circular indeterminate class="mb-0"></v-progress-circular>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<C2PAInfo class="attachment-info__detail-box" v-if="hasC2PA" :flags="attachment?.proofHintFlags" />
|
||||||
|
<EXIFInfo class="attachment-info__detail-box" v-if="hasExif" :exif="attachment?.proof?.integrity?.exif" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import C2PAInfo from "../content-credentials/C2PAInfo.vue";
|
||||||
|
import EXIFInfo from "../content-credentials/EXIFInfo.vue";
|
||||||
|
import { computed, onMounted, Ref, ref } from "vue";
|
||||||
|
import { EventAttachment } from "../../models/eventAttachment";
|
||||||
|
import proofmode from "../../plugins/proofmode";
|
||||||
|
import { extractProofHintFlags } from "../../models/proof";
|
||||||
|
|
||||||
|
const { attachment } = defineProps<{
|
||||||
|
attachment?: EventAttachment;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const loadingProof: Ref<boolean> = ref(false);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (attachment?.proofHintFlags && attachment.proof === undefined) {
|
||||||
|
const a = attachment;
|
||||||
|
loadingProof.value = true;
|
||||||
|
a.loadSrc()
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.data) {
|
||||||
|
return proofmode.proofCheckSource(data.data).then((res) => {
|
||||||
|
a.proof = res;
|
||||||
|
a.proofHintFlags = extractProofHintFlags(a.proof);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
loadingProof.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasC2PA = computed(() => {
|
||||||
|
return attachment?.proof?.integrity?.c2pa !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasExif = computed(() => {
|
||||||
|
return attachment?.proof?.integrity?.exif !== undefined;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use "@/assets/css/chat.scss" as *;
|
||||||
|
@use "@/assets/css/sendattachments.scss" as *;
|
||||||
|
</style>
|
||||||
|
|
@ -5,62 +5,76 @@
|
||||||
<v-icon @click.stop="$emit('close')" color="white" class="clickable">arrow_back</v-icon>
|
<v-icon @click.stop="$emit('close')" color="white" class="clickable">arrow_back</v-icon>
|
||||||
<div class="room-name no-upper">{{ displayDate }}</div>
|
<div class="room-name no-upper">{{ displayDate }}</div>
|
||||||
<div>
|
<div>
|
||||||
<v-icon v-if="showInfoButton" color="white" class="clickable">info_outline</v-icon>
|
<v-icon @click.stop="showInfo = true" v-if="showInfoButton" color="white" class="clickable"
|
||||||
|
>info_outline</v-icon
|
||||||
|
>
|
||||||
<v-icon @click.stop="showMoreMenu = true" color="white" class="clickable">more_vert</v-icon>
|
<v-icon @click.stop="showMoreMenu = true" color="white" class="clickable">more_vert</v-icon>
|
||||||
</div>
|
</div>
|
||||||
</v-container>
|
</v-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gallery-current-item">
|
<div class="gallery-current-item">
|
||||||
<ThumbnailView :item="items[currentItemIndex]" />
|
<ThumbnailView :item="currentAttachment" />
|
||||||
<div class="download-button clickable" @click.stop="downloadOne">
|
<div class="download-button clickable" @click.stop="downloadOne">
|
||||||
<v-icon color="black">arrow_downward</v-icon>
|
<v-icon color="black">arrow_downward</v-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gallery-thumbnail-container">
|
<div class="gallery-thumbnail-container">
|
||||||
<div
|
<div
|
||||||
:class="{ 'file-drop-thumbnail': true, clickable: true, current: id == currentItemIndex }"
|
:class="{ 'file-drop-thumbnail': true, clickable: true, current: currentAttachment == attachment }"
|
||||||
@click="currentItemIndex = id"
|
@click="currentAttachment = attachment"
|
||||||
v-for="(currentImageInput, id) in items"
|
v-for="(attachment, index) in items"
|
||||||
:key="id"
|
:key="index"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-img
|
||||||
v-if="currentImageInput"
|
v-if="attachment"
|
||||||
:src="currentImageInput.thumbnail ? currentImageInput.thumbnail : currentImageInput.src"
|
:src="attachment.thumbnail ? attachment.thumbnail : attachment.src"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MORE MENU POPUP -->
|
<!-- MORE MENU POPUP -->
|
||||||
<MoreMenuPopup :show="showMoreMenu" :menuItems="moreMenuItems" :showProfile="false" @close="showMoreMenu = false" />
|
<MoreMenuPopup :show="showMoreMenu" :menuItems="moreMenuItems" :showProfile="false" @close="showMoreMenu = false" />
|
||||||
|
<v-bottom-sheet v-model="showInfo" theme="dark" height="80%">
|
||||||
|
<v-card class="text-center send-attachments-info-popup">
|
||||||
|
<v-card-title class="d-flex flex-column pa-0">
|
||||||
|
<div class="align-self-end done-button clickable" @click="showInfo = false">{{ $t("menu.done") }}</div>
|
||||||
|
<v-divider />
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-title class="d-flex"> </v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<EventAttachmentInfo :attachment="currentAttachment" />
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-bottom-sheet>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import MoreMenuPopup from "../MoreMenuPopup.vue";
|
import MoreMenuPopup from "../MoreMenuPopup.vue";
|
||||||
import util, { CLIENT_EVENT_PROOF_HINT } from "../../plugins/utils";
|
import util from "../../plugins/utils";
|
||||||
import ThumbnailView from "./ThumbnailView.vue";
|
import ThumbnailView from "./ThumbnailView.vue";
|
||||||
import { EventAttachment, KeanuEvent } from "../../models/eventAttachment";
|
import { EventAttachment, KeanuEvent } from "../../models/eventAttachment";
|
||||||
import { computed, inject, onBeforeUnmount, onMounted, Ref, ref, watch } from "vue";
|
import { computed, inject, onBeforeUnmount, onMounted, Ref, ref, watch } from "vue";
|
||||||
|
import EventAttachmentInfo from "./EventAttachmentInfo.vue";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const $matrix: any = inject("globalMatrix");
|
const $matrix: any = inject("globalMatrix");
|
||||||
|
|
||||||
const props = defineProps<{ originalEvent: KeanuEvent, items: EventAttachment[]; initialItem: EventAttachment | undefined }>();
|
const props = defineProps<{
|
||||||
|
originalEvent: KeanuEvent;
|
||||||
|
items: EventAttachment[];
|
||||||
|
initialItem: EventAttachment | undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
const currentItemIndex: Ref<number> = ref(0);
|
const currentAttachment: Ref<EventAttachment | undefined> = ref(undefined);
|
||||||
const showMoreMenu: Ref<boolean> = ref(false);
|
const showMoreMenu: Ref<boolean> = ref(false);
|
||||||
|
const showInfo: Ref<boolean> = ref(false);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.body.classList.add("dark");
|
document.body.classList.add("dark");
|
||||||
if (props.initialItem) {
|
currentAttachment.value = props.initialItem ? props.initialItem : props.items[0];
|
||||||
currentItemIndex.value = props.items.findIndex((v) => v === props.initialItem);
|
|
||||||
if (currentItemIndex.value < 0) {
|
|
||||||
currentItemIndex.value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|
@ -72,11 +86,7 @@ const displayDate = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const showInfoButton = computed(() => {
|
const showInfoButton = computed(() => {
|
||||||
const item = currentItemIndex.value >= 0 && currentItemIndex.value < props.items.length ? props.items[currentItemIndex.value] : undefined;
|
return currentAttachment.value?.proofHintFlags !== undefined;
|
||||||
if (item) {
|
|
||||||
return item.event.getContent()[CLIENT_EVENT_PROOF_HINT] !== undefined;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const moreMenuItems = computed(() => {
|
const moreMenuItems = computed(() => {
|
||||||
|
|
@ -94,15 +104,15 @@ const moreMenuItems = computed(() => {
|
||||||
watch(props.items, (newValue: EventAttachment[], oldValue: EventAttachment[]) => {
|
watch(props.items, (newValue: EventAttachment[], oldValue: EventAttachment[]) => {
|
||||||
// Added or removed?
|
// Added or removed?
|
||||||
if (newValue && oldValue && newValue.length > oldValue.length) {
|
if (newValue && oldValue && newValue.length > oldValue.length) {
|
||||||
currentItemIndex.value = oldValue.length;
|
currentAttachment.value = newValue[oldValue.length];
|
||||||
} else if (newValue) {
|
} else if (newValue) {
|
||||||
currentItemIndex.value = newValue.length - 1;
|
currentAttachment.value = newValue[newValue.length - 1];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const downloadOne = () => {
|
const downloadOne = () => {
|
||||||
if (currentItemIndex.value >= 0 && currentItemIndex.value < props.items.length) {
|
if (currentAttachment.value) {
|
||||||
util.download($matrix.matrixClient, $matrix.useAuthedMedia, props.items[currentItemIndex.value].event);
|
util.download($matrix.matrixClient, $matrix.useAuthedMedia, currentAttachment.value.event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import {
|
||||||
import proofmode from "../plugins/proofmode";
|
import proofmode from "../plugins/proofmode";
|
||||||
import imageResize from "image-resize";
|
import imageResize from "image-resize";
|
||||||
import { computed, ref, Ref, shallowReactive, unref } 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, CLIENT_EVENT_PROOF_HINT } from "@/plugins/utils";
|
||||||
import { extractProofHintFlags } from "./proof";
|
import { extractProofHintFlags } from "./proof";
|
||||||
|
|
||||||
export class AttachmentManager {
|
export class AttachmentManager {
|
||||||
|
|
@ -220,6 +220,8 @@ export class AttachmentManager {
|
||||||
|
|
||||||
const fileSize = this.getSrcFileSize(event);
|
const fileSize = this.getSrcFileSize(event);
|
||||||
|
|
||||||
|
let proofHintFlags = event.getContent()[CLIENT_EVENT_PROOF_HINT];
|
||||||
|
|
||||||
const attachment: EventAttachment = {
|
const attachment: EventAttachment = {
|
||||||
event: event,
|
event: event,
|
||||||
name: this.getFileName(event),
|
name: this.getFileName(event),
|
||||||
|
|
@ -227,6 +229,8 @@ export class AttachmentManager {
|
||||||
srcProgress: -1,
|
srcProgress: -1,
|
||||||
thumbnailProgress: -1,
|
thumbnailProgress: -1,
|
||||||
autoDownloadable: fileSize <= this.maxSizeAutoDownloads,
|
autoDownloadable: fileSize <= this.maxSizeAutoDownloads,
|
||||||
|
proofHintFlags: proofHintFlags ? JSON.parse(proofHintFlags) : proofHintFlags,
|
||||||
|
proof: undefined,
|
||||||
loadSrc: () => Promise.reject("Not implemented"),
|
loadSrc: () => Promise.reject("Not implemented"),
|
||||||
loadThumbnail: () => Promise.reject("Not implemented"),
|
loadThumbnail: () => Promise.reject("Not implemented"),
|
||||||
loadBlob: () => Promise.reject("Not implemented"),
|
loadBlob: () => Promise.reject("Not implemented"),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { MatrixEvent, Room } from "matrix-js-sdk";
|
import { MatrixEvent, Room } from "matrix-js-sdk";
|
||||||
import { AttachmentBatch } from "./attachment";
|
import { AttachmentBatch } from "./attachment";
|
||||||
|
import { Proof, ProofHintFlags } from "./proof";
|
||||||
|
|
||||||
export type KeanuEventExtension = {
|
export type KeanuEventExtension = {
|
||||||
isMxThread?: boolean;
|
isMxThread?: boolean;
|
||||||
|
|
@ -26,6 +27,8 @@ export type EventAttachment = {
|
||||||
thumbnailProgress: number;
|
thumbnailProgress: number;
|
||||||
thumbnailPromise?: Promise<EventAttachmentUrlData>;
|
thumbnailPromise?: Promise<EventAttachmentUrlData>;
|
||||||
autoDownloadable: boolean;
|
autoDownloadable: boolean;
|
||||||
|
proof?: Proof;
|
||||||
|
proofHintFlags?: ProofHintFlags;
|
||||||
loadSrc: () => Promise<EventAttachmentUrlData>;
|
loadSrc: () => Promise<EventAttachmentUrlData>;
|
||||||
loadThumbnail: () => Promise<EventAttachmentUrlData>;
|
loadThumbnail: () => Promise<EventAttachmentUrlData>;
|
||||||
loadBlob: () => Promise<{data: Blob}>;
|
loadBlob: () => Promise<{data: Blob}>;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,20 @@ class ProofMode {
|
||||||
const worker = await this.getProofcheckWorker();
|
const worker = await this.getProofcheckWorker();
|
||||||
const res = await worker.checkFiles([file]);
|
const res = await worker.checkFiles([file]);
|
||||||
if (res && res.files && res.files.length == 1) {
|
if (res && res.files && res.files.length == 1) {
|
||||||
|
delete res.files[0].data; // Don't need to hang on to the data!
|
||||||
|
return res.files[0];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async proofCheckSource(src: string): Promise<ProofCheckResult | undefined> {
|
||||||
|
try {
|
||||||
|
const worker = await this.getProofcheckWorker();
|
||||||
|
const res = await worker.checkURLs([src]);
|
||||||
|
if (res && res.files && res.files.length == 1) {
|
||||||
|
delete res.files[0].data; // Don't need to hang on to the data!
|
||||||
return res.files[0];
|
return res.files[0];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -35,4 +49,6 @@ class ProofMode {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default new ProofMode();
|
export default new ProofMode();
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Observable, Subject } from "threads/observable";
|
import { Observable, Subject } from "threads/observable";
|
||||||
import { expose } from "threads/worker";
|
import { expose } from "threads/worker";
|
||||||
import { checkFiles } from "@guardianproject/proofmode";
|
import { checkFiles, checkURLs } from "@guardianproject/proofmode";
|
||||||
|
|
||||||
let subject = new Subject();
|
let subject = new Subject();
|
||||||
|
|
||||||
|
|
@ -12,6 +12,9 @@ const check = {
|
||||||
checkFiles: (files) => {
|
checkFiles: (files) => {
|
||||||
return checkFiles(files, sendMessage);
|
return checkFiles(files, sendMessage);
|
||||||
},
|
},
|
||||||
|
checkURLs: (urls) => {
|
||||||
|
return checkURLs(urls, sendMessage);
|
||||||
|
},
|
||||||
values: () => {
|
values: () => {
|
||||||
return Observable.from(subject);
|
return Observable.from(subject);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue