memories/src/components/modal/ShareModal.vue

264 lines
6.4 KiB
Vue

<template>
<Modal @close="close" size="normal" v-if="photo">
<template #title>
{{ t('memories', 'Share File') }}
</template>
<div class="loading-icon fill-block" v-if="loading > 0">
<XLoadingIcon />
</div>
<ul class="options" v-else>
<NcListItem
v-if="canShareNative && !isVideo && !isLocal"
:title="t('memories', 'Reduced Size')"
:bold="false"
@click.prevent="sharePreview()"
>
<template #icon>
<PhotoIcon class="avatar" :size="24" />
</template>
<template #subtitle>
{{ t('memories', 'Share a lower resolution image preview') }}
</template>
</NcListItem>
<NcListItem
v-if="canShareNative && canShareHighRes"
:title="t('memories', 'High Resolution')"
:bold="false"
@click.prevent="shareHighRes()"
>
<template #icon>
<LargePhotoIcon class="avatar" :size="24" />
</template>
<template #subtitle>
{{
isVideo
? t('memories', 'Share the video as a high quality MOV')
: t('memories', 'Share the image as a high quality JPEG')
}}
</template>
</NcListItem>
<NcListItem
v-if="canShareNative"
:title="t('memories', 'Original File')"
:bold="false"
@click.prevent="shareOriginal()"
>
<template #icon>
<FileIcon class="avatar" :size="24" />
</template>
<template #subtitle>
{{ t('memories', 'Share the original image / video file') }}
</template>
</NcListItem>
<NcListItem v-if="canShareLink" :title="t('memories', 'Public Link')" :bold="false" @click.prevent="shareLink()">
<template #icon>
<LinkIcon class="avatar" :size="24" />
</template>
<template #subtitle>
{{ t('memories', 'Share an external Nextcloud link') }}
</template>
</NcListItem>
</ul>
</Modal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { showError } from '@nextcloud/dialogs';
import axios from '@nextcloud/axios';
const NcListItem = () => import('@nextcloud/vue/dist/Components/NcListItem');
import Modal from './Modal.vue';
import UserConfig from '../../mixins/UserConfig';
import { IPhoto } from '../../types';
import { API } from '../../services/API';
import * as dav from '../../services/DavRequests';
import * as utils from '../../services/Utils';
import * as nativex from '../../native';
import PhotoIcon from 'vue-material-design-icons/Image.vue';
import LargePhotoIcon from 'vue-material-design-icons/ImageArea.vue';
import LinkIcon from 'vue-material-design-icons/LinkVariant.vue';
import FileIcon from 'vue-material-design-icons/File.vue';
export default defineComponent({
name: 'ShareModal',
components: {
NcListItem,
Modal,
PhotoIcon,
LargePhotoIcon,
LinkIcon,
FileIcon,
},
mixins: [UserConfig],
data: () => {
return {
photo: null as IPhoto | null,
loading: 0,
};
},
created() {
globalThis.sharePhoto = (photo: IPhoto) => {
this.photo = photo;
this.loading = 0;
};
},
computed: {
isVideo(): boolean {
return !!this.photo && (this.photo.mimetype?.startsWith('video/') || !!(this.photo.flag & this.c.FLAG_IS_VIDEO));
},
canShareNative(): boolean {
return 'share' in navigator || nativex.has();
},
canShareHighRes(): boolean {
return !this.isLocal && (!this.isVideo || !this.config.vod_disable);
},
canShareLink(): boolean {
return !!this.photo?.imageInfo?.permissions?.includes('S');
},
isLocal(): boolean {
return utils.isLocalPhoto(this.photo);
},
},
methods: {
close() {
this.photo = null;
},
async l(cb: Function) {
try {
this.loading++;
await cb();
} finally {
this.loading--;
}
},
async sharePreview() {
const src = utils.getPreviewUrl({
photo: this.photo!,
size: 2048,
});
this.shareWithHref(src, true);
},
async shareHighRes() {
const fileid = this.photo!.fileid;
const src = this.isVideo ? API.VIDEO_TRANSCODE(fileid, 'max.mov') : API.IMAGE_DECODABLE(fileid, this.photo!.etag);
this.shareWithHref(src, !this.isVideo);
},
async shareOriginal() {
if (this.isLocal) {
return this.l(async () => await nativex.shareLocal(this.photo!.fileid));
}
this.shareWithHref(dav.getDownloadLink(this.photo!));
},
async shareLink() {
this.l(async () => {
const fileInfo = (await dav.getFiles([this.photo!]))[0];
globalThis.shareNodeLink(fileInfo.filename, true);
});
this.close();
},
/**
* Download a file and then share the blob.
*/
async shareWithHref(href: string, replaceExt = false) {
if (nativex.has()) {
return await this.l(async () => nativex.shareBlobFromUrl(href));
}
let blob: Blob | undefined;
await this.l(async () => {
const res = await axios.get(href, { responseType: 'blob' });
blob = res.data;
});
if (!blob) {
showError(this.t('memories', 'Failed to download file'));
return;
}
let basename = this.photo?.basename ?? 'blank';
if (replaceExt) {
// Fix basename extension
let targetExts: string[] = [];
if (blob.type === 'image/png') {
targetExts = ['png'];
} else {
targetExts = ['jpg', 'jpeg'];
}
// Append extension if not found
const baseExt = basename.split('.').pop()?.toLowerCase() ?? '';
if (!targetExts.includes(baseExt)) {
basename += '.' + targetExts[0];
}
}
const data = {
files: [
new File([blob], basename, {
type: blob.type,
}),
],
};
if (!(<any>navigator).canShare(data)) {
showError(this.t('memories', 'Cannot share this type of data'));
}
try {
await navigator.share(data);
} catch (e) {
// Don't show this error because it's silly stuff
// like "share canceled"
console.error(e);
}
},
},
});
</script>
<style lang="scss" scoped>
.loading-icon {
min-height: 240px;
}
ul.options {
padding-top: 10px;
padding-bottom: 5px;
:deep .avatar {
padding: 0 0.5em;
}
@media (max-width: 512px) {
font-size: 0.9em;
}
}
</style>