Work in progress: CharacterSheet implementation and FeatureChoice rework

This commit is contained in:
Clément Pons
2025-09-29 17:53:41 +02:00
parent b1ac379f1a
commit 1642cd513f
13 changed files with 889 additions and 664 deletions

View File

@@ -2,25 +2,16 @@
import characterConfig from '#shared/character-config.json';
import { Icon } from '@iconify/vue/dist/iconify.js';
import PreviewA from '~/components/prose/PreviewA.vue';
import { clamp } from '#shared/general.util';
import { clamp, unifySlug } from '#shared/general.util';
import type { CompiledCharacter, SpellConfig } from '~/types/character';
import type { CharacterConfig } from '~/types/character';
import { abilityTexts, CharacterCompiler, defaultCharacter, elementTexts, spellTypeTexts } from '#shared/character.util';
import { abilityTexts, CharacterCompiler, CharacterSheet, defaultCharacter, elementTexts, spellTypeTexts } from '#shared/character.util';
import { getText } from '#shared/i18n';
import { fakeA } from '#shared/proses';
import { preview } from '#shared/proses';
import { div, dom, icon, text } from '#shared/dom.util';
import markdown from '#shared/markdown.util';
import { button, foldable } from '#shared/components.util';
import { fullblocker, tooltip } from '~/shared/floating.util';
const config = characterConfig as CharacterConfig;
const id = useRouter().currentRoute.value.params.id;
const { user } = useUserSession();
const { data, status, error } = await useFetch(`/api/character/${id}`);
const compiler = new CharacterCompiler(data.value ?? defaultCharacter);
const character = ref<CompiledCharacter>(compiler.compiled);
/*
text-light-red dark:text-dark-red border-light-red dark:border-dark-red bg-light-red dark:bg-dark-red
text-light-blue dark:text-dark-blue border-light-blue dark:border-dark-blue bg-light-blue dark:bg-dark-blue
@@ -33,76 +24,26 @@ text-light-yellow dark:text-dark-yellow border-light-yellow dark:border-dark-yel
text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-purple bg-light-purple dark:bg-dark-purple
*/
function openSpellPanel() {
const availableSpells = Object.values(config.spells).filter(spell => {
if (spell.rank === 4) return false;
if (character.value.spellranks[spell.type] < spell.rank) return false;
return true;
});
const config = characterConfig as CharacterConfig;
const textAmount = text(character.value.variables.spells.length.toString()), textMax = text(character.value.spellslots.toString());
const container = div("border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-1/2 flex flex-col gap-4 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]", [
div("flex flex-row justify-between items-center mb-4", [
dom("h2", { class: "text-xl font-bold", text: "Ajouter un sort" }),
div('flex flex-row gap-4 items-center', [ dom('span', { class: 'italic text-light-70 dark:text-dark-70 text-sm' }, [ textAmount, text(' / '), textMax, text(' sorts maitrisés') ]), tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
setTimeout(blocker.close, 150);
container.setAttribute('data-state', 'inactive');
}, "p-1"), "Fermer", "left") ])
]),
div('flex flex-col divide-y *:py-2 -my-2 overflow-y-auto', availableSpells.map(spell => {
let state = character.value.lists.spells?.includes(spell.id) ? 'given' : character.value.variables.spells.includes(spell.id) ? 'choosen' : 'empty';
const toggleText = text(state === 'choosen' ? 'Supprimer' : state === 'given' ? 'Inné' : 'Ajouter'), toggleButton = button(toggleText, () => {
if(state === 'choosen')
{
compiler.variable('spells', character.value.variables.spells.filter(e => e !== spell.id));
state = 'empty';
}
else if(state === 'empty')
{
compiler.variable('spells', [...character.value.variables.spells, spell.id]);
state = 'choosen';
}
character.value = compiler.compiled;
toggleText.textContent = state === 'choosen' ? 'Supprimer' : state === 'given' ? 'Inné' : 'Ajouter';
textAmount.textContent = character.value.variables.spells.length.toString();
}, "px-2 py-1 text-sm font-normal");
toggleButton.disabled = state === 'given';
return foldable(() => [
markdown(spell.effect),
], [ div("flex flex-row justify-between gap-2", [
dom("span", { class: "text-lg font-bold", text: spell.name }),
div("flex flex-row items-center gap-6", [
div("flex flex-row text-sm gap-2",
spell.elements.map(el =>
dom("span", {
class: [`border !border-opacity-50 rounded-full !bg-opacity-20 px-2 py-px`, elementTexts[el].class],
text: elementTexts[el].text
})
)
),
div("flex flex-row text-sm gap-1", [
...(spell.rank !== 4 ? [
dom("span", { text: `Rang ${spell.rank}` }),
text("/"),
dom("span", { text: spellTypeTexts[spell.type] }),
text("/")
] : []),
dom("span", { text: `${spell.cost} mana` }),
text("/"),
dom("span", { text: typeof spell.speed === "string" ? spell.speed : `${spell.speed} minutes` })
]),
toggleButton,
]),
]) ], { open: false, class: { container: "px-2 flex flex-col border-light-35 dark:border-dark-35", content: 'py-2' } });
}))
]);
const blocker = fullblocker([ container ], { closeWhenOutside: true });
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
}
const id = useRouter().currentRoute.value.params.id ? unifySlug(useRouter().currentRoute.value.params.id!) : undefined;
const { user } = useUserSession();
const container = useTemplateRef('container');
onMounted(() => {
queueMicrotask(() => {
if(container.value && id)
{
const character = new CharacterSheet(id, user);
container.value.appendChild(character.container);
}
});
});
</script>
<template>
<div v-if="status === 'pending'">
<div ref="container"></div>
<!-- <div v-if="status === 'pending'">
<Head>
<Title>d[any] - Chargement ...</Title>
</Head>
@@ -125,9 +66,9 @@ function openSpellPanel() {
<span>{{ config.peoples[character.race]?.name ?? 'Peuple inconnu' }}</span>
</div>
</div>
<div class="flex flex-col lg:border-l border-light-30 dark:border-dark-30 py-4 ps-4">
<span class="flex flex-row items-center gap-2">PV: {{ character.health - character.variables.health }}/{{ character.health }}</span>
<span class="flex flex-row items-center gap-2">Mana: {{ character.mana - character.variables.mana }}/{{ character.mana }}</span>
<div class="flex flex-row lg:border-l border-light-30 dark:border-dark-30 py-4 ps-4 gap-8">
<span class="flex flex-row items-center gap-2 text-3xl font-light">PV: <span class="font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35">{{ character.health - character.variables.health }}</span>/ {{ character.health }}</span>
<span class="flex flex-row items-center gap-2 text-3xl font-light">Mana: <span class="font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35">{{ character.mana - character.variables.mana }}</span>/ {{ character.mana }}</span>
</div>
</div>
<div class="self-center">
@@ -135,8 +76,8 @@ function openSpellPanel() {
</div>
</div>
<div class="flex flex-1 flex-col justify-center gap-4 *:py-2">
<div class="grid 2xl:grid-cols-10 grid-cols-1 gap-4 items-center border-b border-light-30 dark:border-dark-30 me-4 pe-4">
<div class="flex relative justify-between ps-4 gap-2 2xl:col-span-6">
<div class="flex flex-row gap-4 items-center border-b border-light-30 dark:border-dark-30 me-4 pe-4 divide-x divide-light-30 dark:divide-dark-30">
<div class="flex relative justify-between ps-4 gap-2">
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.strength }}</span><span class="text-sm 2xl:text-base">Force</span></div>
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.dexterity }}</span><span class="text-sm 2xl:text-base">Dextérité</span></div>
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.constitution }}</span><span class="text-sm 2xl:text-base">Constitution</span></div>
@@ -145,9 +86,11 @@ function openSpellPanel() {
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.charisma }}</span><span class="text-sm 2xl:text-base">Charisme</span></div>
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.psyche }}</span><span class="text-sm 2xl:text-base">Psyché</span></div>
</div>
<div class="flex flex-1 relative 2xl:border-l border-light-30 dark:border-dark-30 ps-4 2xl:col-span-4 flex-row items-center justify-between">
<div class="flex flex-1 relative ps-4 flex-row items-center justify-between">
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.initiative }}</span><span>Initiative</span></div>
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">{{ character.speed === false ? "Aucun déplacement" : `${character.speed} cases` }}</span><span>Course</span></div>
</div>
<div class="flex flex-1 relative ps-4 flex-row items-center justify-between">
<Icon icon="ph:shield-checkered" class="w-8 h-8" />
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">{{ clamp(character.defense.static + character.defense.passivedodge + character.defense.passiveparry, 0, character.defense.hardcap) }}</span><span class="text-sm 2xl:text-base">Passive</span></div>
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">{{ clamp(character.defense.static + character.defense.passivedodge + character.defense.activeparry, 0, character.defense.hardcap) }}</span><span class="text-sm 2xl:text-base">Blocage</span></div>
@@ -205,24 +148,24 @@ function openSpellPanel() {
<div class="flex flex-col col-span-2">
<span class="text-lg font-semibold">Actions</span>
<span class="text-sm text-light-70 dark:text-dark-70">Attaquer - Saisir - Faire chuter - Déplacer - Courir - Pas de coté - Lancer un sort - S'interposer - Se transformer - Utiliser un objet - Anticiper une action - Improviser</span>
<MarkdownRenderer :content="character.lists.action?.map(e => getText(e))?.join('\n')" :properties="{ tags: { a: fakeA } }" />
<MarkdownRenderer :content="character.lists.action?.map(e => getText(e))?.join('\n')" :properties="{ tags: { a: preview } }" />
</div>
<div class="flex flex-col gap-2">
<div class="flex flex-col">
<span class="text-lg font-semibold">Réactions</span>
<span class="text-sm text-light-70 dark:text-dark-70">Parade - Esquive - Saisir une opportunité - Prendre en tenaille - Intercepter - Désarmer</span>
<MarkdownRenderer :content="character.lists.reaction?.map(e => getText(e))?.join('\n')" :properties="{ tags: { a: fakeA } }" />
<MarkdownRenderer :content="character.lists.reaction?.map(e => getText(e))?.join('\n')" :properties="{ tags: { a: preview } }" />
</div>
<div class="flex flex-col">
<span class="text-lg font-semibold">Actions libre</span>
<span class="text-sm text-light-70 dark:text-dark-70">Analyser une situation - Communiquer</span>
<MarkdownRenderer :content="character.lists.freeaction?.map(e => getText(e))?.join('\n')" :properties="{ tags: { a: fakeA } }" />
<MarkdownRenderer :content="character.lists.freeaction?.map(e => getText(e))?.join('\n')" :properties="{ tags: { a: preview } }" />
</div>
</div>
</div>
<div class="flex flex-col">
<span class="text-lg font-semibold">Aptitudes</span>
<MarkdownRenderer :content="character.lists.passive?.map(e => getText(e))?.map(e => `> ${e}`).join('\n\n')" :properties="{ tags: { a: fakeA } }" />
<MarkdownRenderer :content="character.lists.passive?.map(e => getText(e))?.map(e => `> ${e}`).join('\n\n')" :properties="{ tags: { a: preview } }" />
</div>
</div>
</TabsContent>
@@ -251,9 +194,7 @@ function openSpellPanel() {
</div>
</TabsContent>
<TabsContent value="inventory" v-if="character.capacity !== false" class="overflow-y-auto max-h-full">
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
<MarkdownRenderer :content="character.notes" />
</div>
</TabsContent>
<TabsContent value="notes" class="overflow-y-auto max-h-full">
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
@@ -269,5 +210,5 @@ function openSpellPanel() {
<Title>d[any] - Erreur</Title>
</Head>
<div>Erreur de chargement</div>
</div>
</div> -->
</template>