mirror of
https://github.com/ArthurDanjou/artchat.git
synced 2026-01-14 13:54:01 +01:00
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:
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user