feat: preview multiple images

zio/stable
Anthony Fu 2022-11-30 11:27:19 +08:00
parent 568a333d7c
commit cf7cd1fd6c
7 changed files with 92 additions and 23 deletions

View File

@ -45,11 +45,21 @@ function getFieldNameIcon(fieldName: string) {
}
function previewHeader() {
openImagePreviewDialog({ src: account.header, alt: `${account.username}'s profile header` })
openMediaPreview([{
id: `${account.acct}:header`,
type: 'image',
previewUrl: account.header,
description: `${account.username}'s profile header`,
}])
}
function previewAvatar() {
openImagePreviewDialog({ src: account.avatar, alt: account.username })
openMediaPreview([{
id: `${account.acct}:avatar`,
type: 'image',
previewUrl: account.avatar,
description: `${account.username}'s avatar`,
}])
}
watchEffect(() => {

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import {
isEditHistoryDialogOpen,
isImagePreviewDialogOpen,
isMediaPreviewOpen,
isPreviewHelpOpen,
isPublishDialogOpen,
isSigninDialogOpen,
@ -18,8 +18,8 @@ import {
<ModalDialog v-model="isPublishDialogOpen">
<PublishWidget :draft-key="dialogDraftKey" expanded min-w-180 />
</ModalDialog>
<ModalDialog v-model="isImagePreviewDialogOpen" type="preview">
<img :src="imagePreview.src" :alt="imagePreview.alt" max-w-95vw max-h-95vh>
<ModalDialog v-model="isMediaPreviewOpen" close-button>
<ModalMediaPreview v-if="isMediaPreviewOpen" @close="closeMediaPreview()" />
</ModalDialog>
<ModalDialog v-model="isEditHistoryDialogOpen">
<StatusEditPreview :edit="statusEdit" />

View File

@ -1,12 +1,14 @@
<script setup lang='ts'>
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
type DialogType = 'top' | 'right' | 'bottom' | 'left' | 'dialog' | 'preview'
type DialogType = 'top' | 'right' | 'bottom' | 'left' | 'dialog'
const {
type = 'dialog',
closeButton = false,
} = defineProps<{
type?: DialogType
closeButton?: boolean
}>()
const { modelValue } = defineModel<{
@ -21,8 +23,6 @@ const positionClass = computed(() => {
switch (type) {
case 'dialog':
return 'border rounded top-1/2 -translate-y-1/2 left-1/2 -translate-x-1/2'
case 'preview':
return 'border rounded top-1/2 -translate-y-1/2 left-1/2 -translate-x-1/2'
case 'bottom':
return 'bottom-0 left-0 right-0 border-t'
case 'top':
@ -41,8 +41,6 @@ const transformClass = computed(() => {
switch (type) {
case 'dialog':
return 'op0'
case 'preview':
return 'op0'
case 'bottom':
return 'translate-y-[100%]'
case 'top':
@ -123,7 +121,13 @@ function onTransitionEnd() {
>
<slot />
</div>
<button v-if="type === 'preview'" btn-action-icon bg="black/20" aria-label="Close" hover:bg="black/40" dark:bg="white/10" dark:hover:bg="white/20" absolute top-0 right-0 m1 @click="close">
<button
v-if="closeButton"
btn-action-icon bg="black/20" aria-label="Close"
hover:bg="black/40" dark:bg="white/10" dark:hover:bg="white/20"
absolute top-0 right-0 m1
@click="close"
>
<div i-ri:close-fill text-white />
</button>
</div>

View File

@ -0,0 +1,44 @@
<script setup lang="ts">
const emit = defineEmits(['close'])
const current = computed(() => mediaPreviewList.value[mediaPreviewIndex.value])
const hasNext = computed(() => mediaPreviewIndex.value < mediaPreviewList.value.length - 1)
const hasPrev = computed(() => mediaPreviewIndex.value > 0)
const keys = useMagicKeys()
whenever(keys.arrowLeft, prev)
whenever(keys.arrowRight, next)
function next() {
if (hasNext.value)
mediaPreviewIndex.value++
}
function prev() {
if (hasPrev.value)
mediaPreviewIndex.value--
}
function onClick(e: MouseEvent) {
const path = e.composedPath() as HTMLElement[]
const el = path.find(el => ['A', 'BUTTON', 'IMG', 'VIDEO'].includes(el.tagName?.toUpperCase()))
if (!el)
emit('close')
}
</script>
<template>
<div relative h-screen w-screen flex select-none @click="onClick">
<div absolute top-0 left-0 right-0 text-center>
{{ mediaPreviewIndex + 1 }} / {{ mediaPreviewList.length }}
</div>
<button v-if="hasNext" btn-action-icon absolute top="1/2" right-1 title="Next" @click="next">
<div i-ri:arrow-right-s-line />
</button>
<button v-if="hasPrev" btn-action-icon absolute top="1/2" left-1 title="Next" @click="prev">
<div i-ri:arrow-left-s-line />
</button>
<img :src="current.url || current.previewUrl" :alt="current.description || ''" max-w-95vw max-h-95vh ma>
</div>
</template>

View File

@ -4,6 +4,7 @@ import type { Attachment } from 'masto'
const { attachment } = defineProps<{
attachment: Attachment
attachments?: Attachment[]
}>()
const src = $computed(() => attachment.remoteUrl || attachment.url || attachment.previewUrl!)
@ -62,10 +63,7 @@ const aspectRatio = computed(() => {
focus:ring="2 primary inset"
rounded-lg
aria-label="Open image preview dialog"
@click="openImagePreviewDialog({
src,
alt: attachment.description!,
})"
@click="openMediaPreview(attachments ? attachments : [attachment], attachments?.indexOf(attachment) || 0)"
>
<CommonBlurhash
:blurhash="attachment.blurhash"

View File

@ -9,7 +9,11 @@ const { status } = defineProps<{
<template>
<div class="status-media-container" :class="`status-media-container-${status.mediaAttachments.length}`">
<template v-for="attachment of status.mediaAttachments" :key="attachment.id">
<StatusAttachment :attachment="attachment" class="w-full h-full" />
<StatusAttachment
:attachment="attachment"
:attachments="status.mediaAttachments"
class="w-full h-full"
/>
</template>
</div>
</template>

View File

@ -1,20 +1,24 @@
import type { StatusEdit } from 'masto'
import type { Attachment, StatusEdit } from 'masto'
import type { Draft } from './statusDrafts'
import { STORAGE_KEY_FIRST_VISIT, STORAGE_KEY_ZEN_MODE } from '~/constants'
export const imagePreview = ref({ src: '', alt: '' })
export const mediaPreviewList = ref<Attachment[]>([])
export const mediaPreviewIndex = ref(0)
export const statusEdit = ref<StatusEdit>()
export const dialogDraftKey = ref<string>()
export const isFirstVisit = useLocalStorage(STORAGE_KEY_FIRST_VISIT, !process.mock)
export const isZenMode = useLocalStorage(STORAGE_KEY_ZEN_MODE, false)
export const toggleZenMode = useToggle(isZenMode)
export const isSigninDialogOpen = ref(false)
export const isPublishDialogOpen = ref(false)
export const isImagePreviewDialogOpen = ref(false)
export const isMediaPreviewOpen = ref(false)
export const isEditHistoryDialogOpen = ref(false)
export const isPreviewHelpOpen = ref(isFirstVisit.value)
export const toggleZenMode = useToggle(isZenMode)
export function openSigninDialog() {
isSigninDialogOpen.value = true
}
@ -46,9 +50,14 @@ if (isPreviewHelpOpen.value) {
})
}
export function openImagePreviewDialog(image: { src: string; alt: string }) {
imagePreview.value = image
isImagePreviewDialogOpen.value = true
export function openMediaPreview(attachments: Attachment[], index = 0) {
mediaPreviewList.value = attachments
mediaPreviewIndex.value = index
isMediaPreviewOpen.value = true
}
export function closeMediaPreview() {
isMediaPreviewOpen.value = false
}
export function openEditHistoryDialog(edit: StatusEdit) {