mirror of
https://github.com/ArthurDanjou/artchat.git
synced 2026-01-29 21:28:13 +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
Vue
117 lines
3.1 KiB
Vue
<script setup lang="ts">
|
|
import type { MinimapProps } from '../types'
|
|
|
|
const props = defineProps<MinimapProps>()
|
|
|
|
const MINIMAP_SIZE = 120
|
|
|
|
/**
|
|
* Calculate the scale factor to fit canvas in minimap
|
|
*/
|
|
const scale = computed(() => {
|
|
const { width, height } = props.canvasBounds
|
|
return Math.min(MINIMAP_SIZE / width, MINIMAP_SIZE / height)
|
|
})
|
|
|
|
/**
|
|
* Actual minimap dimensions after scaling
|
|
*/
|
|
const dimensions = computed(() => ({
|
|
width: props.canvasBounds.width * scale.value,
|
|
height: props.canvasBounds.height * scale.value,
|
|
}))
|
|
|
|
/**
|
|
* Positioning to center the minimap content
|
|
*/
|
|
const centerOffset = computed(() => ({
|
|
x: (MINIMAP_SIZE - dimensions.value.width) / 2,
|
|
y: (MINIMAP_SIZE - dimensions.value.height) / 2,
|
|
}))
|
|
|
|
/**
|
|
* Transform grid items to minimap coordinates with proper scaling
|
|
*/
|
|
const items = computed(() =>
|
|
props.gridItems.map(item => ({
|
|
index: item.index,
|
|
x: item.position.x * scale.value,
|
|
y: item.position.y * scale.value,
|
|
width: Math.max(2, item.width * scale.value),
|
|
height: Math.max(2, item.height * scale.value),
|
|
})),
|
|
)
|
|
|
|
/**
|
|
* Calculate viewport rectangle in minimap space
|
|
*/
|
|
const viewport = computed(() => {
|
|
const { width, height } = props.containerDimensions
|
|
const { zoom } = props
|
|
|
|
// Canvas coordinates of current viewport
|
|
const viewX = -props.offset.x / zoom
|
|
const viewY = -props.offset.y / zoom
|
|
const viewWidth = width / zoom
|
|
const viewHeight = height / zoom
|
|
|
|
// Convert to minimap coordinates
|
|
const x = viewX * scale.value
|
|
const y = viewY * scale.value
|
|
const w = viewWidth * scale.value
|
|
const h = viewHeight * scale.value
|
|
|
|
return {
|
|
x: Math.max(0, Math.min(dimensions.value.width - w, x)),
|
|
y: Math.max(0, Math.min(dimensions.value.height - h, y)),
|
|
width: Math.min(dimensions.value.width, w),
|
|
height: Math.min(dimensions.value.height, h),
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative overflow-hidden rounded-lg border border-default bg-default/40 p-2 backdrop-blur-sm shadow-lg">
|
|
<!-- Minimap container -->
|
|
<div
|
|
class="relative"
|
|
:style="{ width: `${MINIMAP_SIZE}px`, height: `${MINIMAP_SIZE}px` }"
|
|
>
|
|
<!-- Centered minimap content -->
|
|
<div
|
|
class="absolute bg-muted/50"
|
|
:style="{
|
|
left: `${centerOffset.x}px`,
|
|
top: `${centerOffset.y}px`,
|
|
width: `${dimensions.width}px`,
|
|
height: `${dimensions.height}px`,
|
|
}"
|
|
>
|
|
<!-- Items with real shapes -->
|
|
<div
|
|
v-for="item in items"
|
|
:key="item.index"
|
|
class="absolute bg-accented border border-inverted/30 rounded-sm"
|
|
:style="{
|
|
width: `${item.width}px`,
|
|
height: `${item.height}px`,
|
|
left: `${item.x}px`,
|
|
top: `${item.y}px`,
|
|
}"
|
|
/>
|
|
|
|
<!-- Viewport indicator -->
|
|
<div
|
|
class="absolute border border-primary bg-primary/10"
|
|
:style="{
|
|
left: `${viewport.x}px`,
|
|
top: `${viewport.y}px`,
|
|
width: `${viewport.width}px`,
|
|
height: `${viewport.height}px`,
|
|
}"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|