forked from jsnbuchanan/crowd-funder-for-time-pwa
- Add configurable entity display logic via function props to EntityGrid - Implement comprehensive test suite for EntityGrid function props in TestView - Apply consistent code formatting across 15 components and views - Fix linting issues with trailing commas and line breaks - Add new EntityGridFunctionPropTest.vue for component testing - Update endorserServer with improved error handling and logging - Streamline PlatformServiceMixin with better cache management - Enhance component documentation and type safety Changes span 15 files with 159 additions and 69 deletions, focusing on component flexibility, code quality, and testing infrastructure.
96 lines
2.5 KiB
Vue
96 lines
2.5 KiB
Vue
<template>
|
|
<Teleport to="body">
|
|
<Transition name="fade">
|
|
<div v-if="isOpen" class="fixed inset-0 z-50 flex flex-col bg-black/90">
|
|
<!-- Header bar - fixed height to prevent overlap -->
|
|
<div class="h-16 flex justify-between items-center px-4 bg-black">
|
|
<button
|
|
class="text-white text-2xl p-2 rounded-full hover:bg-white/10"
|
|
@click="close"
|
|
>
|
|
<font-awesome icon="xmark" />
|
|
</button>
|
|
|
|
<!-- Mobile share button -->
|
|
<button
|
|
v-if="isMobile"
|
|
class="text-white text-xl p-2 rounded-full hover:bg-white/10"
|
|
@click="handleShare"
|
|
>
|
|
<font-awesome icon="ellipsis" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Image container - fill remaining space -->
|
|
<div class="flex-1 flex items-center justify-center p-2">
|
|
<div class="w-full h-full flex items-center justify-center">
|
|
<img
|
|
:src="transformedImageUrl"
|
|
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
|
|
alt="expanded shared content"
|
|
@click="close"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Vue, Prop } from "vue-facing-decorator";
|
|
import { UAParser } from "ua-parser-js";
|
|
import { logger } from "../utils/logger";
|
|
|
|
@Component({ emits: ["update:isOpen"] })
|
|
export default class ImageViewer extends Vue {
|
|
@Prop() imageUrl!: string;
|
|
@Prop() imageData!: Blob | null;
|
|
@Prop() isOpen!: boolean;
|
|
|
|
userAgent = new UAParser();
|
|
|
|
get isMobile() {
|
|
const os = this.userAgent.getOS().name;
|
|
return os === "iOS" || os === "Android";
|
|
}
|
|
|
|
close() {
|
|
this.$emit("update:isOpen", false);
|
|
}
|
|
|
|
async handleShare() {
|
|
const os = this.userAgent.getOS().name;
|
|
|
|
try {
|
|
if (os === "iOS" || os === "Android") {
|
|
if (navigator.share) {
|
|
// Always share the URL since it's more reliable across platforms
|
|
await navigator.share({
|
|
url: this.imageUrl,
|
|
});
|
|
} else {
|
|
// Fallback for browsers without share API
|
|
window.open(this.imageUrl, "_blank");
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logger.warn("Share failed, opening in new tab:", error);
|
|
window.open(this.imageUrl, "_blank");
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
</style>
|