Merge branch 'dev'

This commit is contained in:
Alexis
2024-04-29 23:37:25 +02:00
35 changed files with 830 additions and 285 deletions

View File

@@ -2,7 +2,7 @@
import { useDark } from '@vueuse/core' import { useDark } from '@vueuse/core'
import { RouterView } from 'vue-router' import { RouterView } from 'vue-router'
const dark = useDark() useDark()
</script> </script>
<template> <template>

View File

@@ -1,28 +1,30 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useCalendar } from '@/stores/CalendarStore' import { useCalendar } from '@/stores/CalendarStore'
import { computed } from 'vue' import { computed, type Component, type ComputedRef } from 'vue'
import CalendarMenu from './CalendarMenu.vue' import CalendarMenu from './CalendarMenu.vue'
import Century from './state/Century.vue'
import Decade from './state/Decade.vue' import MonthlyLayout from './state/monthly/Layout.vue'
import Monthly from './state/Monthly.vue' import CenturyLayout from './state/centennially/Layout.vue'
import Year from './state/Year.vue' import DecadeLayout from './state/decennially/Layout.vue'
import YearLayout from './state/yearly/Layout.vue'
const { currentConfig } = useCalendar() const { currentConfig } = useCalendar()
const currentViewComponent = computed(() => { const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
switch (currentConfig.viewType) { switch (currentConfig.viewType) {
case 'month': case 'month':
return Monthly return MonthlyLayout
case 'year': case 'year':
return Year return YearLayout
case 'decade': case 'decade':
return Decade return DecadeLayout
case 'century': case 'century':
default: default:
return Century return CenturyLayout
} }
}) })
</script> </script>
@@ -30,6 +32,8 @@ const currentViewComponent = computed(() => {
<template> <template>
<div class="h-full grid grid-rows-[auto,1fr]"> <div class="h-full grid grid-rows-[auto,1fr]">
<CalendarMenu /> <CalendarMenu />
<component :is="currentViewComponent" /> <KeepAlive>
<component :is="currentViewComponent" />
</KeepAlive>
</div> </div>
</template> </template>

View File

@@ -0,0 +1,24 @@
<script lang="ts" setup>
import { getRelativeString } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { PhMapPin } from '@phosphor-icons/vue'
const { defaultDate, getFormattedDateTitle } = useCalendar()
const { selectedDate } = storeToRefs(useCalendar())
const mainDateTitle = computed(() => getFormattedDateTitle(selectedDate.value, true))
const dateDifference = computed(() => getRelativeString(defaultDate, selectedDate.value))
</script>
<template>
<h1 class="text-2xl font-bold flex items-center gap-1">
<PhMapPin size="26" weight="bold" /> {{ mainDateTitle }}
</h1>
<h2 class="text-lg italic opacity-75">
{{ dateDifference }}
</h2>
</template>

View File

@@ -33,6 +33,6 @@ defineProps<{
</button> </button>
</PopoverTrigger> </PopoverTrigger>
<CalendarEventDetails :event /> <CalendarEventDetails :event v-once />
</Popover> </Popover>
</template> </template>

View File

@@ -2,7 +2,6 @@
import { getRelativeString } from '@/models/Date' import { getRelativeString } from '@/models/Date'
import type { CalendarEvent } from '@/models/Events' import type { CalendarEvent } from '@/models/Events'
import { useCalendar } from '@/stores/CalendarStore' import { useCalendar } from '@/stores/CalendarStore'
import { computed } from 'vue'
import { PhHourglassMedium } from '@phosphor-icons/vue' import { PhHourglassMedium } from '@phosphor-icons/vue'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
@@ -12,11 +11,30 @@ const { defaultDate, getFormattedDateTitle } = useCalendar()
const props = defineProps<{ event: CalendarEvent }>() const props = defineProps<{ event: CalendarEvent }>()
const dateDifference = computed(() => getRelativeString(defaultDate, props.event.date)) const dateDifference: string = getRelativeString(defaultDate, props.event.date)
</script> </script>
<template> <template>
<PopoverContent class="w-96" align="start" :align-offset="50" :collision-padding="20"> <PopoverContent
class="w-96"
align="start"
:align-offset="50"
:collision-padding="20"
:class="{
'border-slate-800': !event.category,
'border-lime-800': event.category === 'naissance',
'border-stone-800': event.category === 'mort',
'border-orange-800': event.category === 'catastrophe',
'border-pink-800': event.category === 'catastrophe naturelle',
'border-sky-800': event.category === 'législation',
'border-purple-800': event.category === 'religion',
'border-emerald-800': event.category === 'joueurs',
'border-amber-800': event.category === 'inauguration',
'border-green-800': event.category === 'invention',
'border-cyan-800': event.category === 'science',
'border-yellow-800': event.category === 'bénédiction'
}"
>
<div class="grid gap-1"> <div class="grid gap-1">
<div class="text-lg font-semibold"> <div class="text-lg font-semibold">
{{ event.title }} {{ event.title }}
@@ -51,3 +69,42 @@ const dateDifference = computed(() => getRelativeString(defaultDate, props.event
</div> </div>
</PopoverContent> </PopoverContent>
</template> </template>
<style lang="scss">
.border-slate-800 {
background-color: color-mix(in srgb, var(--color-slate-800), var(--color-slate-950) 85%);
}
.border-lime-800 {
background-color: color-mix(in srgb, var(--color-lime-800), var(--color-slate-950) 85%);
}
.border-stone-800 {
background-color: color-mix(in srgb, var(--color-stone-800), var(--color-slate-950) 85%);
}
.border-orange-800 {
background-color: color-mix(in srgb, var(--color-orange-800), var(--color-slate-950) 85%);
}
.border-pink-800 {
background-color: color-mix(in srgb, var(--color-pink-800), var(--color-slate-950) 85%);
}
.border-sky-800 {
background-color: color-mix(in srgb, var(--color-sky-800), var(--color-slate-950) 85%);
}
.border-purple-800 {
background-color: color-mix(in srgb, var(--color-purple-800), var(--color-slate-950) 85%);
}
.border-emerald-800 {
background-color: color-mix(in srgb, var(--color-emerald-800), var(--color-slate-950) 85%);
}
.border-amber-800 {
background-color: color-mix(in srgb, var(--color-amber-800), var(--color-slate-950) 85%);
}
.border-green-800 {
background-color: color-mix(in srgb, var(--color-green-800), var(--color-slate-950) 85%);
}
.border-cyan-800 {
background-color: color-mix(in srgb, var(--color-cyan-800), var(--color-slate-950) 85%);
}
.border-yellow-800 {
background-color: color-mix(in srgb, var(--color-yellow-800), var(--color-slate-950) 85%);
}
</style>

View File

@@ -1,60 +1,45 @@
<script lang="ts" setup> <script lang="ts" setup>
import { getRelativeString } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore' import { useCalendar } from '@/stores/CalendarStore'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { PhMapPin } from '@phosphor-icons/vue' import { Button } from '@/components/ui/button'
import CalendarMenuNav from './CalendarMenuNav.vue' import CalendarMenuNav from './CalendarMenuNav.vue'
import CalendarMenuToday from './CalendarMenuToday.vue' import CalendarMenuToday from './CalendarMenuToday.vue'
import CalendarSwitch from './CalendarSwitch.vue' import CalendarSwitch from './CalendarSwitch.vue'
import CalendarSearch from './search/CalendarSearch.vue' import CalendarCurrentDate from './CalendarCurrentDate.vue'
const { defaultDate, getFormattedDateTitle, currentDate } = useCalendar() const { currentDate, revealAdvancedSearch } = useCalendar()
const { selectedDate } = storeToRefs(useCalendar())
const mainDateTitle = computed(() => getFormattedDateTitle(selectedDate.value, true))
const dateDifference = computed(() => getRelativeString(defaultDate, selectedDate.value))
</script> </script>
<template> <template>
<header class="border-slate-700 border-b-[1px]"> <header class="pt-4 border-slate-700 border-b-[1px]">
<div class="pt-4 container"> <div class="px-6 flex justify-between">
<div class="grid md:grid-cols-12 items-center"> <menu class="flex items-center gap-2">
<div class="md:col-span-9"> <li class="flex items-center">
<div class="flex items-center gap-6"> <CalendarMenuToday />
<menu class="flex items-center gap-2"> </li>
<li> <li>
<CalendarSwitch /> <CalendarMenuNav />
</li> </li>
<li> <li class="ml-6">
<CalendarMenuToday /> <CalendarCurrentDate />
</li> </li>
<li> </menu>
<CalendarMenuNav />
</li> <menu class="flex items-center gap-2">
</menu> <li>
<div> <Button search-slash @click="revealAdvancedSearch()">
<h1 class="text-2xl font-bold flex items-center gap-1"> <PhMagnifyingGlass size="20" weight="light" />
<PhMapPin size="26" weight="bold" /> {{ mainDateTitle }} Recherche avancée
</h1> </Button>
<h2 class="text-lg italic opacity-75"> </li>
{{ dateDifference }} <li>
</h2> <CalendarSwitch />
</div> </li>
</div> </menu>
</div>
<div class="md:col-span-3 flex justify-end">
<CalendarSearch />
</div>
</div>
</div> </div>
<div class="container"> <div class="ml-12 flex">
<div class="flex"> <div class="px-4 py-2 border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
<div class="px-4 py-2 border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm"> <span class="text-sm">{{ currentDate.currentDateTitle }}</span>
<span class="text-sm">{{ currentDate.currentDateTitle }}</span>
</div>
</div> </div>
</div> </div>
</header> </header>

View File

@@ -9,22 +9,151 @@ import {
PhCaretRight PhCaretRight
} from '@phosphor-icons/vue' } from '@phosphor-icons/vue'
import Button from '../ui/button/Button.vue' import Button from '../ui/button/Button.vue'
import { computed, type ComputedRef } from 'vue'
const { decrementMonth, incrementMonth, decrementYear, incrementYear } = useCalendar() interface DirectionLabels {
pastFar: string
pastNear: string
futureNear: string
futureFar: string
}
const { currentConfig, decrementMonth, incrementMonth, decrementYear, incrementYear } =
useCalendar()
const activeDirectionLabels: ComputedRef<DirectionLabels> = computed(() => {
switch (currentConfig.viewType) {
case 'month':
return {
pastFar: 'Année précédente',
pastNear: 'Mois précédent',
futureNear: 'Mois suivant',
futureFar: 'Année suivante'
}
case 'year':
return {
pastFar: 'Décennie précédente',
pastNear: 'Année précédente',
futureNear: 'Année suivante',
futureFar: 'Décennie suivante'
}
case 'decade':
return {
pastFar: 'Siècle précédent',
pastNear: 'Décennie précédente',
futureNear: 'Décennie suivante',
futureFar: 'Siècle suivant'
}
case 'century':
default:
return {
pastFar: 'Millénaire précédent',
pastNear: 'Siècle précédent',
futureNear: 'Siècle suivant',
futureFar: 'Millénaire suivant'
}
}
})
function toPastFar(): void {
switch (currentConfig.viewType) {
case 'month':
decrementYear()
break
case 'year':
decrementYear(10)
break
case 'decade':
decrementYear(100)
break
case 'century':
default:
decrementYear(1000)
break
}
}
function toPastNear(): void {
switch (currentConfig.viewType) {
case 'month':
decrementMonth()
break
case 'year':
decrementYear()
break
case 'decade':
decrementYear(10)
break
case 'century':
default:
decrementYear(100)
break
}
}
function toFutureNear(): void {
switch (currentConfig.viewType) {
case 'month':
incrementMonth()
break
case 'year':
incrementYear()
break
case 'decade':
incrementYear(10)
break
case 'century':
default:
incrementYear(100)
break
}
}
function toFutureFar(): void {
switch (currentConfig.viewType) {
case 'month':
incrementYear()
break
case 'year':
incrementYear(10)
break
case 'decade':
incrementYear(100)
break
case 'century':
default:
incrementYear(1000)
break
}
}
</script> </script>
<template> <template>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<!-- Implement decrementDate to account for other mods -->
<TooltipProvider :delayDuration="250"> <TooltipProvider :delayDuration="250">
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<Button variant="outline" size="icon" @click="decrementYear()"> <Button variant="outline" size="icon" @click="toPastFar()">
<PhCaretDoubleLeft size="18" /> <PhCaretDoubleLeft size="18" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Année précédente</p> <p>{{ activeDirectionLabels.pastFar }}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@@ -32,12 +161,12 @@ const { decrementMonth, incrementMonth, decrementYear, incrementYear } = useCale
<TooltipProvider :delayDuration="250"> <TooltipProvider :delayDuration="250">
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<Button variant="outline" size="icon" @click="decrementMonth()"> <Button variant="outline" size="icon" @click="toPastNear()">
<PhCaretLeft size="18" /> <PhCaretLeft size="18" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Mois précédent</p> <p>{{ activeDirectionLabels.pastNear }}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@@ -45,12 +174,12 @@ const { decrementMonth, incrementMonth, decrementYear, incrementYear } = useCale
<TooltipProvider :delayDuration="250"> <TooltipProvider :delayDuration="250">
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<Button variant="outline" size="icon" @click="incrementMonth()"> <Button variant="outline" size="icon" @click="toFutureNear()">
<PhCaretRight size="18" /> <PhCaretRight size="18" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Mois suivant</p> <p>{{ activeDirectionLabels.futureNear }}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@@ -58,12 +187,12 @@ const { decrementMonth, incrementMonth, decrementYear, incrementYear } = useCale
<TooltipProvider :delayDuration="250"> <TooltipProvider :delayDuration="250">
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<Button variant="outline" size="icon" @click="incrementYear()"> <Button variant="outline" size="icon" @click="toFutureFar()">
<PhCaretDoubleRight size="18" /> <PhCaretDoubleRight size="18" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Année suivante</p> <p>{{ activeDirectionLabels.futureFar }}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>

View File

@@ -10,7 +10,7 @@ import { areDatesIdentical } from '@/models/Date'
const { defaultDate, selectedDate } = storeToRefs(useCalendar()) const { defaultDate, selectedDate } = storeToRefs(useCalendar())
const { jumpToDefaultDate, getFormattedDateTitle } = useCalendar() const { jumpToDefaultDate, getFormattedDateTitle } = useCalendar()
const defaultDateFormatted = computed(() => getFormattedDateTitle(defaultDate.value, true)) const defaultDateFormatted = getFormattedDateTitle(defaultDate.value, true)
const isDefaultDate = computed(() => { const isDefaultDate = computed(() => {
return areDatesIdentical(selectedDate.value, defaultDate.value) return areDatesIdentical(selectedDate.value, defaultDate.value)

View File

@@ -1,83 +0,0 @@
<script lang="ts" setup>
import { areDatesIdentical, type LeimDate } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { useCalendarEvents } from '@/stores/EventStore'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import CalendarEvent from './CalendarEvent.vue'
const props = defineProps<{
date: LeimDate
faded?: boolean
}>()
const { defaultDate, selectDate } = useCalendar()
const { selectedDate } = storeToRefs(useCalendar())
const { currentEvents } = storeToRefs(useCalendarEvents())
const eventsForTheDay = computed(() => {
return currentEvents.value.filter((currentEvent) => {
return areDatesIdentical(currentEvent.date, props.date)
})
})
const isDefaultDate = computed(() => {
return areDatesIdentical(props.date, defaultDate)
})
const isSelectedDate = computed(() => {
return areDatesIdentical(props.date, selectedDate.value)
})
</script>
<template>
<div
class="tile relative text-xs p-2 border-slate-700"
:class="{
'text-slate-500': props.faded,
'text-slate-300': !props.faded
}"
>
<!-- Used for "display all events" -->
<button class="absolute inset-0 w-full h-full cursor-default z-0" />
<button
class="relative z-10 group block w-full text-center cursor-pointer"
@click="selectDate(date)"
>
<span
class="inline-flex w-8 h-8 aspect-square items-center justify-center rounded-full border-2 border-transparent font-bold transition-colors group-hover:border-slate-800"
:class="{
'bg-slate-800': isDefaultDate && !isSelectedDate,
'text-white bg-blue-500': isSelectedDate
}"
>
{{ date.day }}
</span>
</button>
<ul
v-if="eventsForTheDay.length > 0"
class="absolute top-12 bottom-2 inset-x-2 grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity"
:class="{
'opacity-40': props.faded && !isSelectedDate
}"
>
<li v-for="event in eventsForTheDay" :key="event.title" class="grid pointer-events-auto">
<CalendarEvent :event />
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.tile {
&:not(:nth-child(10n)) {
border-right-width: 1px;
}
&:nth-child(n + 11) {
border-top-width: 1px;
}
}
</style>

View File

@@ -0,0 +1,56 @@
<script lang="ts" setup>
import { useCalendar } from '@/stores/CalendarStore'
import { PhList, PhHouse, PhMagnifyingGlass } from '@phosphor-icons/vue'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import Button from '../ui/button/Button.vue'
const { revealAdvancedSearch } = useCalendar()
</script>
<template>
<nav class="w-16 py-6 border-r-[1px] border-l-slate-500 grid justify-center">
<menu class="flex flex-col gap-4">
<li class="mb-12">
<Button variant="ghost" size="icon" @click="console.log" class="rounded-full">
<PhList size="27" />
</Button>
</li>
<li>
<TooltipProvider :delayDuration="100">
<Tooltip>
<TooltipTrigger>
<Button variant="ghost" size="icon" class="rounded-full" as-child>
<RouterLink to="/">
<PhHouse size="24" weight="fill" />
</RouterLink>
</Button>
</TooltipTrigger>
<TooltipContent :side="'right'">
<p>Retourner aux outils</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</li>
<li>
<TooltipProvider :delayDuration="100">
<Tooltip>
<TooltipTrigger>
<Button
variant="ghost"
size="icon"
class="rounded-full"
@click="revealAdvancedSearch()"
>
<PhMagnifyingGlass size="24" weight="fill" />
</Button>
</TooltipTrigger>
<TooltipContent :side="'right'">
<p>Recherche avancée</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</li>
</menu>
</nav>
</template>

View File

@@ -62,7 +62,7 @@ import SearchList from './lists/SearchList.vue'
const { characters } = useCharacters() const { characters } = useCharacters()
const { baseEvents } = useCalendarEvents() const { baseEvents } = useCalendarEvents()
const modalOpen = ref<boolean>(false) const modalOpen = defineModel({ default: false })
const searchQuery = ref<string>('') const searchQuery = ref<string>('')
// const searchEnough = computed<boolean>(() => searchQuery.value.length >= 2) // const searchEnough = computed<boolean>(() => searchQuery.value.length >= 2)
@@ -243,13 +243,6 @@ function handleCategorySelect(e: any) {
<template> <template>
<Dialog v-model:open="modalOpen" @update:open="resetSearch()"> <Dialog v-model:open="modalOpen" @update:open="resetSearch()">
<DialogTrigger>
<Button search-slash>
<PhMagnifyingGlass size="20" weight="light" />
Recherche avancée
</Button>
</DialogTrigger>
<DialogContent <DialogContent
class="flex flex-col flex-nowrap top-16 -translate-y-0 data-[state=closed]:slide-out-to-top-[5%] data-[state=open]:slide-in-from-top-[5%]" class="flex flex-col flex-nowrap top-16 -translate-y-0 data-[state=closed]:slide-out-to-top-[5%] data-[state=open]:slide-in-from-top-[5%]"
:class="{ :class="{
@@ -289,10 +282,10 @@ function handleCategorySelect(e: any) {
v-model="selectedEntity" v-model="selectedEntity"
@update:model-value="handleEntitySwitch()" @update:model-value="handleEntitySwitch()"
> >
<ToggleGroupItem value="events" aria-label="Uniquement les évènements"> <ToggleGroupItem value="events" aria-label="Uniquement les évènements" v-once>
Évènements Évènements
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem value="characters" aria-label="Uniquement les personnages"> <ToggleGroupItem value="characters" aria-label="Uniquement les personnages" v-once>
Personnages Personnages
</ToggleGroupItem> </ToggleGroupItem>
</ToggleGroup> </ToggleGroup>

View File

@@ -1,3 +0,0 @@
<template>
<div>Annuel</div>
</template>

View File

@@ -0,0 +1,139 @@
<script lang="ts" setup>
import { areDatesIdentical, type LeimDate } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { useCalendarEvents } from '@/stores/EventStore'
import { useElementBounding } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { computed, ref, type ComputedRef } from 'vue'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import CalendarEventButton from '../../CalendarEvent.vue'
import type { CalendarEvent } from '@/models/Events'
const props = defineProps<{
date: LeimDate
faded?: boolean
}>()
const calendarTile = ref()
const calendarEventsList = ref()
const { defaultDate, selectDate } = useCalendar()
const { selectedDate } = storeToRefs(useCalendar())
const { currentEvents } = storeToRefs(useCalendarEvents())
const eventsForTheDay = computed(() => {
return currentEvents.value.filter((currentEvent) => {
return areDatesIdentical(currentEvent.date, props.date)
})
})
const isDefaultDate = computed(() => {
return areDatesIdentical(props.date, defaultDate)
})
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)
})
const eventsToDisplay: ComputedRef<CalendarEvent[]> = computed(() => {
return [...eventsForTheDay.value].splice(0, numberOfEventsToFit.value)
})
const eventsNotDisplayed = computed(
() => eventsForTheDay.value.length - eventsToDisplay.value.length
)
</script>
<template>
<div
ref="calendarTile"
class="tile relative text-xs p-2 border-slate-700"
:class="{
'text-slate-500': props.faded,
'text-slate-300': !props.faded
}"
>
<!-- Used for "display all events" -->
<button class="absolute inset-0 w-full h-full cursor-default z-0" />
<button
class="relative z-10 group block w-full text-center cursor-pointer"
@click="selectDate(date)"
>
<span
class="inline-flex w-8 h-8 aspect-square items-center justify-center rounded-full border-2 border-transparent font-bold transition-colors group-hover:border-slate-800"
:class="{
'bg-slate-800': isDefaultDate && !isSelectedDate,
'text-white bg-blue-500': isSelectedDate
}"
>
{{ date.day }}
</span>
</button>
<ul
ref="calendarEventsList"
class="absolute top-12 bottom-2 inset-x-2 grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity"
:class="{
'opacity-40': props.faded && !isSelectedDate
}"
>
<li v-for="event in eventsToDisplay" :key="event.title" class="grid pointer-events-auto">
<CalendarEventButton :event />
</li>
<li v-if="eventsNotDisplayed > 0" class="pointer-events-auto">
<Popover>
<PopoverTrigger as-child>
<button
class="text-xs px-2 py-1 block w-full text-left font-bold rounded-sm whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer transition-colors hover:bg-slate-800"
>
{{ eventsNotDisplayed }} autre{{ eventsNotDisplayed > 1 ? 's' : '' }}
</button>
</PopoverTrigger>
<PopoverContent class="w-80" :align="'center'" :side="'right'">
<div class="text-center mb-4">
<span
class="inline-flex w-12 h-12 aspect-square items-center justify-center text-lg font-semibold text-slate-300 bg-slate-800 rounded-full"
>
{{ date.day }}
</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 />
</li>
</ul>
</PopoverContent>
</Popover>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.tile {
&:not(:nth-child(10n)) {
border-right-width: 1px;
}
&:nth-child(n + 11) {
border-top-width: 1px;
}
}
</style>

View File

@@ -2,13 +2,12 @@
import type { LeimDate } from '@/models/Date' import type { LeimDate } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore' import { useCalendar } from '@/stores/CalendarStore'
import { useThrottleFn } from '@vueuse/core' import { useThrottleFn } from '@vueuse/core'
import { computed } from 'vue'
import CalendarTile from '../CalendarTile.vue' import DayTile from './DayTile.vue'
const { staticConfig, currentDate, decrementMonth, incrementMonth } = useCalendar() const { staticConfig, currentDate, decrementMonth, incrementMonth } = useCalendar()
const daysPerMonth = computed(() => staticConfig.daysPerMonth) const daysPerMonth = staticConfig.daysPerMonth
function getNextMonthDate(day: number): LeimDate { function getNextMonthDate(day: number): LeimDate {
let nextDay = day let nextDay = day
@@ -54,18 +53,17 @@ const moveCalendarRight = useThrottleFn(() => {
</script> </script>
<template> <template>
<div class="grid" :class="`grid-cols-10`" @wheel="handleWheel"> <div class="grid grid-cols-10" @wheel="handleWheel">
<CalendarTile <DayTile
v-for="day in daysPerMonth" v-for="day in daysPerMonth"
:key="day" :key="day"
:date="{ :date="{
day: day, day: day,
month: currentDate.currentMonth, month: currentDate.currentMonth,
year: currentDate.currentYear, year: currentDate.currentYear
period: currentDate.currentPeriod
}" }"
/> />
<CalendarTile <DayTile
v-for="nextMonthDay in 8" v-for="nextMonthDay in 8"
:key="nextMonthDay" :key="nextMonthDay"
faded faded

View File

@@ -0,0 +1,56 @@
<script lang="ts" setup>
import { areDatesIdentical, type LeimDate } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { useCalendarEvents } from '@/stores/EventStore'
import { storeToRefs } from 'pinia'
import { computed, type ComputedRef } from 'vue'
const { currentDate, defaultDate, selectDate } = useCalendar()
const { selectedDate } = storeToRefs(useCalendar())
const { currentEvents } = storeToRefs(useCalendarEvents())
const props = defineProps<{
monthNumber: number
dayNumber: number
}>()
const tileDate: ComputedRef<LeimDate> = 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.date, 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="{
'hover:bg-slate-800 hover:border-slate-800': !isDefaultDate && !isSelectedDate,
'bg-slate-800 hover:bg-slate-600 hover:border-slate-600': isDefaultDate && !isSelectedDate,
'text-white bg-blue-500 hover:bg-blue-600 hover:border-blue-600': isSelectedDate,
'after:bg-green-600': hasAtLeastOneEvent,
'after:bg-slate-950': hasAtLeastOneEvent && isSelectedDate
}"
@click="selectDate(tileDate)"
>
<span ref="tileRef" class="text-slate-300 text-[.85em]">{{ dayNumber }}</span>
</button>
</template>

View File

@@ -0,0 +1,37 @@
<script lang="ts" setup>
import { useCalendar } from '@/stores/CalendarStore'
import { useThrottleFn } from '@vueuse/core'
import MonthTile from './MonthTile.vue'
const { staticConfig, decrementYear, incrementYear } = useCalendar()
function handleWheel(e: WheelEvent) {
const isMovingUp = e.deltaY < 0
if (isMovingUp) {
moveCalendarLeft()
} else {
moveCalendarRight()
}
}
const moveCalendarLeft = useThrottleFn(() => {
decrementYear()
}, 100)
const moveCalendarRight = useThrottleFn(() => {
incrementYear()
}, 100)
</script>
<template>
<div class="container mt-[10vh] mb-auto" @wheel="handleWheel">
<div ref="test" class="grid grid-cols-5 gap-x-8 gap-y-16">
<MonthTile
v-for="month in staticConfig.monthsPerYear"
:key="month"
:month-number="month - 1"
/>
</div>
</div>
</template>

View File

@@ -0,0 +1,29 @@
<script lang="ts" setup>
import { useCalendar } from '@/stores/CalendarStore'
import DayTile from './DayTile.vue'
const { staticConfig, getMonthName } = useCalendar()
const props = defineProps<{
monthNumber: number
}>()
const tileMonthName: string = getMonthName(props.monthNumber)
</script>
<template>
<div>
<div class="font-medium">
{{ tileMonthName }}
</div>
<div class="grid grid-cols-7">
<DayTile
v-for="day in staticConfig.daysPerMonth"
:key="day"
:month-number="monthNumber"
:day-number="day"
/>
</div>
</div>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
import { RouterLink } from 'vue-router'
const props = defineProps<{
class?: HTMLAttributes['class']
link?: string
}>()
</script>
<template>
<div
:class="
cn('rounded-lg border bg-card text-card-foreground shadow-sm transition-all', props.class, {
'relative outline outline-2 outline-offset-4 outline-transparent hover:bg-sky-950 hover:-translate-y-[.2rem] focus-within:outline-sky-900':
props.link
})
"
>
<slot />
<RouterLink
v-if="props.link"
:to="props.link"
class="absolute inset-0 focus-visible:outline-none"
/>
</div>
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div :class="cn('p-6 pt-0 text-muted-foreground', props.class)">
<slot />
</div>
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<p :class="cn('text-sm text-muted-foreground mt-1', props.class)">
<slot />
</p>
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div :class="cn('flex items-center p-6 pt-0', props.class)">
<slot />
</div>
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div :class="cn('flex flex-col gap-y-1.5 px-6 pt-6 pb-3', props.class)">
<slot />
</div>
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<h3 :class="cn('text-2xl font-semibold leading-none tracking-tight', props.class)">
<slot />
</h3>
</template>

View File

@@ -0,0 +1,6 @@
export { default as Card } from './Card.vue'
export { default as CardHeader } from './CardHeader.vue'
export { default as CardTitle } from './CardTitle.vue'
export { default as CardDescription } from './CardDescription.vue'
export { default as CardContent } from './CardContent.vue'
export { default as CardFooter } from './CardFooter.vue'

View File

@@ -83,28 +83,28 @@ export const regularEvents: CalendarEvent[] = [
}, },
{ {
title: '2ème disparation à Cantane', title: '2ème disparation à Cantane',
description: 'Seconde victime de la série de disparitions qui affecte Cantane', description: 'Donovane le mineur kéturien disparait sans laisser de traces.',
date: { day: 32, month: 7, year: 3209 }, date: { day: 32, month: 7, year: 3209 },
category: 'mort', category: 'mort',
hidden: true hidden: true
}, },
{ {
title: '3ème disparation à Cantane', title: '3ème disparation à Cantane',
description: 'Troisième victime de la série de disparitions qui affecte Cantane', description: 'Disparition de Sébastien, gredin sauride.',
date: { day: 10, month: 8, year: 3209 }, date: { day: 10, month: 8, year: 3209 },
category: 'mort', category: 'mort',
hidden: true hidden: true
}, },
{ {
title: '4ème disparation à Cantane', title: '4ème disparation à Cantane',
description: 'Quatrième victime de la série de disparitions qui affecte Cantane', description: 'Disparition de Thérence, patrouilleur sauride de la Vieille Garde.',
date: { day: 19, month: 8, year: 3209 }, date: { day: 19, month: 8, year: 3209 },
category: 'mort', category: 'mort',
hidden: true hidden: true
}, },
{ {
title: '5ème disparation à Cantane', title: '5ème disparation à Cantane',
description: 'Cinquième victime de la série de disparitions qui affecte Cantane', description: 'Disparition de Mathilda Boulais, vendeuse de pierres.',
date: { day: 22, month: 8, year: 3209 }, date: { day: 22, month: 8, year: 3209 },
category: 'mort', category: 'mort',
hidden: true hidden: true
@@ -114,7 +114,6 @@ export const regularEvents: CalendarEvent[] = [
description: description:
'Les artisans et mineurs de Rougefer se réunissent à Cantane pour vendre le fruit de leur dur labeur.', 'Les artisans et mineurs de Rougefer se réunissent à Cantane pour vendre le fruit de leur dur labeur.',
date: { day: 23, month: 8, year: 3209 }, date: { day: 23, month: 8, year: 3209 },
category: 'inauguration', category: 'inauguration'
hidden: true
} }
] ]

View File

@@ -1,13 +1,19 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue' import HomePage from '@/views/HomePage.vue'
import CalendarPage from '@/views/CalendarPage.vue'
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ {
path: '/', path: '/',
name: 'home', name: 'accueil',
component: HomeView component: HomePage
},
{
path: '/calendrier',
name: 'calendrier',
component: CalendarPage
} }
] ]
}) })

