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

Jason Buchanan 1 week ago
parent
commit
f4c7805266
No known key found for this signature in database GPG Key ID: 1A12361012426318
  1. 56
      src/components/ImageViewer.vue
  2. 22
      src/views/HomeView.vue

56
src/components/ImageViewer.vue

@ -3,38 +3,84 @@
<Transition name="fade"> <Transition name="fade">
<div <div
v-if="isOpen" v-if="isOpen"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/90" class="fixed inset-0 z-50 flex flex-col bg-black/90"
@click="close"
> >
<!-- Header bar - fixed height to prevent overlap -->
<div class="h-16 flex justify-between items-center px-4 bg-black">
<button <button
class="absolute top-4 right-4 text-white text-2xl p-2 rounded-full hover:bg-white/10" class="text-white text-2xl p-2 rounded-full hover:bg-white/10"
@click="close" @click="close"
> >
<fa icon="xmark" /> <fa icon="xmark" />
</button> </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 <img
:src="imageUrl" :src="imageUrl"
class="max-h-screen max-w-screen-md w-full object-contain p-4" class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
@click.stop @click.stop
alt="expanded shared content" alt="expanded shared content"
/> />
</div> </div>
</div>
</div>
</Transition> </Transition>
</Teleport> </Teleport>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop } from "vue-facing-decorator"; 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 { export default class ImageViewer extends Vue {
@Prop() imageUrl!: string; @Prop() imageUrl!: string;
@Prop() imageData!: Blob | null;
@Prop() isOpen!: boolean; @Prop() isOpen!: boolean;
userAgent = new UAParser();
get isMobile() {
const os = this.userAgent.getOS().name;
return os === "iOS" || os === "Android";
}
close() { close() {
this.$emit("update:isOpen", false); 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> </script>

22
src/views/HomeView.vue

@ -355,6 +355,7 @@
:src="record.image" :src="record.image"
class="w-full aspect-[3/2] object-cover rounded-xl mt-2" class="w-full aspect-[3/2] object-cover rounded-xl mt-2"
alt="shared content" alt="shared content"
@load="cacheImageData($event, record.image)"
/> />
</div> </div>
</div> </div>
@ -376,7 +377,11 @@
<ChoiceButtonDialog ref="choiceButtonDialog" /> <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> </template>
<script lang="ts"> <script lang="ts">
@ -501,7 +506,9 @@ export default class HomeView extends Vue {
showShortcutBvc = false; showShortcutBvc = false;
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
selectedImage = ""; selectedImage = "";
selectedImageData: Blob | null = null;
isImageViewerOpen = false; isImageViewerOpen = false;
imageCache: Map<string, Blob | null> = new Map();
async mounted() { async mounted() {
try { 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.selectedImage = imageUrl;
this.isImageViewerOpen = true; this.isImageViewerOpen = true;
} }

Loading…
Cancel
Save