added calendar and rough structure of calendar

This commit is contained in:
CoGomu
2025-05-11 19:35:39 +02:00
parent f88402d04e
commit f8cb42962a
13 changed files with 591 additions and 14 deletions

View File

@@ -0,0 +1,113 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import CalendarHeader from './CalendarHeader.vue';
import CalendarCollumn from './CalendarCollumn.vue';
import moment, { type Moment } from 'moment';
const events = defineModel<Event[]>('events', { required: true })
const date = ref(moment())
const draggedEvent = ref<DraggedEvent | undefined>()
type Day = {
date: Moment
events: CollissionWrapper[][]
}
const week = computed(() => {
return moment(date.value).startOf('isoWeek')
})
function pushEventWithCollisionUpdate(array: CollissionWrapper[], event: Event, collisions: CollissionWrapper[], collisionCount: number) {
array.push({event: event, collisions: collisionCount })
for (let collision of collisions) {
collision.collisions = collisionCount
}
}
const days = computed<Day[]>(() => {
return [1, 2, 3, 4, 5, 6, 7].map((i) => {
const filteredEvents = events.value.filter(
(event) => event.from >= moment(week.value).weekday(i).startOf('day') && event.to <= moment(week.value).weekday(i).endOf('day')
)
const sortedEvents = filteredEvents.sort((a, b) => a.from.valueOf() - b.from.valueOf())
const columns: CollissionWrapper[][] = [[]]
for (let event of sortedEvents) {
let collisions: CollissionWrapper[] = []
for (let i = 0; i < columns.length; i++) {
const column = columns[i]
if (column.length === 0) {
pushEventWithCollisionUpdate(column, event, collisions, collisions.length)
break
}
if (event.from.valueOf() > column[column.length - 1].event.to.valueOf()) {
pushEventWithCollisionUpdate(column, event, collisions, collisions.length)
break
} else {
collisions.push(column[column.length - 1])
}
if (columns.length === i + 1) {
columns.push([])
pushEventWithCollisionUpdate(columns[i + 1], event, collisions, collisions.length)
break
}
}
}
return {
date: moment(week.value).weekday(i),
events: columns
}
})
})
const emits = defineEmits<{
(e: 'create', timespan: Event): void
}>()
const seperators = ref<Seperator[]>([
{ text: '3 AM', time: moment().hour(3) },
{ text: '6 AM', time: moment().hour(6) },
{ text: '9 AM', time: moment().hour(9) },
{ text: '12 PM', time: moment().hour(12) },
{ text: '3 PM', time: moment().hour(15) },
{ text: '6 PM', time: moment().hour(18) },
{ text: '9 PM', time: moment().hour(21) },
])
function quickCreate(date: Moment, timespan: Timespan) {
const eventTitle = prompt("Event title")
if (eventTitle === null) {
return
}
const newEvent: Event = new Event(
eventTitle,
moment(date).startOf('day').minutes(timespan.from * 24 * 60),
moment(date).startOf('day').minutes(timespan.to * 24 * 60)
)
emits('create', newEvent)
events.value.push(newEvent)
}
</script>
<template>
<div class="w-full h-full flex flex-col">
<div class="calendar flex flex-row w-full flex-1 items-stretch">
<CalendarHeader :seperators="seperators" />
<CalendarCollumn v-for="day in days" :seperators="seperators" :day="day.date" :events="day.events" v-model:draggedEvent="draggedEvent"
@quick-create="quickCreate" />
</div>
</div>
</template>

View File