View File

@@ -89,22 +89,10 @@ export const useCalendar = defineStore('calendar', () => {
return { return {
day: defaultDay, day: defaultDay,
month: defaultMonth, month: defaultMonth,
year: defaultYear, year: defaultYear
period: 'nante'
} }
}) })
// Assign default values if no params exist in URL
if (!params.day || typeof params.day === 'object' || !isInt(params.day)) {
params.day = defaultDay.toString()
}
if (!params.month || typeof params.month === 'object' || !isDigit(params.month)) {
params.month = defaultMonth.toString()
}
if (!params.year || typeof params.year === 'object' || !isSignedInt(params.year)) {
params.year = defaultYear.toString()
}
const currentDay = computed<number>(() => { const currentDay = computed<number>(() => {
return Number(params.day) return Number(params.day)
}) })
@@ -192,6 +180,19 @@ export const useCalendar = defineStore('calendar', () => {
} }
}) })
function validateUrlParams(): void {
// Assign default values if no params exist in URL
if (!params.day || typeof params.day === 'object' || !isInt(params.day)) {
params.day = defaultDate.value.day.toString()
}
if (!params.month || typeof params.month === 'object' || !isDigit(params.month)) {
params.month = defaultDate.value.month.toString()
}
if (!params.year || typeof params.year === 'object' || !isSignedInt(params.year)) {
params.year = defaultDate.value.year.toString()
}
}
/** /**
* Moves the current date forward one month * Moves the current date forward one month
*/ */
@@ -378,7 +379,6 @@ export const useCalendar = defineStore('calendar', () => {
*/ */
function jumpToDefaultDate(): void { function jumpToDefaultDate(): void {
jumpToDate(defaultDate.value) jumpToDate(defaultDate.value)
currentConfig.value.viewType = 'month'
} }
/** /**
@@ -388,6 +388,12 @@ export const useCalendar = defineStore('calendar', () => {
selectedDate.value = date selectedDate.value = date
} }
const isAdvancedSearchOpen: Ref<boolean> = ref<boolean>(false)
function revealAdvancedSearch() {
isAdvancedSearchOpen.value = true
}
return { return {
staticConfig, staticConfig,
viewTypeOptions, viewTypeOptions,
@@ -398,6 +404,7 @@ export const useCalendar = defineStore('calendar', () => {
selectedDate, selectedDate,
selectDate, selectDate,
params, params,
validateUrlParams,
getPeriodOfYear, getPeriodOfYear,
incrementMonth, incrementMonth,
decrementMonth, decrementMonth,
@@ -412,6 +419,8 @@ export const useCalendar = defineStore('calendar', () => {
getMonthName, getMonthName,
setViewType, setViewType,
getViewTypeTitle, getViewTypeTitle,
isCurrentScreenActive isCurrentScreenActive,
isAdvancedSearchOpen,
revealAdvancedSearch
} }
}) })

View File

@@ -1,20 +1,15 @@
import { charactersList } from '@/data/Characters' import { charactersList } from '@/data/Characters'
import type { Character } from '@/models/Characters' import type { Character } from '@/models/Characters'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { computed, type ComputedRef } from 'vue'
export const useCharacters = defineStore('characters', () => { export const useCharacters = defineStore('characters', () => {
const characters: Character[] = charactersList const characters: Character[] = charactersList
// Get all birth events // Get all birth events
const charactersWithBirthData: ComputedRef<Character[]> = computed(() => const charactersWithBirthData: Character[] = characters.filter((character) => character.birth)
characters.filter((character) => character.birth)
)
// Get all death events // Get all death events
const charactersWithDeathData: ComputedRef<Character[]> = computed(() => const charactersWithDeathData: Character[] = characters.filter((character) => character.death)
characters.filter((character) => character.death)
)
return { characters, charactersWithBirthData, charactersWithDeathData } return { characters, charactersWithBirthData, charactersWithDeathData }
}) })

View File

@@ -1,40 +1,34 @@
import type { CalendarEvent } from '@/models/Events'
import { defineStore } from 'pinia'
import { computed, ref, watch, type Ref } from 'vue'
import { useCalendar } from './CalendarStore'
import { useCharacters } from './CharacterStore'
import { regularEvents } from '@/data/Events' import { regularEvents } from '@/data/Events'
import { convertDateToDays, daysPerMonth } from '@/models/Date' import { convertDateToDays, daysPerMonth } from '@/models/Date'
import type { CalendarEvent } from '@/models/Events'
import { defineStore } from 'pinia'
import { ref, watch, type Ref } from 'vue'
import { useCalendar } from './CalendarStore'
import { useCharacters } from './CharacterStore'
export const useCalendarEvents = defineStore('calendar-events', () => { export const useCalendarEvents = defineStore('calendar-events', () => {
const { currentDate, currentConfig, currentLeimDate } = useCalendar() const { currentDate, currentConfig } = useCalendar()
const { charactersWithBirthData, charactersWithDeathData } = useCharacters() const { charactersWithBirthData, charactersWithDeathData } = useCharacters()
const baseEvents = ref<CalendarEvent[]>(regularEvents) const baseEvents: CalendarEvent[] = regularEvents
const characterBirthEvents = computed(() => { const characterBirthEvents = charactersWithBirthData.map((character) => {
return charactersWithBirthData.map((character) => { return {
return { title: `Naissance de ${character.name}`,
title: `Naissance de ${character.name}`, date: character.birth,
date: character.birth, category: 'naissance'
category: 'naissance' } as CalendarEvent
} as CalendarEvent
})
}) })
const characterDeathEvents = computed(() => { const characterDeathEvents = charactersWithDeathData.map((character) => {
return charactersWithDeathData.map((character) => { return {
return { title: `Décès de ${character.name}`,
title: `Décès de ${character.name}`, date: character.death,
date: character.death, category: 'mort'
category: 'mort' } as CalendarEvent
} as CalendarEvent
})
}) })
const allEvents = computed(() => { const allEvents = [...characterBirthEvents, ...characterDeathEvents, ...baseEvents]
return [...characterBirthEvents.value, ...characterDeathEvents.value, ...baseEvents.value]
})
// Gets all current event in its default state // Gets all current event in its default state
const currentEvents: Ref<CalendarEvent[]> = ref(computeCurrentEvents()) const currentEvents: Ref<CalendarEvent[]> = ref(computeCurrentEvents())
@@ -100,7 +94,7 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
* @returns A list of events that can appear in the current view * @returns A list of events that can appear in the current view
*/ */
function computeCurrentEvents(): CalendarEvent[] { function computeCurrentEvents(): CalendarEvent[] {
return allEvents.value.filter((event) => shouldEventBeDisplayed(event)) return allEvents.filter((event) => shouldEventBeDisplayed(event))
} }
return { baseEvents, allEvents, currentEvents } return { baseEvents, allEvents, currentEvents }

View File

@@ -0,0 +1,23 @@
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { onMounted } from 'vue'
import { useCalendar } from '@/stores/CalendarStore'
import Calendar from '@/components/calendar/Calendar.vue'
import Sidebar from '@/components/calendar/Sidebar.vue'
import CalendarSearch from '@/components/calendar/search/CalendarSearch.vue'
const { validateUrlParams } = useCalendar()
const { isAdvancedSearchOpen } = storeToRefs(useCalendar())
onMounted(() => validateUrlParams())
</script>
<template>
<main class="h-full grid grid-cols-[auto_1fr]">
<Sidebar />
<Calendar />
</main>
<CalendarSearch v-model:model-value="isAdvancedSearchOpen" />
</template>

22
src/views/HomePage.vue Normal file
View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
</script>
<template>
<main class="h-full grid place-items-center" v-once>
<Card class="w-1/3 min-w-72" link="/calendrier">
<CardHeader>
<CardTitle>Calendrier</CardTitle>
<CardDescription>
<Badge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
chronologie
</Badge>
</CardDescription>
</CardHeader>
<CardContent>
Chronologie complète de Léïm, rassemblant les évènements et personnages clés du monde
</CardContent>
</Card>
</main>
</template>

View File

@@ -1,49 +0,0 @@
<script lang="ts" setup>
import Calendar from '@/components/calendar/Calendar.vue'
</script>
<template>
<main class="h-full">
<Calendar />
<!-- <pre>
{{ currentDate }}
</pre>
<div class="grid gap-2">
<div class="flex gap-2">
<button class="bg-slate-800 py-2 px-4 rounded-sm" @click="incrementMonth()">Month +</button>
<button class="bg-slate-800 py-2 px-4 rounded-sm" @click="decrementMonth()">Month -</button>
</div>
<div class="flex gap-2">
<button class="bg-slate-800 py-2 px-4 rounded-sm" @click="incrementYear()">Year +</button>
<button class="bg-slate-800 py-2 px-4 rounded-sm" @click="decrementYear()">Year -</button>
</div>
<div class="flex gap-2">
<button class="bg-slate-800 py-2 px-4 rounded-sm" @click="incrementYear(100)">
Century +
</button>
<button class="bg-slate-800 py-2 px-4 rounded-sm" @click="decrementYear(100)">
Century -
</button>
</div>
<div class="flex gap-2">
<button class="bg-slate-800 py-2 px-4 rounded-sm" @click="incrementYear(1000)">
Millenia +
</button>
<button class="bg-slate-800 py-2 px-4 rounded-sm" @click="decrementYear(1000)">
Millenia -
</button>
</div>
<div class="flex gap-2">
<input class="bg-slate-900" type="number" v-model="monthTarget" />
<button class="bg-slate-800 py-2 px-4 rounded-sm" @click="setMonth(monthTarget)">
Set Month
</button>
</div>
</div>
<pre>
Current events:
{{ currentEvents }}
</pre> -->
</main>
</template>

View File

@@ -89,5 +89,25 @@ module.exports = {
} }
} }
}, },
plugins: [animate] plugins: [
animate,
function ({ addBase, theme }) {
function extractColorVars(colorObj, colorGroup = '') {
return Object.keys(colorObj).reduce((vars, colorKey) => {
const value = colorObj[colorKey]
const newVars =
typeof value === 'string'
? { [`--color${colorGroup}-${colorKey}`]: value }
: extractColorVars(value, `-${colorKey}`)
return { ...vars, ...newVars }
}, {})
}
addBase({
':root': extractColorVars(theme('colors'))
})
}
]
} }