refactor: remove infinite canvas module and related components

- Deleted the `useImagePreloader` composable and its associated types.
- Removed the `useInfiniteCanvas` composable along with its types and constants.
- Eliminated the `index.ts` file for the infinite canvas module.
- Removed utility functions related to touch and video handling.
- Deleted the screenshots module and its functionality.
- Updated package.json to remove `capture-website` dependency.
- Added new images for documentation purposes.
This commit is contained in:
2025-09-05 11:48:35 +02:00
parent 5dadb20607
commit b140760cfe
25 changed files with 16 additions and 2028 deletions

View File

@@ -48,7 +48,7 @@ defineShortcuts({
const isMobile = computed(() => {
if (!import.meta.client)
return false
return isMobileDevice(navigator.userAgent, window.innerWidth)
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768
})
const activeElement = useActiveElement()
watch(openMessageModal, async () => {
@@ -206,23 +206,6 @@ function goHome() {
@click.prevent="goHome"
/>
</UTooltip>
<UTooltip
v-if="router.currentRoute.value.name !== 'canva'"
:text="t('palette.tooltip.canva')"
arrow
:content="toolTipContent"
:delay-duration="0"
>
<UButton
:label="t('palette.cmd.canva')"
variant="outline"
color="neutral"
size="xl"
icon="i-ph-presentation-duotone"
href="/canva"
class="rounded-lg cursor-pointer p-2 w-full justify-center"
/>
</UTooltip>
</UFieldGroup>
<ClientOnly>
<UFieldGroup class="flex items-center justify-center">

View File

@@ -1,263 +0,0 @@
<script lang="ts" setup>
import type { CanvasItem } from '~~/modules/infinite-canvas/types'
const { data, error } = await useAsyncData(`canva`, async () => {
const items: CanvasItem[] = []
const projects = await queryCollection('projects')
.select('title', 'description', 'cover', 'tags', 'slug', 'canva')
.all()
projects.forEach((project) => {
if (!project.cover)
return
items.push({
title: project.title,
description: project.description || '',
image: `/projects/${project.cover}`,
link: `/projects/${project.slug}`,
tags: project.tags || [],
height: project.canva?.height ?? 270,
width: project.canva?.width ?? 480,
})
})
const writings = await queryCollection('writings')
.select('title', 'description', 'cover', 'slug', 'tags', 'canva')
.all()
writings.forEach((writing) => {
if (!writing.cover)
return
items.push({
title: writing.title,
description: writing.description || '',
image: `/writings/${writing.cover}`,
link: `/writings/${writing.slug}`,
tags: writing.tags || [],
height: writing.canva?.height ?? 270,
width: writing.canva?.width ?? 480,
})
})
return items
})
const { t } = useI18n()
const isMobile = computed(() => {
if (!import.meta.client)
return false
return isMobileDevice(navigator.userAgent, window.innerWidth)
})
const router = useRouter()
function handleItemClick(item: CanvasItem) {
if (import.meta.client) {
router.push(item.link)
}
}
const imageUrls = computed(() => data.value?.map(item => item.cover))
const loaderProgress = ref(0)
const showLoader = ref(true)
const isImagesLoaded = ref(false)
const hoveredItemIndex = ref<number | null>(null)
const { startPreloading } = useImagePreloader({
images: imageUrls.value || [],
onProgress: (newProgress) => {
loaderProgress.value = newProgress
},
onComplete: () => {
isImagesLoaded.value = true
setTimeout(() => {
showLoader.value = false
}, 500)
},
})
const canvasRef = ref<any>(null)
onMounted(() => {
startPreloading()
nextTick(() => {
if (canvasRef.value?.updateDimensions) {
canvasRef.value.updateDimensions()
}
})
})
// Prevent browser navigation gestures
if (import.meta.client) {
document.documentElement.style.overscrollBehavior = 'none'
document.body.style.overscrollBehavior = 'none'
document.body.style.touchAction = 'manipulation'
}
</script>
<template>
<main v-if="data" class="canvas-page relative h-screen w-screen overflow-hidden" style="touch-action: none; overscroll-behavior: none;">
<Canvas
ref="canvasRef"
:items="data || []"
:base-gap="50"
:zoom-options="{
minZoom: 0.4,
maxZoom: 2.2,
zoomFactor: 1.08,
enableCtrl: true,
enableMeta: true,
enableAlt: true,
}"
class="absolute inset-0"
@item-click="handleItemClick"
>
<template #default="{ item, index, onItemClick }">
<Motion
:initial="{
opacity: 0,
filter: 'blur(20px)',
scale: 0.8,
}"
:animate="isImagesLoaded ? {
opacity: 1,
filter: 'blur(0px)',
scale: 1,
} : {
opacity: 0,
filter: 'blur(20px)',
scale: 0.8,
}"
:transition="{
duration: 0.8,
delay: isImagesLoaded ? Math.random() * 0.8 : 0,
ease: 'easeOut',
}"
class="group relative size-full select-none overflow-hidden hover:scale-105 active:scale-95 transition-all duration-300"
:class="[
isMobile ? 'cursor-default' : 'cursor-pointer',
index % 2 === 0 ? 'rotate-1' : '-rotate-1',
]"
data-canvas-item
@click="onItemClick"
@mouseenter="hoveredItemIndex = index"
@mouseleave="hoveredItemIndex = null"
>
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br p-1 border-2 border-default/50">
<div class="relative size-full overflow-hidden rounded-xl">
<video
v-if="item && isVideo(item.image)"
:src="item.image"
class="size-full object-cover"
autoplay
loop
muted
playsinline
:draggable="false"
/>
<img
v-else-if="item"
:src="item.image"
:alt="item.title"
class="size-full object-cover"
:draggable="false"
>
<!-- Item details overlay -->
<Motion
v-if="!isMobile"
:initial="{ opacity: 0, scale: 0.95 }"
:animate="hoveredItemIndex === index ? {
opacity: 1,
scale: 1,
transformOrigin: 'bottom left',
} : {
opacity: 0,
scale: 0.95,
transformOrigin: 'bottom left',
}"
:transition="{
duration: 0.2,
ease: 'easeOut',
}"
class="absolute inset-0 flex flex-col justify-end bg-gradient-to-t from-black/90 via-black/60 to-transparent backdrop-blur-[2px] p-4 rounded-xl"
>
<Motion
:initial="{ y: 20, opacity: 0 }"
:animate="hoveredItemIndex === index ? {
y: 0,
opacity: 1,
} : {
y: 20,
opacity: 0,
}"
:transition="{
duration: 0.25,
delay: 0.05,
ease: 'easeOut',
}"
>
<div class="flex items-start gap-2">
<div class="flex-1">
<h3 class="font-medium text-white mb-1 line-clamp-2 leading-tight">
{{ item?.title }}
</h3>
<p
v-if="item?.description"
class="text-sm text-white/85 line-clamp-2"
>
{{ item.description }}
</p>
</div>
<UIcon name="i-heroicons-arrow-top-right-on-square" class="size-4 text-white/70 flex-shrink-0 mt-0.5" />
</div>
</Motion>
</Motion>
</div>
</div>
</Motion>
</template>
</Canvas>
<div v-if="canvasRef" class="fixed bottom-2 right-2 sm:bottom-4 sm:right-4 pointer-events-none">
<CanvasMinimap
:items="data || []"
:grid-items="canvasRef.gridItems || []"
:offset="canvasRef.offset || { x: 0, y: 0 }"
:zoom="canvasRef.zoom || 1"
:container-dimensions="canvasRef.containerDimensions || { width: 0, height: 0 }"
:canvas-bounds="canvasRef.canvasBounds || { width: 0, height: 0 }"
class="scale-85 sm:scale-100 origin-bottom-right"
/>
</div>
<div class="pointer-events-none absolute bottom-2 left-2 sm:bottom-4 sm:left-4 z-40 flex flex-col gap-2">
<div class="rounded-lg bg-default/80 px-3 py-2 text-highlighted backdrop-blur-sm">
<p class="text-xs opacity-75">
<span class="sm:hidden">{{ data?.length }} items</span><span class="hidden sm:inline">Click items to open links {{ data?.length }} items</span>
<span v-if="canvasRef?.zoom" class="ml-2 opacity-60">
{{ Math.round((canvasRef.zoom || 1) * 100) }}%
</span>
</p>
</div>
<div class="hidden sm:block rounded-lg bg-default/80 px-3 py-2 text-highlighted backdrop-blur-sm">
<p class="text-xs opacity-75">
Hold Ctrl//Alt + scroll to zoom (40%-220%)
</p>
</div>
</div>
<CanvasLoader
:progress="loaderProgress"
:is-visible="showLoader"
:title="t('canva.title')"
/>
</main>
<main v-else>
{{ error }}
</main>
</template>