264 lines
6.4 KiB
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>
|