@@ -0,0 +1,119 @@
<script setup lang="ts">
import { computed, ref, useTemplateRef } from 'vue';
import CalendarSeperator from './CalendarSeperator.vue';
import type { Moment } from 'moment';
import CalendarEvent from './CalendarEvent.vue';
import moment from 'moment';
const props = defineProps<{
seperators: Seperator[],
day: Moment
events: CollissionWrapper[][]
}>()
const emit = defineEmits<{
(e: 'quick-create', day: Moment, event: EventDimensions): void,
}>()
const isDragging = ref(false)
const startY = ref(0)
const endY = ref(0)
const column = useTemplateRef('column')
const draggedEvent = defineModel<DraggedEvent | undefined>('draggedEvent')
const height = computed(() => {
return Math.abs(endY.value - startY.value)
})
const top = computed(() => {
return Math.min(startY.value, endY.value)
})
function mousedown(e: MouseEvent) {
startY.value = absoluteToRelativeY(e.clientY)
endY.value = absoluteToRelativeY(e.clientY)
isDragging.value = true
}
function mouseover(e: MouseEvent) {
if (!isDragging.value) {
return
}
endY.value = absoluteToRelativeY(e.clientY)
}
function absoluteToRelativeY(n: number) {
return n - (column.value?.getBoundingClientRect().top ?? 0)
}
function mouseup(_: MouseEvent) {
isDragging.value = false
if (column.value === null) {
console.error('column element is undefined')
return
}
const timeFrom = Math.min(endY.value, startY.value) / column.value.offsetHeight
const timeTo = Math.max(endY.value, startY.value) / column.value.offsetHeight
emit('quick-create', moment(props.day), {
from: timeFrom,
to: timeTo
})
startY.value = 0
endY.value = 0
}
function eventMove(mouseEvent: MouseEvent, event: Event) {
draggedEvent.value = event.toDraggedEvent(column.value?.offsetHeight ?? 0, absoluteToRelativeY(mouseEvent.clientY))
}
function dragover(e: DragEvent) {
if (draggedEvent.value === undefined) {
return
}
if (!draggedEvent.value.date.isSame(props.day)) {
draggedEvent.value.date = props.day
}
draggedEvent.value.top = absoluteToRelativeY(e.clientY) - draggedEvent.value.offset
}
function dragDrop(_: DragEvent) {
draggedEvent.value?.target.updateWithDraggedEvent(draggedEvent.value, column.value?.offsetHeight ?? 0)
draggedEvent.value = undefined
}
</script>
<template>
<div class="flex flex-col h-full grow">
<div class="flex justify-center items-center flex-col bg-gray-600 h-18 text-white border-b-2 border-white">
<div>{{ props.day.format('dd').toUpperCase() }}</div>
<div>{{ props.day.date() }}</div>
</div>
<div id="col" ref="column" @mousedown="mousedown" @mouseup="mouseup" @mousemove="mouseover" @dragover="dragover"
@dragend="dragDrop" class="bg-gray-600 text-white relative flex flex-col grow items-center">
<CalendarSeperator v-for="sep in seperators" :seperator="sep">
<hr class="w-full">
</CalendarSeperator>
<div class="absolute w-11/12 top-20 bg-black opacity-45 rounded-lg"
:style="{ height: `${height}px`, top: `${top}px` }"></div>
<div v-for="[index, column] in events.entries()" class="flex flex-row w-11/12 h-full absolute top-0">
<CalendarEvent v-for="event in column" :event="event" :columnIndex="index" @move="eventMove" />
</div>
<div v-if="draggedEvent !== undefined && draggedEvent.date.isSame(props.day)" class="absolute w-11/12 top-20 bg-black opacity-45 rounded-lg"
:style="{ height: `${draggedEvent.height}px`, top: `${draggedEvent.top}px` }"></div>
</div>
</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,50 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
const props = defineProps<{
event: CollissionWrapper
columnIndex: number
}>()
const emit = defineEmits<{
(e: 'move', mouseEvent: MouseEvent, event: Event): void,
}>()
const visible = ref(true)
const dimensions = computed<EventDimensions>(() => {
return props.event.event.getPercentDimensions()
})
const left = computed(() => {
return (100 / (props.event.collisions + 1)) * props.columnIndex
})
const widht = computed(() => {
return (100 / (props.event.collisions + 1))
})
const height = computed(() => {
return Math.abs(dimensions.value.from - dimensions.value.to)
})
const top = computed(() => {
return Math.min(dimensions.value.from, dimensions.value.to)
})
function dragStart(e: DragEvent) {
console.log("start drag")
emit('move', e, props.event.event)
visible.value = false
}
</script>
<template>
<div class="absolute rounded-lg h-0 top-20 bg-black opacity-45 p-2 flex flex-col z-10" @mousedown.stop
@mouseover.stop @mouseup.stop draggable="true" @dragstart="dragStart"
:style="{ top: `${top}%`, height: `${height}%`, left: `${left}%`, width: `${widht}%` }">
<div>{{ event.event.from.format('HH:mm') }} - {{ event.event.to.format('HH:mm') }}</div>
<div>{{ event.event.title }}</div>
</div>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
import CalendarSeperator from './CalendarSeperator.vue';
defineProps<{
seperators: Seperator[],
}>()
</script>
<template>
<div class="flex flex-col h-full grow">
<div class="flex justify-center items-center bg-gray-600 h-18 text-white border-b-white border-b-2">vue-calendar
</div>
<div class="calendar-legend bg-gray-600 text-white relative flex flex-col grow justify-evenly">
<CalendarSeperator v-for="sep in seperators" :seperator="sep">
{{ sep.text }}
</CalendarSeperator>
</div>
</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
import type { Moment } from 'moment';
defineProps<{
seperator: Seperator
}>();
const relativePositionOf = function (time: Moment) {
return `${(time.hours() / 24) * 100}%`
}
</script>
<template>
<div :style="{top: relativePositionOf(seperator.time)}" class="h-10 w-full flex justify-center items-center text-white border-white absolute -translate-y-1/2">
<slot />
</div>
</template>
<style scoped></style>