mirror of
https://github.com/ArthurDanjou/artchat.git
synced 2026-01-21 16:23:17 +01:00
- 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.
117 lines
3.1 KiB
TypeScript
117 lines
3.1 KiB
TypeScript
/* eslint-disable ts/no-use-before-define */
|
|
/**
|
|
* @fileoverview Composable for preloading images and videos
|
|
*/
|
|
|
|
import type { ImagePreloaderOptions, UseImagePreloaderReturn } from '../types'
|
|
import { isVideo } from '../utils'
|
|
|
|
/**
|
|
* Preloads a single media file (image or video)
|
|
* @param src Media URL to preload
|
|
* @returns Promise that resolves when loaded
|
|
*/
|
|
function preloadMedia(src: string): Promise<void> {
|
|
return new Promise((resolve) => {
|
|
if (isVideo(src)) {
|
|
const video = document.createElement('video')
|
|
video.preload = 'metadata'
|
|
video.muted = true
|
|
|
|
const handleLoad = () => {
|
|
video.removeEventListener('loadedmetadata', handleLoad)
|
|
video.removeEventListener('error', handleError)
|
|
resolve()
|
|
}
|
|
|
|
const handleError = () => {
|
|
video.removeEventListener('loadedmetadata', handleLoad)
|
|
video.removeEventListener('error', handleError)
|
|
resolve() // Don't reject to avoid breaking the flow
|
|
}
|
|
|
|
video.addEventListener('loadedmetadata', handleLoad)
|
|
video.addEventListener('error', handleError)
|
|
video.src = src
|
|
}
|
|
else {
|
|
const img = new Image()
|
|
|
|
const handleLoad = () => {
|
|
img.removeEventListener('load', handleLoad)
|
|
img.removeEventListener('error', handleError)
|
|
resolve()
|
|
}
|
|
|
|
const handleError = () => {
|
|
img.removeEventListener('load', handleLoad)
|
|
img.removeEventListener('error', handleError)
|
|
resolve() // Don't reject to avoid breaking the flow
|
|
}
|
|
|
|
img.addEventListener('load', handleLoad)
|
|
img.addEventListener('error', handleError)
|
|
img.src = src
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Composable for preloading multiple media files with progress tracking
|
|
* @param options Configuration options
|
|
* @returns Preloader state and controls
|
|
*/
|
|
export function useImagePreloader(options: ImagePreloaderOptions): UseImagePreloaderReturn {
|
|
const { images, onProgress, onComplete } = options
|
|
|
|
const progress = ref(0)
|
|
const loadedCount = ref(0)
|
|
const isLoading = ref(true)
|
|
const isComplete = ref(false)
|
|
|
|
/**
|
|
* Updates progress and triggers callbacks
|
|
*/
|
|
const updateProgress = () => {
|
|
const newProgress = loadedCount.value / images.length
|
|
progress.value = newProgress
|
|
onProgress?.(newProgress)
|
|
|
|
if (loadedCount.value === images.length) {
|
|
isComplete.value = true
|
|
isLoading.value = false
|
|
onComplete?.()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Starts the preloading process for all media files
|
|
*/
|
|
const startPreloading = async () => {
|
|
if (images.length === 0) {
|
|
progress.value = 1
|
|
isComplete.value = true
|
|
isLoading.value = false
|
|
onComplete?.()
|
|
return
|
|
}
|
|
|
|
// Preload all media in parallel with individual progress tracking
|
|
const preloadPromises = images.map(async (src) => {
|
|
await preloadMedia(src)
|
|
loadedCount.value++
|
|
updateProgress()
|
|
})
|
|
|
|
await Promise.all(preloadPromises)
|
|
}
|
|
|
|
return {
|
|
progress: readonly(progress),
|
|
loadedCount: readonly(loadedCount),
|
|
isLoading: readonly(isLoading),
|
|
isComplete: readonly(isComplete),
|
|
startPreloading,
|
|
}
|
|
}
|