feat: preview multiple images
This commit is contained in:
parent
568a333d7c
commit
cf7cd1fd6c
|
@ -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(() => {
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
44
components/modal/ModalMediaPreview.vue
Normal file
44
components/modal/ModalMediaPreview.vue
Normal 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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue