Merge branch 'features/nuxt-supabase' into features/nuxt

This commit is contained in:
Alexis
2024-05-24 20:03:21 +02:00
44 changed files with 1335 additions and 1094 deletions

View File

@@ -2,18 +2,62 @@
import { useCalendar } from '@/stores/CalendarStore'
import { computed, type Component, type ComputedRef } from 'vue'
import CalendarMenu from './CalendarMenu.vue'
import MonthlyLayout from './state/monthly/Layout.vue'
import CenturyLayout from './state/centennially/Layout.vue'
import DecadeLayout from './state/decennially/Layout.vue'
import YearLayout from './state/yearly/Layout.vue'
const { currentConfig } = useCalendar()
const { selectedDate, jumpToDate } = useCalendar()
const route = useRoute()
const worldId = route.params.id
const { fetchEvents } = useCalendarEvents()
fetchEvents()
const { setMonths, setDefaultDate, currentConfig, selectedDate, jumpToDate } = useCalendar()
const { setEvents } = useCalendarEvents()
const { setCharacters } = useCharacters()
const { data: calendar, pending: calPending, refresh: calRefresh } = await useLazyFetch(`/api/calendars/query?world_id=${worldId}`)
const { data: characters, pending: charPending, refresh: charRefresh } = await useLazyFetch(`/api/characters/query?world_id=${worldId}`)
if (!calendar.value) {
await calRefresh()
} else {
if (calendar.value?.data?.months) {
setMonths(calendar.value?.data?.months)
}
if (calendar.value?.data?.today) {
setDefaultDate(calendar.value?.data?.today)
}
if (calendar.value?.data?.events) {
setEvents(calendar.value?.data?.events)
}
}
if (!characters.value) {
await charRefresh()
} else {
if (characters.value?.data) {
setCharacters(characters.value?.data)
}
}
watch(calPending, (newStatus) => {
if (!newStatus) {
if (calendar.value?.data?.months) {
setMonths(calendar.value?.data?.months)
}
if (calendar.value?.data?.today) {
setDefaultDate(calendar.value?.data?.today)
}
if (calendar.value?.data?.events) {
setEvents(calendar.value?.data?.events)
}
}
})
watch(charPending, (newStatus) => {
if (!newStatus) {
if (characters.value?.data) {
setCharacters(characters.value?.data)
}
}
})
const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
switch (currentConfig.viewType) {
@@ -38,11 +82,20 @@ onMounted(() => {
</script>
<template>
<div class="h-full grid grid-rows-[auto,1fr]">
<CalendarMenu />
<div class="h-full">
<template v-if="calPending || charPending">
<div class="h-full grid place-items-center">
Loading notamment
</div>
</template>
<template v-else>
<div class="h-full grid grid-rows-[auto,1fr]">
<CalendarMenu />
<KeepAlive>
<component :is="currentViewComponent" />
</KeepAlive>
<KeepAlive>
<component :is="currentViewComponent"/>
</KeepAlive>
</div>
</template>
</div>
</template>

View File

@@ -1,14 +1,14 @@
<script lang="ts" setup>
import { getRelativeString } from '@/models/Date'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { PhMapPin } from '@phosphor-icons/vue'
const { defaultDate, getFormattedDateTitle } = useCalendar()
const { defaultDate, getFormattedDateTitle, getRelativeString } = useCalendar()
const { selectedDate } = storeToRefs(useCalendar())
const mainDateTitle = computed(() => getFormattedDateTitle(selectedDate.value, true))
// const mainDateTitle = computed(() => convertDateToDays(selectedDate.value))
const dateDifference = computed(() => getRelativeString(defaultDate, selectedDate.value))
</script>

View File

@@ -1,16 +1,16 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { cn } from '@/lib/utils'
import { areDatesIdentical, type LeimDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/Events'
import CalendarEventDetails from './CalendarEventDetails.vue'
import type { RPGDate } from '@/models/Date'
import type { CalendarEvent } from '~/models/CalendarEvent'
const props = defineProps<{
event: CalendarEvent
tileDate: LeimDate
tileDate: RPGDate
}>()
const { areDatesIdentical } = useCalendar()
const spansMultipleDays = Boolean(props.event.startDate && props.event.endDate)
const isStartEvent = spansMultipleDays && areDatesIdentical(props.tileDate, props.event.startDate)
@@ -32,24 +32,24 @@ function handleClosePopover() {
:class="
cn({
'text-white bg-slate-600 hover:bg-slate-700': !event.category,
'text-white bg-lime-600 hover:bg-lime-700': event.category === 'naissance',
'text-white bg-stone-500 hover:bg-stone-700': event.category === 'mort',
'text-white bg-orange-600 hover:bg-orange-700': event.category === 'catastrophe',
'text-white bg-pink-600 hover:bg-pink-700': event.category === 'catastrophe naturelle',
'text-white bg-sky-600 hover:bg-sky-700': event.category === 'législation',
'text-white bg-purple-600 hover:bg-purple-700': event.category === 'religion',
'text-white bg-emerald-600 hover:bg-emerald-700': event.category === 'joueurs',
'text-slate-900 bg-amber-300 hover:bg-amber-400': event.category === 'inauguration',
'text-slate-900 bg-emerald-200 hover:bg-emerald-300': event.category === 'invention',
'text-slate-900 bg-cyan-300 hover:bg-cyan-400': event.category === 'science',
'text-slate-900 bg-white hover:bg-yellow-200': event.category === 'bénédiction',
'text-slate-900 bg-purple-200 hover:bg-purple-300': event.category === 'découverte',
'text-slate-900 bg-indigo-200 hover:bg-indigo-300': event.category === 'exploration',
'text-white bg-amber-600 hover:bg-amber-700': event.category === 'construction',
'text-slate-900 bg-violet-200 hover:bg-violet-300': event.category === 'arcanologie',
'text-white bg-rose-600 hover:bg-rose-700': event.category === 'criminalité',
'text-white bg-stone-600 hover:bg-stone-700': event.category === 'scandale',
'text-slate-900 bg-yellow-500 hover:bg-yellow-600': event.category === 'commerce',
'text-white bg-lime-600 hover:bg-lime-700': event.category?.name === 'naissance',
'text-white bg-stone-500 hover:bg-stone-700': event.category?.name === 'mort',
'text-white bg-orange-600 hover:bg-orange-700': event.category?.name === 'catastrophe',
'text-white bg-pink-600 hover:bg-pink-700': event.category?.name === 'catastrophe naturelle',
'text-white bg-sky-600 hover:bg-sky-700': event.category?.name === 'législation',
'text-white bg-purple-600 hover:bg-purple-700': event.category?.name === 'religion',
'text-white bg-emerald-600 hover:bg-emerald-700': event.category?.name === 'joueurs',
'text-slate-900 bg-amber-300 hover:bg-amber-400': event.category?.name === 'inauguration',
'text-slate-900 bg-emerald-200 hover:bg-emerald-300': event.category?.name === 'invention',
'text-slate-900 bg-cyan-300 hover:bg-cyan-400': event.category?.name === 'science',
'text-slate-900 bg-white hover:bg-yellow-200': event.category?.name === 'bénédiction',
'text-slate-900 bg-purple-200 hover:bg-purple-300': event.category?.name === 'découverte',
'text-slate-900 bg-indigo-200 hover:bg-indigo-300': event.category?.name === 'exploration',
'text-white bg-amber-600 hover:bg-amber-700': event.category?.name === 'construction',
'text-slate-900 bg-violet-200 hover:bg-violet-300': event.category?.name === 'arcanologie',
'text-white bg-rose-600 hover:bg-rose-700': event.category?.name === 'criminalité',
'text-white bg-stone-600 hover:bg-stone-700': event.category?.name === 'scandale',
'text-slate-900 bg-yellow-500 hover:bg-yellow-600': event.category?.name === 'commerce',
'rounded-r-none': isStartEvent,
'rounded-l-none': isEndEvent
})

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { getRelativeString, type LeimDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/Events'
import type { RPGDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/CalendarEvent'
import { useCalendar } from '@/stores/CalendarStore'
import { useCalendarEvents } from '@/stores/EventStore'
@@ -27,12 +27,14 @@ const emit = defineEmits<{
(e: 'query:close-popover'): void
}>()
const { getRelativeString } = useCalendar()
const dateDifference: string = getRelativeString(defaultDate, props.event.startDate)
const dateDuration: string | null = props.event.endDate
? getRelativeString(props.event.startDate, props.event.endDate, 'compact')
: null
function handleJumpToDate(date: LeimDate) {
function handleJumpToDate(date: RPGDate) {
jumpToDate(date)
emit('query:close-popover')
}
@@ -58,24 +60,24 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
:hide-when-detached="true"
:class="{
'border-slate-800': !event.category,
'border-lime-800': event.category === 'naissance',
'border-stone-600': 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-slate-600': event.category === 'bénédiction',
'border-purple-700': event.category === 'découverte',
'border-indigo-700': event.category === 'exploration',
'border-amber-700': event.category === 'construction',
'border-violet-700': event.category === 'arcanologie',
'border-rose-800': event.category === 'criminalité',
'border-stone-700': event.category === 'scandale',
'border-yellow-600': event.category === 'commerce'
'border-lime-800': event.category?.name === 'naissance',
'border-stone-600': event.category?.name === 'mort',
'border-orange-800': event.category?.name === 'catastrophe',
'border-pink-800': event.category?.name === 'catastrophe naturelle',
'border-sky-800': event.category?.name === 'législation',
'border-purple-800': event.category?.name === 'religion',
'border-emerald-800': event.category?.name === 'joueurs',
'border-amber-800': event.category?.name === 'inauguration',
'border-green-800': event.category?.name === 'invention',
'border-cyan-800': event.category?.name === 'science',
'border-slate-600': event.category?.name === 'bénédiction',
'border-purple-700': event.category?.name === 'découverte',
'border-indigo-700': event.category?.name === 'exploration',
'border-amber-700': event.category?.name === 'construction',
'border-violet-700': event.category?.name === 'arcanologie',
'border-rose-800': event.category?.name === 'criminalité',
'border-stone-700': event.category?.name === 'scandale',
'border-yellow-600': event.category?.name === 'commerce'
}"
>
<div class="grid gap-1">
@@ -110,13 +112,13 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
<ul class="flex gap-1">
<li v-if="event.category">
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
{{ event.category }}
{{ event.category?.name }}
</UiBadge>
</li>
<li v-for="cat in event.secondaryCategories" :key="cat">
<li v-for="cat in event.secondaryCategories" :key="cat.id">
<UiBadge class="mix-blend-luminosity bg-gray-600" variant="secondary">
{{ cat }}
{{ cat.name }}
</UiBadge>
</li>
</ul>

View File

@@ -2,11 +2,6 @@
import { useCalendar } from '@/stores/CalendarStore'
import { PhMagnifyingGlass } from '@phosphor-icons/vue'
import CalendarMenuNav from './CalendarMenuNav.vue'
import CalendarMenuSubnav from './CalendarMenuSubnav.vue'
import CalendarMenuToday from './CalendarMenuToday.vue'
import CalendarSwitch from './CalendarSwitch.vue'
import CalendarCurrentDate from './CalendarCurrentDate.vue'
const { revealAdvancedSearch } = useCalendar()
</script>

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { daysPerMonth, monthsPerYear, type LeimDate } from '@/models/Date'
import { type RPGDate } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { useCalendarEvents } from '@/stores/EventStore'
@@ -9,7 +9,12 @@ const { currentDate, currentConfig, jumpToDate } = useCalendar()
const { getRelativeEventFromDate } = useCalendarEvents()
function handleGotoPreviousEventPage(position: 'next' | 'prev' = 'next') {
let fromDate: LeimDate
let fromDate: RPGDate
// To modify, obviously
const daysPerMonth = 32
const monthsPerYear = 10
const toDay = position === 'next' ? daysPerMonth : 1
const toMonth = position === 'next' ? monthsPerYear : 0

View File

@@ -3,9 +3,7 @@ import { useCalendar } from '@/stores/CalendarStore'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { areDatesIdentical } from '@/models/Date'
const { defaultDate } = useCalendar()
const { defaultDate, areDatesIdentical } = useCalendar()
const { selectedDate } = storeToRefs(useCalendar())
const { jumpToDefaultDate, getFormattedDateTitle } = useCalendar()

View File

@@ -1,17 +1,13 @@
<script lang="ts" setup>
import {
characterCategories,
isCharacter,
type Character,
type CharacterCategory
} from '@/models/Characters'
import type { LeimDateOrder } from '@/models/Date'
import type { RPGDateOrder } from '@/models/Date'
import {
calendarEventCategories,
isCalendarEvent,
type CalendarEvent,
type CalendarEventCategory
} from '@/models/Events'
} from '~/models/CalendarEvent'
import { useCharacters } from '@/stores/CharacterStore'
import { useCalendarEvents } from '@/stores/EventStore'
import { capitalize } from '@/utils/Strings'
@@ -29,6 +25,7 @@ import {
} from 'radix-vue'
import SearchList from './lists/SearchList.vue'
import type { Category } from '~/models/Category'
const { characters } = storeToRefs(useCharacters())
const { allEvents } = storeToRefs(useCalendarEvents())
@@ -41,7 +38,7 @@ const searchQuery = ref<string>('')
const selectedEntity = useStorage('se', 'events' as SearchMode)
// Order
const selectedOrder = ref<LeimDateOrder>('asc')
const selectedOrder = ref<RPGDateOrder>('asc')
function setOrderAsc() {
selectedOrder.value = 'asc'
resetPage()
@@ -105,7 +102,7 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
// Handle categories logic
let hitCategories: boolean = false
const allCategories: CalendarEventCategory[] = []
const allCategories: Category[] = []
if (item.category) {
allCategories.push(item.category)
@@ -116,7 +113,7 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
}
hitCategories = selectedCategories.value.every((selectedCat) => {
return allCategories.includes(selectedCat as CalendarEventCategory)
return allCategories.includes(selectedCat as Category)
})
return (hitTitle || hitDesc) && hitCategories
@@ -139,7 +136,7 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
// Handle categories logic
let hitCategories: boolean = false
const allCategories: CharacterCategory[] = []
const allCategories: Category[] = []
if (item.category) {
allCategories.push(item.category)
@@ -150,7 +147,7 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
}
hitCategories = selectedCategories.value.every((selectedCat) => {
return allCategories.includes(selectedCat as CharacterCategory)
return allCategories.includes(selectedCat as Category)
})
return hitTitle && hitCategories
@@ -207,14 +204,10 @@ watch([currentPage, selectedEntity], () => {
// Compute categories based on current selectedEntity
const currentCategories = computed(() => {
if (selectedEntity.value === 'characters') {
return [...characterCategories]
} else {
return [...calendarEventCategories]
}
return []
})
const selectedCategories = ref<(CharacterCategory | CalendarEventCategory)[]>([])
const selectedCategories = ref<(Category)[]>([])
const categoryFilterOpened = ref<boolean>(false)
const searchCategory = ref<string>('')
@@ -227,7 +220,7 @@ const filteredCategories = computed(() =>
*
* @param e Radix Change Event
*/
function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
function handleCategorySelect(e: (Category)) {
if (typeof e === 'string') {
searchCategory.value = ''
selectedCategories.value.push(e)
@@ -290,9 +283,9 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
</div>
<div class="flex items-center gap-1">
<UiTagsInput class="px-0 gap-0 w-72" :model-value="selectedCategories">
<UiTagsInput class="px-0 gap-0 w-72">
<div class="flex gap-2 flex-wrap items-center px-3">
<UiTagsInputItem v-for="item in selectedCategories" :key="item" :value="item">
<UiTagsInputItem v-for="item in selectedCategories" :key="item.id" :value="item.name">
<UiTagsInputItemText class="capitalize" />
<UiTagsInputItemDelete />
</UiTagsInputItem>

View File

@@ -1,21 +1,23 @@
<script lang="ts" setup>
import type { Character } from '@/models/Characters'
import type { LeimDate } from '@/models/Date'
import type { RPGDate } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { Button } from '@/components/ui/button'
import { PhArrowSquareOut, PhPlant, PhSkull } from '@phosphor-icons/vue'
defineProps<{
const props = defineProps<{
character: Character
}>()
defineEmits<{
(e: 'query:date-jump', payload: LeimDate): void
(e: 'query:date-jump', payload: RPGDate): void
}>()
const { getFormattedDateTitle } = useCalendar()
console.log(props.character.birth)
</script>
<template>

View File

@@ -1,10 +1,8 @@
<script lang="ts" setup>
import { getRelativeString, type LeimDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/Events'
import type { RPGDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/CalendarEvent'
import { useCalendar } from '@/stores/CalendarStore'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { PhArrowSquareOut, PhHourglassMedium, PhAlarm } from '@phosphor-icons/vue'
const props = defineProps<{
@@ -12,9 +10,11 @@ const props = defineProps<{
}>()
defineEmits<{
(e: 'query:date-jump', payload: LeimDate): void
(e: 'query:date-jump', payload: RPGDate): void
}>()
const { getRelativeString } = useCalendar()
const { defaultDate, getFormattedDateTitle } = useCalendar()
const dateDifference: string = getRelativeString(defaultDate, props.event.startDate)
@@ -28,24 +28,24 @@ const dateDuration: string | null = props.event.endDate
class="relative block w-full text-left py-3 px-4 rounded-sm transition-colors"
:class="{
'text-white bg-slate-600 hover:bg-slate-700': !event.category,
'text-white bg-lime-600 hover:bg-lime-700': event.category === 'naissance',
'text-white bg-stone-500 hover:bg-stone-700': event.category === 'mort',
'text-white bg-orange-600 hover:bg-orange-700': event.category === 'catastrophe',
'text-white bg-pink-600 hover:bg-pink-700': event.category === 'catastrophe naturelle',
'text-white bg-sky-600 hover:bg-sky-700': event.category === 'législation',
'text-white bg-purple-600 hover:bg-purple-700': event.category === 'religion',
'text-white bg-emerald-600 hover:bg-emerald-700': event.category === 'joueurs',
'text-slate-900 bg-amber-300 hover:bg-amber-400': event.category === 'inauguration',
'text-slate-900 bg-emerald-200 hover:bg-emerald-300': event.category === 'invention',
'text-slate-900 bg-cyan-300 hover:bg-cyan-400': event.category === 'science',
'text-slate-900 bg-white hover:bg-yellow-200': event.category === 'bénédiction',
'text-slate-900 bg-purple-200 hover:bg-purple-300': event.category === 'découverte',
'text-slate-900 bg-indigo-200 hover:bg-indigo-300': event.category === 'exploration',
'text-white bg-amber-600 hover:bg-amber-700': event.category === 'construction',
'text-slate-900 bg-violet-200 hover:bg-violet-300': event.category === 'arcanologie',
'text-white bg-rose-600 hover:bg-rose-700': event.category === 'criminalité',
'text-white bg-stone-600 hover:bg-stone-700': event.category === 'scandale',
'text-slate-900 bg-yellow-500 hover:bg-yellow-600': event.category === 'commerce'
'text-white bg-lime-600 hover:bg-lime-700': event.category?.name === 'naissance',
'text-white bg-stone-500 hover:bg-stone-700': event.category?.name === 'mort',
'text-white bg-orange-600 hover:bg-orange-700': event.category?.name === 'catastrophe',
'text-white bg-pink-600 hover:bg-pink-700': event.category?.name === 'catastrophe naturelle',
'text-white bg-sky-600 hover:bg-sky-700': event.category?.name === 'législation',
'text-white bg-purple-600 hover:bg-purple-700': event.category?.name === 'religion',
'text-white bg-emerald-600 hover:bg-emerald-700': event.category?.name === 'joueurs',
'text-slate-900 bg-amber-300 hover:bg-amber-400': event.category?.name === 'inauguration',
'text-slate-900 bg-emerald-200 hover:bg-emerald-300': event.category?.name === 'invention',
'text-slate-900 bg-cyan-300 hover:bg-cyan-400': event.category?.name === 'science',
'text-slate-900 bg-white hover:bg-yellow-200': event.category?.name === 'bénédiction',
'text-slate-900 bg-purple-200 hover:bg-purple-300': event.category?.name === 'découverte',
'text-slate-900 bg-indigo-200 hover:bg-indigo-300': event.category?.name === 'exploration',
'text-white bg-amber-600 hover:bg-amber-700': event.category?.name === 'construction',
'text-slate-900 bg-violet-200 hover:bg-violet-300': event.category?.name === 'arcanologie',
'text-white bg-rose-600 hover:bg-rose-700': event.category?.name === 'criminalité',
'text-white bg-stone-600 hover:bg-stone-700': event.category?.name === 'scandale',
'text-slate-900 bg-yellow-500 hover:bg-yellow-600': event.category?.name === 'commerce'
}"
@click="$emit('query:date-jump', event.startDate)"
>
@@ -54,12 +54,12 @@ const dateDuration: string | null = props.event.endDate
{{ event.title }}
</h2>
<div v-if="event.wiki">
<Button variant="link" size="xs" as-child class="text-inherit">
<UiButton variant="link" size="xs" as-child class="text-inherit">
<a :href="event.wiki" target="_blank">
Wiki
<PhArrowSquareOut size="16" weight="fill" />
</a>
</Button>
</UiButton>
</div>
</div>
@@ -91,15 +91,15 @@ const dateDuration: string | null = props.event.endDate
<div v-if="event.category || event.secondaryCategories" class="absolute top-3 right-4">
<ul class="flex gap-1">
<li v-if="event.category">
<Badge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
{{ event.category }}
</Badge>
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
{{ event.category?.name }}
</UiBadge>
</li>
<li v-for="cat in event.secondaryCategories" :key="cat">
<Badge class="mix-blend-luminosity bg-gray-600" variant="secondary">
{{ cat }}
</Badge>
<li v-for="cat in event.secondaryCategories" :key="cat.id">
<UiBadge class="mix-blend-luminosity bg-gray-600" variant="secondary">
{{ cat.name }}
</UiBadge>
</li>
</ul>
</div>

View File

@@ -1,9 +1,9 @@
<script lang="ts" setup>
import { isCharacter, type Character } from '@/models/Characters'
import { compareDates, type LeimDate, type LeimDateOrder } from '@/models/Date'
import { isCalendarEvent, type CalendarEvent } from '@/models/Events'
import type { RPGDate, RPGDateOrder } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { computed } from 'vue'
import { isCalendarEvent, type CalendarEvent } from '~/models/CalendarEvent'
import type { SearchMode } from '../../SearchMode'
import CharacterCallout from './CharacterCallout.vue'
@@ -12,7 +12,7 @@ import EventCallout from './EventCallout.vue'
const props = defineProps<{
results: (Character | CalendarEvent)[]
currentEntity: SearchMode
order: LeimDateOrder
order: RPGDateOrder
startAt: number
endAt: number
limit?: number
@@ -20,9 +20,9 @@ const props = defineProps<{
const emit = defineEmits(['jumpedToDate'])
const { jumpToDate } = useCalendar()
const { jumpToDate, compareDates } = useCalendar()
function handleJumpToDate(date?: LeimDate) {
function handleJumpToDate(date?: RPGDate) {
if (!date) return
jumpToDate(date)
@@ -32,8 +32,8 @@ function handleJumpToDate(date?: LeimDate) {
// Initial sorting of the results
const sortedResults = computed(() => {
return [...props.results].sort((a, b) => {
let firstDate: LeimDate
let secondDate: LeimDate
let firstDate: RPGDate
let secondDate: RPGDate
if (isCalendarEvent(a)) {
firstDate = a.startDate

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { areDatesIdentical, type LeimDate } from '@/models/Date'
import type { RPGDate } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { useCalendarEvents } from '@/stores/EventStore'
import { useElementBounding } from '@vueuse/core'
@@ -7,17 +7,17 @@ import { storeToRefs } from 'pinia'
import { computed, ref, type ComputedRef } from 'vue'
import CalendarEventButton from '../../CalendarEvent.vue'
import type { CalendarEvent } from '@/models/Events'
import type { CalendarEvent } from '~/models/CalendarEvent'
const props = defineProps<{
date: LeimDate
date: RPGDate
faded?: boolean
}>()
const calendarTile = ref()
const calendarEventsList = ref()
const { defaultDate, selectDate } = useCalendar()
const { defaultDate, selectDate, areDatesIdentical } = useCalendar()
const { selectedDate } = storeToRefs(useCalendar())
const { currentEvents } = storeToRefs(useCalendarEvents())

View File

@@ -1,38 +1,9 @@
<script lang="ts" setup>
import type { LeimDate } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { useThrottleFn } from '@vueuse/core'
import DayTile from './DayTile.vue'
const { staticConfig, currentDate, decrementMonth, incrementMonth } = useCalendar()
const daysPerMonth = staticConfig.daysPerMonth
function getNextMonthDate(day: number): LeimDate {
let nextDay = day
let nextMonth = currentDate.currentMonth + 1
let nextYear = currentDate.currentYear
let nextPeriod = currentDate.currentPeriod
// If the new value would exceed the max number of month per year
if (nextMonth >= staticConfig.monthsPerYear) {
nextMonth = 0
// Increment the year
nextYear++
}
if (nextYear >= 0) {
nextPeriod = 'nante'
}
return {
day: nextDay,
month: nextMonth,
year: nextYear,
period: nextPeriod
}
}
const { currentDate, decrementMonth, incrementMonth } = useCalendar()
const { currentMonthData } = storeToRefs(useCalendar())
function handleWheel(e: WheelEvent) {
const isMovingUp = e.deltaY < 0
@@ -54,8 +25,8 @@ const moveCalendarRight = useThrottleFn(() => {
<template>
<div class="grid grid-cols-10" @wheel="handleWheel">
<DayTile
v-for="day in daysPerMonth"
<CalendarStateMonthlyDayTile
v-for="day in currentMonthData.days"
:key="day"
:date="{
day: day,
@@ -63,11 +34,5 @@ const moveCalendarRight = useThrottleFn(() => {
year: currentDate.currentYear
}"
/>
<DayTile
v-for="nextMonthDay in 8"
:key="nextMonthDay"
faded
:date="getNextMonthDate(nextMonthDay)"
/>
</div>
</template>

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { areDatesIdentical, type LeimDate } from '@/models/Date'
import type { RPGDate } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { useCalendarEvents } from '@/stores/EventStore'
import { storeToRefs } from 'pinia'
@@ -14,7 +14,9 @@ const props = defineProps<{
dayNumber: number
}>()
const tileDate: ComputedRef<LeimDate> = computed(() => {
const { areDatesIdentical } = useCalendar()
const tileDate: ComputedRef<RPGDate> = computed(() => {
return {
day: props.dayNumber,
month: props.monthNumber,

View File

@@ -2,9 +2,8 @@
import { useCalendar } from '@/stores/CalendarStore'
import { useThrottleFn } from '@vueuse/core'
import MonthTile from './MonthTile.vue'
const { staticConfig, decrementYear, incrementYear } = useCalendar()
const { decrementYear, incrementYear } = useCalendar()
const { sortedMonths: months } = storeToRefs(useCalendar())
function handleWheel(e: WheelEvent) {
const isMovingUp = e.deltaY < 0
@@ -27,10 +26,10 @@ const moveCalendarRight = useThrottleFn(() => {
<template>
<div class="container mt-[10vh] mb-auto" @wheel="handleWheel">
<div class="grid grid-cols-5 gap-x-8 gap-y-16">
<MonthTile
v-for="month in staticConfig.monthsPerYear"
:key="month"
:month-number="month - 1"
<CalendarStateYearlyMonthTile
v-for="month in months"
:key="month.id"
:month
/>
</div>
</div>

View File

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

View File

@@ -0,0 +1,55 @@
<script lang="ts" setup>
const user = useSupabaseUser()
const { data: worlds } = await useLazyFetch('/api/worlds')
</script>
<template>
<main class="p-8">
<Heading>{{ user?.user_metadata.full_name }}</Heading>
<section v-if="worlds?.data" class="mt-4">
<h2 class="mb-4 text-lg font-bold">
Mondes
</h2>
<ul class="grid md:grid-cols-3 gap-2">
<li v-for="(world, i) in worlds.data" :key="i">
<UiCard
class="w-full transition-all"
:link="`/calendar/${world.id}`"
:class="{
'hover:bg-slate-50 dark:hover:bg-sky-950 dark:focus-within:outline-sky-900': !world.color,
'bg-red-100 dark:bg-red-950 border-red-200 hover:bg-red-50 dark:hover:bg-red-900 dark:border-red-900 dark:focus-within:outline-red-900': world.color === 'red',
'bg-orange-100 dark:bg-orange-950 border-orange-200 hover:bg-orange-50 dark:hover:bg-orange-900 dark:border-orange-900 dark:focus-within:outline-orange-900': world.color === 'orange',
'bg-amber-100 dark:bg-amber-950 border-amber-200 hover:bg-amber-50 dark:hover:bg-amber-900 dark:border-amber-900 dark:focus-within:outline-amber-900': world.color === 'amber',
'bg-yellow-100 dark:bg-yellow-950 border-yellow-200 hover:bg-yellow-50 dark:hover:bg-yellow-900 dark:border-yellow-900 dark:focus-within:outline-yellow-900': world.color === 'yellow',
'bg-lime-100 dark:bg-lime-950 border-lime-200 hover:bg-lime-50 dark:hover:bg-lime-900 dark:border-lime-900 dark:focus-within:outline-lime-900': world.color === 'lime',
' bg-green-100 dark:bg-green-950 border-green-200 hover:bg-green-50 dark:hover:bg-green-900 dark:border-green-900 dark:focus-within:outline-green-900': world.color === 'green',
'bg-emerald-100 dark:bg-emerald-950 border-emerald-200 hover:bg-emerald-50 dark:hover:bg-emerald-900 dark:border-emerald-900 dark:focus-within:outline-emerald-900': world.color === 'emerald',
'bg-teal-100 dark:bg-teal-950 border-teal-200 hover:bg-teal-50 dark:hover:bg-teal-900 dark:border-teal-900 dark:focus-within:outline-teal-900': world.color === 'teal',
'bg-cyan-100 dark:bg-cyan-950 border-cyan-200 hover:bg-cyan-50 dark:hover:bg-cyan-900 dark:border-cyan-900 dark:focus-within:outline-cyan-900': world.color === 'cyan',
'bg-sky-100 dark:bg-sky-950 border-sky-200 hover:bg-sky-50 dark:hover:bg-sky-900 dark:border-sky-900 dark:focus-within:outline-sky-900': world.color === 'sky',
'bg-blue-100 dark:bg-blue-950 border-blue-200 hover:bg-blue-50 dark:hover:bg-blue-900 dark:border-blue-900 dark:focus-within:outline-blue-900': world.color === 'blue',
'bg-indigo-100 dark:bg-indigo-950 border-indigo-200 hover:bg-indigo-50 dark:hover:bg-indigo-900 dark:border-indigo-900 dark:focus-within:outline-indigo-900': world.color === 'indigo',
'bg-violet-100 dark:bg-violet-950 border-violet-200 hover:bg-violet-50 dark:hover:bg-violet-900 dark:border-violet-900 dark:focus-within:outline-violet-900': world.color === 'violet',
'bg-purple-100 dark:bg-purple-950 border-purple-200 hover:bg-purple-50 dark:hover:bg-purple-900 dark:border-purple-900 dark:focus-within:outline-purple-900': world.color === 'purple',
'bg-fuchsia-100 dark:bg-fuchsia-950 border-fuchsia-200 hover:bg-fuchsia-50 dark:hover:bg-fuchsia-900 dark:border-fuchsia-900 dark:focus-within:outline-fuchsia-900': world.color === 'fuchsia',
'bg-pink-100 dark:bg-pink-950 border-pink-200 hover:bg-pink-50 dark:hover:bg-pink-900 dark:border-pink-900 dark:focus-within:outline-pink-900': world.color === 'pink',
'bg-pink-100 dark:bg-rose-950 border-rose-200 hover:bg-rose-50 dark:hover:bg-rose-900 dark:border-rose-900 dark:focus-within:outline-rose-900': world.color === 'rose',
'text-slate-100 bg-slate-900 border-slate-700 hover:bg-slate-700 dark:hover:bg-slate-800 dark:border-slate-900 dark:focus-within:outline-slate-900': world.color === 'black',
' hover:bg-slate-50 dark:hover:text-slate-900 dark:hover:bg-slate-300 dark:border-slate-500 dark:focus-within:outline-slate-100': world.color === 'white',
}"
>
<UiCardHeader>
<UiCardTitle>{{ world.name }}</UiCardTitle>
</UiCardHeader>
<UiCardContent>
<p class="italic">{{ world.description }}</p>
</UiCardContent>
</UiCard>
</li>
</ul>
</section>
</main>
</template>

View File

@@ -1,9 +0,0 @@
<script lang="ts" setup>
const user = useSupabaseUser()
</script>
<template>
<div class="p-8">
<Heading>{{ user?.user_metadata.full_name }}</Heading>
</div>
</template>

View File

@@ -12,7 +12,7 @@ const props = defineProps<{
<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':
'relative outline outline-2 outline-offset-4 outline-transparent hover:-translate-y-[.2rem]':
props.link
})
"

15
models/CalendarConfig.ts Normal file
View File

@@ -0,0 +1,15 @@
import type { CalendarEvent } from "./CalendarEvent"
import type { CalendarMonth } from "./CalendarMonth"
import type { RPGDate } from "./Date"
export interface CalendarConfig {
months: CalendarMonth[]
daysPerMonth: number
today: RPGDate
}
export interface Calendar extends CalendarConfig {
id: number
name: string
events: CalendarEvent[]
}

19
models/CalendarEvent.ts Normal file
View File

@@ -0,0 +1,19 @@
import type { Category } from './Category'
import type { RPGDate } from './Date'
export interface CalendarEvent {
id: number
title: string
startDate: RPGDate
endDate?: RPGDate
description?: string
category?: Category
secondaryCategories?: Category[]
hidden?: boolean
wiki?: string
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isCalendarEvent(object: any): object is CalendarEvent {
return 'startDate' in object
}

6
models/CalendarMonth.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface CalendarMonth {
id: number
days: number,
name: string,
position: number,
}

4
models/Category.ts Normal file
View File

@@ -0,0 +1,4 @@
export interface Category {
id: number
name: string
}

View File

@@ -1,31 +1,19 @@
import type { LeimDate } from './Date'
import type { Category } from './Category'
import type { RPGDate } from './Date'
export interface Character {
name: string
description?: string
birth?: LeimDate
death?: LeimDate
birth?: RPGDate
death?: RPGDate
hiddenBirth?: boolean
hiddenDeath?: boolean
category?: CharacterCategory
secondaryCategories?: CharacterCategory[]
category?: Category
secondaryCategories?: Category[]
wiki?: string
}
export const characterCategories = [
'joueur',
'comte',
'scientifique',
'mage',
'professeur',
'criminel',
'étincelle',
'buse blanche',
'ecclésiastique',
'sentinelle'
] as const
export type CharacterCategory = (typeof characterCategories)[number]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isCharacter(object: any): object is Character {
return 'birth' in object
}

View File

@@ -1,187 +1,16 @@
export interface LeimDate {
export interface RPGDate {
day: number
month: number
year: number
period?: LeimPeriod
}
export type LeimPeriod = 'ante' | 'nante'
export type LeimPeriodShort = 'A.R' | 'N.R'
export type LeimDateOrder = 'asc' | 'desc'
export type RPGDateOrder = 'asc' | 'desc'
export const monthsPerYear: number = 10
export const daysPerYear: number = 320
export const daysPerMonth: number = 32
export const daysPerWeek: number = 6
/**
* Check whether two dates are identical
*
* @param date1 First date
* @param date2 Second date
* @returns True if the dates are identical
*/
export function areDatesIdentical(date1: LeimDate, date2: LeimDate): boolean {
return convertDateToDays({ ...date1 }) === convertDateToDays({ ...date2 })
}
/**
* Compare dates
*
* @param date1 First date
* @param date2 Second date
* @returns 1 means the first date comes before the second, -1 means the second comes before the first, and 0 if they're identical
*/
export function compareDates(a: LeimDate, b: LeimDate, order: LeimDateOrder = 'desc'): number {
// Reverses the order if specified
const orderFactor: number = order === 'desc' ? 1 : -1
// Compare eras
if ((a.period === 'ante' && b.period === 'nante') || (a.year < 0 && b.year >= 0))
return -1 * orderFactor
if ((a.period === 'nante' && b.period === 'ante') || (a.year >= 0 && b.year < 0))
return 1 * orderFactor
// Compare years
if (a.year < b.year) return -1 * orderFactor
if (a.year > b.year) return 1 * orderFactor
// Compare months
if (a.month < b.month) return -1 * orderFactor
if (a.month > b.month) return 1 * orderFactor
// Compare days
if (a.day < b.day) return -1 * orderFactor
if (a.day > b.day) return 1 * orderFactor
return 0
}
/**
* Converts a LeimDate to its equivalent in days
*
* @todo Handle negative dates
* @param dateToConvert The date object
* @returns How many days does it represent
*/
export function convertDateToDays(dateToConvert: LeimDate): number {
let numberOfDays: number = dateToConvert.day
numberOfDays = numberOfDays + dateToConvert.month * daysPerMonth
numberOfDays = numberOfDays + dateToConvert.year * daysPerYear
return numberOfDays
}
/**
* From two dates, get the difference in days between them
* @param baseDate The base date
* @param relativeDate The year to compare it to
* @returns The number of days separating the two dates (both positive and negative numbers)
*/
export function getDifferenceInDays(baseDate: LeimDate, relativeDate: LeimDate): number {
return convertDateToDays(relativeDate) - convertDateToDays(baseDate)
}
/**
* From two dates, gives information on how many years, months and days it has been / will be between them
*
* @param baseDate The base date
* @param relativeDate The year to compare it to
* @returns A string with info on how the relative date differs to the base date
*/
export function getRelativeString(
baseDate: LeimDate,
relativeDate: LeimDate,
formatting: 'compact' | 'complex' = 'complex'
): string {
const differenceInDays: number = getDifferenceInDays(baseDate, relativeDate)
let output: string = ''
let direction: 'past' | 'present' | 'future' = 'present'
let directionPrefix: string = ''
// Check whether it's a past or future date
if (differenceInDays > 0) {
direction = 'future'
} else if (differenceInDays < 0) {
direction = 'past'
}
if (formatting === 'complex') {
// Handle if it's the same date
if (direction === 'present') {
return "Aujourd'hui"
}
if (differenceInDays === -2) {
return 'Avant-hier'
}
if (differenceInDays === -1) {
return 'Hier'
}
if (differenceInDays === 1) {
return 'Demain'
}
if (differenceInDays === 2) {
return 'Après-demain'
}
// Get relevant prefix for the string
if (direction === 'future') {
directionPrefix = 'Dans '
} else if (direction === 'past') {
directionPrefix = 'Il y a '
}
output += directionPrefix
}
const yearPackets: number = Math.abs(Math.trunc(differenceInDays / daysPerYear))
const monthPackets: number = Math.abs(Math.trunc(differenceInDays / daysPerMonth) % monthsPerYear)
const remainingDays: number =
Math.abs(differenceInDays) - (yearPackets * daysPerYear + monthPackets * daysPerMonth)
// Assign year part
if (yearPackets) {
if (yearPackets === 1) {
output += `${yearPackets} an`
} else {
output += `${yearPackets} ans`
}
}
// Assign month part
if (monthPackets) {
// If there was a year packet(s), separate from them
if (yearPackets) {
output += ','
}
output += ` ${monthPackets} mois`
}
// Assign day part
if (remainingDays) {
// If there was a year OR month packet(s), separate from them
if (yearPackets || monthPackets) {
output += ' et'
}
if (remainingDays === 1) {
output += ` ${remainingDays} jour`
} else {
output += ` ${remainingDays} jours`
}
}
return output
}
// export function getRelativeDate(baseDate: LeimDate, relativeDate: LeimDate) {
// export function getRelativeDate(baseDate: RPGDate, relativeDate: RPGDate) {
// let newDay: number
// let newMonth: number
// let newYear: number
// let newPeriod: LeimPeriod
// let newPeriod: RPGPeriod
// const differenceInDays = getDifferenceInDays(baseDate, relativeDate)

View File

@@ -1,39 +0,0 @@
import type { LeimDate } from './Date'
export interface CalendarEvent {
title: string
startDate: LeimDate
endDate?: LeimDate
description?: string
category?: CalendarEventCategory
secondaryCategories?: CalendarEventCategory[]
hidden?: boolean
wiki?: string
}
export const calendarEventCategories = [
'naissance',
'mort',
'catastrophe',
'législation',
'catastrophe naturelle',
'inauguration',
'religion',
'invention',
'science',
'bénédiction',
'joueurs',
'découverte',
'exploration',
'construction',
'arcanologie',
'criminalité',
'scandale',
'commerce'
] as const
export type CalendarEventCategory = (typeof calendarEventCategories)[number]
export function isCalendarEvent(object: any): object is CalendarEvent {
return 'startDate' in object
}

7
models/World.ts Normal file
View File

@@ -0,0 +1,7 @@
export interface World {
id: number
uuid: string
name: string
description?: string
color?: string
}

View File

@@ -28,7 +28,8 @@
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"vue": "^3.4.27",
"vue-router": "^4.3.2"
"vue-router": "^4.3.2",
"zod": "^3.23.8"
},
"devDependencies": {
"@nuxtjs/color-mode": "^3.4.1",

40
pages/calendar/[id].vue Normal file
View File

@@ -0,0 +1,40 @@
<script lang="ts" setup>
import { PhMagnifyingGlass } from '@phosphor-icons/vue';
import type { MenuItem } from '~/components/global/SidebarProps';
useHead({
title: 'Calendrier'
})
definePageMeta({
middleware: ['auth-guard']
})
const user = useSupabaseUser()
// Redirect user back home when they log out on the page
watch(user, (n, _o) => {
if (!n) {
navigateTo('/')
}
})
const { revealAdvancedSearch } = useCalendar()
const { isAdvancedSearchOpen } = storeToRefs(useCalendar())
const sidebarMenu: MenuItem[] = [
{
phIcon: PhMagnifyingGlass,
tooltip: 'Recherche avancée',
clickHandler: revealAdvancedSearch
}
]
</script>
<template>
<div class="h-full grid grid-cols-[auto_1fr]">
<Sidebar :menu-items="sidebarMenu" />
<Calendar />
<CalendarSearch v-model:model-value="isAdvancedSearchOpen" />
</div>
</template>

View File

@@ -11,21 +11,5 @@ const sidebarMenu: MenuItem[] = []
<template>
<main class="h-full grid grid-cols-[auto_1fr]">
<Sidebar :menu-items="sidebarMenu" is-home />
<div class="h-full grid place-items-center">
<UiCard class="w-1/3 min-w-72" link="/calendar">
<UiCardHeader>
<UiCardTitle>Calendrier</UiCardTitle>
<UiCardDescription>
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
chronologie
</UiBadge>
</UiCardDescription>
</UiCardHeader>
<UiCardContent>
Chronologie complète de Léïm, rassemblant les évènements et personnages clés du monde
</UiCardContent>
</UiCard>
</div>
</main>
</template>

View File

@@ -21,9 +21,9 @@ const sidebarMenu: MenuItem[] = []
</script>
<template>
<main class="h-full grid grid-cols-[auto_1fr]">
<div class="h-full grid grid-cols-[auto_1fr]">
<Sidebar :menu-items="sidebarMenu" />
<ProfileLayout />
</main>
<ProfileDashboard />
</div>
</template>

8
pnpm-lock.yaml generated
View File

@@ -56,6 +56,9 @@ importers:
vue-router:
specifier: ^4.3.2
version: 4.3.2(vue@3.4.27(typescript@5.4.5))
zod:
specifier: ^3.23.8
version: 3.23.8
devDependencies:
'@nuxtjs/color-mode':
specifier: ^3.4.1
@@ -4734,6 +4737,9 @@ packages:
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
engines: {node: '>= 14'}
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
snapshots:
'@alloc/quick-lru@5.2.0': {}
@@ -10202,3 +10208,5 @@ snapshots:
archiver-utils: 5.0.2
compress-commons: 6.0.2
readable-stream: 4.5.2
zod@3.23.8: {}

View File

@@ -0,0 +1,37 @@
import { serverSupabaseClient } from "#supabase/server";
import { z } from 'zod'
import type { Calendar } from "~/models/CalendarConfig";
const querySchema = z.object({
world_id: z.number({ coerce: true }).positive().int()
})
export default defineEventHandler(async (event) => {
const client = await serverSupabaseClient(event)
const query = await getValidatedQuery(event, querySchema.parse)
const output = client
.from('calendars')
.select(`
id,
name,
today,
months:calendar_months (*),
events:calendar_events (
id,
title,
description,
hidden,
startDate:start_date,
endDate:end_date,
wiki,
category:calendar_event_categories!calendar_events_category_fkey (*),
secondaryCategories:calendar_event_categories!calendar_event_categories_links (*)
)
`)
.eq('world_id', query.world_id)
.limit(1)
.single<Calendar>()
return output
})

View File

@@ -1,252 +0,0 @@
export default defineEventHandler(() => {
return [
/**
* Player characters
*/
// "Les Milles Cages"
{
name: 'Sulvan Trois-Barbes',
birth: { day: 20, month: 3, year: 3169 },
wiki: 'https://alexcreates.fr/leim/index.php/Sulvan_Trois-Barbes',
category: 'joueur',
secondaryCategories: ['criminel', 'étincelle']
},
{
name: 'Vascylly',
birth: { day: 3, month: 5, year: 3181 },
wiki: 'https://alexcreates.fr/leim/index.php/Vascylly',
category: 'joueur',
secondaryCategories: ['mage']
},
{
name: 'Tara Belyus',
birth: { day: 14, month: 9, year: 3186 },
category: 'joueur',
secondaryCategories: ['mage']
},
// "Les Cloches de Cantane"
{
name: 'Malik',
birth: { day: 22, month: 8, year: 3181 },
category: 'joueur',
secondaryCategories: ['scientifique']
},
{
name: 'Elie',
birth: { day: 5, month: 6, year: 3192 },
category: 'joueur',
secondaryCategories: ['ecclésiastique']
},
{
name: 'Clayron',
birth: { day: 3, month: 5, year: 3178 },
category: 'joueur',
secondaryCategories: ['sentinelle']
},
/**
* NPC
*/
// Counts of the Alliance
{
name: 'Alfrid de Lagarde',
birth: { day: 29, month: 7, year: 3193 },
wiki: 'https://alexcreates.fr/leim/index.php/Alfrid_de_Lagarde',
category: 'comte',
secondaryCategories: ['sentinelle']
},
{
name: 'Alaric de Lagarde',
birth: { day: 23, month: 8, year: 3192 },
wiki: 'https://alexcreates.fr/leim/index.php/Alaric_de_Lagarde',
category: 'comte',
secondaryCategories: ['sentinelle']
},
{
name: 'Anastael III de Quillon',
birth: { day: 1, month: 5, year: 3184 },
wiki: 'https://alexcreates.fr/leim/index.php/Anastael_III_de_Quillon',
category: 'comte',
secondaryCategories: ['sentinelle', 'buse blanche']
},
{
name: 'Antoine de Mireloin',
birth: { day: 31, month: 5, year: 3190 },
wiki: 'https://alexcreates.fr/leim/index.php/Antoine_de_Mireloin',
category: 'comte'
},
{
name: 'Armance Ronchère',
birth: { day: 1, month: 1, year: 3132 },
wiki: 'https://alexcreates.fr/leim/index.php/Armance_Ronchère',
category: 'comte'
},
{
name: 'Baranne Rougefer',
birth: { day: 1, month: 0, year: 3175 },
wiki: 'https://alexcreates.fr/leim/index.php/Baranne_Rougefer',
category: 'comte',
secondaryCategories: ['sentinelle']
},
{
name: 'Béatrice II de Grandlac',
birth: { day: 21, month: 4, year: 3158 },
wiki: 'https://alexcreates.fr/leim/index.php/Béatrice_II_de_Grandlac',
category: 'comte',
secondaryCategories: ['mage']
},
{
name: 'Favio Asharos-Losantelle',
birth: { day: 17, month: 3, year: 3091 },
wiki: 'https://alexcreates.fr/leim/index.php/Favio_Asharos-Losantelle',
category: 'comte',
secondaryCategories: ['mage']
},
{
name: 'Firmin de Montardieu',
birth: { day: 9, month: 2, year: 3203 },
wiki: 'https://alexcreates.fr/leim/index.php/Firmin_de_Montardieu',
category: 'comte'
},
{
name: 'Laura de Montardieu',
birth: { day: 32, month: 3, year: 3167 },
death: { day: 1, month: 4, year: 3217 },
hiddenDeath: true,
wiki: 'https://alexcreates.fr/leim/index.php/Laura_de_Montardieu',
category: 'comte'
},
{
name: 'Lazarus Tymos',
birth: { day: 29, month: 9, year: 3145 },
wiki: 'https://alexcreates.fr/leim/index.php/Lazarus_Tymos',
category: 'comte',
secondaryCategories: ['mage', 'buse blanche']
},
{
name: 'Marion de Corambre',
birth: { day: 14, month: 7, year: 3190 },
wiki: 'https://alexcreates.fr/leim/index.php/Marion_de_Corambre',
category: 'comte',
secondaryCategories: ['scientifique', 'ecclésiastique']
},
{
name: 'Relforg Pergaré',
birth: { day: 18, month: 9, year: 3182 },
wiki: 'https://alexcreates.fr/leim/index.php/Relforg_Pergaré',
category: 'comte'
},
{
name: 'Vilgarde de Ternâcre',
birth: { day: 3, month: 3, year: 2998 },
wiki: 'https://alexcreates.fr/leim/index.php/Vilgarde_de_Ternâcre',
category: 'comte'
},
{
name: 'Ysildy Milopée',
birth: { day: 3, month: 1, year: 3187 },
wiki: 'https://alexcreates.fr/leim/index.php/Ysildy_Milopée',
category: 'comte',
secondaryCategories: ['mage', 'scientifique', 'professeur']
},
// Sparks
{
name: 'Izàc Tymos',
birth: { day: 13, month: 6, year: 3192 },
wiki: 'https://alexcreates.fr/leim/index.php/Izàc_Tymos',
category: 'mage',
secondaryCategories: ['étincelle', 'criminel', 'professeur', 'scientifique']
},
{
name: 'Tvernée',
birth: { day: 19, month: 2, year: 3205 },
wiki: 'https://alexcreates.fr/leim/index.php/Tvernée',
category: 'étincelle',
secondaryCategories: ['criminel']
},
// Pirates
{
name: 'Räzal',
birth: { day: 13, month: 8, year: 3178 },
wiki: 'https://alexcreates.fr/leim/index.php/Räzal'
},
// Legends
{
name: 'Jorhas Kirendre',
birth: { day: 2, month: 9, year: -452 },
death: { day: 2, month: 2, year: -419 },
category: 'sentinelle',
wiki: 'https://alexcreates.fr/leim/index.php/Jorhas_Kirendre'
},
// "Les Milles Cages"
{
name: 'Ernestin Pomel',
birth: { day: 11, month: 2, year: 3179 },
wiki: 'https://alexcreates.fr/leim/index.php/Ernestin_Pomel',
category: 'mage'
},
{
name: 'Quacille Lévios',
birth: { day: 3, month: 6, year: 3162 },
wiki: 'https://alexcreates.fr/leim/index.php/Quacille_Lévios',
category: 'professeur',
secondaryCategories: ['mage']
},
{
name: 'Morel Lévios',
birth: { day: 26, month: 3, year: 3157 },
wiki: 'https://alexcreates.fr/leim/index.php/Morel_Lévios',
category: 'sentinelle',
secondaryCategories: ['mage', 'buse blanche']
},
{
name: 'Anastael Simon',
birth: { day: 32, month: 2, year: 3166 },
wiki: 'https://alexcreates.fr/leim/index.php/Anastael_Simon',
category: 'étincelle',
secondaryCategories: ['mage']
},
{
name: 'Grestain',
birth: { day: 9, month: 2, year: 3162 },
wiki: 'https://alexcreates.fr/leim/index.php/Grestain',
category: 'criminel',
secondaryCategories: ['mage']
},
{
name: 'Lucien Malanth',
birth: { day: 31, month: 4, year: 3167 },
wiki: 'https://alexcreates.fr/leim/index.php/Lucien_Malanth',
category: 'criminel',
secondaryCategories: ['mage']
},
{
name: 'Tivian Rodhus',
birth: { day: 13, month: 3, year: 3157 },
wiki: 'https://alexcreates.fr/leim/index.php/Tivian_Rodhus',
category: 'professeur',
secondaryCategories: ['mage', 'criminel']
},
// "Les Cloches de Cantane"
{
name: 'Bénédicte Vaht',
description: "Moine d'Ikami ayant entrepris la construction du Pilier Blanc de Cantane",
birth: { day: 28, month: 6, year: 3139 },
category: 'ecclésiastique'
},
{
name: 'Taleb Vaht',
description:
"Fils de Bénédicte, il s'est engagé auprès des Étincelles comme alchimiste artificier.",
birth: { day: 9, month: 1, year: 3180 },
death: { day: 28, month: 7, year: 3209 },
category: 'étincelle',
secondaryCategories: ['scientifique']
}
]
})

View File

@@ -0,0 +1,31 @@
import { serverSupabaseClient } from "#supabase/server";
import { z } from 'zod'
import type { Character } from "~/models/Characters";
const querySchema = z.object({
world_id: z.number({ coerce: true }).positive().int()
})
export default defineEventHandler(async (event) => {
const client = await serverSupabaseClient(event)
const query = await getValidatedQuery(event, querySchema.parse)
const output = client
.from('characters')
.select(`
id,
name,
description,
birth,
death,
wiki,
category:character_categories!characters_category_fkey (*),
secondaryCategories:character_categories!character_categories_links (*)
`)
if (query.world_id) {
output.eq('world_id', query.world_id)
}
return output.returns<Character[]>()
})

View File

@@ -1,217 +0,0 @@
export default defineEventHandler(() => {
return [
// Histoire Antique
{
title: "Laurdieu devient la première cité de l'empire de Kaliatos",
startDate: { day: 3, month: 4, year: -1932 },
description:
"L'empire de Kaliatos établi sa capitale dans la cité de Laurdieu, qui connaitra une prospérité nouvelle au sein d'Aldys.",
category: 'législation',
secondaryCategories: ['inauguration'],
wiki: 'https://alexcreates.fr/leim/index.php/Laurdieu',
hidden: true
},
{
title: "Apparition d'Asménys",
startDate: { day: 19, month: 7, year: -1358 },
description:
"La défunte chanteuse Asménys apparaît à un barde pendant son jeune public, démarrant la religion des Prêtresses d'Asménys.",
category: 'religion',
secondaryCategories: ['bénédiction'],
wiki: 'https://alexcreates.fr/leim/index.php/Pr%C3%AAtresses_d%27Asm%C3%A9nys',
hidden: true
},
{
title: 'La Rupture',
startDate: { day: 26, month: 5, year: -756 },
endDate: { day: 4, month: 9, year: 29 },
description:
"Les Abysses se déversent à la surface de Léim, à travers plusieurs brèches. Plusieurs hordes de démons se rapprochent des villes, et ce qu'on appellera l'Âge des Abysses commencent alors sur la planète entière.",
category: 'catastrophe',
secondaryCategories: ['catastrophe naturelle'],
wiki: 'https://alexcreates.fr/leim/index.php/Seconde_Rupture',
hidden: true
},
{
title: 'Marche du sang',
startDate: { day: 18, month: 9, year: -420 },
endDate: { day: 27, month: 1, year: -419 },
description:
"L'empereur de Kaliatos ordonne personnellement la traque de Jorhas Kirendre pour connivence avec les démons. Plusieurs bataillons sont affectés à la chasse de Jorhas, qui se terminera par son incarcération ainsi que la mort de plusieurs centaines de soldats.",
category: 'criminalité',
wiki: 'https://alexcreates.fr/leim/index.php/Jorhas_Kirendre',
hidden: true
},
{
title: 'Exécution de Tyhos',
startDate: { day: 1, month: 0, year: 0 },
description:
"Le léviathan Tyhos rend l'âme après un combat de plusieurs années contre Lystos, le dieu du Soleil.",
category: 'bénédiction',
secondaryCategories: ['mort'],
wiki: 'https://alexcreates.fr/leim/index.php/Tyhos',
hidden: true
},
{
title: 'Traité de Kadel',
startDate: { day: 29, month: 4, year: 100 },
description: '',
category: 'inauguration',
secondaryCategories: ['législation'],
wiki: 'https://alexcreates.fr/leim/index.php/Alliance_Kald%C3%A9lienne#Trait%C3%A9_de_Kadel'
},
{
title: 'Découverte des Plaines de Poussières',
startDate: { day: 17, month: 7, year: 305 },
description:
"Les troupes de la reconquête aldienne explorent le littoral d'une immense étendue grise et inerte.",
category: 'découverte',
secondaryCategories: ['exploration'],
wiki: 'https://alexcreates.fr/leim/index.php/Plaines_de_poussi%C3%A8re',
hidden: true
},
{
title: 'Construction du Rempart de Laurdieu',
startDate: { day: 30, month: 2, year: 340 },
endDate: { day: 27, month: 9, year: 355 },
description:
"Le Grand Conseil Kaldélien ordonne la construction d'une muraille autour des Plaines de Poussières, afin de contenir les démons y sortant.",
category: 'construction',
wiki: 'https://alexcreates.fr/leim/index.php/Plaines_de_poussi%C3%A8re',
hidden: true
},
// Histoire Récente
{
title: "Inauguration de l'Académie Artistique Arcanique",
startDate: { day: 11, month: 6, year: 2545 },
description:
"Scäd Sceni ouvre son institut artistique dédié à l'apprentissage des arts arcaniques",
category: 'inauguration',
secondaryCategories: ['arcanologie'],
wiki: 'https://alexcreates.fr/leim/index.php/Buse_(arme)'
},
{
title: 'Création de la buse kaldélienne',
startDate: { day: 4, month: 2, year: 3113 },
description:
'Sophia de Rougefer invente la buse kaldélienne, une arme à feu souple à deux canons.',
category: 'invention',
secondaryCategories: ['science'],
wiki: 'https://alexcreates.fr/leim/index.php/Buse_(arme)'
},
{
title: 'Feux dans les champs de Bamast',
startDate: { day: 9, month: 5, year: 3200 },
description:
"Plusieurs incendies criminels se propagent à travers les champs de sérille des fermiers des littoraux de Bamast. Aucun suspect ni coupable ne sera trouvé et l'enquête sera baclée.",
category: 'catastrophe',
secondaryCategories: ['criminalité'],
wiki: 'https://alexcreates.fr/leim/index.php/Vascylly'
},
{
title: 'Meurtre de Darléon Typhos',
startDate: { day: 21, month: 6, year: 3200 },
description:
'Darléon Typhos surprend Vascylly fouillant sa demeure peu après la tombée de la nuit. Le majordome de la famille Typhos découvre le corps sans vie de son maître le lendemain.',
category: 'criminalité',
secondaryCategories: ['joueurs'],
wiki: 'https://alexcreates.fr/leim/index.php/Vascylly'
},
{
title: 'Scandale Rodhus',
startDate: { day: 25, month: 9, year: 3208 },
description:
"Tivian Rodhus, un professeur estimé de l'Académie Centrale Kaldélienne, est emprisonné pour corruption et aggression sexuelle. Le corps enseignant y est remanié sur ordre de Lazarus Tymos, comte de Nacride.",
category: 'criminalité',
secondaryCategories: ['scandale'],
wiki: 'https://alexcreates.fr/leim/index.php/Tivian_Rodhus'
},
// "Les Milles Cages"
{
title: "Arrivée d'aventuriers à Borélis",
startDate: { day: 12, month: 7, year: 3209 },
description:
'Tara Belyus, Vascylly et Adol Sulvan livrent 3 condamnés à Handany. Ils partent pour la mer durant la journée.',
category: 'joueurs'
},
{
title: "Naufrage de l'Éclipse",
description:
"L'Éclipse, le navire de la garde contenant des condamnés à destination des Cages Handaniennes, s'échoue au large des côtes montagneuses de la Lance d'Aldys.",
startDate: { day: 14, month: 7, year: 3209 },
category: 'catastrophe'
},
{
title: 'Emprisonnement de Tivian Rodhus',
description: "Celui qu'on surnomme la Bête d'Ambrose arrive à Handany, où il purgera sa peine.",
startDate: { day: 14, month: 7, year: 3209 },
category: 'législation'
},
{
title: 'Sulvan et Anastael atteignent Bamast',
startDate: { day: 19, month: 2, year: 3210 },
category: 'joueurs'
},
{
title: 'Jugement de Bormis Griloup',
description:
"Bromis Griloup est jugé coupable d'escroquerie et sabotage aux Cours d'Acier de Tourgrise. Il purgera une peine de 10 ans au sein des prisons royales.",
startDate: { day: 4, month: 8, year: 3209 },
category: 'législation'
},
// "Les Cloches de Cantane"
{
title: 'Inauguration de la Cloche du Pilier',
description: "Le Moine Premier inaugure la grande cloche d'argent au sommet du Pilier d'Ikami.",
startDate: { day: 29, month: 5, year: 3209 },
category: 'religion',
secondaryCategories: ['inauguration']
},
{
title: '1ère disparation à Cantane',
description: "Taleb Vaht décède dans une grotte à la suite d'une attaque d'ischiels enragées.",
startDate: { day: 28, month: 7, year: 3209 },
category: 'mort',
hidden: true
},
{
title: '2ème disparation à Cantane',
description: 'Donovane le mineur kéturien disparait sans laisser de traces.',
startDate: { day: 32, month: 7, year: 3209 },
category: 'mort',
hidden: true
},
{
title: '3ème disparation à Cantane',
description: 'Disparition de Sébastien, gredin sauride.',
startDate: { day: 10, month: 8, year: 3209 },
category: 'mort',
hidden: true
},
{
title: '4ème disparation à Cantane',
description: 'Disparition de Thérence, patrouilleur sauride de la Vieille Garde.',
startDate: { day: 19, month: 8, year: 3209 },
category: 'mort',
hidden: true
},
{
title: '5ème disparation à Cantane',
description: 'Disparition de Mathilda Boulais, vendeuse de pierres.',
startDate: { day: 22, month: 8, year: 3209 },
category: 'mort',
hidden: true
},
{
title: 'Grande Banque Minérale de Cantane',
description:
'Les artisans et mineurs de Rougefer se réunissent à Cantane pour vendre le fruit de leur dur labeur.',
startDate: { day: 23, month: 8, year: 3209 },
endDate: { day: 26, month: 8, year: 3209 },
category: 'commerce'
}
]
})

View File

@@ -0,0 +1,27 @@
import { serverSupabaseClient } from "#supabase/server";
import { z } from 'zod'
import type { CalendarEvent } from "~/models/CalendarEvent";
const querySchema = z.object({
world_id: z.number({ coerce: true }).positive().int()
})
export default defineEventHandler(async (event) => {
const client = await serverSupabaseClient(event)
const query = await getValidatedQuery(event, querySchema.parse)
const output = client
.from('calendar_events')
.select(`
id,
title,
description,
world_calendars (id, world_id)
`)
if (query.world_id) {
output.eq('world_calendars.world_id', query.world_id)
}
return output.returns<CalendarEvent[]>()
})

View File

@@ -0,0 +1,10 @@
import { serverSupabaseClient } from "#supabase/server";
import type { World } from "~/models/World";
export default defineEventHandler(async (event) => {
const client = await serverSupabaseClient(event)
return client.from('worlds')
.select(`id, name, description, color`)
.returns<World[]>()
})

View File

@@ -1,26 +1,14 @@
import {
monthsPerYear,
daysPerWeek,
daysPerMonth,
daysPerYear,
type LeimDate,
type LeimPeriod,
type LeimPeriodShort
type RPGDate,
type RPGDateOrder,
} from '@/models/Date'
import { useLocalStorage, useUrlSearchParams } from '@vueuse/core'
import { defineStore, skipHydrate } from 'pinia'
import { computed, ref, type ComputedRef, type Ref } from 'vue'
import type { CalendarMonth } from '~/models/CalendarMonth'
type CalendarViewType = 'month' | 'year' | 'decade' | 'century'
type CalendarStaticConfig = {
months: string[]
monthsPerYear: number
daysPerYear: number
daysPerMonth: number
daysPerWeek: number
}
type CalendarCurrentConfig = {
viewType: CalendarViewType
}
@@ -30,8 +18,6 @@ type CalendarCurrentDate = {
currentMonth: ComputedRef<number>
currentMonthName: ComputedRef<string>
currentYear: ComputedRef<number>
currentPeriod: Ref<LeimPeriod>
currentPeriodAbbr: Ref<LeimPeriodShort>
currentDateTitle: ComputedRef<string>
}
@@ -40,31 +26,6 @@ export const useCalendar = defineStore('calendar', () => {
* Static calendar config
* This shouldn't change
*/
/**
* Month list
*/
const months: string[] = [
'Jalen',
'Malsen',
'Verlys',
'Nalys',
'Verdore',
'Sidore',
'Lyllion',
'Rion',
'Farene',
'Dalvene'
]
// Assign the static config
const staticConfig: CalendarStaticConfig = {
months,
monthsPerYear,
daysPerYear,
daysPerMonth,
daysPerWeek
}
const currentConfig: Ref<CalendarCurrentConfig> = ref({
viewType: 'month'
})
@@ -75,15 +36,34 @@ export const useCalendar = defineStore('calendar', () => {
'century'
])
/**
* Month list (queried from API)
*/
const months: Ref<CalendarMonth[]> = ref<CalendarMonth[]>([])
function setMonths(data: CalendarMonth[]) {
months.value = data
}
/**
* Sorted month data using the raw months
*/
const sortedMonths = computed<CalendarMonth[]>(() => months.value.sort((a, b) => a.position - b.position))
const monthsPerYear = computed(() => months.value.length)
const daysPerYear = computed(() => months.value.reduce((acc, o) => acc + o.days, 0))
// Default date settings (current day in the world)
const defaultDay: number = 23
const defaultMonth: number = 8
const defaultYear: number = 3209
const defaultDate: ComputedRef<LeimDate> = computed(() => {
// The base setting is the first day / month of year 0
const defaultDay: Ref<number> = ref<number>(1)
const defaultMonth: Ref<number> = ref<number>(0)
const defaultYear: Ref<number> = ref<number>(0)
// Object representation
const defaultDate: ComputedRef<RPGDate> = computed(() => {
return {
day: defaultDay,
month: defaultMonth,
year: defaultYear
day: defaultDay.value,
month: defaultMonth.value,
year: defaultYear.value
}
})
@@ -97,6 +77,24 @@ export const useCalendar = defineStore('calendar', () => {
}
})
/**
* Sets the new defaultDate (aka the "today" value from the calendar)
*
* @param date The new data to set as defaultDate
*/
function setDefaultDate(date: RPGDate) {
defaultDay.value = date.day
defaultMonth.value = date.month
defaultYear.value = date.year
}
// Everytime the defaultDate changes / is set, we should update the params in the URL
watch(defaultDate, () => {
params.day = String(defaultDate.value.day)
params.month = String(defaultDate.value.month)
params.year = String(defaultDate.value.year)
})
const currentDay = computed<number>(() => {
return Number(params.day)
})
@@ -104,6 +102,9 @@ export const useCalendar = defineStore('calendar', () => {
const currentMonth = computed<number>(() => {
return Number(params.month)
})
const currentMonthData = computed<CalendarMonth>(() => {
return sortedMonths.value[currentMonth.value]
})
// Gets the label from currentMonth index
const currentMonthName = computed<string>(() => getMonthName(currentMonth.value))
@@ -111,12 +112,6 @@ export const useCalendar = defineStore('calendar', () => {
return Number(params.year)
})
// Get period from currentYear
const currentPeriod = computed<LeimPeriod>(() => getPeriodOfYear(currentYear.value).long)
const currentPeriodAbbr = computed<LeimPeriodShort>(
() => getPeriodOfYear(currentYear.value).short
)
const currentDateTitle = computed<string>(() => {
switch (currentConfig.value.viewType) {
case 'month':
@@ -124,17 +119,16 @@ export const useCalendar = defineStore('calendar', () => {
day: currentDate.currentDay.value,
month: currentDate.currentMonth.value,
year: currentDate.currentYear.value,
period: currentDate.currentPeriod.value
})
case 'year':
return `Année ${currentYear.value} ${currentPeriodAbbr.value}`
return `Année ${currentYear.value}`
case 'decade':
return `Années ${currentYear.value} ${getPeriodOfYear(currentYear.value).short} - ${currentYear.value + 10} ${getPeriodOfYear(currentYear.value + 10).short}`
return `Années ${currentYear.value} - ${currentYear.value + 10}`
case 'century':
return `Années ${currentYear.value} ${getPeriodOfYear(currentYear.value).short} - ${currentYear.value + 100} ${getPeriodOfYear(currentYear.value + 100).short}`
return `Années ${currentYear.value} - ${currentYear.value + 100}`
default:
return 'Date inconnue'
@@ -147,21 +141,18 @@ export const useCalendar = defineStore('calendar', () => {
currentMonth,
currentMonthName,
currentYear,
currentPeriod,
currentPeriodAbbr,
currentDateTitle
}
const currentLeimDate = computed<LeimDate>(() => {
const currentRPGDate = computed<RPGDate>(() => {
return {
day: currentDate.currentDay.value,
month: currentDate.currentMonth.value,
year: currentDate.currentYear.value,
period: currentDate.currentPeriod.value
}
})
const selectedDate = useLocalStorage<LeimDate>('selected-date', currentLeimDate.value, { deep: true })
const selectedDate = useLocalStorage<RPGDate>('selected-date', currentRPGDate.value, { deep: true })
/**
* Check whether the current viewType is active
@@ -192,7 +183,7 @@ export const useCalendar = defineStore('calendar', () => {
let newValue = Number(params.month) + 1
// If the new value would exceed the max number of month per year
if (newValue >= staticConfig.monthsPerYear) {
if (newValue >= monthsPerYear.value) {
newValue = 0
// Increment the year
incrementYear()
@@ -209,7 +200,7 @@ export const useCalendar = defineStore('calendar', () => {
// If the new value would go below 0
if (newValue < 0) {
newValue = staticConfig.monthsPerYear - 1
newValue = monthsPerYear.value - 1
// Decrement the year
decrementYear()
}
@@ -227,7 +218,7 @@ export const useCalendar = defineStore('calendar', () => {
const target: number = monthNumber - 1
if (target < 0) {
return monthsPerYear - 1
return monthsPerYear.value - 1
}
return target
@@ -242,7 +233,7 @@ export const useCalendar = defineStore('calendar', () => {
function getNextMonth(monthNumber: number): number {
const target: number = monthNumber + 1
if (target + 1 >= monthsPerYear) {
if (target + 1 > monthsPerYear.value) {
return 0
}
@@ -254,7 +245,7 @@ export const useCalendar = defineStore('calendar', () => {
*/
function setMonth(target: number): void {
// If the target is outside the month bounds
if (target < 0 || target >= staticConfig.monthsPerYear) {
if (target < 0 || target >= monthsPerYear.value) {
return
}
@@ -279,23 +270,6 @@ export const useCalendar = defineStore('calendar', () => {
params.year = newValue.toString()
}
/**
* From a given year, returns a set of LeimPeriod identifier
*
* This is used in range use-cases
*
* @param year The year to display
* @returns An object containing both short and long LeimPeriod
*/
function getPeriodOfYear(year: string | number): { long: LeimPeriod; short: LeimPeriodShort } {
const numYear = year as number
return {
long: numYear >= 0 ? 'nante' : 'ante',
short: numYear >= 0 ? 'N.R' : 'A.R'
}
}
/**
* Get the formatted month name given its index
*
@@ -304,7 +278,23 @@ export const useCalendar = defineStore('calendar', () => {
*/
function getMonthName(monthNumber: number): string {
const index = Number(monthNumber)
return staticConfig.months[index]
if (sortedMonths.value[index]) {
return sortedMonths.value[index].name
}
return ''
}
/**
* State for advanced search modal
*/
const isAdvancedSearchOpen: Ref<boolean> = ref<boolean>(false)
/**
* Opens the search modal
*/
function revealAdvancedSearch() {
isAdvancedSearchOpen.value = true
}
/**
@@ -316,6 +306,9 @@ export const useCalendar = defineStore('calendar', () => {
currentConfig.value.viewType = viewType
}
/**
* FORMATTING
*/
/**
* Gets the formatted viewType title
*
@@ -340,26 +333,29 @@ export const useCalendar = defineStore('calendar', () => {
}
/**
* From a LeimDate, returns the legible date title
* From a RPGDate, returns the legible date title
*
* @param date Date target
* @param showNumber Should the date show the day number
* @returns Formatted date name
*/
function getFormattedDateTitle(date: LeimDate, showNumber?: boolean): string {
function getFormattedDateTitle(date: RPGDate, showNumber?: boolean): string {
if (showNumber) {
return `${date.day} ${getMonthName(date.month)} ${date.year} ${getPeriodOfYear(date.year).short}`
return `${date.day} ${getMonthName(date.month)} ${date.year}`
}
return `${getMonthName(date.month)} ${date.year} ${getPeriodOfYear(date.year).short}`
return `${getMonthName(date.month)} ${date.year}`
}
/**
* DATE JUMPS & SELECTION
*/
/**
* Jumps the calendar to the given date
*
* @param date Target date
*/
function jumpToDate(date: LeimDate): void {
function jumpToDate(date: RPGDate): void {
params.day = date.day.toString()
params.month = date.month.toString()
params.year = date.year.toString()
@@ -376,27 +372,247 @@ export const useCalendar = defineStore('calendar', () => {
/**
* Jump the calendar to the default date
*/
function selectDate(date: LeimDate): void {
function selectDate(date: RPGDate): void {
selectedDate.value = date
}
const isAdvancedSearchOpen: Ref<boolean> = ref<boolean>(false)
/**
* DATE OPERATIONS
*
* Used to convert dates, sort them and compare them
*/
/**
* Converts a RPGDate to its equivalent in days
*
* @param dateToConvert The date object
* @returns How many days does it represent
*/
function convertDateToDays(dateToConvert: RPGDate): number {
let numberOfDays: number = dateToConvert.day
function revealAdvancedSearch() {
isAdvancedSearchOpen.value = true
// Get only the remaining months on the year
const remainingMonths = sortedMonths.value.filter((m) => dateToConvert.month >= m.position)
// From remaining months, reduce their days value
const monthDaysToAdd = remainingMonths.reduce((a, b) => {
return a + b.days
}, 0)
numberOfDays = numberOfDays + monthDaysToAdd
numberOfDays = numberOfDays + dateToConvert.year * daysPerYear.value
return numberOfDays - 1
}
/**
* From two dates, get the difference in days between them
* @param baseDate The base date
* @param relativeDate The year to compare it to
* @returns The number of days separating the two dates (both positive and negative numbers)
*/
function getDifferenceInDays(baseDate: RPGDate, relativeDate: RPGDate): number {
return convertDateToDays(relativeDate) - convertDateToDays(baseDate)
}
/**
* Check whether two dates are identical
*
* @param date1 First date
* @param date2 Second date
* @returns True if the dates are identical
*/
function areDatesIdentical(date1: RPGDate, date2: RPGDate): boolean {
return getDifferenceInDays({ ...date1 }, { ...date2 }) === 0
}
/**
* Compare dates (used for array sorting)
*
* @param date1 First date
* @param date2 Second date
* @returns 1 means the first date comes before the second, -1 means the second comes before the first, and 0 if they're identical
*/
function compareDates(a: RPGDate, b: RPGDate, order: RPGDateOrder = 'desc'): number {
// Reverses the order if specified
const orderFactor: number = order === 'desc' ? 1 : -1
// Compare years
if (a.year < b.year) return -1 * orderFactor
if (a.year > b.year) return 1 * orderFactor
// Compare months
if (a.month < b.month) return -1 * orderFactor
if (a.month > b.month) return 1 * orderFactor
// Compare days
if (a.day < b.day) return -1 * orderFactor
if (a.day > b.day) return 1 * orderFactor
return 0
}
/**
* From two dates, gives information on how many years, months and days it has been / will be between them
*
* @param baseDate The base date
* @param relativeDate The year to compare it to
* @returns A string with info on how the relative date differs to the base date
*/
function getRelativeString(
baseDate: RPGDate,
relativeDate: RPGDate,
formatting: 'compact' | 'complex' = 'complex'
): string {
const differenceInDays: number = getDifferenceInDays(baseDate, relativeDate)
let output: string = ''
let direction: 'past' | 'present' | 'future' = 'present'
let directionPrefix: string = ''
// Check whether it's a past or future date
if (differenceInDays > 0) {
direction = 'future'
} else if (differenceInDays < 0) {
direction = 'past'
}
if (formatting === 'complex') {
// Handle if it's the same date
if (direction === 'present') {
return "Aujourd'hui"
}
if (differenceInDays === -2) {
return 'Avant-hier'
}
if (differenceInDays === -1) {
return 'Hier'
}
if (differenceInDays === 1) {
return 'Demain'
}
if (differenceInDays === 2) {
return 'Après-demain'
}
// Get relevant prefix for the string
if (direction === 'future') {
directionPrefix = 'Dans '
} else if (direction === 'past') {
directionPrefix = 'Il y a '
}
output += directionPrefix
}
const isSameMonth = baseDate.month === relativeDate.month
const isSameYear = baseDate.year === relativeDate.year
// At this point, we are beyond simple scenarios and must calculate the distance accurately
// Get which date should come first
let ancientDate: RPGDate
let futureDate: RPGDate
if (direction === "future") {
ancientDate = baseDate
futureDate = relativeDate
} else {
ancientDate = relativeDate
futureDate = baseDate
}
// The pivot point that will help proceed towards the relativeDate
const datePivot: RPGDate = { ...ancientDate }
// The accumulator that will hold the distance between the two dates
const dateAcc: RPGDate = { day: 0, month: 0, year: 0 }
// If we are on the same month / year during acceleration, just don't rev up
if (isSameMonth && isSameYear) {
dateAcc.day = futureDate.day - datePivot.day
if (dateAcc.day === 1) {
output += ` ${dateAcc.day} jour`
} else {
output += ` ${dateAcc.day} jours`
}
return output
}
// If we are on the same year during acceleration, just don't rev up the next month, it might overflow to the next year
else if (isSameYear) {
dateAcc.month = futureDate.month - datePivot.month
output += ` ${dateAcc.month} mois`
return output
}
// Else, we need to accelerate and decelerate
else {
// First, get all the remaining days in the current month
const currentMonth = sortedMonths.value[datePivot.month]
dateAcc.day = currentMonth.days - datePivot.day
if (direction === 'future') {
datePivot.month = getNextMonth(datePivot.month)
} else {
datePivot.month = getPreviousMonth(datePivot.month)
}
datePivot.day = 1
// Then, get the remaining months in the year
const remainingMonthsStart = sortedMonths.value.filter((m) => datePivot.month < m.position )
if (direction === 'future') {
dateAcc.month = remainingMonthsStart.length + 1
} else {
dateAcc.month = remainingMonthsStart.length - 1
}
datePivot.month = 0
// Then, Accelerate through years
dateAcc.month = dateAcc.month + ((futureDate.year - datePivot.year) - 1) * monthsPerYear.value
datePivot.year = futureDate.year
// Once we reached the relativeYear, decelerate and get through the extra months
const remainingMonthsEnd = futureDate.month
dateAcc.month = dateAcc.month + remainingMonthsEnd
datePivot.month = futureDate.month
// Then at the destination month, decelerate to get the rest of the days
dateAcc.day = dateAcc.day + futureDate.day
datePivot.day = futureDate.day
const computedYear = Math.trunc(dateAcc.month / monthsPerYear.value)
const remainderMonths = dateAcc.month % monthsPerYear.value
if (computedYear >= 1 && remainderMonths) {
output += ` ${computedYear} an(s) et ${remainderMonths} mois`
}
else if (computedYear >= 1 && !remainderMonths) {
output += ` ${computedYear} an(s)`;
}
else {
output += ` ${remainderMonths} mois`
}
return output
}
}
return {
staticConfig,
months,
setMonths,
sortedMonths,
daysPerYear,
monthsPerYear,
viewTypeOptions,
currentConfig,
currentDate,
currentLeimDate,
currentRPGDate,
currentMonthData,
defaultDate,
setDefaultDate,
selectedDate: skipHydrate(selectedDate),
selectDate,
params,
getPeriodOfYear,
incrementMonth,
decrementMonth,
setMonth,
@@ -412,6 +628,11 @@ export const useCalendar = defineStore('calendar', () => {
getViewTypeTitle,
isCurrentScreenActive,
isAdvancedSearchOpen,
convertDateToDays,
getDifferenceInDays,
areDatesIdentical,
compareDates,
getRelativeString,
revealAdvancedSearch
}
})

View File

@@ -3,25 +3,10 @@ import { defineStore } from 'pinia'
export const useCharacters = defineStore('characters', () => {
const characters = ref<Character[]>([])
const charactersAreLoading = ref<boolean>(false)
const charactersLoaded = ref<boolean>(false)
async function fetchCharacters() {
try {
charactersAreLoading.value = true
const fetched = await useFetch<Character[]>('/api/characters')
if (fetched.data.value) {
charactersLoaded.value = true
characters.value = fetched.data.value
}
} catch (err) {
console.log(err)
} finally {
charactersAreLoading.value = false
}
async function setCharacters(data: Character[]) {
characters.value = data
}
fetchCharacters()
return { characters }
return { characters, setCharacters }
})

View File

@@ -1,15 +1,17 @@
import { compareDates, convertDateToDays, daysPerMonth, type LeimDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/Events'
import type { RPGDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/CalendarEvent'
import { defineStore } from 'pinia'
import { ref, watch, type Ref } from 'vue'
import { useCalendar } from './CalendarStore'
export const useCalendarEvents = defineStore('calendar-events', () => {
const { currentDate, currentConfig } = useCalendar()
const { currentDate, currentConfig, convertDateToDays, compareDates } = useCalendar()
const baseEvents = ref<CalendarEvent[]>([])
const eventsAreLoading = ref<boolean>(false)
const eventsLoaded = ref<boolean>(false)
function setEvents(data: CalendarEvent[]) {
baseEvents.value = data
}
const allEvents = computed(() => baseEvents.value.sort((a, b) => {
return compareDates(a.startDate, b.startDate, 'desc')
@@ -23,22 +25,6 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
currentEvents.value = computeCurrentEvents()
})
async function fetchEvents() {
try {
eventsAreLoading.value = true
const fetched = await useFetch<CalendarEvent[]>('/api/events')
if (fetched.data.value) {
eventsLoaded.value = true
baseEvents.value = fetched.data.value
}
} catch (err) {
console.log(err)
} finally {
eventsAreLoading.value = false
}
}
/**
* Determines if the event can appear in the front end
*
@@ -48,8 +34,8 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
* @returns Whether the event should appear in the current view
*/
function shouldEventBeDisplayed(event: CalendarEvent): boolean {
const eventStartDateToDays = convertDateToDays(event.startDate)
const eventEndDateToDays: number = event.endDate ? convertDateToDays(event.endDate) : 0
// const eventStartDateToDays = convertDateToDays(event.startDate)
// const eventEndDateToDays: number = event.endDate ? convertDateToDays(event.endDate) : 0
const isEventOnCurrentScreen =
(event.startDate.year === currentDate.currentYear &&
@@ -60,23 +46,26 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
// Check whether the event is on the last 8 tiles
// This is to allow leap events from appearing on the last 8 tiles
const firstDayOfCurrentMonth = convertDateToDays({
day: 1,
month: currentDate.currentMonth,
year: currentDate.currentYear
})
const lastDayOfCurrentMonth = firstDayOfCurrentMonth + daysPerMonth
const last8Tiles = lastDayOfCurrentMonth + 8
//
// This is not used for now
//
// const firstDayOfCurrentMonth = convertDateToDays({
// day: 1,
// month: currentDate.currentMonth,
// year: currentDate.currentYear
// })
// const lastDayOfCurrentMonth = firstDayOfCurrentMonth + daysPerMonth
// const last8Tiles = lastDayOfCurrentMonth + 8
const isEventOnNext8Tiles =
(eventStartDateToDays <= last8Tiles && eventStartDateToDays >= lastDayOfCurrentMonth) ||
(Boolean(event.endDate) &&
eventEndDateToDays <= last8Tiles &&
eventEndDateToDays >= lastDayOfCurrentMonth)
// const isEventOnNext8Tiles =
// (eventStartDateToDays <= last8Tiles && eventStartDateToDays >= lastDayOfCurrentMonth) ||
// (Boolean(event.endDate) &&
// eventEndDateToDays <= last8Tiles &&
// eventEndDateToDays >= lastDayOfCurrentMonth)
switch (currentConfig.viewType) {
case 'month':
return isEventOnCurrentScreen || isEventOnNext8Tiles
return isEventOnCurrentScreen!
case 'year':
return event.startDate.year === currentDate.currentYear
@@ -119,8 +108,8 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
event: CalendarEvent,
position: 'next' | 'prev' = 'next',
initialIsEnd: boolean = false
): { event: CalendarEvent; targetDate: LeimDate } {
let dateToParse: LeimDate // Day value of the date that the user interacted with
): { event: CalendarEvent; targetDate: RPGDate } {
let dateToParse: RPGDate // Day value of the date that the user interacted with
if (initialIsEnd && event.endDate) {
dateToParse = event.endDate
@@ -132,9 +121,9 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
}
function getRelativeEventFromDate(
date: LeimDate,
date: RPGDate,
position: 'next' | 'prev' = 'next'
): { event: CalendarEvent; targetDate: LeimDate } {
): { event: CalendarEvent; targetDate: RPGDate } {
const pivotValue = convertDateToDays(date)
let t: { eventData: CalendarEvent; distance: number; targetKey: 'startDate' | 'endDate' }[] = []
@@ -210,5 +199,5 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
}
}
return { allEvents, eventsAreLoading, eventsLoaded, currentEvents, fetchEvents, getRelativeEventFromDate, getRelativeEventFromEvent }
return { allEvents, setEvents, currentEvents, getRelativeEventFromDate, getRelativeEventFromEvent }
})

View File

@@ -4,9 +4,11 @@
-- Custom types
create type public.app_permission as enum ('events.see.hidden', 'users.ban');
create type public.app_role as enum ('sa', 'gm');
create type public.app_role as enum ('sa', 'admin', 'moderator', 'user');
create type public.app_colors as enum ('red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose', 'black', 'white');
-- USERS
-- DATA STRUCTURES
-- Users
create table public.users (
id uuid references auth.users not null primary key, -- UUID from auth.users
username text
@@ -14,16 +16,16 @@ create table public.users (
comment on table public.users is 'Profile data for each user.';
comment on column public.users.id is 'References the internal Supabase Auth user.';
-- USER ROLES
-- Roles
create table public.user_roles (
id bigint generated by default as identity primary key,
user_id uuid references public.users on delete cascade not null,
role app_role not null,
user_id uuid references public.users on delete cascade not null,
unique (user_id, role)
);
comment on table public.user_roles is 'Application roles for each user.';
-- ROLE PERMISSIONS
-- Permissions
create table public.role_permissions (
id bigint generated by default as identity primary key,
role app_role not null,
@@ -32,6 +34,96 @@ create table public.role_permissions (
);
comment on table public.role_permissions is 'Application permissions for each role.';
-- Worlds
create table public.worlds (
id bigint generated by default as identity primary key,
name text not null,
description text,
color app_colors,
gm_id uuid references public.users on delete cascade
);
comment on table public.worlds is 'Worlds belonging to a single user ; a game master.';
-- World Calendars
create table public.calendars (
id bigint generated by default as identity primary key,
name text not null,
today json not null,
world_id bigint references public.worlds on delete cascade not null
);
comment on table public.calendars is 'Calendar settings and configuration attached to a single world.';
-- Calendar Months
create table public.calendar_months (
id bigint generated by default as identity primary key,
name text not null,
days int not null,
position int not null,
calendar_id bigint references public.calendars on delete cascade not null
);
comment on table public.calendar_months is 'A calendar month.';
-- Event categories
create table public.calendar_event_categories (
id bigint generated by default as identity primary key,
name text not null,
unique (name)
);
comment on table public.calendar_event_categories is 'Categories describing events.';
-- Events
create table public.calendar_events (
id bigint generated by default as identity primary key,
title text not null,
description text,
start_date json not null,
end_date json,
category bigint references public.calendar_event_categories on delete cascade,
hidden boolean,
wiki text,
calendar_id bigint references public.calendars on delete cascade not null
);
comment on table public.calendar_events is 'Events linked to a world';
-- Link table for events - categories
create table public.calendar_event_categories_links (
calendar_event_id bigint references public.calendar_events on delete cascade,
calendar_event_category_id bigint references public.calendar_event_categories on delete cascade,
primary key (calendar_event_id, calendar_event_category_id)
);
comment on table public.calendar_event_categories_links is 'Link tables for multiple event categories.';
-- Character categories
create table public.character_categories (
id bigint generated by default as identity primary key,
name text not null,
unique (name)
);
comment on table public.character_categories is 'Categories describing characters';
-- Characters
create table public.characters (
id bigint generated by default as identity primary key,
name text not null,
description text,
birth json not null,
death json,
category bigint references public.character_categories on delete cascade,
hidden_birth boolean,
hidden_death boolean,
wiki text,
world_id bigint references public.worlds on delete cascade not null
);
comment on table public.characters is 'Characters linked to a world';
-- Link table for events - categories
create table public.character_categories_links (
character_id bigint references public.characters on delete cascade,
character_category_id bigint references public.character_categories on delete cascade,
primary key (character_id, character_category_id)
);
comment on table public.character_categories_links is 'Link tables for multiple character categories.';
-- authorize with role-based access control (RBAC)
create function public.authorize(
requested_permission app_permission
@@ -54,11 +146,114 @@ $$ language plpgsql security definer set search_path = public;
alter table public.users enable row level security;
alter table public.user_roles enable row level security;
alter table public.role_permissions enable row level security;
alter table public.worlds enable row level security;
alter table public.character_categories enable row level security;
alter table public.character_categories_links enable row level security;
alter table public.characters enable row level security;
alter table public.calendars enable row level security;
alter table public.calendar_months enable row level security;
alter table public.calendar_events enable row level security;
alter table public.calendar_event_categories enable row level security;
alter table public.calendar_event_categories_links enable row level security;
-- User policies
create policy "Allow logged-in read access" on public.users for select using ( auth.role() = 'authenticated' );
create policy "Allow individual insert access" on public.users for insert with check ( auth.uid() = id );
create policy "Allow individual update access" on public.users for update using ( auth.uid() = id );
create policy "Allow individual read access" on public.user_roles for select using ( auth.uid() = user_id );
-- World policies
create policy "Allow individual read access for GMs" on public.worlds for select using ( ( auth.uid() = gm_id ) );
create policy "Allow individual insert access for GMs" on public.worlds for insert with check ( auth.uid() = gm_id );
create policy "Allow individual update access for GMs" on public.worlds for update using ( auth.uid() = gm_id );
-- Calendar policies
create policy "Allow individual read access for GMs" on public.calendars for select using (
exists (
select 1 from worlds
where worlds.id = calendars.world_id
)
);
create policy "Allow individual insert access for GMs" on public.calendars for insert with check (
exists (
select 1 from worlds
where worlds.id = calendars.world_id
)
);
create policy "Allow individual update access for GMs" on public.calendars for update with check (
exists (
select 1 from worlds
where worlds.id = calendars.world_id
)
);
-- Month policies
create policy "Allow individual read access for GMs" on public.calendar_months for select using (
exists (
select 1 from calendars
where calendars.id = calendar_months.calendar_id
)
);
create policy "Allow individual insert access for GMs" on public.calendar_months for insert with check (
exists (
select 1 from calendars
where calendars.id = calendar_months.calendar_id
)
);
create policy "Allow individual update access for GMs" on public.calendar_months for update with check (
exists (
select 1 from calendars
where calendars.id = calendar_months.calendar_id
)
);
-- Event policies
create policy "Allow individual read access for GMs" on public.calendar_events for select using (
exists (
select 1 from calendars
where calendars.id = calendar_events.calendar_id
)
);
create policy "Allow individual insert access for GMs" on public.calendar_events for insert with check (
exists (
select 1 from calendars
where calendars.id = calendar_events.calendar_id
)
);
create policy "Allow individual update access for GMs" on public.calendar_events for update with check (
exists (
select 1 from calendars
where calendars.id = calendar_events.calendar_id
)
);
-- Character policies
create policy "Allow individual read access for GMs" on public.characters for select using (
exists (
select 1 from worlds
where worlds.id = characters.world_id
)
);
create policy "Allow individual insert access for GMs" on public.characters for insert with check (
exists (
select 1 from worlds
where worlds.id = characters.world_id
)
);
create policy "Allow individual update access for GMs" on public.characters for update with check (
exists (
select 1 from worlds
where worlds.id = characters.world_id
)
);
-- Categories are public to view but not to insert
-- Needs to be refactored maybe, if in the future we want a default set AND user defined ones
create policy "Allow logged-in read access" on public.calendar_event_categories for select using ( auth.role() = 'authenticated' );
create policy "Allow logged-in read access" on public.calendar_event_categories_links for select using ( auth.role() = 'authenticated' );
create policy "Allow logged-in read access" on public.character_categories for select using ( auth.role() = 'authenticated' );
create policy "Allow logged-in read access" on public.character_categories_links for select using ( auth.role() = 'authenticated' );
-- Send "previous data" on change
alter table public.users replica identity full;
@@ -69,6 +264,8 @@ declare is_admin boolean;
begin
insert into public.users (id, username)
values (new.id, new.email);
insert into public.user_roles (role, user_id)
values ('user', new.id);
return new;
end;

View File

@@ -1,4 +1,301 @@
insert into public.role_permissions (role, permission) values ('gm', 'events.see.hidden');
insert into public.role_permissions (role, permission) values ('sa', 'events.see.hidden');
insert into public.role_permissions (role, permission) values ('sa', 'users.ban');
-- Event categories
insert into public.calendar_event_categories (name) values ('naissance');
insert into public.calendar_event_categories (name) values ('mort');
insert into public.calendar_event_categories (name) values ('catastrophe');
insert into public.calendar_event_categories (name) values ('catastrophe naturelle');
insert into public.calendar_event_categories (name) values ('inauguration');
insert into public.calendar_event_categories (name) values ('religion');
insert into public.calendar_event_categories (name) values ('invention');
insert into public.calendar_event_categories (name) values ('science');
insert into public.calendar_event_categories (name) values ('bénédiction');
insert into public.calendar_event_categories (name) values ('joueurs');
insert into public.calendar_event_categories (name) values ('découverte');
insert into public.calendar_event_categories (name) values ('exploration');
insert into public.calendar_event_categories (name) values ('construction');
insert into public.calendar_event_categories (name) values ('arcanologie');
insert into public.calendar_event_categories (name) values ('criminalité');
insert into public.calendar_event_categories (name) values ('scandale');
insert into public.calendar_event_categories (name) values ('commerce');
insert into public.calendar_event_categories (name) values ('législation');
-- Character categories
insert into public.character_categories (name) values ('joueur');
insert into public.character_categories (name) values ('comte');
insert into public.character_categories (name) values ('scientifique');
insert into public.character_categories (name) values ('mage');
insert into public.character_categories (name) values ('professeur');
insert into public.character_categories (name) values ('criminel');
insert into public.character_categories (name) values ('étincelle');
insert into public.character_categories (name) values ('buse blanche');
insert into public.character_categories (name) values ('ecclésiastique');
insert into public.character_categories (name) values ('militaire');
insert into public.character_categories (name) values ('activiste');
insert into public.character_categories (name) values ('commerçant');
-- Worlds
insert into public.worlds (name, description, color) values ('Léïm', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis aliquet congue aliquet. Curabitur eu iaculis diam. Nunc iaculis nibh orci, eu semper nunc congue congue. Praesent euismod tortor eget metus tristique lobortis vel in risus. In volutpat ligula orci, id pharetra lectus egestas at.', 'black');
-- Worlds' calendars
insert into public.calendars (world_id, name, today) values (1, 'Calendrier solaire', '{ "day": 23, "month": 8, "year": 3209 }');
-- Calendar's months
insert into public.calendar_months (calendar_id, name, days, position) values (1, 'Jalen', 32, 1);
insert into public.calendar_months (calendar_id, name, days, position) values (1, 'Malsen', 32, 2);
insert into public.calendar_months (calendar_id, name, days, position) values (1, 'Verlys', 32, 3);
insert into public.calendar_months (calendar_id, name, days, position) values (1, 'Nalys', 32, 4);
insert into public.calendar_months (calendar_id, name, days, position) values (1, 'Verdore', 32, 5);
insert into public.calendar_months (calendar_id, name, days, position) values (1, 'Sidore', 32, 6);
insert into public.calendar_months (calendar_id, name, days, position) values (1, 'Lyllion', 32, 7);
insert into public.calendar_months (calendar_id, name, days, position) values (1, 'Rion', 32, 8);
insert into public.calendar_months (calendar_id, name, days, position) values (1, 'Farene', 32, 9);
insert into public.calendar_months (calendar_id, name, days, position) values (1, 'Dalvene', 32, 10);
-- Events
insert into public.calendar_events (title, description, start_date, category, hidden, wiki, calendar_id) values (
'Laurdieu devient la première cité de l''empire de Kaliatos',
'L''empire de Kaliatos établi sa capitale dans la cité de Laurdieu, qui connaitra une prospérité nouvelle au sein d''Aldys.',
'{ "day": 3, "month": 4, "year": -1932 }',
18,
true,
'https://alexcreates.fr/leim/index.php/Laurdieu',
1
);
insert into public.calendar_events (title, description, start_date, category, hidden, wiki, calendar_id) values (
'Apparition d''Asménys',
'La défunte chanteuse Asménys apparaît à un barde pendant son jeune public, démarrant la religion des Prêtresses d''Asménys.',
'{ "day": 19, "month": 7, "year": -1358 }',
6,
true,
'https://alexcreates.fr/leim/index.php/Pr%C3%AAtresses_d%27Asm%C3%A9nys',
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'La Rupture',
'Les Abysses se déversent à la surface de Léim, à travers plusieurs brèches. Plusieurs hordes de démons se rapprochent des villes, et ce qu''on appellera l''Âge des Abysses commencent alors sur la planète entière.',
'{ "day": 26, "month": 5, "year": -756 }',
'{ "day": 4, "month": 9, "year": 29 }',
3,
true,
'https://alexcreates.fr/leim/index.php/Seconde_Rupture',
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Marche du sang',
'L''empereur de Kaliatos ordonne personnellement la traque de Jorhas Kirendre pour connivence avec les démons. Plusieurs bataillons sont affectés à la chasse de Jorhas, qui se terminera par son incarcération ainsi que la mort de plusieurs centaines de soldats.',
'{ "day": 18, "month": 9, "year": -420 }',
'{ "day": 27, "month": 1, "year": -419 }',
15,
true,
'https://alexcreates.fr/leim/index.php/Jorhas_Kirendre',
1
);
insert into public.calendar_events (title, description, start_date, category, hidden, wiki, calendar_id) values (
'Exécution de Tyhos',
'Le léviathan Tyhos rend l''âme après un combat de plusieurs années contre Lystos, le dieu du Soleil.',
'{ "day": 1, "month": 0, "year": 0 }',
9,
true,
'https://alexcreates.fr/leim/index.php/Tyhos',
1
);
insert into public.calendar_events (title, start_date, category, wiki, calendar_id) values (
'Traité de Kadel',
'{ "day": 29, "month": 4, "year": 100 }',
5,
'https://alexcreates.fr/leim/index.php/Alliance_Kald%C3%A9lienne#Trait%C3%A9_de_Kadel',
1
);
insert into public.calendar_events (title, description, start_date, category, hidden, wiki, calendar_id) values (
'Découverte des Plaines de Poussières',
'Les troupes de la reconquête aldienne explorent le littoral d''une immense étendue grise et inerte.',
'{ "day": 17, "month": 7, "year": 305 }',
11,
true,
'https://alexcreates.fr/leim/index.php/Plaines_de_poussi%C3%A8re',
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Construction du Rempart de Laurdieu',
'Le Grand Conseil Kaldélien ordonne la construction d''une muraille autour des Plaines de Poussières, afin de contenir les démons y sortant.',
'{ "day": 30, "month": 2, "year": 340 }',
'{ "day": 27, "month": 9, "year": 355 }',
13,
true,
'https://alexcreates.fr/leim/index.php/Plaines_de_poussi%C3%A8re',
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Inauguration de l''Académie Artistique Arcanique',
'Scäd Sceni ouvre son institut artistique dédié à l''apprentissage des arts arcaniques.',
'{ "day": 11, "month": 6, "year": 2545 }',
null,
5,
false,
'https://alexcreates.fr/leim/index.php/Acad%C3%A9mie_Artistique_Arcanique',
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Création de la buse kaldélienne',
'Sophia de Rougefer invente la buse kaldélienne, une arme à feu souple à deux canons.',
'{ "day": 4, "month": 2, "year": 3113 }',
null,
7,
false,
'https://alexcreates.fr/leim/index.php/Buse_(arme)',
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Feux dans les champs de Bamast',
'Plusieurs incendies criminels se propagent à travers les champs de sérille des fermiers des littoraux de Bamast. Aucun suspect ni coupable ne sera trouvé et l''enquête sera baclée.',
'{ "day": 9, "month": 5, "year": 3200 }',
null,
15,
false,
'https://alexcreates.fr/leim/index.php/Vascylly',
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Meurtre de Darléon Typhos',
'Darléon Typhos surprend Vascylly fouillant sa demeure peu après la tombée de la nuit. Le majordome de la famille Typhos découvre le corps sans vie de son maître le lendemain.',
'{ "day": 21, "month": 6, "year": 3200 }',
null,
15,
false,
'https://alexcreates.fr/leim/index.php/Vascylly',
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Scandale Rodhus',
'Tivian Rodhus, un professeur estimé de l''Académie Centrale Kaldélienne, est emprisonné pour corruption et aggression sexuelle. Le corps enseignant y est remanié sur ordre de Lazarus Tymos, comte de Nacride.',
'{ "day": 25, "month": 9, "year": 3208 }',
null,
15,
false,
'https://alexcreates.fr/leim/index.php/Tivian_Rodhus',
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Arrivée d''aventuriers à Borélis',
'Tara Belyus, Vascylly et Adol Sulvan livrent 3 condamnés à Handany. Ils partent pour la mer durant la journée.',
'{ "day": 12, "month": 7, "year": 3209 }',
null,
10,
false,
null,
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Naufrage de l''Éclipse',
'L''Éclipse, le navire de la garde contenant des condamnés à destination des Cages Handaniennes, s''échoue au large des côtes montagneuses de la Lance d''Aldys.',
'{ "day": 14, "month": 7, "year": 3209 }',
null,
3,
false,
null,
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Emprisonnement de Tivian Rodhus',
'Celui qu''on surnomme la Bête d''Ambrose arrive à Handany, où il purgera sa peine.',
'{ "day": 14, "month": 7, "year": 3209 }',
null,
18,
false,
'https://alexcreates.fr/leim/index.php/Tivian_Rodhus',
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Sulvan et Anastael atteignent Bamast',
null,
'{ "day": 19, "month": 2, "year": 3210 }',
null,
10,
false,
null,
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Jugement de Bormis Griloup',
'Bormis Griloup est jugé coupable d''escroquerie et sabotage aux Cours d''Acier de Tourgrise. Il purgera une peine de 10 ans au sein des prisons royales.',
'{ "day": 4, "month": 8, "year": 3209 }',
null,
18,
false,
null,
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Inauguration de la Cloche du Pilier',
'Le Moine Premier inaugure la grande cloche d''argent au sommet du Pilier d''Ikami.',
'{ "day": 29, "month": 5, "year": 3209 }',
null,
6,
false,
null,
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'1ère disparation à Cantane',
'Taleb Vaht décède dans une grotte à la suite d''une attaque d''ischiels enragées.',
'{ "day": 28, "month": 7, "year": 3209 }',
null,
2,
true,
null,
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'2ème disparation à Cantane',
'Donovane le mineur kéturien disparait sans laisser de traces.',
'{ "day": 32, "month": 7, "year": 3209 }',
null,
2,
true,
null,
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'3ème disparation à Cantane',
'Disparition de Sébastien, gredin sauride.',
'{ "day": 10, "month": 8, "year": 3209 }',
null,
2,
true,
null,
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'4ème disparation à Cantane',
'Disparition de Thérence, patrouilleur sauride de la Vieille Garde.',
'{ "day": 19, "month": 8, "year": 3209 }',
null,
2,
true,
null,
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'5ème disparation à Cantane',
'Disparition de Mathilda Boulais, vendeuse de pierres.',
'{ "day": 22, "month": 8, "year": 3209 }',
null,
2,
true,
null,
1
);
insert into public.calendar_events (title, description, start_date, end_date, category, hidden, wiki, calendar_id) values (
'Grande Banque Minérale de Cantane',
'Les artisans et mineurs de Rougefer se réunissent à Cantane pour vendre le fruit de leur dur labeur.',
'{ "day": 23, "month": 8, "year": 3209 }',
'{ "day": 26, "month": 8, "year": 3209 }',
17,
false,
null,
1
);