Files
leim-tools/pages/my/index.vue
2025-03-07 21:36:10 +01:00

160 lines
4.3 KiB
Vue

<script lang="ts" setup>
import type { RealtimeChannel } from "@supabase/supabase-js"
import type { World, WorldChannelPayload } from "~/models/World";
const supabase = useSupabaseClient()
const user = useSupabaseUser()
definePageMeta({
middleware: ["auth-guard"]
})
const { data: worlds } = await useLazyFetch<{ data: World[] }>("/api/worlds/query", { query: { gmId: user?.value!.id } })
const sortedWorlds = computed(() => worlds.value?.data ? [...worlds.value.data].sort((a, b) => (a.id ?? 0) - (b.id ?? 0)) : [])
// Redirect user back home when they log out on the page
watch(user, (n) => {
if (!n) {
navigateTo("/")
}
})
const isCreateWorldModalOpen = ref<boolean>(false)
function hideCreateDialog() {
isCreateWorldModalOpen.value = false
}
/**
* === World subscriptions ===
*/
/** Active world channel */
let worldChannel: RealtimeChannel
/** Handles world insertion realtime events */
function handleInsertedWorld(newWorld: WorldChannelPayload) {
if (!worlds.value?.data) return
newWorld.createdAt = newWorld.created_at;
newWorld.gmId = newWorld.gm_id;
try {
worlds.value?.data.push(newWorld)
} catch (err) {
console.log(err)
}
}
/** Handles world deletion realtime events */
function handleDeletedWorld(id: number) {
if (!worlds.value?.data) return
try {
worlds.value.data.splice(worlds.value.data.findIndex(w => w.id === id), 1)
} catch (err) {
console.log(err)
}
}
onMounted(() => {
worldChannel = supabase.channel("realtime-world-channel")
.on(
"postgres_changes",
{ event: "*", schema: "public", table: "worlds" },
async (payload) => {
switch (payload.eventType) {
case "INSERT":
handleInsertedWorld(payload.new as World)
break
case "DELETE":
handleDeletedWorld(payload.old.id)
break
case "UPDATE":
if (!worlds.value?.data) return
worlds.value.data = (await $fetch("/api/worlds/query", { query: { gmId: user?.value!.id } })).data as World[]
break
default:
console.log("Unknown event has been triggered. This should not happen unless Supabase added one somehow.")
console.log(payload)
break
}
}
)
.subscribe()
})
onUnmounted(() => {
// Unsubscribe from realtime
supabase.removeChannel(worldChannel)
})
const markedWorld = ref<World | null>(null)
const isEditWorldModalOpen = ref<boolean>(false)
const isDeleteWorldModalOpen = ref<boolean>(false)
function deployDeleteModal(world: World) {
isDeleteWorldModalOpen.value = true
markedWorld.value = world
}
function hideDeleteModal() {
isDeleteWorldModalOpen.value = false
markedWorld.value = null
}
function deployEditModal(world: World) {
isEditWorldModalOpen.value = true
markedWorld.value = world
}
function hideEditModal() {
isEditWorldModalOpen.value = false
markedWorld.value = null
}
</script>
<template>
<main class="p-8">
<Head>
<Title>{{ $t("entity.world.namePlural") }}</Title>
</Head>
<Heading level="h1">{{ user?.user_metadata.full_name }}</Heading>
<section class="mt-4">
<Spacing size="lg">
<div class="flex items-center gap-3">
<Heading level="h2">
{{ $t('entity.world.namePlural') }}
</Heading>
</div>
<ul v-if="worlds?.data" class="grid lg:grid-cols-3 gap-2">
<li v-for="world in sortedWorlds" :key="world.id">
<WorldPreviewCard :world="world" @on-edit="() => deployEditModal(world)" @on-delete="() => deployDeleteModal(world)" />
</li>
<li class="md:w-fit">
<AddCard @on-click="() => isCreateWorldModalOpen = true">
<template v-if="worlds?.data?.length > 0">
{{ $t('entity.world.addSingle') }}
</template>
<template v-else>
{{ $t('entity.world.addSingleFirst') }}
</template>
</AddCard>
</li>
</ul>
</Spacing>
</section>
<WorldDialogCreate :modal-state="isCreateWorldModalOpen" @on-close="hideCreateDialog" />
<WorldDialogEdit :world="markedWorld" :modal-state="isEditWorldModalOpen" @on-close="hideEditModal" />
<WorldDialogDelete :world="markedWorld" :modal-state="isDeleteWorldModalOpen" @on-close="hideDeleteModal" />
</main>
</template>