Migration to nuxt 4
Used codemods CLI and reworked most alias'd path that stopped working
This commit is contained in:
5
app/components/calendar/state/centennially/Layout.vue
Normal file
5
app/components/calendar/state/centennially/Layout.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<template>
|
||||
<div>Century</div>
|
||||
</template>
|
||||
3
app/components/calendar/state/decennially/Layout.vue
Normal file
3
app/components/calendar/state/decennially/Layout.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>Decade</div>
|
||||
</template>
|
||||
194
app/components/calendar/state/monthly/DayTile.vue
Normal file
194
app/components/calendar/state/monthly/DayTile.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RPGDate } from "@@/models/Date"
|
||||
import type { CalendarEvent } from "@@/models/CalendarEvent"
|
||||
import { breakpointsTailwind, useElementBounding } from "@vueuse/core"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { computed, ref, type ComputedRef } from "vue"
|
||||
|
||||
import CalendarEventButton from "../../event/Event.vue"
|
||||
|
||||
const props = defineProps<{
|
||||
date: RPGDate
|
||||
faded?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
"on-open-create-dialog": [date: RPGDate]
|
||||
}>()
|
||||
|
||||
const calendarTile = ref()
|
||||
const calendarEventsList = ref()
|
||||
|
||||
const { defaultDate, selectDate, areDatesIdentical, getFormattedDateTitle } = useCalendar()
|
||||
const { selectedDate, currentEvents, isReadOnly } = storeToRefs(useCalendar())
|
||||
|
||||
const breakpoints = useBreakpoints(
|
||||
breakpointsTailwind
|
||||
)
|
||||
|
||||
/**
|
||||
* All events with a startDate / endDate that starts or ends on the tile
|
||||
*/
|
||||
const eventsForTheDay = computed(() => {
|
||||
return currentEvents.value.filter((currentEvent) => {
|
||||
return (
|
||||
areDatesIdentical(currentEvent.startDate, props.date) ||
|
||||
areDatesIdentical(currentEvent.endDate!, props.date)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Is the tile on today's date ?
|
||||
*/
|
||||
const isDefaultDate = computed(() => {
|
||||
return areDatesIdentical(props.date, defaultDate)
|
||||
})
|
||||
|
||||
/**
|
||||
* Is the tile on the hightlighted date ?
|
||||
*/
|
||||
const isSelectedDate = computed(() => {
|
||||
return areDatesIdentical(props.date, selectedDate.value)
|
||||
})
|
||||
|
||||
// Get bounding elements for both tile and events list
|
||||
const { height: tileHeight, top: tileTop } = useElementBounding(calendarTile)
|
||||
const { top: tileListTop } = useElementBounding(calendarEventsList)
|
||||
|
||||
// Compute the available number of events that can be displayed from refs heights
|
||||
const numberOfEventsToFit: ComputedRef<number> = computed(() => {
|
||||
if (!eventsForTheDay.value.length) return 0
|
||||
|
||||
return Math.trunc((tileHeight.value - (tileListTop.value - tileTop.value)) / 40)
|
||||
})
|
||||
|
||||
/**
|
||||
* Events that can fit in the tile's space
|
||||
*/
|
||||
const eventsToDisplay: ComputedRef<CalendarEvent[]> = computed<CalendarEvent[]>(() => {
|
||||
return [...eventsForTheDay.value].splice(0, numberOfEventsToFit.value)
|
||||
})
|
||||
|
||||
/**
|
||||
* Remaining events that are not displayed
|
||||
*
|
||||
* Used if not all events can be seen in the tile's space
|
||||
*/
|
||||
const eventsNotDisplayed: ComputedRef<number> = computed<number>(() => eventsForTheDay.value.length - eventsToDisplay.value.length)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="calendarTile"
|
||||
class="tile relative p-1 md:p-2 transition-colors"
|
||||
:class="{
|
||||
'text-slate-300 dark:text-slate-500': props.faded,
|
||||
'text-slate-500 dark:text-slate-300': !props.faded
|
||||
}"
|
||||
>
|
||||
<button
|
||||
class="relative z-10 group block w-full text-2xs md:text-xs text-center cursor-pointer"
|
||||
@click="selectDate(date)"
|
||||
>
|
||||
<ClientOnly>
|
||||
<span
|
||||
class="inline-flex size-7 md:size-8 aspect-square items-center justify-center rounded-full border-1 md:border-2 border-transparent font-bold transition-colors group-hover:border-indigo-300 dark:group-hover:border-indigo-700"
|
||||
:class="{
|
||||
'text-white bg-slate-600 dark:bg-slate-800': isDefaultDate && !isSelectedDate,
|
||||
'text-white bg-indigo-500': isSelectedDate
|
||||
}"
|
||||
>
|
||||
{{ date.day }}
|
||||
</span>
|
||||
</ClientOnly>
|
||||
</button>
|
||||
|
||||
<ClientOnly>
|
||||
<ul
|
||||
ref="calendarEventsList"
|
||||
class="absolute top-9 md:top-12 bottom-1 md:bottom-2 inset-x-2 grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity"
|
||||
:class="{
|
||||
'opacity-40': props.faded && !isSelectedDate
|
||||
}"
|
||||
>
|
||||
<TransitionGroup name="event">
|
||||
<li v-for="event in eventsToDisplay" :key="event.id" class="grid w-full pointer-events-auto">
|
||||
<CalendarEventButton :event :tile-date="date" />
|
||||
</li>
|
||||
</TransitionGroup>
|
||||
|
||||
<li v-if="eventsNotDisplayed > 0" class="pointer-events-auto">
|
||||
<UiPopover>
|
||||
<UiPopoverTrigger as-child>
|
||||
<button
|
||||
class="text-2xs md:text-xs px-[5px] py-[5px] md:px-2 md:py-1 block w-full text-left font-bold rounded-sm whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer transition-colors hover:text-foreground hover:bg-foreground/10"
|
||||
>
|
||||
{{ eventsNotDisplayed }} autre{{ eventsNotDisplayed > 1 ? 's' : '' }}
|
||||
</button>
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent
|
||||
class="w-80"
|
||||
:align="'center'"
|
||||
:align-offset="breakpoints.lg.value ? 50 : 25"
|
||||
:collision-padding="breakpoints.lg.value ? 50 : 25"
|
||||
:side="breakpoints.lg.value ? 'left' : 'bottom'"
|
||||
>
|
||||
<div class="text-center mb-2">
|
||||
<span
|
||||
class="text-lg font-semibold rounded-full"
|
||||
>
|
||||
{{ getFormattedDateTitle(date, true) }}
|
||||
</span>
|
||||
</div>
|
||||
<ul class="grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity">
|
||||
<li
|
||||
v-for="event in eventsForTheDay"
|
||||
:key="event.title"
|
||||
class="grid pointer-events-auto"
|
||||
>
|
||||
<CalendarEventButton :event :tile-date="date" />
|
||||
</li>
|
||||
</ul>
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</li>
|
||||
</ul>
|
||||
</ClientOnly>
|
||||
|
||||
<ClientOnly>
|
||||
<LazyCalendarDialogCreateEvent v-if="!isReadOnly && breakpoints.lg.value" :date btn-class="absolute inset-0 w-full h-full cursor-default z-0" />
|
||||
<button
|
||||
v-else-if="!isReadOnly && !breakpoints.lg.value"
|
||||
class="absolute inset-0 w-full h-full cursor-default z-0"
|
||||
@click="emit('on-open-create-dialog', props.date)"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tile {
|
||||
border-right-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.event-enter-active {
|
||||
transition: all 120ms ease-in-out;
|
||||
}
|
||||
.event-move,
|
||||
.event-leave-active {
|
||||
position: absolute;
|
||||
transition: all 200ms ease-in-out;
|
||||
}
|
||||
.event-enter-from,
|
||||
.event-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.event-enter-from {
|
||||
transform: translateX(.15rem);
|
||||
}
|
||||
.event-leave-to {
|
||||
transform: translateX(-.5rem);
|
||||
}
|
||||
</style>
|
||||
100
app/components/calendar/state/monthly/Layout.vue
Normal file
100
app/components/calendar/state/monthly/Layout.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script lang="ts" setup>
|
||||
import { useCalendar } from "@/stores/CalendarStore"
|
||||
import { useThrottleFn } from "@vueuse/core"
|
||||
import type { RPGDate } from "@@/models/Date"
|
||||
import { VisuallyHidden } from "radix-vue"
|
||||
|
||||
const { currentDate, decrementViewMonth, incrementViewMonth, resetSkeleton } = useCalendar()
|
||||
const { currentMonthData, operationInProgress, eventSkeleton } = storeToRefs(useCalendar())
|
||||
|
||||
function handleWheel(e: WheelEvent) {
|
||||
// Prevent scrolling
|
||||
e.preventDefault()
|
||||
|
||||
const isMovingUp = e.deltaY < 0
|
||||
if (isMovingUp) {
|
||||
moveCalendarLeft()
|
||||
} else {
|
||||
moveCalendarRight()
|
||||
}
|
||||
}
|
||||
|
||||
const moveCalendarLeft = useThrottleFn(() => {
|
||||
decrementViewMonth()
|
||||
}, 100)
|
||||
|
||||
const moveCalendarRight = useThrottleFn(() => {
|
||||
incrementViewMonth()
|
||||
}, 100)
|
||||
|
||||
const isDialogOpen = ref<boolean>(false);
|
||||
|
||||
/**
|
||||
* Opens event creation's popover
|
||||
*/
|
||||
function handleDialogOpen(date: RPGDate) {
|
||||
// If another operation is in progress, whether it's another create popup or a modal, don't bother opening it
|
||||
if (operationInProgress.value) {
|
||||
isDialogOpen.value = false
|
||||
return
|
||||
}
|
||||
|
||||
resetSkeleton()
|
||||
|
||||
isDialogOpen.value = true
|
||||
|
||||
// Set skeleton initial startDate if it's known
|
||||
if (date) {
|
||||
eventSkeleton.value.startDate = date
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDialog() {
|
||||
isDialogOpen.value = !isDialogOpen.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prevents the modal from closing if's still loading
|
||||
*/
|
||||
function handleClosing() {
|
||||
setTimeout(() => resetSkeleton(), 100)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-5 md:grid-cols-7 lg:grid-cols-10" @wheel="handleWheel">
|
||||
<template v-if="currentMonthData">
|
||||
<CalendarStateMonthlyDayTile
|
||||
v-for="day in currentMonthData?.days"
|
||||
:key="`layout-month-grid-${day}`"
|
||||
:date="{
|
||||
day: day,
|
||||
month: currentDate.currentMonth,
|
||||
year: currentDate.currentYear
|
||||
}"
|
||||
@on-open-create-dialog="handleDialogOpen"
|
||||
/>
|
||||
|
||||
<UiDialog v-model:open="isDialogOpen">
|
||||
<UiDialogContent
|
||||
class="max-md:translate-0 max-md:inset-0 max-md:w-full max-md:block"
|
||||
:trap-focus="true"
|
||||
@escape-key-down="handleClosing"
|
||||
@pointer-down-outside.prevent="null"
|
||||
>
|
||||
<UiDialogTitle class="max-md:mb-8">
|
||||
{{ $t("entity.calendar.event.addSingle") }}
|
||||
</UiDialogTitle>
|
||||
|
||||
<VisuallyHidden>
|
||||
<UiDialogDescription>
|
||||
{{ $t("entity.calendar.event.addSingleDescription") }}
|
||||
</UiDialogDescription>
|
||||
</VisuallyHidden>
|
||||
|
||||
<CalendarFormCreateEvent @event-created="toggleDialog" />
|
||||
</UiDialogContent>
|
||||
</UiDialog>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
53
app/components/calendar/state/yearly/DayTile.vue
Normal file
53
app/components/calendar/state/yearly/DayTile.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RPGDate } from "@@/models/Date"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { computed, type ComputedRef } from "vue"
|
||||
|
||||
const { currentDate, defaultDate, selectDate, areDatesIdentical } = useCalendar()
|
||||
const { selectedDate, currentEvents } = storeToRefs(useCalendar())
|
||||
|
||||
const props = defineProps<{
|
||||
monthNumber: number
|
||||
dayNumber: number
|
||||
}>()
|
||||
|
||||
const tileDate: ComputedRef<RPGDate> = computed(() => {
|
||||
return {
|
||||
day: props.dayNumber,
|
||||
month: props.monthNumber,
|
||||
year: currentDate.currentYear
|
||||
}
|
||||
})
|
||||
|
||||
const isDefaultDate = computed<boolean>(() => {
|
||||
return areDatesIdentical(tileDate.value, defaultDate)
|
||||
})
|
||||
|
||||
const isSelectedDate = computed<boolean>(() => {
|
||||
return areDatesIdentical(tileDate.value, selectedDate.value)
|
||||
})
|
||||
|
||||
const hasAtLeastOneEvent = computed<boolean>(() => {
|
||||
return Boolean(
|
||||
currentEvents.value.find((currentEvent) => {
|
||||
return areDatesIdentical(currentEvent.startDate, tileDate.value)
|
||||
})
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="relative grid place-items-center aspect-square rounded-full border-2 border-transparent transition-colors after:content-[''] after:absolute after:top-1 after:right-1 after:w-[.3rem] after:h-[.3rem] after:rounded-full after:transition-colors"
|
||||
:class="{
|
||||
'text-slate-500 hover:border-indigo-300 hover:text-slate-900': !isDefaultDate && !isSelectedDate,
|
||||
'font-semibold text-white bg-slate-600 dark:bg-slate-800 hover:border-indigo-400': isDefaultDate && !isSelectedDate,
|
||||
'font-semibold text-white bg-indigo-500 hover:bg-indigo-600 hover:border-indigo-500': isSelectedDate,
|
||||
'after:bg-green-600': hasAtLeastOneEvent,
|
||||
'after:bg-slate-950': hasAtLeastOneEvent && isSelectedDate
|
||||
}"
|
||||
@click="selectDate(tileDate)"
|
||||
>
|
||||
<span ref="tileRef" class="text-[.85em]">{{ dayNumber }}</span>
|
||||
</button>
|
||||
</template>
|
||||
36
app/components/calendar/state/yearly/Layout.vue
Normal file
36
app/components/calendar/state/yearly/Layout.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import { useCalendar } from "@/stores/CalendarStore"
|
||||
import { useThrottleFn } from "@vueuse/core"
|
||||
|
||||
const { decrementViewYear, incrementViewYear } = useCalendar()
|
||||
const { sortedMonths: months } = storeToRefs(useCalendar())
|
||||
|
||||
function handleWheel(e: WheelEvent) {
|
||||
const isMovingUp = e.deltaY < 0
|
||||
if (isMovingUp) {
|
||||
moveCalendarLeft()
|
||||
} else {
|
||||
moveCalendarRight()
|
||||
}
|
||||
}
|
||||
|
||||
const moveCalendarLeft = useThrottleFn(() => {
|
||||
decrementViewYear()
|
||||
}, 100)
|
||||
|
||||
const moveCalendarRight = useThrottleFn(() => {
|
||||
incrementViewYear()
|
||||
}, 100)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto mt-[10vh] mb-auto" @wheel="handleWheel">
|
||||
<div class="grid grid-cols-5 gap-x-8 gap-y-16">
|
||||
<CalendarStateYearlyMonthTile
|
||||
v-for="month in months"
|
||||
:key="month.id"
|
||||
:month
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
23
app/components/calendar/state/yearly/MonthTile.vue
Normal file
23
app/components/calendar/state/yearly/MonthTile.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CalendarMonth } from "@@/models/CalendarMonth";
|
||||
|
||||
defineProps<{
|
||||
month: CalendarMonth
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="font-medium">
|
||||
{{ month.name }}
|
||||
</div>
|
||||
<div class="grid grid-cols-7 gap-1">
|
||||
<CalendarStateYearlyDayTile
|
||||
v-for="day in month.days"
|
||||
:key="day"
|
||||
:month-number="month.position"
|
||||
:day-number="day"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user