Skip to content

Commit

Permalink
feat: add photosphere viewer integration
Browse files Browse the repository at this point in the history
Fixes #1036
  • Loading branch information
jovanbulck committed Nov 27, 2024
1 parent a1d38f8 commit 6bfcaf4
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 2 deletions.
45 changes: 45 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"@nextcloud/sharing": "^0.2.3",
"@nextcloud/upload": "1.6.0",
"@nextcloud/vue": "^8.19.0",
"@photo-sphere-viewer/autorotate-plugin": "^5.11.1",
"@photo-sphere-viewer/core": "^5.11.1",
"filerobot-image-editor": "^4.8.1",
"fuse.js": "^7.0.0",
"hammerjs": "^2.0.8",
Expand Down
137 changes: 137 additions & 0 deletions src/components/viewer/PhotoSphere.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<template>
<div id="viewer" class="viewer__photosphere top-left fill-block">
<NcButton
id="close-photosphere-viewer"
:ariaLabel="t('memories', 'Close')"
:title="t('memories', 'Close')"
type="tertiary"
@click="close"
>
<CloseThickIcon :size="20" />
</NcButton>
</div>
</template>

<script lang="ts">
import { defineComponent, type PropType } from 'vue';
import { API } from '@services/API';
import type { IPhoto } from '@typings';
import * as utils from '@services/utils';
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js';
import CloseThickIcon from 'vue-material-design-icons/CloseThick.vue';
import { Viewer } from '@photo-sphere-viewer/core';
import { AutorotatePlugin } from '@photo-sphere-viewer/autorotate-plugin';
import '@photo-sphere-viewer/core/index.css';
export default defineComponent({
props: {
photo: {
type: Object as PropType<IPhoto>,
required: true,
},
},
components: {
NcButton,
CloseThickIcon,
},
emits: {
close: () => true,
},
data: () => ({
viewer: null as Viewer | null,
}),
async mounted() {
// Create the photosphere viewer
console.assert(document.getElementById('viewer'), 'PhotoSphere container not found');
this.viewer = new Viewer({
container: 'viewer',
panorama: API.IMAGE_DECODABLE(this.photo.fileid, this.photo.etag),
caption: this.exifTitle() + this.exifDate(),
description: this.exifDesc(),
navbar: ['autorotate', 'zoom', 'move', 'description', 'caption', 'fullscreen'],
plugins: [
[
AutorotatePlugin,
{
autorotatePitch: '5deg',
autostartOnIdle: false,
autostartDelay: null,
},
],
],
});
// Handle keyboard
window.addEventListener('keydown', this.handleKeydown, true);
},
beforeDestroy() {
this.close();
},
methods: {
exifTitle(): string {
const title = this.photo?.imageInfo?.exif?.Title;
if (title) return '<b>' + title + '</b> — ';
return '';
},
exifDesc(): string | undefined {
const desc = this.photo?.imageInfo?.exif?.Description;
return desc;
},
exifDate(): string {
const date = this.photo?.imageInfo?.datetaken;
if (!date) return '';
return utils.getLongDateStr(new Date(date * 1000), false, true);
},
close() {
this.viewer?.destroy();
window.removeEventListener('keydown', this.handleKeydown, true);
this.$emit('close');
},
handleKeydown(event: KeyboardEvent) {
event.stopImmediatePropagation();
if (event.key === 'Escape') {
event.preventDefault();
this.close();
}
},
},
});
</script>

<style lang="scss">
// Take full screen size
.viewer__photosphere {
z-index: 10100;
background-color: black;
box-sizing: content-box;
.psv-container,
.psv-container * {
box-sizing: content-box !important;
}
// Overlay top-right close button
#close-photosphere-viewer {
position: absolute;
right: 0;
top: 0;
z-index: 9999;
//background-color: transparent;
margin-right: 10px;
}
}
</style>
20 changes: 18 additions & 2 deletions src/components/viewer/Viewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
@fullscreenchange="fullscreenChange"
>
<ImageEditor v-if="editorOpen && currentPhoto" :photo="currentPhoto" @close="editorOpen = false" />
<PhotoSphere v-if="sphereOpen && currentPhoto" :photo="currentPhoto" @close="sphereOpen = false" />

<!-- Loading indicator -->
<XLoadingIcon class="loading-icon centered" v-if="loading" />

<div
ref="inner"
class="inner"
v-show="!editorOpen"
v-show="!editorOpen && !sphereOpen"
@pointermove.passive="setUiVisible"
@pointerdown.passive="setUiVisible"
>
Expand Down Expand Up @@ -66,6 +67,7 @@ import * as utils from '@services/utils';
import * as nativex from '@native';
import ImageEditor from './ImageEditor.vue';
import PhotoSphere from './PhotoSphere.vue';
import PhotoSwipe, { type PhotoSwipeOptions } from 'photoswipe';
import 'photoswipe/style.css';
import PsImage from './PsImage';
Expand All @@ -84,6 +86,7 @@ import DownloadIcon from 'vue-material-design-icons/Download.vue';
import InfoIcon from 'vue-material-design-icons/InformationOutline.vue';
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue';
import TuneIcon from 'vue-material-design-icons/Tune.vue';
import PanoramaSphereOutlineIcon from 'vue-material-design-icons/PanoramaSphereOutline.vue';
import SlideshowIcon from 'vue-material-design-icons/PlayBox.vue';
import EditFileIcon from 'vue-material-design-icons/FileEdit.vue';
import AlbumRemoveIcon from 'vue-material-design-icons/BookRemove.vue';
Expand Down Expand Up @@ -116,6 +119,7 @@ export default defineComponent({
NcActions,
NcActionButton,
ImageEditor,
PhotoSphere,
},
mixins: [UserConfig],
Expand All @@ -125,6 +129,7 @@ export default defineComponent({
isOpen: false,
originalTitle: null as string | null,
editorOpen: false,
sphereOpen: false,
editorSrc: '',
show: false,
Expand Down Expand Up @@ -277,6 +282,13 @@ export default defineComponent({
callback: this.toggleSidebar,
if: true,
},
{
id: 'sphere',
name: this.t('memories', 'Open in PhotoSphere'),
icon: PanoramaSphereOutlineIcon,
callback: this.openSphere,
if: !this.isVideo,
},
{
id: 'edit',
name: this.t('memories', 'Edit'),
Expand Down Expand Up @@ -382,7 +394,7 @@ export default defineComponent({
/** Allow closing the viewer */
allowClose(): boolean {
return !this.editorOpen && !dav.isSingleItem() && !this.slideshowTimer;
return !this.editorOpen && !this.sphereOpen && !dav.isSingleItem() && !this.slideshowTimer;
},
/** Get date taken string */
Expand Down Expand Up @@ -1016,6 +1028,10 @@ export default defineComponent({
this.editorOpen = true;
},
async openSphere() {
this.sphereOpen = true;
},
/** Share the current photo externally */
shareCurrent() {
_m.modals.sharePhotos([this.currentPhoto!]);
Expand Down

0 comments on commit 6bfcaf4

Please sign in to comment.