Browse Source

feat(feed): improving the image viewer, to be more conventional, and also allowing the viewer to download the image on mobile with `...` control

pull/124/head
Jason Buchanan 7 days ago
parent
commit
f4c7805266
No known key found for this signature in database GPG Key ID: 1A12361012426318
  1. 78
      src/components/ImageViewer.vue
  2. 24
      src/views/HomeView.vue

78
src/components/ImageViewer.vue

@ -3,22 +3,38 @@
<Transition name="fade">
<div
v-if="isOpen"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/90"
@click="close"
class="fixed inset-0 z-50 flex flex-col bg-black/90"
>
<button
class="absolute top-4 right-4 text-white text-2xl p-2 rounded-full hover:bg-white/10"
@click="close"
>
<fa icon="xmark" />
</button>
<img
:src="imageUrl"
class="max-h-screen max-w-screen-md w-full object-contain p-4"
@click.stop
alt="expanded shared content"
/>
<!-- 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"
>
<fa 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"
>
<fa 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="imageUrl"
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
@click.stop
alt="expanded shared content"
/>
</div>
</div>
</div>
</Transition>
</Teleport>
@ -26,15 +42,45 @@
<script lang="ts">
import { Component, Vue, Prop } from "vue-facing-decorator";
import { UAParser } from "ua-parser-js";
@Component
@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) {
console.warn("Share failed, opening in new tab:", error);
window.open(this.imageUrl, "_blank");
}
}
}
</script>

24
src/views/HomeView.vue

@ -282,7 +282,7 @@
</span>
</span>
<span class="col-span-10 justify-self-stretch overflow-hidden">
<!-- show giver and/or receiver profiles... which seemed like a good idea but actually adds clutter
<!-- show giver and/or receiver profiles... which seemed like a good idea but actually adds clutter
<span
v-if="
record.giver.profileImageUrl ||
@ -355,6 +355,7 @@
:src="record.image"
class="w-full aspect-[3/2] object-cover rounded-xl mt-2"
alt="shared content"
@load="cacheImageData($event, record.image)"
/>
</div>
</div>
@ -376,7 +377,11 @@
<ChoiceButtonDialog ref="choiceButtonDialog" />
<ImageViewer :image-url="selectedImage" v-model:is-open="isImageViewerOpen" />
<ImageViewer
:image-url="selectedImage"
:image-data="selectedImageData"
v-model:is-open="isImageViewerOpen"
/>
</template>
<script lang="ts">
@ -501,7 +506,9 @@ export default class HomeView extends Vue {
showShortcutBvc = false;
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
selectedImage = "";
selectedImageData: Blob | null = null;
isImageViewerOpen = false;
imageCache: Map<string, Blob | null> = new Map();
async mounted() {
try {
@ -984,7 +991,18 @@ export default class HomeView extends Vue {
});
}
openImageViewer(imageUrl: string) {
async cacheImageData(event: Event, imageUrl: string) {
try {
// For images that might fail CORS, just store the URL
// The Web Share API will handle sharing the URL appropriately
this.imageCache.set(imageUrl, null);
} catch (error) {
console.warn("Failed to cache image:", error);
}
}
async openImageViewer(imageUrl: string) {
this.selectedImageData = this.imageCache.get(imageUrl) ?? null;
this.selectedImage = imageUrl;
this.isImageViewerOpen = true;
}

Loading…
Cancel
Save