Add spell picker in the character sheet
This commit is contained in:
parent
c93cc4078c
commit
423df7bc42
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -3,11 +3,15 @@ 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 type { SpellConfig } from '~/types/character';
|
||||
import type { CompiledCharacter, SpellConfig } from '~/types/character';
|
||||
import type { CharacterConfig } from '~/types/character';
|
||||
import { abilityTexts, CharacterCompiler, defaultCharacter, elementTexts, spellTypeTexts } from '~/shared/character.util';
|
||||
import { getText } from '~/shared/i18n';
|
||||
import { fakeA } from '~/shared/proses';
|
||||
import { abilityTexts, CharacterCompiler, defaultCharacter, elementTexts, spellTypeTexts } from '#shared/character.util';
|
||||
import { getText } from '#shared/i18n';
|
||||
import { fakeA } 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;
|
||||
|
||||
|
|
@ -16,7 +20,7 @@ const { user } = useUserSession();
|
|||
|
||||
const { data, status, error } = await useFetch(`/api/character/${id}`);
|
||||
const compiler = new CharacterCompiler(data.value ?? defaultCharacter);
|
||||
const character = ref(compiler.compiled);
|
||||
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
|
||||
|
|
@ -29,9 +33,71 @@ 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 manageSpell()
|
||||
{
|
||||
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 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);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -162,7 +228,7 @@ function manageSpell()
|
|||
</TabsContent>
|
||||
<TabsContent v-if="character.spellslots > 0" value="spells" class="overflow-y-auto max-h-full">
|
||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-2">
|
||||
<div class="flex flex-1 justify-between items-baseline px-2"><div></div><div class="flex gap-4 items-baseline"><span class="italic text-light-70 dark:text-dark-70 text-sm">{{ character.variables.spells.length }} / {{ character.spellslots }} sorts maitrisés</span><Button class="!font-normal" @click="manageSpell">Modifier</Button></div></div>
|
||||
<div class="flex flex-1 justify-between items-baseline px-2"><div></div><div class="flex gap-4 items-baseline"><span class="italic text-light-70 dark:text-dark-70 text-sm">{{ character.variables.spells.length }} / {{ character.spellslots }} sorts maitrisés</span><Button class="!font-normal" @click="openSpellPanel">Modifier</Button></div></div>
|
||||
<div class="flex flex-col" v-if="[...(character.lists.spells ?? []), ...character.variables.spells].length > 0">
|
||||
<div class="pb-4 px-2 mt-4 border-b last:border-none border-light-30 dark:border-dark-30 flex flex-col" v-for="spell of [...(character.lists.spells ?? []), ...character.variables.spells].map(e => config.spells.find((f: SpellConfig) => f.id === e)).filter(e => !!e)">
|
||||
<div class="flex flex-row justify-between">
|
||||
|
|
|
|||
|
|
@ -2499,7 +2499,7 @@
|
|||
{
|
||||
"id": "9jq3pkj7sgfgq6q4ovwoanig6ha8g2ic",
|
||||
"name": "Dévastation élémentaire",
|
||||
"rank": 1,
|
||||
"rank": 4,
|
||||
"type": "precision",
|
||||
"cost": 8,
|
||||
"speed": "action",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Ability, Alignment, Character, CharacterConfig, CompiledCharacter, FeatureItem, Level, MainStat, Resistance, SpellElement, SpellType, TrainingLevel } from "~/types/character";
|
||||
import type { Ability, Alignment, Character, CharacterConfig, CharacterVariables, CompiledCharacter, FeatureItem, Level, MainStat, Resistance, SpellElement, SpellType, TrainingLevel } from "~/types/character";
|
||||
import { z } from "zod/v4";
|
||||
import characterConfig from '#shared/character-config.json';
|
||||
import { fakeA } from "#shared/proses";
|
||||
|
|
@ -293,6 +293,11 @@ export class CharacterCompiler
|
|||
return substring;
|
||||
})
|
||||
}
|
||||
variable<T extends keyof CharacterVariables>(prop: T, value: CharacterVariables[T])
|
||||
{
|
||||
this._character.variables[prop] = value;
|
||||
this._result.variables[prop] = value;
|
||||
}
|
||||
protected add(feature?: string)
|
||||
{
|
||||
if(!feature)
|
||||
|
|
@ -1125,22 +1130,9 @@ class AspectPicker extends BuilderTab
|
|||
}
|
||||
static override validate(builder: CharacterBuilder): boolean
|
||||
{
|
||||
/* const physic = Object.values(builder.character.training['strength']).length + Object.values(builder.character.training['dexterity']).length + Object.values(builder.character.training['constitution']).length;
|
||||
const mental = Object.values(builder.character.training['intelligence']).length + Object.values(builder.character.training['curiosity']).length;
|
||||
const personality = Object.values(builder.character.training['charisma']).length + Object.values(builder.character.training['psyche']).length; */
|
||||
|
||||
if(builder.character.aspect === undefined)
|
||||
return false;
|
||||
|
||||
/* const aspect = config.aspects[builder.character.aspect]!
|
||||
|
||||
if(physic > aspect.physic.max || physic < aspect.physic.min)
|
||||
return false;
|
||||
if(mental > aspect.mental.max || mental < aspect.mental.min)
|
||||
return false;
|
||||
if(personality > aspect.personality.max || personality < aspect.personality.min)
|
||||
return false; */
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -35,9 +35,18 @@ export function async(size: 'small' | 'normal' | 'large' = 'normal', fn: Promise
|
|||
}
|
||||
export function button(content: Node, onClick?: () => void, cls?: Class)
|
||||
{
|
||||
return dom('button', { class: [`text-light-100 dark:text-dark-100 font-semibold hover:bg-light-30 dark:hover:bg-dark-30 inline-flex items-center justify-center bg-light-25 dark:bg-dark-25 leading-none outline-none
|
||||
const btn = dom('button', { class: [`text-light-100 dark:text-dark-100 font-semibold hover:bg-light-30 dark:hover:bg-dark-30 inline-flex items-center justify-center bg-light-25 dark:bg-dark-25 leading-none outline-none
|
||||
border border-light-25 dark:border-dark-25 hover:border-light-30 dark:hover:border-dark-30 active:border-light-40 dark:active:border-dark-40 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40
|
||||
disabled:bg-light-10 dark:disabled:bg-dark-10 disabled:border-none disabled:text-light-50 dark:disabled:text-dark-50`, cls], listeners: { click: onClick } }, [ content ]);
|
||||
disabled:bg-light-10 dark:disabled:bg-dark-10 disabled:border-none disabled:text-light-50 dark:disabled:text-dark-50`, cls], listeners: { click: () => disabled || (onClick && onClick()) } }, [ content ]);
|
||||
let disabled = false;
|
||||
Object.defineProperty(btn, 'disabled', {
|
||||
get: () => disabled,
|
||||
set: (v) => {
|
||||
disabled = !!v;
|
||||
btn.toggleAttribute('disabled', disabled);
|
||||
}
|
||||
})
|
||||
return btn;
|
||||
}
|
||||
export type Option<T> = { text: string, render?: () => HTMLElement, value: T | Option<T>[] } | undefined;
|
||||
type StoredOption<T> = { item: Option<T>, dom: HTMLElement, container?: HTMLElement, children?: Array<StoredOption<T>> };
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import characterConfig from '#shared/character-config.json';
|
|||
|
||||
const config = characterConfig as CharacterConfig;
|
||||
|
||||
export function getText(id?: i18nID, lang?: string)
|
||||
export function getText(id?: i18nID, lang?: string): string
|
||||
{
|
||||
return id ? (config.texts.hasOwnProperty(id) ? config.texts[id][lang ?? "default"] : '') : undefined;
|
||||
return id ? (config.texts.hasOwnProperty(id) ? config.texts[id][lang ?? "default"] : '') : '';
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ function renderContent(node: RootContent, proses: Record<string, Prose>): Node
|
|||
{
|
||||
const children = node.children.map(e => renderContent(e, proses)), properties = { ...node.properties, class: node.properties.className as string | string[] };
|
||||
if(node.tagName in proses)
|
||||
return prose(node.tagName, proses[node.tagName], children, properties);
|
||||
return prose(node.tagName, proses[node.tagName] ?? { class: '' }, children, properties);
|
||||
else
|
||||
return dom(node.tagName as keyof HTMLElementTagNameMap, properties, children);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue