obsidian-visualiser/shared/campaign.util.ts

165 lines
8.7 KiB
TypeScript

import { z } from "zod/v4";
import type { User } from "~/types/auth";
import type { Campaign } from "~/types/campaign";
import { div, dom, icon, span, text } from "#shared/dom.util";
import { button, loading } from "#shared/components.util";
import { CharacterCompiler } from "#shared/character.util";
import { tooltip } from "./floating.util";
export const CampaignValidation = z.object({
id: z.number(),
name: z.string().nonempty(),
description: z.string()
});
/*
const { data: campaign, error, status } = await useFetch(`/api/campaign/${id}`);
const copied = ref(false);
const stopCopy = useDebounceFn(() => copied.value = false, 5000);
async function copyLink()
{
await navigator.clipboard.writeText(`https://d-any.com/campaign/join/${ encodeURIComponent(campaign.value!.link) }`);
copied.value = true;
stopCopy();
}
*/
type PlayerState = {
status: boolean;
statusDOM: HTMLElement;
statusTooltip: Text;
user: User;
};
function defaultPlayerState(user: User): PlayerState
{
const statusTooltip = text('Absent');
return {
status: false,
statusDOM: tooltip(span('rounded-full w-3 h-3 block border-light-50 dark:border-dark-50 border-2 border-dashed'), statusTooltip, 'right'),
statusTooltip,
user
}
}
export class CampaignSheet
{
user: ComputedRef<User | null>;
campaign?: Campaign;
container: HTMLElement = div('flex flex-1 h-full w-full items-start justify-center');
dm!: PlayerState;
constructor(id: string, user: ComputedRef<User | null>)
{
this.user = user;
const load = div("flex justify-center items-center w-full h-full", [ loading('large') ]);
this.container.replaceChildren(load);
useRequestFetch()(`/api/campaign/${id}`).then(campaign => {
if(campaign)
{
this.campaign = campaign;
this.dm = defaultPlayerState(campaign.owner);
document.title = `d[any] - Campagne ${campaign.name}`;
this.render();
load.remove();
}
else throw new Error();
}).catch((e) => {
console.error(e);
this.container.replaceChildren(div('flex flex-col items-center justify-center flex-1 h-full gap-4', [
span('text-2xl font-bold tracking-wider', 'Campagne introuvable'),
span(undefined, 'Cette campagne n\'existe pas ou est privé.'),
div('flex flex-row gap-4 justify-center items-center', [
button(text('Mes campagnes'), () => useRouter().push({ name: 'campaign' }), 'px-2 py-1'),
button(text('Créer une campagne'), () => useRouter().push({ name: 'campaign-create' }), 'px-2 py-1')
])
]));
});
}
render()
{
this.container.replaceChildren(div('flex flex-col w-full h-full items-center gap-4', [
div('flex flex-row gap-8 items-center', [
div('text-2xl font-semibold', [text(this.campaign.name)]),
div('border border-light-35 dark:border-dark-35 p-1 flex flex-row items-center gap-2', [
dom('pre', { class: 'ps-1 w-[400px] truncate' }, [ text(`https://d-any.com/campaign/join/${ encodeURIComponent(this.campaign.link) }`) ]),
button(icon('radix-icons:clipboard', { width: 16, height: 16 })),
])
]),
div('flex flex-row gap-4 flex-1', [
div('flex flex-col w-64', [
div('flex flex-row items-center gap-4', [ span('text-lg font-bold tracking-tight flex-1', 'Maitre de jeu'), span('border-b border-dashed border-light-35 dark:border-dark-35 flex-1') ]),
div('flex flex-col divide-y divide-light-25 dark:divide-dark-25', [
div('flex flex-col py-1 my-1', [
div('flex flex-row items-center justify-between', [
span(undefined, this.campaign.owner.username),
this.dm.statusDOM,
])
])
]),
div('flex flex-row items-center gap-4', [ span('text-lg font-bold tracking-tight', 'Joueurs'), span('border-b border-dashed border-light-35 dark:border-dark-35 flex-1') ]),
div('flex flex-col divide-y divide-light-25 dark:divide-dark-25',
this.campaign.members.length > 0 ? this.campaign.members.map((player: any) => div('flex flex-col py-1 my-1', [
div('flex flex-row items-center justify-between', [
span(undefined, player.member.username),
undefined,
]),
player.characters.length > 0 ? div('flex flex-col',
player.characters.map((character: any) => div('flex flex-row items-center justify-between', [
span(undefined, 'joue'), span('text-bold', character.name)
]))
) : span('text-sm italic text-light-70 dark:text-dark-70', 'Pas de personnages'),
])) : span('text-sm italic py-2 text-center', 'Invitez des joueurs via le lien'),
)
]),
div('border-l border-light-35 dark:border-dark-35'),
div('flex flex-col divide-y divide-light-25 dark:divide-dark-25 w-[800px]')
])
]));
/*
<div class="flex flex-col w-full h-full items-center gap-4" v-else-if="campaign && status === 'success'">
<div class="flex flex-row gap-4 flex-1">
<div class="flex flex-col">
<div class="flex flex-row items-center gap-4"><span class="text-lg font-bold tracking-tight flex-1">Maitre de jeu</span><span class="border-b border-dashed border-light-35 dark:border-dark-35 flex-1"></span></div>
<div class="flex flex-col divide-y divide-light-25 dark:divide-dark-25 w-64">
<div class="flex flex-col py-1 my-1">
<div class="flex flex-row items-center justify-between">
<span>{{ campaign.owner.username }}</span>
<Tooltip message="Absent" side="right" :delay="0"><span class="rounded-full w-3 h-3 block border-light-50 dark:border-dark-50 border-2 border-dashed"></span></Tooltip>
</div>
</div>
</div>
<div class="flex flex-row items-center gap-4"><span class="text-lg font-bold tracking-tight">Joueurs</span><span class="border-b border-dashed border-light-35 dark:border-dark-35 w-full"></span></div>
<div class="flex flex-col divide-y divide-light-25 dark:divide-dark-25 w-64">
<template v-if="campaign.members.length > 0">
<div v-for="player of campaign.members" class="flex flex-col py-1 my-1">
<div class="flex flex-row items-center justify-between">
<span>{{ player.member?.username }}</span>
<Tooltip message="Absent" side="right" :delay="0"><span class="rounded-full w-3 h-3 block border-light-50 dark:border-dark-50 border-2 border-dashed"></span></Tooltip>
</div>
<template v-if="player.characters.length > 0">
<div class="flex flex-col">
<div class="flex flex-row items-center justify-between" v-for="character of player.characters">
<span>joue {{ character.character.name }}</span>
</div>
</div>
</template>
<template v-else>
<span class="text-sm italic text-light-70 dark:text-dark-70">Sans personnage</span>
</template>
</div>
</template>
<template v-else>
<span class="text-sm italic py-2 text-center">Invitez des joueurs via le lien d'invitation</span>
</template>
</div>
</div>
<div class="border-l border-light-35 dark:border-dark-35"></div>
<div class="flex flex-col divide-y divide-light-25 dark:divide-dark-25 w-[800px]">
</div>
</div>
</div>
*/
}
}