After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 931 B |
After Width: | Height: | Size: 927 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 709 B |
After Width: | Height: | Size: 562 B |
After Width: | Height: | Size: 316 B |
After Width: | Height: | Size: 748 B |
After Width: | Height: | Size: 759 B |
After Width: | Height: | Size: 742 B |
After Width: | Height: | Size: 516 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 669 B |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 895 B |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 978 B |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 413 B |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 702 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 769 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 983 B |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 796 B |
After Width: | Height: | Size: 619 B |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 639 B |
After Width: | Height: | Size: 3.3 KiB |
@ -0,0 +1,5 @@
|
||||
<script lang="ts" setup></script> |
||||
|
||||
<template> |
||||
<WorkspaceIntegrationsView /> |
||||
</template> |
@ -1,156 +1,379 @@
|
||||
<script lang="ts" setup> |
||||
import { onKeyDown } from '@vueuse/core' |
||||
import type { CarouselApi } from '../../nc/Carousel/interface' |
||||
import { useAttachmentCell } from './utils' |
||||
import { isOffice } from '~/utils/fileUtils' |
||||
|
||||
const { selectedImage, visibleItems, downloadFile } = useAttachmentCell()! |
||||
const { selectedFile, visibleItems, downloadAttachment, removeFile, renameFile, isPublic, isReadonly, isRenameModalOpen } = |
||||
useAttachmentCell()! |
||||
|
||||
const carouselRef = ref() |
||||
const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false)) |
||||
|
||||
const container = ref() |
||||
const { isSharedForm } = useSmartsheetStoreOrThrow() |
||||
|
||||
const imageItems = computed(() => visibleItems.value.filter((item) => isImage(item.title, item.mimetype))) |
||||
/* |
||||
const openComments = ref(false) |
||||
*/ |
||||
|
||||
const { isUIAllowed } = useRoles() |
||||
|
||||
const container = ref<HTMLElement | null>(null) |
||||
|
||||
const emblaMainApi: CarouselApi = ref() |
||||
const emblaThumbnailApi: CarouselApi = ref() |
||||
const selectedIndex = ref() |
||||
|
||||
const filetoDelete = reactive({ |
||||
title: '', |
||||
i: 0, |
||||
}) |
||||
const isModalOpen = ref(false) |
||||
|
||||
function onRemoveFileClick(title: any, i: number) { |
||||
isModalOpen.value = true |
||||
filetoDelete.title = title |
||||
filetoDelete.i = i |
||||
} |
||||
|
||||
const handleFileDelete = (i: number) => { |
||||
removeFile(i) |
||||
isModalOpen.value = false |
||||
filetoDelete.i = 0 |
||||
filetoDelete.title = '' |
||||
} |
||||
|
||||
const { getPossibleAttachmentSrc } = useAttachment() |
||||
|
||||
/** navigate to previous image on button click */ |
||||
onKeyDown( |
||||
(e) => ['Left', 'ArrowLeft', 'A'].includes(e.key), |
||||
() => { |
||||
if (carouselRef.value) carouselRef.value.prev() |
||||
}, |
||||
) |
||||
|
||||
/** navigate to next image on button click */ |
||||
onKeyDown( |
||||
(e) => ['Right', 'ArrowRight', 'D'].includes(e.key), |
||||
() => { |
||||
if (carouselRef.value) carouselRef.value.next() |
||||
}, |
||||
) |
||||
|
||||
/** set our selected image when slide changes */ |
||||
function onSlideChange(index: number) { |
||||
selectedImage.value = imageItems.value[index] |
||||
useEventListener(container, 'click', (e) => { |
||||
const target = e.target as HTMLElement |
||||
if (!target.closest('.keep-open') && !target.closest('.nc-button') && !target.closest('img') && !target.closest('video')) { |
||||
selectedFile.value = false |
||||
} |
||||
}) |
||||
|
||||
const onThumbClick = (index: number) => { |
||||
if (!emblaMainApi.value || !emblaThumbnailApi.value) return |
||||
|
||||
emblaMainApi.value.scrollTo(index) |
||||
emblaThumbnailApi.value.scrollTo(index) |
||||
} |
||||
|
||||
const onSelect = () => { |
||||
if (!emblaMainApi.value || !emblaThumbnailApi.value) return |
||||
|
||||
const newSnap = emblaMainApi.value.selectedScrollSnap() |
||||
|
||||
selectedIndex.value = newSnap |
||||
selectedFile.value = visibleItems.value[newSnap] |
||||
emblaThumbnailApi.value.scrollTo(newSnap) |
||||
} |
||||
|
||||
/** set our carousel ref and move to initial slide */ |
||||
const setCarouselRef = (el: Element) => { |
||||
carouselRef.value = el |
||||
const goPrev = () => { |
||||
if (!emblaMainApi.value || !emblaThumbnailApi.value) return |
||||
|
||||
carouselRef.value?.goTo( |
||||
imageItems.value.findIndex((item) => item === selectedImage.value), |
||||
true, |
||||
) |
||||
emblaMainApi.value.scrollPrev() |
||||
emblaThumbnailApi.value.scrollPrev() |
||||
} |
||||
|
||||
/** close overlay view when clicking outside of image */ |
||||
useEventListener(container, 'click', (e) => { |
||||
if (!(e.target as HTMLElement)?.closest('.keep-open') && !(e.target as HTMLElement)?.closest('img')) { |
||||
selectedImage.value = false |
||||
const goNext = () => { |
||||
if (!emblaMainApi.value || !emblaThumbnailApi.value) return |
||||
|
||||
emblaMainApi.value.scrollNext() |
||||
emblaThumbnailApi.value.scrollNext() |
||||
} |
||||
|
||||
// When the carousel is initialized, we set the selected index to the index of the selected file |
||||
// and scroll to that index. We only need to do this once, so we use watchOnce. |
||||
watchOnce(emblaMainApi, async (emblaMainApi) => { |
||||
if (!emblaMainApi) return |
||||
|
||||
// The focus is set to the container so that the keyboard navigation works |
||||
container.value?.focus() |
||||
|
||||
emblaThumbnailApi.value?.on('reInit', onSelect) |
||||
|
||||
emblaMainApi.on('select', onSelect) |
||||
|
||||
await nextTick(() => { |
||||
if (!selectedIndex.value) { |
||||
const newIndex = visibleItems.value.findIndex((item) => { |
||||
if (selectedFile.value?.path) return item?.path === selectedFile.value.path |
||||
if (selectedFile.value?.url) return item?.url === selectedFile.value.url |
||||
return selectedFile.value?.title === item?.title |
||||
}) |
||||
|
||||
selectedIndex.value = newIndex |
||||
emblaMainApi.scrollTo(newIndex) |
||||
} |
||||
}) |
||||
}) |
||||
|
||||
onMounted(() => { |
||||
document.addEventListener('keydown', onKeyDown) |
||||
}) |
||||
|
||||
onUnmounted(() => { |
||||
document.removeEventListener('keydown', onKeyDown) |
||||
}) |
||||
|
||||
function onKeyDown(event: KeyboardEvent) { |
||||
if (isRenameModalOpen.value) return |
||||
const prevKey = ['ArrowLeft', 'Left', 'a', 'A'] |
||||
const nextKey = ['ArrowRight', 'Right', 'd', 'D'] |
||||
|
||||
if (prevKey.includes(event.key)) { |
||||
event.preventDefault() |
||||
emblaMainApi.value?.scrollPrev() |
||||
return |
||||
} |
||||
|
||||
if (nextKey.includes(event.key)) { |
||||
event.preventDefault() |
||||
emblaMainApi.value?.scrollNext() |
||||
} |
||||
} |
||||
|
||||
/* const toggleComment = () => { |
||||
openComments.value = !openComments.value |
||||
} |
||||
|
||||
onMounted(() => { |
||||
if (!isPublic.value && !isExpandedFormOpen.value && isUIAllowed('commentList')) { |
||||
const { loadComments } = useRowCommentsOrThrow() |
||||
loadComments() |
||||
} |
||||
}) |
||||
*/ |
||||
|
||||
const initEmblaApi = (val: any) => { |
||||
emblaMainApi.value = val |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<GeneralOverlay v-model="selectedImage" :z-index="1001" class="bg-gray-500 bg-opacity-50"> |
||||
<template v-if="selectedImage"> |
||||
<div ref="container" class="overflow-hidden p-12 text-center relative xs:h-screen"> |
||||
<div class="text-white group absolute top-5 right-5"> |
||||
<component |
||||
:is="iconMap.closeCircle" |
||||
class="group-hover:text-red-500 cursor-pointer text-4xl" |
||||
@click.stop="selectedImage = false" |
||||
/> |
||||
</div> |
||||
<GeneralOverlay v-model="selectedFile" transition :z-index="isExpandedFormOpen ? 1000 : 50" class="bg-black bg-opacity-90"> |
||||
<div class="flex w-full h-full"> |
||||
<div |
||||
v-if="selectedFile" |
||||
ref="container" |
||||
class="flex w-full overflow-hidden justify-center text-center relative h-screen items-center" |
||||
> |
||||
<NcButton |
||||
class="top-5 !absolute cursor-pointer !z-30 !hover:bg-transparent left-5" |
||||
size="xsmall" |
||||
type="text" |
||||
@click.stop="selectedFile = false" |
||||
> |
||||
<component :is="iconMap.close" class="text-white" /> |
||||
</NcButton> |
||||
|
||||
<div |
||||
class="keep-open select-none group hover:(ring-1 ring-accent) ring-opacity-100 cursor-pointer leading-8 inline-block px-3 py-1 bg-gray-300 text-white mb-4 text-center rounded shadow" |
||||
@click.stop="downloadFile(selectedImage)" |
||||
class="keep-open select-none absolute top-5 pointer-events-none inset-x-0 mx-auto group flex items-center justify-center leading-8 inline-block text-center rounded shadow" |
||||
> |
||||
<h3 class="group-hover:text-primary">{{ selectedImage && selectedImage.title }}</h3> |
||||
<h3 |
||||
style="width: max-content" |
||||
class="hover:underline pointer-events-auto font-semibold cursor-pointer text-white" |
||||
@click.stop="downloadAttachment(selectedFile)" |
||||
> |
||||
{{ selectedFile && selectedFile.title }} |
||||
</h3> |
||||
</div> |
||||
|
||||
<a-carousel |
||||
v-if="!!selectedImage" |
||||
:ref="setCarouselRef" |
||||
dots-class="slick-dots slick-thumb" |
||||
:after-change="onSlideChange" |
||||
arrows |
||||
<NcCarousel class="!absolute inset-y-16 inset-x-24 keep-open flex justify-center items-center" @init-api="initEmblaApi"> |
||||
<NcCarouselContent> |
||||
<NcCarouselItem v-for="(item, index) in visibleItems" :key="index"> |
||||
<div v-if="selectedIndex === index" class="justify-center w-full h-full flex items-center"> |
||||
<LazyCellAttachmentPreviewImage |
||||
v-if="isImage(item.title, item.mimeType)" |
||||
class="nc-attachment-img-wrapper" |
||||
object-fit="contain" |
||||
:alt="item.title" |
||||
:srcs="getPossibleAttachmentSrc(item)" |
||||
/> |
||||
|
||||
<LazyCellAttachmentPreviewVideo |
||||
v-else-if="isVideo(item.title, item.mimeType)" |
||||
class="flex items-center w-full" |
||||
:mime-type="item.mimeType" |
||||
:title="item.title" |
||||
:src="getPossibleAttachmentSrc(item)" |
||||
/> |
||||
<LazyCellAttachmentPreviewPdf |
||||
v-else-if="isPdf(item.title, item.mimeType)" |
||||
class="keep-open" |
||||
:src="getPossibleAttachmentSrc(item)" |
||||
/> |
||||
<LazyCellAttachmentPreviewMiscOffice |
||||
v-else-if="isOffice(item.title, item.mimeType)" |
||||
class="keep-open" |
||||
:src="getPossibleAttachmentSrc(item)" |
||||
/> |
||||
<div v-else class="bg-white h-full flex flex-col justify-center rounded-md gap-1 items-center w-full"> |
||||
<component :is="iconMap.file" class="text-gray-600 w-20 h-20" /> |
||||
<div class="text-gray-800 text-sm">{{ item.title }}</div> |
||||
</div> |
||||
</div> |
||||
</NcCarouselItem> |
||||
</NcCarouselContent> |
||||
</NcCarousel> |
||||
|
||||
<div |
||||
v-if="emblaMainApi?.canScrollPrev()" |
||||
:key="selectedIndex" |
||||
class="left-2 carousel-navigation keep-open" |
||||
@click="goPrev" |
||||
> |
||||
<template #prevArrow> |
||||
<div class="custom-slick-arrow left-2 z-1 keep-open"> |
||||
<MaterialSymbolsArrowCircleLeftRounded class="rounded-full" /> |
||||
</div> |
||||
</template> |
||||
<component :is="iconMap.arrowLeft" class="text-7xl" /> |
||||
</div> |
||||
<div |
||||
v-if="emblaMainApi?.canScrollNext()" |
||||
:key="selectedIndex" |
||||
class="right-2 carousel-navigation keep-open" |
||||
@click="goNext" |
||||
> |
||||
<component :is="iconMap.arrowRight" class="text-7xl" /> |
||||
</div> |
||||
|
||||
<template #nextArrow> |
||||
<div class="custom-slick-arrow !right-2 z-1 keep-open"> |
||||
<MaterialSymbolsArrowCircleRightRounded class="rounded-full" /> |
||||
<!-- <div v-if="isUIAllowed('commentList') && !isExpandedFormOpen" class="absolute top-2 right-2"> |
||||
<NcButton class="!hover:bg-transparent" type="text" size="small" @click="toggleComment"> |
||||
<div class="flex gap-1 text-white justify-center items-center"> |
||||
Comments |
||||
<GeneralIcon icon="messageCircle" /> |
||||
</div> |
||||
</template> |
||||
</NcButton> |
||||
</div> --> |
||||
|
||||
<template #customPaging="props"> |
||||
<div class="cursor-pointer h-full nc-attachment-img-wrapper"> |
||||
<LazyCellAttachmentImage |
||||
class="!block m-auto h-full w-full" |
||||
:alt="imageItems[props.i].title || `#${props.i}`" |
||||
:srcs="getPossibleAttachmentSrc(imageItems[props.i])" |
||||
/> |
||||
</div> |
||||
</template> |
||||
<div v-for="(item, idx) of imageItems" :key="idx"> |
||||
<LazyCellAttachmentImage :srcs="getPossibleAttachmentSrc(item)" class="max-w-70vw max-h-70vh" /> |
||||
</div> |
||||
</a-carousel> |
||||
</div> |
||||
</template> |
||||
</GeneralOverlay> |
||||
</template> |
||||
<div class="text-white absolute right-2 top-2 cursor-pointer"></div> |
||||
|
||||
<style scoped> |
||||
.ant-carousel :deep(.custom-slick-arrow .nc-icon):hover { |
||||
@apply !bg-white; |
||||
} |
||||
.ant-carousel :deep(.slick-dots) { |
||||
@apply relative mt-4; |
||||
} |
||||
<div class="absolute w-full !bottom-2 max-h-18 z-30 flex items-center justify-center"> |
||||
<NcCarousel class="absolute max-w-sm" @init-api="(val) => (emblaThumbnailApi = val)"> |
||||
<NcCarouselContent class="!flex !gap-2"> |
||||
<NcCarouselItem |
||||
v-for="(item, index) in visibleItems" |
||||
:key="index" |
||||
:class="{ |
||||
'!opacity-100': index === selectedIndex, |
||||
'!basis-1/4': visibleItems.length >= 4, |
||||
'!basis-1/3': visibleItems.length === 3, |
||||
'!basis-1/2': visibleItems.length === 2, |
||||
}" |
||||
class="px-2 keep-open opacity-50 cursor-pointer" |
||||
@click="onThumbClick(index)" |
||||
> |
||||
<div class="flex items-center justify-center"> |
||||
<LazyCellAttachmentPreviewImage |
||||
v-if="isImage(item.title, item.mimeType)" |
||||
class="nc-attachment-img-wrapper h-12" |
||||
object-fit="contain" |
||||
:alt="item.title" |
||||
:srcs="getPossibleAttachmentSrc(item, 'tiny')" |
||||
/> |
||||
<div |
||||
v-else-if="isVideo(item.title, item.mimeType)" |
||||
class="h-full flex items-center h-6 justify-center rounded-md px-2 py-1 border-1 border-gray-200" |
||||
> |
||||
<GeneralIcon class="text-white" icon="play" /> |
||||
</div> |
||||
|
||||
.ant-carousel :deep(.slick-slide) { |
||||
@apply w-full; |
||||
} |
||||
<div |
||||
v-else-if="isPdf(item.title, item.mimeType)" |
||||
class="h-full flex items-center h-6 justify-center rounded-md px-2 py-1 border-1 border-gray-200" |
||||
> |
||||
<GeneralIcon class="text-white" icon="pdfFile" /> |
||||
</div> |
||||
|
||||
.ant-carousel :deep(.slick-slide img) { |
||||
@apply border-1 m-auto; |
||||
} |
||||
<div v-else class="h-full flex items-center h-6 justify-center rounded-md px-2 py-1 border-1 border-gray-200"> |
||||
<GeneralIcon class="text-white" icon="file" /> |
||||
</div> |
||||
</div> |
||||
</NcCarouselItem> |
||||
</NcCarouselContent> |
||||
</NcCarousel> |
||||
</div> |
||||
|
||||
.ant-carousel :deep(.slick-thumb) { |
||||
@apply bottom-2; |
||||
} |
||||
<div class="absolute keep-open right-2 z-30 bottom-3 transition-all gap-3 transition-ease-in-out !h-6 flex items-center"> |
||||
<NcTooltip |
||||
v-if="!isSharedForm || (!isReadonly && isUIAllowed('dataEdit') && !isPublic)" |
||||
color="light" |
||||
placement="bottom" |
||||
> |
||||
<template #title> {{ $t('title.renameFile') }} </template> |
||||
<NcButton |
||||
size="xsmall" |
||||
class="nc-attachment-rename !hover:text-gray-400 !hover:bg-transparent !text-white" |
||||
type="text" |
||||
@click="renameFile(selectedFile, selectedIndex, true)" |
||||
> |
||||
<component :is="iconMap.rename" class="!hover:text-gray-400" /> |
||||
</NcButton> |
||||
</NcTooltip> |
||||
|
||||
.ant-carousel :deep(.slick-thumb li) { |
||||
@apply w-[60px] h-[45px]; |
||||
} |
||||
<NcTooltip v-if="!isReadonly" color="light" placement="bottom"> |
||||
<template #title> {{ $t('title.downloadFile') }} </template> |
||||
<NcButton |
||||
class="!hover:bg-transparent !text-white" |
||||
size="xsmall" |
||||
type="text" |
||||
@click="downloadAttachment(selectedFile)" |
||||
> |
||||
<component :is="iconMap.download" class="!hover:text-gray-400" /> |
||||
</NcButton> |
||||
</NcTooltip> |
||||
|
||||
.ant-carousel :deep(.slick-thumb li img) { |
||||
@apply w-full h-full block; |
||||
filter: grayscale(100%); |
||||
} |
||||
<NcTooltip v-if="!isReadonly" color="light" placement="bottom"> |
||||
<template #title> {{ $t('title.removeFile') }} </template> |
||||
<NcButton |
||||
class="!hover:bg-transparent !text-white" |
||||
size="xsmall" |
||||
type="text" |
||||
@click="onRemoveFileClick(selectedFile.title, selectedIndex)" |
||||
> |
||||
<component |
||||
:is="iconMap.delete" |
||||
v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic)" |
||||
class="!hover:text-gray-400" |
||||
/> |
||||
</NcButton> |
||||
</NcTooltip> |
||||
</div> |
||||
<GeneralDeleteModal v-model:visible="isModalOpen" entity-name="File" :on-delete="() => handleFileDelete(filetoDelete.i)"> |
||||
<template #entity-preview> |
||||
<span> |
||||
<div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4"> |
||||
<GeneralIcon icon="file" class="nc-view-icon"></GeneralIcon> |
||||
<div |
||||
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75" |
||||
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }" |
||||
> |
||||
{{ filetoDelete.title }} |
||||
</div> |
||||
</div> |
||||
</span> |
||||
</template> |
||||
</GeneralDeleteModal> |
||||
</div> |
||||
<!-- <div v-if="openComments && isUIAllowed('commentList') && !isExpandedFormOpen" class="bg-white w-88 min-w-88 max-w-88"> |
||||
<LazySmartsheetExpandedFormSidebarComments /> |
||||
</div> --> |
||||
</div> |
||||
</GeneralOverlay> |
||||
</template> |
||||
|
||||
.ant-carousel :deep(.slick-thumb li.slick-active img) { |
||||
filter: grayscale(0%); |
||||
<style scoped lang="scss"> |
||||
.carousel-navigation { |
||||
@apply absolute text-gray-400 hover:text-white cursor-pointer text-white h-full flex items-center inset-y-0 my-0; |
||||
} |
||||
</style> |
||||
|
||||
.ant-carousel :deep(.slick-arrow.custom-slick-arrow) { |
||||
@apply text-4xl text-white hover:text-primary active:text-accent opacity-100 cursor-pointer z-1; |
||||
} |
||||
.ant-carousel :deep(.custom-slick-arrow:before) { |
||||
display: none; |
||||
} |
||||
.ant-carousel :deep(.custom-slick-arrow:hover) { |
||||
opacity: 0.5; |
||||
<style lang="scss"> |
||||
.nc-attachment-carousel { |
||||
@apply w-max; |
||||
} |
||||
|
||||
.nc-attachment-img-wrapper { |
||||
width: fit-content !important; |
||||
.carousel-container { |
||||
@apply !w-full flex items-center h-full; |
||||
|
||||
.embla__container { |
||||
@apply items-center h-full w-full; |
||||
} |
||||
} |
||||
</style> |
||||
|
@ -0,0 +1,46 @@
|
||||
<script setup lang="ts"> |
||||
interface Props { |
||||
src: string[] |
||||
class?: string |
||||
} |
||||
|
||||
const props = defineProps<Props>() |
||||
|
||||
const currentIndex = ref(0) |
||||
|
||||
const handleError = () => { |
||||
if (currentIndex.value < props.src.length - 1) { |
||||
currentIndex.value = currentIndex.value + 1 |
||||
} else { |
||||
currentIndex.value = -1 |
||||
} |
||||
} |
||||
|
||||
const openMethod = ref<'google' | undefined>() |
||||
</script> |
||||
|
||||
<template> |
||||
<div v-if="!openMethod" :class="props.class" class="flex flex-col text-white gap-2 items-center justify-center"> |
||||
<GeneralIcon class="w-28 h-28" icon="pdfFile" /> |
||||
|
||||
<NcButton type="secondary" @click="openMethod = 'google'"> |
||||
<div class="flex items-center gap-1"> |
||||
<GeneralIcon class="w-4 h-4" icon="googleDocs" /> |
||||
|
||||
Open with Google Docs |
||||
</div> |
||||
</NcButton> |
||||
</div> |
||||
|
||||
<iframe |
||||
v-else-if="openMethod === 'google'" |
||||
:class="props.class" |
||||
:src="`https://docs.google.com/viewer?url=${encodeURIComponent(src[currentIndex])}&embedded=true`" |
||||
width="100%" |
||||
height="100%" |
||||
frameborder="0" |
||||
@error="handleError" |
||||
></iframe> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"></style> |
@ -0,0 +1,63 @@
|
||||
<script setup lang="ts"> |
||||
interface Props { |
||||
src: string[] |
||||
class?: string |
||||
} |
||||
|
||||
const props = defineProps<Props>() |
||||
|
||||
const currentIndex = ref(0) |
||||
|
||||
const handleError = () => { |
||||
if (currentIndex.value < props.src.length - 1) { |
||||
currentIndex.value = currentIndex.value + 1 |
||||
} else { |
||||
currentIndex.value = -1 |
||||
} |
||||
} |
||||
|
||||
const openMethod = ref<'browser' | 'google' | undefined>() |
||||
</script> |
||||
|
||||
<template> |
||||
<div v-if="!openMethod" :class="props.class" class="flex flex-col text-white gap-2 items-center justify-center"> |
||||
<GeneralIcon class="w-28 h-28" icon="pdfFile" /> |
||||
|
||||
<div class="flex items-center justify-center gap-2"> |
||||
<NcButton class="!w-52" type="secondary" @click="openMethod = 'browser'"> |
||||
<div class="flex items-center gap-1"> |
||||
<GeneralIcon icon="globe" class="!text-gray-700" /> |
||||
Open in browser |
||||
</div> |
||||
</NcButton> |
||||
<NcButton type="secondary" class="!w-52" @click="openMethod = 'google'"> |
||||
<div class="flex items-center gap-1"> |
||||
<GeneralIcon class="w-4 h-4" icon="googleDocs" /> |
||||
|
||||
Open with Google Docs |
||||
</div> |
||||
</NcButton> |
||||
</div> |
||||
</div> |
||||
|
||||
<pdf-object |
||||
v-if="openMethod === 'browser'" |
||||
:class="props.class" |
||||
:url="src[currentIndex]" |
||||
class="w-full h-full" |
||||
@error="handleError" |
||||
/> |
||||
|
||||
<iframe |
||||
v-else-if="openMethod === 'google'" |
||||
:class="props.class" |
||||
type="application/pdf" |
||||
:src="`https://docs.google.com/viewer?url=${encodeURIComponent(src[currentIndex])}&embedded=true`" |
||||
width="100%" |
||||
height="100%" |
||||
frameborder="0" |
||||
@error="handleError" |
||||
></iframe> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"></style> |
@ -0,0 +1,55 @@
|
||||
<script setup lang="ts"> |
||||
import Plyr from 'plyr' |
||||
import 'plyr/dist/plyr.css' |
||||
|
||||
interface Props { |
||||
src?: string[] |
||||
mimeType?: string |
||||
class?: string |
||||
title?: string |
||||
} |
||||
|
||||
const props = withDefaults(defineProps<Props>(), { |
||||
class: '', |
||||
}) |
||||
|
||||
const emit = defineEmits<Emits>() |
||||
|
||||
interface Emits { |
||||
(event: 'init', player: any): void |
||||
} |
||||
|
||||
const videoPlayer = ref<HTMLElement>() |
||||
|
||||
const player = ref() |
||||
|
||||
onMounted(() => { |
||||
if (!videoPlayer.value) return |
||||
player.value = new Plyr(videoPlayer.value, { |
||||
previewThumbnails: {}, |
||||
}) |
||||
emit('init', player.value) |
||||
}) |
||||
|
||||
onBeforeUnmount(() => { |
||||
if (player.value) { |
||||
player.value.destroy() |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<video |
||||
ref="videoPlayer" |
||||
controls |
||||
playsinline |
||||
:class="{ |
||||
[props.class]: props.class, |
||||
}" |
||||
class="videoplayer !min-w-128 !min-h-72 w-full" |
||||
> |
||||
<source v-for="(source, id) in props.src" :key="id" :src="source" :type="mimeType" /> |
||||
</video> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"></style> |