feat(infinite-canvas): add infinite canvas component with drag and zoom functionality

- Implemented InfiniteCanvas.vue for rendering an infinite canvas with drag and zoom capabilities.
- Created useInfiniteCanvas composable for managing canvas state and interactions.
- Added useImagePreloader composable for preloading images and videos.
- Introduced constants for physics, touch interactions, viewport settings, and zoom defaults.
- Developed utility functions for touch handling and media type detection.
- Defined TypeScript types for canvas items, grid items, and composables.
- Registered components and composables in the Nuxt module.
- Added screenshot generation functionality for content files.
- Updated package.json to include capture-website dependency.
This commit is contained in:
2025-09-05 11:01:11 +02:00
parent 97d7cddaa5
commit 5dadb20607
21 changed files with 2031 additions and 13 deletions

View File

@@ -0,0 +1,221 @@
/**
* @fileoverview Type definitions for the Infinite Canvas module
*/
/**
* Basic item that can be displayed on the canvas
*/
export interface CanvasItem {
/** URL to the image or video */
image: string
/** Display title */
title: string
/** External link URL */
link: string
/** Item width in pixels (optional, defaults to 300) */
width?: number
/** Item height in pixels (optional, defaults to 300) */
height?: number
/** Additional properties */
[key: string]: any
}
/**
* 2D coordinate position
*/
export interface Position {
/** X coordinate */
x: number
/** Y coordinate */
y: number
}
/**
* Grid item with position and dimensions
*/
export interface GridItem {
/** Position on the canvas */
position: Position
/** Index in the original items array */
index: number
/** Item width in pixels */
width: number
/** Item height in pixels */
height: number
/** Whether the item is currently visible (computed) */
isVisible?: boolean
}
/**
* Zoom configuration options
*/
export interface ZoomOptions {
/** Minimum zoom level (default: 0.4) */
minZoom?: number
/** Maximum zoom level (default: 2.2) */
maxZoom?: number
/** Zoom factor per step (default: 1.08) */
zoomFactor?: number
/** Enable Ctrl key for zooming (default: true) */
enableCtrl?: boolean
/** Enable Meta key for zooming (default: true) */
enableMeta?: boolean
/** Enable Alt key for zooming (default: true) */
enableAlt?: boolean
}
/**
* Canvas boundaries and dimensions
*/
export interface CanvasBounds {
/** Canvas width in pixels */
width: number
/** Canvas height in pixels */
height: number
/** Center X coordinate (computed) */
centerX?: number
/** Center Y coordinate (computed) */
centerY?: number
}
/**
* Container dimensions
*/
export interface ContainerDimensions {
/** Container width in pixels */
width: number
/** Container height in pixels */
height: number
}
/**
* Options for the useInfiniteCanvas composable
*/
export interface UseInfiniteCanvasOptions {
/** Array of items to display */
items: CanvasItem[]
/** Minimum gap between items in pixels (default: 40) */
baseGap?: number
/** Zoom configuration */
zoomOptions?: ZoomOptions
/** Reference to the container element */
containerRef: Ref<HTMLElement | null>
}
/**
* Return type of the useInfiniteCanvas composable
*/
export interface UseInfiniteCanvasReturn {
/** Current canvas offset */
offset: Readonly<Ref<Position>>
/** Current zoom level */
zoom: Readonly<Ref<number>>
/** Currently visible items */
visibleItems: ComputedRef<GridItem[]>
/** All positioned grid items */
gridItems: ComputedRef<GridItem[]>
/** Container dimensions */
containerDimensions: Readonly<Ref<ContainerDimensions>>
/** Canvas boundaries */
canvasBounds: Readonly<Ref<CanvasBounds>>
/** Whether clicks are allowed (not dragging) */
canClick: Readonly<Ref<boolean>>
/** Update container dimensions */
updateDimensions: () => void
/** Handle pointer down events */
handlePointerDown: (clientX: number, clientY: number) => void
/** Handle pointer move events */
handlePointerMove: (clientX: number, clientY: number) => void
/** Handle pointer up events */
handlePointerUp: (clientX: number, clientY: number) => void
/** Handle wheel events */
handleWheel: (event: WheelEvent) => void
/** Handle touch start events */
handleTouchStart: (event: TouchEvent) => void
/** Handle touch move events */
handleTouchMove: (event: TouchEvent) => void
/** Handle touch end events */
handleTouchEnd: (event: TouchEvent) => void
/** Navigate to a specific position */
navigateTo: (position: Position) => void
}
/**
* Options for the image preloader
*/
export interface ImagePreloaderOptions {
/** Array of image/video URLs to preload */
images: string[]
/** Progress callback (0-1) */
onProgress?: (progress: number) => void
/** Completion callback */
onComplete?: () => void
}
/**
* Return type of the useImagePreloader composable
*/
export interface UseImagePreloaderReturn {
/** Loading progress (0-1) */
progress: Readonly<Ref<number>>
/** Number of loaded items */
loadedCount: Readonly<Ref<number>>
/** Whether currently loading */
isLoading: Readonly<Ref<boolean>>
/** Whether loading is complete */
isComplete: Readonly<Ref<boolean>>
/** Start the preloading process */
startPreloading: () => Promise<void>
}
/**
* Props for the minimap component
*/
export interface MinimapProps {
/** Canvas items */
items: CanvasItem[]
/** Grid items with positions */
gridItems: GridItem[]
/** Current canvas offset */
offset: Position
/** Current zoom level */
zoom: number
/** Container dimensions */
containerDimensions: ContainerDimensions
/** Canvas boundaries */
canvasBounds: CanvasBounds
}
/**
* Props for the loader component
*/
export interface LoaderProps {
/** Loading progress (0-1) */
progress: number
/** Whether the loader is visible */
isVisible: boolean
/** Optional title */
title?: string
/** Optional description */
description?: string
}
/**
* Props for the InfiniteCanvas component
*/
export interface InfiniteCanvasProps<T = any> {
/** Array of items to display */
items: T[]
/** Minimum gap between items (default: 40) */
baseGap?: number
/** Zoom configuration */
zoomOptions?: ZoomOptions
}
/**
* Events emitted by the InfiniteCanvas component
*/
export interface InfiniteCanvasEmits<T = any> {
/** Emitted when an item is clicked */
itemClick: [item: T, index: number]
}