Try to add character editor inside the character sheet
This commit is contained in:
parent
898d95793a
commit
9face0ac3b
|
|
@ -11,9 +11,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Content } from '~~/shared/content';
|
import { Content } from '#shared/content';
|
||||||
import * as Floating from '~~/shared/floating';
|
import * as Floating from '#shared/floating';
|
||||||
import { Toaster } from '~~/shared/components';
|
import { Toaster } from '#shared/components';
|
||||||
import { init } from '#shared/i18n';
|
import { init } from '#shared/i18n';
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-row w-full max-w-full h-full max-h-full" style="--sidebar-width: 300px">
|
<div class="flex flex-row w-full max-w-full h-full max-h-full group/sidebar" :data-active="open || undefined" style="--sidebar-width: 300px">
|
||||||
<div class="bg-light-0 dark:bg-dark-0 w-[var(--sidebar-width)] border-r border-light-30 dark:border-dark-30 flex flex-col gap-2">
|
<div class="bg-light-0 dark:bg-dark-0 transition-[width] w-0 group-data-[active]/sidebar:w-[var(--sidebar-width)] overflow-hidden border-r border-light-30 dark:border-dark-30 flex flex-col gap-2 absolute top-0 bottom-0 left-0">
|
||||||
<NuxtLink class="flex flex-row items-center justify-center group gap-2 my-2" aria-label="Accueil" :to="{ name: 'index', force: true }">
|
<span class="w-full h-14"></span>
|
||||||
<Avatar src="/logo.dark.svg" class="dark:block hidden" />
|
|
||||||
<Avatar src="/logo.light.svg" class="block dark:hidden" />
|
|
||||||
<span class="text-xl font-semibold group-hover:text-light-70 dark:group-hover:text-dark-70">d[any]</span>
|
|
||||||
</NuxtLink>
|
|
||||||
<div class="flex-1 px-2 max-w-full max-h-full overflow-y-auto overflow-x-hidden" ref="treeParent"></div>
|
<div class="flex-1 px-2 max-w-full max-h-full overflow-y-auto overflow-x-hidden" ref="treeParent"></div>
|
||||||
<div class="flex flex-col my-4 items-center justify-center gap-1 text-xs text-light-60 dark:text-dark-60">
|
<div class="flex flex-col my-4 items-center justify-center gap-1 text-xs text-light-60 dark:text-dark-60">
|
||||||
<NuxtLink class="hover:underline" :to="{ name: 'legal' }">Mentions légales</NuxtLink>
|
<NuxtLink class="hover:underline" :to="{ name: 'legal' }">Mentions légales</NuxtLink>
|
||||||
|
|
@ -13,38 +9,52 @@
|
||||||
Copyright Peaceultime - 2025
|
Copyright Peaceultime - 2025
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col flex-1 h-full w-[calc(100vw-var(--sidebar-width))]">
|
<div class="flex flex-col flex-1 h-full w-full">
|
||||||
<div class="flex flex-row border-b border-light-30 dark:border-dark-30 justify-between px-8">
|
<div class="flex flex-row w-full border-b border-light-30 dark:border-dark-30">
|
||||||
<div class="flex flex-row gap-16 items-center">
|
<div class="w-[var(--sidebar-width)] z-40 flex flex-row gap-4 items-center justify-between px-4">
|
||||||
<NavigationMenuRoot class="relative">
|
<div class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
||||||
<NavigationMenuList class="flex items-center gap-8 max-md:hidden">
|
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
||||||
<NavigationMenuItem>
|
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
||||||
<NavigationMenuTrigger>
|
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 p-1" @click="() => open = !open"><Icon :icon="open ? 'radix-icons:pin-left' : 'radix-icons:pin-right'" height="12" width="12"/></div>
|
||||||
<NuxtLink :href="{ name: 'character' }" class="flex flex-row gap-2 items-center border-b-2 border-transparent hover:border-accent-blue py-4 select-none" active-class="!text-accent-blue"><span class="px-3 flex-1 truncate">Personnages</span><Icon icon="radix-icons:caret-down" /></NuxtLink>
|
<NuxtLink class="flex flex-row items-center justify-center group gap-2 my-2" aria-label="Accueil" :to="{ name: 'index', force: true }">
|
||||||
</NavigationMenuTrigger>
|
<Avatar src="/logo.dark.svg" class="dark:block hidden" />
|
||||||
<NavigationMenuContent class="absolute top-0 w-full sm:w-auto bg-light-0 dark:bg-dark-0 border border-light-30 dark:border-dark-30 py-2 z-20 flex flex-col">
|
<Avatar src="/logo.light.svg" class="block dark:hidden" />
|
||||||
<NuxtLink :href="{ name: 'character-list' }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Personnages publics</span></NuxtLink>
|
<span class="text-xl font-semibold group-hover:text-light-70 dark:group-hover:text-dark-70">d[any]</span>
|
||||||
<NuxtLink :href="{ name: 'character-id-edit', params: { id: 'new' } }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Nouveau personnage</span></NuxtLink>
|
</NuxtLink>
|
||||||
</NavigationMenuContent>
|
<div></div>
|
||||||
</NavigationMenuItem>
|
|
||||||
</NavigationMenuList>
|
|
||||||
<div class="absolute top-full left-0 flex w-full justify-center">
|
|
||||||
<NavigationMenuViewport class="h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] flex justify-center overflow-hidden sm:w-[var(--radix-navigation-menu-viewport-width)]" />
|
|
||||||
</div>
|
|
||||||
</NavigationMenuRoot>
|
|
||||||
<NuxtLink :href="{ name: 'campaign' }" class="flex flex-row gap-2 items-center border-b-2 border-transparent hover:border-accent-blue py-4 select-none" active-class="!text-accent-blue"><span class="px-3 flex-1 truncate">Campagnes</span></NuxtLink>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row gap-16 items-center">
|
<div class="flex flex-1 flex-row justify-between px-8">
|
||||||
<template v-if="!loggedIn">
|
<div class="flex flex-row gap-16 items-center">
|
||||||
<NuxtLink class="text-light-100 dark:text-dark-100 hover:text-light-70 dark:hover:text-dark-70" :to="{ name: 'user-login' }">Se connecter</NuxtLink>
|
<NavigationMenuRoot class="relative">
|
||||||
<NuxtLink class="text-light-100 dark:text-dark-100 hover:text-light-70 dark:hover:text-dark-70 max-md:hidden" :to="{ name: 'user-register' }">Créer un compte</NuxtLink>
|
<NavigationMenuList class="flex items-center gap-8 max-md:hidden">
|
||||||
</template>
|
<NavigationMenuItem>
|
||||||
<template v-else>
|
<NavigationMenuTrigger>
|
||||||
<NuxtLink class="text-light-100 dark:text-dark-100 hover:text-light-70 dark:hover:text-dark-70" :to="{ name: 'user-login' }">{{ user!.username }}</NuxtLink>
|
<NuxtLink :href="{ name: 'character' }" class="flex flex-row gap-2 items-center border-b-2 border-transparent hover:border-accent-blue py-4 select-none" active-class="!text-accent-blue"><span class="px-3 flex-1 truncate">Personnages</span><Icon icon="radix-icons:caret-down" /></NuxtLink>
|
||||||
</template>
|
</NavigationMenuTrigger>
|
||||||
|
<NavigationMenuContent class="absolute top-0 w-full sm:w-auto bg-light-0 dark:bg-dark-0 border border-light-30 dark:border-dark-30 py-2 z-20 flex flex-col">
|
||||||
|
<NuxtLink :href="{ name: 'character-list' }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Personnages publics</span></NuxtLink>
|
||||||
|
<NuxtLink :href="{ name: 'character-id-edit', params: { id: 'new' } }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Nouveau personnage</span></NuxtLink>
|
||||||
|
</NavigationMenuContent>
|
||||||
|
</NavigationMenuItem>
|
||||||
|
</NavigationMenuList>
|
||||||
|
<div class="absolute top-full left-0 flex w-full justify-center">
|
||||||
|
<NavigationMenuViewport class="h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] flex justify-center overflow-hidden sm:w-[var(--radix-navigation-menu-viewport-width)]" />
|
||||||
|
</div>
|
||||||
|
</NavigationMenuRoot>
|
||||||
|
<NuxtLink :href="{ name: 'campaign' }" class="flex flex-row gap-2 items-center border-b-2 border-transparent hover:border-accent-blue py-4 select-none" active-class="!text-accent-blue"><span class="px-3 flex-1 truncate">Campagnes</span></NuxtLink>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row gap-16 items-center">
|
||||||
|
<template v-if="!loggedIn">
|
||||||
|
<NuxtLink class="text-light-100 dark:text-dark-100 hover:text-light-70 dark:hover:text-dark-70" :to="{ name: 'user-login' }">Se connecter</NuxtLink>
|
||||||
|
<NuxtLink class="text-light-100 dark:text-dark-100 hover:text-light-70 dark:hover:text-dark-70 max-md:hidden" :to="{ name: 'user-register' }">Créer un compte</NuxtLink>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<NuxtLink class="text-light-100 dark:text-dark-100 hover:text-light-70 dark:hover:text-dark-70" :to="{ name: 'user-login' }">{{ user!.username }}</NuxtLink>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<slot></slot>
|
<div class="flex flex-1 ms-0 transition-[margin-inline-start] group-data-[active]/sidebar:ms-[var(--sidebar-width)] overflow-auto"><slot></slot></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -58,14 +68,13 @@ import { unifySlug } from '~~/shared/general';
|
||||||
import { tooltip } from '~~/shared/floating';
|
import { tooltip } from '~~/shared/floating';
|
||||||
import { link, loading } from '~~/shared/components';
|
import { link, loading } from '~~/shared/components';
|
||||||
|
|
||||||
const open = ref(false);
|
const open = useLocalStorage('sidebar', true, { writeDefaults: true });
|
||||||
let tree: TreeDOM | undefined;
|
let tree: TreeDOM | undefined;
|
||||||
const { loggedIn, user } = useUserSession();
|
const { loggedIn, user } = useUserSession();
|
||||||
|
|
||||||
const route = useRouter().currentRoute;
|
const route = useRouter().currentRoute;
|
||||||
const path = computed(() => route.value.params.path ? decodeURIComponent(unifySlug(route.value.params.path)) : undefined);
|
const path = computed(() => route.value.params.path ? decodeURIComponent(unifySlug(route.value.params.path)) : undefined);
|
||||||
|
|
||||||
|
|
||||||
const unmount = useRouter().afterEach((to, from, failure) => {
|
const unmount = useRouter().afterEach((to, from, failure) => {
|
||||||
if(failure)
|
if(failure)
|
||||||
return;
|
return;
|
||||||
|
|
@ -73,10 +82,6 @@ const unmount = useRouter().afterEach((to, from, failure) => {
|
||||||
to.name === 'explore-path' && (unifySlug(to.params.path ?? '').split('/').map((e, i, a) => a.slice(0, i).join('/')) ?? []).forEach(e => tree?.toggle(tree.tree.search('path', e)[0], true));
|
to.name === 'explore-path' && (unifySlug(to.params.path ?? '').split('/').map((e, i, a) => a.slice(0, i).join('/')) ?? []).forEach(e => tree?.toggle(tree.tree.search('path', e)[0], true));
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(route, () => {
|
|
||||||
open.value = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const treeParent = useTemplateRef('treeParent');
|
const treeParent = useTemplateRef('treeParent');
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if(treeParent.value)
|
if(treeParent.value)
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -290,8 +290,8 @@ export class CharacterCompiler
|
||||||
{
|
{
|
||||||
private _dirty: boolean = true;
|
private _dirty: boolean = true;
|
||||||
|
|
||||||
protected _character!: Character;
|
protected _character: Character | undefined;
|
||||||
protected _result!: CompiledCharacter;
|
protected _result: CompiledCharacter | undefined;
|
||||||
protected _buffer: Record<string, PropertySum> = {
|
protected _buffer: Record<string, PropertySum> = {
|
||||||
'modifier/strength': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
'modifier/strength': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
'modifier/dexterity': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
'modifier/dexterity': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
|
@ -304,9 +304,9 @@ export class CharacterCompiler
|
||||||
protected _trees: Record<string, TreeState> = {};
|
protected _trees: Record<string, TreeState> = {};
|
||||||
private _variableDebounce: NodeJS.Timeout = setTimeout(() => {});
|
private _variableDebounce: NodeJS.Timeout = setTimeout(() => {});
|
||||||
|
|
||||||
constructor(character: Character)
|
constructor(character?: Character)
|
||||||
{
|
{
|
||||||
this.character = character;
|
if(character) this.character = character;
|
||||||
}
|
}
|
||||||
|
|
||||||
set character(value: Character)
|
set character(value: Character)
|
||||||
|
|
@ -333,7 +333,7 @@ export class CharacterCompiler
|
||||||
Object.entries(value.abilities).forEach(e => this._buffer[`abilities/${e[0]}`] = { value: 0, _dirty: true, min: -Infinity, list: [{ id: '', operation: 'add', value: e[1] }] });
|
Object.entries(value.abilities).forEach(e => this._buffer[`abilities/${e[0]}`] = { value: 0, _dirty: true, min: -Infinity, list: [{ id: '', operation: 'add', value: e[1] }] });
|
||||||
|
|
||||||
reactivity(() => value.variables.transformed, (v) => {
|
reactivity(() => value.variables.transformed, (v) => {
|
||||||
if(value.aspect && config.aspects[value.aspect])
|
if(this._character && this._result && value.aspect && config.aspects[value.aspect])
|
||||||
{
|
{
|
||||||
const aspect = config.aspects[value.aspect]!;
|
const aspect = config.aspects[value.aspect]!;
|
||||||
if(v)
|
if(v)
|
||||||
|
|
@ -349,19 +349,19 @@ export class CharacterCompiler
|
||||||
idx !== -1 && this._buffer[`modifier/${aspect.stat}`]!.list.splice(idx, 1);
|
idx !== -1 && this._buffer[`modifier/${aspect.stat}`]!.list.splice(idx, 1);
|
||||||
this._buffer[`modifier/${aspect.stat}`]!._dirty = true;
|
this._buffer[`modifier/${aspect.stat}`]!._dirty = true;
|
||||||
}
|
}
|
||||||
this.compile([`modifier/${aspect.stat}`]);
|
this.compile([`modifier/${aspect.stat}`], this._buffer, this._result);
|
||||||
this.saveVariables();
|
this.saveVariables();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
get character(): Character
|
get character(): Character | undefined
|
||||||
{
|
{
|
||||||
return this._character;
|
return this._character;
|
||||||
}
|
}
|
||||||
get compiled(): CompiledCharacter
|
get compiled(): CompiledCharacter | undefined
|
||||||
{
|
{
|
||||||
if(this._dirty)
|
if(this._character && this._result && this._dirty)
|
||||||
{
|
{
|
||||||
Object.keys(this._trees).forEach(tree => {
|
Object.keys(this._trees).forEach(tree => {
|
||||||
if(!this._trees[tree]!._dirty || !config.trees[tree])
|
if(!this._trees[tree]!._dirty || !config.trees[tree])
|
||||||
|
|
@ -376,7 +376,7 @@ export class CharacterCompiler
|
||||||
this._trees[tree]!.validated = validated;
|
this._trees[tree]!.validated = validated;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.compile(Object.keys(this._buffer));
|
this.compile(Object.keys(this._buffer), this._buffer, this._result);
|
||||||
this._dirty = false;
|
this._dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,7 +384,7 @@ export class CharacterCompiler
|
||||||
}
|
}
|
||||||
get values(): Record<string, number>
|
get values(): Record<string, number>
|
||||||
{
|
{
|
||||||
if(this._dirty)
|
if(this._character && this._result && this._dirty)
|
||||||
{
|
{
|
||||||
Object.keys(this._trees).forEach(tree => {
|
Object.keys(this._trees).forEach(tree => {
|
||||||
if(!this._trees[tree]!._dirty || !config.trees[tree])
|
if(!this._trees[tree]!._dirty || !config.trees[tree])
|
||||||
|
|
@ -399,7 +399,7 @@ export class CharacterCompiler
|
||||||
this._trees[tree]!.validated = validated;
|
this._trees[tree]!.validated = validated;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.compile(Object.keys(this._buffer));
|
this.compile(Object.keys(this._buffer), this._buffer, this._result);
|
||||||
this._dirty = false;
|
this._dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -410,16 +410,16 @@ export class CharacterCompiler
|
||||||
}
|
}
|
||||||
get armor()
|
get armor()
|
||||||
{
|
{
|
||||||
const armor = this._character.variables.items.find(e => e.equipped && config.items[e.id]?.category === 'armor');
|
const armor = this._character?.variables.items.find(e => e.equipped && config.items[e.id]?.category === 'armor');
|
||||||
return armor ? { max: (config.items[armor.id] as ArmorConfig).health, current: (config.items[armor.id] as ArmorConfig).health - ((armor.state as ArmorState)?.loss ?? 0), flat: (config.items[armor.id] as ArmorConfig).absorb.static + ((armor.state as ArmorState)?.absorb.flat ?? 0), percent: (config.items[armor.id] as ArmorConfig).absorb.percent + ((armor.state as ArmorState)?.absorb.percent ?? 0) } : undefined;
|
return armor ? { max: (config.items[armor.id] as ArmorConfig).health, current: (config.items[armor.id] as ArmorConfig).health - ((armor.state as ArmorState)?.loss ?? 0), flat: (config.items[armor.id] as ArmorConfig).absorb.static + ((armor.state as ArmorState)?.absorb.flat ?? 0), percent: (config.items[armor.id] as ArmorConfig).absorb.percent + ((armor.state as ArmorState)?.absorb.percent ?? 0) } : { max: 0, current: 0, flat: 0, percent: 0, };
|
||||||
}
|
}
|
||||||
get weight()
|
get weight()
|
||||||
{
|
{
|
||||||
return this._character.variables.items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0) * v.amount, 0);
|
return this._character?.variables.items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0) * v.amount, 0) ?? 0;
|
||||||
}
|
}
|
||||||
get power()
|
get power()
|
||||||
{
|
{
|
||||||
return this._character.variables.items.filter(e => config.items[e.id]?.equippable && e.equipped).reduce((p, v) => p + ((config.items[v.id]?.powercost ?? 0) + (v.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0) * v.amount), 0);
|
return this._character?.variables.items.filter(e => config.items[e.id]?.equippable && e.equipped).reduce((p, v) => p + ((config.items[v.id]?.powercost ?? 0) + (v.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0) * v.amount), 0) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
enchant(item: ItemState)
|
enchant(item: ItemState)
|
||||||
|
|
@ -447,9 +447,11 @@ export class CharacterCompiler
|
||||||
}
|
}
|
||||||
saveVariables()
|
saveVariables()
|
||||||
{
|
{
|
||||||
|
if(!this._character) return;
|
||||||
clearTimeout(this._variableDebounce);
|
clearTimeout(this._variableDebounce);
|
||||||
this._variableDebounce = setTimeout(() => {
|
this._variableDebounce = setTimeout(() => {
|
||||||
useRequestFetch()(`/api/character/${this.character.id}/variables`, {
|
if(!this._character) return;
|
||||||
|
useRequestFetch()(`/api/character/${this._character.id}/variables`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: raw(this._character.variables),
|
body: raw(this._character.variables),
|
||||||
}).then(() => {}).catch(() => {
|
}).then(() => {}).catch(() => {
|
||||||
|
|
@ -459,7 +461,8 @@ export class CharacterCompiler
|
||||||
}
|
}
|
||||||
saveNotes()
|
saveNotes()
|
||||||
{
|
{
|
||||||
return useRequestFetch()(`/api/character/${this.character.id}/notes`, {
|
if(!this._character) return Promise.resolve();
|
||||||
|
return useRequestFetch()(`/api/character/${this._character.id}/notes`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: this._character.notes,
|
body: this._character.notes,
|
||||||
}).then(() => {}).catch(() => {
|
}).then(() => {}).catch(() => {
|
||||||
|
|
@ -482,8 +485,9 @@ export class CharacterCompiler
|
||||||
}
|
}
|
||||||
protected apply(feature?: FeatureItem)
|
protected apply(feature?: FeatureItem)
|
||||||
{
|
{
|
||||||
if(!feature)
|
if(!this._character) return;
|
||||||
return;
|
if(!this._result) return;
|
||||||
|
if(!feature) return;
|
||||||
|
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
switch(feature.category)
|
switch(feature.category)
|
||||||
|
|
@ -529,8 +533,9 @@ export class CharacterCompiler
|
||||||
}
|
}
|
||||||
protected undo(feature?: FeatureItem)
|
protected undo(feature?: FeatureItem)
|
||||||
{
|
{
|
||||||
if(!feature)
|
if(!this._character) return;
|
||||||
return;
|
if(!this._result) return;
|
||||||
|
if(!feature) return;
|
||||||
|
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
switch(feature.category)
|
switch(feature.category)
|
||||||
|
|
@ -574,7 +579,7 @@ export class CharacterCompiler
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected compile(queue: string[], _buffer: Record<string, PropertySum> = this._buffer, _result: Record<string, any> = this._result)
|
protected compile(queue: string[], _buffer: Record<string, PropertySum> = this._buffer, _result: Record<string, any>)
|
||||||
{
|
{
|
||||||
for(let i = 0; i < queue.length; i++)
|
for(let i = 0; i < queue.length; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -724,7 +729,7 @@ function setProperty<T>(root: any, path: string, value: T | ((old: T) => T), for
|
||||||
if(force || object.hasOwnProperty(arr.slice(-1)[0]!))
|
if(force || object.hasOwnProperty(arr.slice(-1)[0]!))
|
||||||
object[arr.slice(-1)[0]!] = typeof value === 'function' ? (value as (old: T) => T)(object[arr.slice(-1)[0]!]) : value;
|
object[arr.slice(-1)[0]!] = typeof value === 'function' ? (value as (old: T) => T)(object[arr.slice(-1)[0]!]) : value;
|
||||||
}
|
}
|
||||||
export class CharacterBuilder extends CharacterCompiler
|
/* export class CharacterBuilder extends CharacterCompiler
|
||||||
{
|
{
|
||||||
private _container: HTMLElement;
|
private _container: HTMLElement;
|
||||||
private _content?: HTMLElement;
|
private _content?: HTMLElement;
|
||||||
|
|
@ -1421,7 +1426,7 @@ class AspectPicker extends BuilderTab
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
export const masteryTexts: Record<CompiledCharacter['mastery'][number], { text: string, href: string }> = {
|
export const masteryTexts: Record<CompiledCharacter['mastery'][number], { text: string, href: string }> = {
|
||||||
"armor/light": { text: "Armure légère", href: "regles/annexes/equipement#Les armures légères" },
|
"armor/light": { text: "Armure légère", href: "regles/annexes/equipement#Les armures légères" },
|
||||||
|
|
@ -1522,105 +1527,112 @@ export const stateFactory = (item: ItemConfig) => {
|
||||||
export class CharacterSheet
|
export class CharacterSheet
|
||||||
{
|
{
|
||||||
private user: ComputedRef<User | null>;
|
private user: ComputedRef<User | null>;
|
||||||
private character?: CharacterCompiler;
|
private compiler: CharacterCompiler = reactive(new CharacterCompiler());
|
||||||
container: HTMLElement = div('flex flex-1 h-full w-full items-start justify-center');
|
container: HTMLElement = div('flex flex-1 h-full w-full items-start justify-between');
|
||||||
|
editingDOM: HTMLElement = div();
|
||||||
|
readingDOM: HTMLElement = div();
|
||||||
|
private state: { exists: boolean, edit: boolean };
|
||||||
private tabs?: HTMLElement;
|
private tabs?: HTMLElement;
|
||||||
private tab: string = localStorage.getItem('character-tab') ?? 'actions';
|
private tab: string = localStorage.getItem('character-tab') ?? 'actions';
|
||||||
|
|
||||||
ws?: Socket;
|
ws?: Socket;
|
||||||
constructor(id: string, user: ComputedRef<User | null>)
|
constructor(id: string, user: ComputedRef<User | null>, editing: boolean = false)
|
||||||
{
|
{
|
||||||
this.user = user;
|
this.user = user;
|
||||||
const load = div("flex justify-center items-center w-full h-full", [ loading('large') ]);
|
const load = div("flex justify-center items-center w-full h-full", [ loading('large') ]);
|
||||||
this.container.replaceChildren(load);
|
this.container.replaceChildren(load);
|
||||||
useRequestFetch()(`/api/character/${id}`).then(character => {
|
this.state = reactive({ exists: id !== 'new', edit: id === 'new' || editing });
|
||||||
if(character)
|
|
||||||
{
|
|
||||||
this.character = new CharacterCompiler(reactive(character));
|
|
||||||
|
|
||||||
if(character.campaign)
|
this.render();
|
||||||
|
this.edit();
|
||||||
|
|
||||||
|
if(this.state.exists)
|
||||||
|
{
|
||||||
|
useRequestFetch()(`/api/character/${id}`).then(character => {
|
||||||
|
if(character)
|
||||||
{
|
{
|
||||||
/* this.ws = new Socket(`/ws/campaign/${character.campaign}`, true);
|
this.compiler.character = reactive(character);
|
||||||
|
|
||||||
this.ws.handleMessage('SYNC', () => {
|
if(character.campaign)
|
||||||
useRequestFetch()(`/api/character/${id}`).then(character => {
|
{
|
||||||
if(character)
|
/* this.ws = new Socket(`/ws/campaign/${character.campaign}`, true);
|
||||||
|
|
||||||
|
this.ws.handleMessage('SYNC', () => {
|
||||||
|
useRequestFetch()(`/api/character/${id}`).then(character => {
|
||||||
|
if(character)
|
||||||
|
{
|
||||||
|
this.character!.character = reactive(character);
|
||||||
|
this.character!.values;
|
||||||
|
'update' in this.container! && this.container!.update!(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
this.ws.handleMessage<{ action: 'set' | 'add' | 'remove', key: keyof CharacterVariables, value: any }>('VARIABLE', (variable) => {
|
||||||
|
const prop = this.character!.character.variables[variable.key];
|
||||||
|
if(variable.action === 'set')
|
||||||
|
this.character!.character.variables[variable.key] = variable.value;
|
||||||
|
else if(Array.isArray(prop))
|
||||||
{
|
{
|
||||||
this.character!.character = reactive(character);
|
if(variable.action === 'add')
|
||||||
this.character!.values;
|
prop.push(variable.value);
|
||||||
'update' in this.container! && this.container!.update!(true);
|
else if(variable.action === 'remove')
|
||||||
|
{
|
||||||
|
const idx = prop.findIndex(e => deepEquals(e, variable.value));
|
||||||
|
if(idx !== -1) prop.splice(idx, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}) */
|
||||||
})
|
}
|
||||||
this.ws.handleMessage<{ action: 'set' | 'add' | 'remove', key: keyof CharacterVariables, value: any }>('VARIABLE', (variable) => {
|
|
||||||
const prop = this.character!.character.variables[variable.key];
|
document.title = `d[any] - ${character.name}`;
|
||||||
if(variable.action === 'set')
|
load.remove();
|
||||||
this.character!.character.variables[variable.key] = variable.value;
|
|
||||||
else if(Array.isArray(prop))
|
this.container.replaceChildren(this.editingDOM, this.readingDOM);
|
||||||
{
|
|
||||||
if(variable.action === 'add')
|
|
||||||
prop.push(variable.value);
|
|
||||||
else if(variable.action === 'remove')
|
|
||||||
{
|
|
||||||
const idx = prop.findIndex(e => deepEquals(e, variable.value));
|
|
||||||
if(idx !== -1) prop.splice(idx, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) */
|
|
||||||
}
|
}
|
||||||
|
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', 'Personnage introuvable'),
|
||||||
|
span(undefined, 'Ce personnage n\'existe pas ou est privé.'),
|
||||||
|
div('flex flex-row gap-4 justify-center items-center', [
|
||||||
|
button(text('Personnages publics'), () => useRouter().push({ name: 'character-list' }), 'px-2 py-1'),
|
||||||
|
button(text('Créer un personange'), () => useRouter().push({ name: 'character-id-edit', params: { id: 'new' } }), 'px-2 py-1')
|
||||||
|
])
|
||||||
|
]))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.state.edit = true;
|
||||||
|
|
||||||
document.title = `d[any] - ${character.name}`;
|
reactivity(this.state, (state) => {
|
||||||
load.remove();
|
state.edit ? this.container.replaceChildren(this.editingDOM, this.readingDOM) : this.container.replaceChildren(this.readingDOM);
|
||||||
|
})
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
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', 'Personnage introuvable'),
|
|
||||||
span(undefined, 'Ce personnage n\'existe pas ou est privé.'),
|
|
||||||
div('flex flex-row gap-4 justify-center items-center', [
|
|
||||||
button(text('Personnages publics'), () => useRouter().push({ name: 'character-list' }), 'px-2 py-1'),
|
|
||||||
button(text('Créer un personange'), () => useRouter().push({ name: 'character-id-edit', params: { id: 'new' } }), 'px-2 py-1')
|
|
||||||
])
|
|
||||||
]))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
if(!this.character)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const character = this.character.compiled;
|
|
||||||
|
|
||||||
const publicNotes = new MarkdownEditor();
|
const publicNotes = new MarkdownEditor();
|
||||||
const privateNotes = new MarkdownEditor();
|
const privateNotes = new MarkdownEditor();
|
||||||
|
|
||||||
const healthPanel = this.healthPanel(character);
|
const healthPanel = this.healthPanel(this.compiler.compiled);
|
||||||
|
|
||||||
const loadableIcon = icon('radix-icons:paper-plane', { width: 16, height: 16 });
|
const loadableIcon = icon('radix-icons:paper-plane', { width: 16, height: 16 });
|
||||||
const saveLoading = loading('small');
|
const saveLoading = loading('small');
|
||||||
const saveNotes = () => { loadableIcon.replaceWith(saveLoading); this.character?.saveNotes().finally(() => { saveLoading.replaceWith(loadableIcon) }); }
|
const saveNotes = () => { loadableIcon.replaceWith(saveLoading); this.compiler.saveNotes().finally(() => { saveLoading.replaceWith(loadableIcon) }); }
|
||||||
|
|
||||||
publicNotes.onChange = (v) => this.character!.character.notes!.public = v;
|
|
||||||
privateNotes.onChange = (v) => this.character!.character.notes!.private = v;
|
|
||||||
publicNotes.content = this.character!.character.notes!.public!;
|
|
||||||
privateNotes.content = this.character!.character.notes!.private!;
|
|
||||||
|
|
||||||
this.tabs = tabgroup([
|
this.tabs = tabgroup([
|
||||||
{ id: 'actions', title: [ text('Actions') ], content: this.actionsTab(character) },
|
{ id: 'actions', title: [ text('Actions') ], content: () => this.actionsTab(this.compiler.compiled) },
|
||||||
|
|
||||||
{ id: 'abilities', title: [ text('Aptitudes') ], content: this.abilitiesTab(character) },
|
{ id: 'abilities', title: [ text('Aptitudes') ], content: () => this.abilitiesTab(this.compiler.compiled) },
|
||||||
|
|
||||||
{ id: 'spells', title: [ text('Sorts') ], content: this.spellTab(character) },
|
{ id: 'spells', title: [ text('Sorts') ], content: () => this.spellTab(this.compiler.compiled) },
|
||||||
|
|
||||||
{ id: 'inventory', title: [ text('Inventaire') ], content: this.itemsTab(character) },
|
{ id: 'inventory', title: [ text('Inventaire') ], content: () => this.itemsTab(this.compiler.compiled) },
|
||||||
|
|
||||||
{ id: 'aspect', title: [ span(() => ({ 'relative before:absolute before:top-0 before:-right-2 before:w-2 before:h-2 before:rounded-full before:bg-accent-blue': character.variables.transformed }), 'Aspect') ], content: () => this.aspectTab(character) },
|
{ id: 'aspect', title: [ span(() => ({ 'relative before:absolute before:top-0 before:-right-2 before:w-2 before:h-2 before:rounded-full before:bg-accent-blue': this.compiler.compiled?.variables?.transformed ?? false }), 'Aspect') ], content: () => this.aspectTab(this.compiler.compiled) },
|
||||||
|
|
||||||
{ id: 'effects', title: [ text('Afflictions') ], content: () => this.effectsTab(character) },
|
{ id: 'effects', title: [ text('Afflictions') ], content: () => this.effectsTab(this.compiler.compiled) },
|
||||||
|
|
||||||
{ id: 'notes', title: [ text('Notes') ], content: () => [
|
{ id: 'notes', title: [ text('Notes') ], content: () => [
|
||||||
div('flex flex-col h-full divide-y divide-light-30 dark:divide-dark-30', [
|
div('flex flex-col h-full divide-y divide-light-30 dark:divide-dark-30', [
|
||||||
|
|
@ -1634,9 +1646,9 @@ export class CharacterSheet
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
] },
|
] },
|
||||||
], { focused: this.tab, class: { container: 'flex-1 gap-4 px-4 max-w-[960px] h-full', content: 'overflow-auto h-full' }, switch: v => { this.tab = v; localStorage.setItem('character-tab', v); } });
|
], { focused: this.tab, class: { container: 'flex-1 gap-4 px-4 max-w-[960px] h-full', content: 'overflow-auto h-full' }, switch: v => { this.tab = v; localStorage.setItem('this.compiler.compiled-tab', v); } });
|
||||||
|
|
||||||
this.container.replaceChildren(div('flex flex-col justify-start gap-1 h-full w-full', [
|
this.readingDOM = div('flex flex-col justify-start gap-1 h-full w-full min-w-half', [
|
||||||
div("flex flex-row gap-4 justify-between", [
|
div("flex flex-row gap-4 justify-between", [
|
||||||
div(),
|
div(),
|
||||||
|
|
||||||
|
|
@ -1649,107 +1661,107 @@ export class CharacterSheet
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("flex flex-col", [
|
div("flex flex-col", [
|
||||||
dom("span", { class: "text-xl font-bold", text: character.name }),
|
span("text-xl font-bold", () => this.compiler.compiled?.name ?? "Inconnu"),
|
||||||
dom("span", { class: "text-sm", text: `De ${character.username}` })
|
span("text-sm", () => this.compiler.compiled ? `De ${this.compiler.compiled.username}` : '')
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("flex flex-col", [
|
div("flex flex-col", [
|
||||||
dom("span", { class: "font-bold", text: `Niveau ${character.level}` }),
|
span("font-bold", () =>`Niveau ${this.compiler.compiled?.level ?? 0}`),
|
||||||
dom("span", { text: config.peoples[character.race]?.name ?? 'Peuple inconnu' })
|
span('', () => this.compiler.compiled && this.compiler.compiled.race ? config.peoples[this.compiler.compiled.race]?.name ?? 'Peuple inconnu' : '')
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("flex flex-row lg:border-l border-light-35 dark:border-dark-35 py-4 ps-4 gap-8", [
|
div("flex flex-row lg:border-l border-light-35 dark:border-dark-35 py-4 ps-4 gap-8", [
|
||||||
dom("span", { class: "flex flex-row items-center gap-2 text-3xl font-light" }, [
|
div("flex flex-row items-center gap-2 text-3xl font-light", [
|
||||||
text("PV: "),
|
text("PV: "),
|
||||||
dom("span", {
|
() => this.compiler.compiled ? dom("span", {
|
||||||
class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35",
|
class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35",
|
||||||
text: () => `${character.health - character.variables.health}`,
|
text: () => `${this.compiler.compiled.health - this.compiler.compiled.variables.health}`,
|
||||||
listeners: { click: healthPanel.show },
|
listeners: { click: healthPanel.show },
|
||||||
}),
|
}) : undefined,
|
||||||
text('/'),
|
() => this.compiler.compiled ? text('/') : text('-'),
|
||||||
text(() => character.health),
|
() => this.compiler.compiled ? text(() => this.compiler.compiled.health) : undefined,
|
||||||
]),
|
]),
|
||||||
dom("span", { class: "flex flex-row items-center gap-2 text-3xl font-light" }, [
|
div("flex flex-row items-center gap-2 text-3xl font-light", [
|
||||||
text("Mana: "),
|
text("Mana: "),
|
||||||
dom("span", {
|
() => this.compiler.compiled ? dom("span", {
|
||||||
class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35",
|
class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35",
|
||||||
text: () => `${character.mana - character.variables.mana}`,
|
text: () => `${this.compiler.compiled.mana - this.compiler.compiled.variables.mana}`,
|
||||||
listeners: { click: healthPanel.show },
|
listeners: { click: healthPanel.show },
|
||||||
}),
|
}) : undefined,
|
||||||
text('/'),
|
() => this.compiler.compiled ? text('/') : text('-'),
|
||||||
text(() => character.mana),
|
() => this.compiler.compiled ? text(() => this.compiler.compiled.mana) : undefined,
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("self-center", [
|
div("self-center", [
|
||||||
this.user.value && this.user.value.id === character.owner ?
|
this.user.value && this.compiler.compiled && this.user.value.id === this.compiler.compiled.owner ?
|
||||||
button(icon("radix-icons:pencil-2"), () => useRouter().push({ name: 'character-id-edit', params: { id: this.character?.character.id } }), "p-1")
|
() => this.state.edit ? button(icon("ph:floppy-disk"), () => this.saveEdits(), "p-1") : button(icon("radix-icons:pencil-2"), () => this.state.edit = true, "p-1")
|
||||||
: div(),
|
: div(),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("flex flex-row justify-center 2xl:gap-4 gap-2 p-4 border-b border-light-35 dark:border-dark-35", [
|
div("flex flex-row justify-center gap-2 p-4 border-b border-light-35 dark:border-dark-35", [
|
||||||
div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
|
div("flex gap-2 flex-row items-center justify-between", [
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'strength' }] }, [ text(() => `+${character.modifier.strength}`) ]),
|
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'strength' }], () => `+${this.compiler.compiled.modifier.strength}`),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Force" })
|
span("text-sm ", "Force")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'dexterity' }] }, [ text(() => `+${character.modifier.dexterity}`) ]),
|
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'dexterity' }], () => `+${this.compiler.compiled.modifier.dexterity}`),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Dextérité" })
|
span("text-sm ", "Dextérité")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'constitution' }] }, [ text(() => `+${character.modifier.constitution}`) ]),
|
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'constitution' }], () => `+${this.compiler.compiled.modifier.constitution}`),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Constitution" })
|
span("text-sm ", "Constitution")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'intelligence' }] }, [ text(() => `+${character.modifier.intelligence}`) ]),
|
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'intelligence' }], () => `+${this.compiler.compiled.modifier.intelligence}`),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Intelligence" })
|
span("text-sm ", "Intelligence")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'curiosity' }] }, [ text(() => `+${character.modifier.curiosity}`) ]),
|
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'curiosity' }], () => `+${this.compiler.compiled.modifier.curiosity}`),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Curiosité" })
|
span("text-sm ", "Curiosité")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'charisma' }] }, [ text(() => `+${character.modifier.charisma}`) ]),
|
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'charisma' }], () => `+${this.compiler.compiled.modifier.charisma}`),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Charisme" })
|
span("text-sm ", "Charisme")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'psyche' }] }, [ text(() => `+${character.modifier.psyche}`) ]),
|
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'psyche' }], () => `+${this.compiler.compiled.modifier.psyche}`),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Psyché" })
|
span("text-sm ", "Psyché")
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div('border-l border-light-35 dark:border-dark-35'),
|
div('border-l border-light-35 dark:border-dark-35'),
|
||||||
|
|
||||||
div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
|
div("flex gap-2 flex-row items-center justify-between", [
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(() => `+${character.initiative}`) ]),
|
span("text-xl font-bold", () => !this.compiler.compiled ? '-' : `+${this.compiler.compiled.initiative}`),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Initiative" })
|
span("text-sm ", "Initiative")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(() => character.speed === false ? "N/A" : `${character.speed}`) ]),
|
span("text-xl font-bold", () => !this.compiler.compiled ? '-' : this.compiler.compiled.speed === false ? "N/A" : `${this.compiler.compiled.speed}`),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Course" })
|
span("text-sm ", "Course")
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div('border-l border-light-35 dark:border-dark-35'),
|
div('border-l border-light-35 dark:border-dark-35'),
|
||||||
|
|
||||||
div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
|
div("flex gap-2 flex-row items-center justify-between", [
|
||||||
icon("game-icons:checked-shield", { width: 32, height: 32 }),
|
icon("game-icons:checked-shield", { width: 32, height: 32 }),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(() => clamp(character.defense.static + character.defense.passiveparry + character.defense.passivedodge, 0, character.defense.hardcap)) ]),
|
span(" text-xl font-bold", () => !this.compiler.compiled ? '-' : clamp(this.compiler.compiled.defense.static + this.compiler.compiled.defense.passiveparry + this.compiler.compiled.defense.passivedodge, 0, this.compiler.compiled.defense.hardcap)),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Passive" })
|
span("text-sm ", "Passive")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(() => clamp(character.defense.static + character.defense.activeparry + character.defense.passivedodge, 0, character.defense.hardcap)) ]),
|
span(" text-xl font-bold", () => !this.compiler.compiled ? '-' : clamp(this.compiler.compiled.defense.static + this.compiler.compiled.defense.activeparry + this.compiler.compiled.defense.passivedodge, 0, this.compiler.compiled.defense.hardcap)),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Blocage" })
|
span("text-sm ", "Blocage")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(() => clamp(character.defense.static + character.defense.passiveparry + character.defense.activedodge, 0, character.defense.hardcap)) ]),
|
span(" text-xl font-bold", () => !this.compiler.compiled ? '-' : clamp(this.compiler.compiled.defense.static + this.compiler.compiled.defense.passiveparry + this.compiler.compiled.defense.activedodge, 0, this.compiler.compiled.defense.hardcap)),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Esquive" })
|
span("text-sm ", "Esquive")
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
@ -1763,10 +1775,10 @@ export class CharacterSheet
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("grid grid-cols-2 gap-2",
|
div("grid grid-cols-2 gap-2",
|
||||||
Object.keys(character.abilities).map((ability) =>
|
ABILITIES.map((ability) =>
|
||||||
div("flex flex-row px-1 justify-between items-center", [
|
div("flex flex-row px-1 justify-between items-center", [
|
||||||
proses('a', preview, [ span("text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline", abilityTexts[ability as Ability] || ability) ], { href: `regles/l'entrainement/competences#${abilityTexts[ability as Ability]}`, label: abilityTexts[ability as Ability], navigate: false }),
|
proses('a', preview, [ span("text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline", abilityTexts[ability as Ability] || ability) ], { href: `regles/l'entrainement/competences#${abilityTexts[ability as Ability]}`, label: abilityTexts[ability as Ability], navigate: false }),
|
||||||
span("font-bold text-base text-light-100 dark:text-dark-100", text(() => `+${character.abilities[ability as Ability] ?? 0}`)),
|
span("font-bold text-base text-light-100 dark:text-dark-100", () => !this.compiler.compiled ? '-' : `+${this.compiler.compiled.abilities[ability as Ability] ?? 0}`),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
@ -1776,12 +1788,12 @@ export class CharacterSheet
|
||||||
div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50")
|
div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50")
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", { list: character.mastery, render: (e, _c) => proses('a', preview, [ text(masteryTexts[e].text) ], { href: masteryTexts[e].href, label: masteryTexts[e].text, class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline', }) }),
|
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", { list: () => this.compiler.compiled?.mastery ?? [], render: (e, _c) => proses('a', preview, [ text(masteryTexts[e].text) ], { href: masteryTexts[e].href, label: masteryTexts[e].text, class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline', }) }),
|
||||||
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", [
|
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", [
|
||||||
() => character.spellranks.precision > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Précision') ], { href: 'regles/la-magie/magie#Les sorts de précision', label: 'Précision', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.precision)) ]) : undefined,
|
() => (this.compiler.compiled?.spellranks?.precision ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Précision') ], { href: 'regles/la-magie/magie#Les sorts de précision', label: 'Précision', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiler.compiled?.spellranks?.precision ?? 0) ]) : undefined,
|
||||||
() => character.spellranks.knowledge > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Savoir') ], { href: 'regles/la-magie/magie#Les sorts de savoir', label: 'Savoir', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.knowledge)) ]) : undefined,
|
() => (this.compiler.compiled?.spellranks?.knowledge ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Savoir') ], { href: 'regles/la-magie/magie#Les sorts de savoir', label: 'Savoir', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiler.compiled?.spellranks?.knowledge ?? 0) ]) : undefined,
|
||||||
() => character.spellranks.instinct > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Instinct') ], { href: 'regles/la-magie/magie#Les sorts instinctif', label: 'Instinct', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.instinct)) ]) : undefined,
|
() => (this.compiler.compiled?.spellranks?.instinct ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Instinct') ], { href: 'regles/la-magie/magie#Les sorts instinctif', label: 'Instinct', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiler.compiled?.spellranks?.instinct ?? 0) ]) : undefined,
|
||||||
() => character.spellranks.arts > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Oeuvres') ], { href: 'regles/annexes/œuvres', label: 'Oeuvres', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.arts)) ]) : undefined,
|
() => (this.compiler.compiled?.spellranks?.arts ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Oeuvres') ], { href: 'regles/annexes/œuvres', label: 'Oeuvres', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiler.compiled?.spellranks?.arts ?? 0) ]) : undefined,
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
@ -1790,9 +1802,91 @@ export class CharacterSheet
|
||||||
|
|
||||||
this.tabs,
|
this.tabs,
|
||||||
])
|
])
|
||||||
]));
|
]);
|
||||||
|
|
||||||
|
reactivity(this.compiler.character, (c) => {
|
||||||
|
if(c)
|
||||||
|
{
|
||||||
|
c.notes ??= { public: '', private: '' };
|
||||||
|
|
||||||
|
publicNotes.onChange = (v) => c.notes!.public = v;
|
||||||
|
privateNotes.onChange = (v) => c.notes!.private = v;
|
||||||
|
|
||||||
|
publicNotes.content = c.notes!.public!;
|
||||||
|
privateNotes.content = c.notes!.private!;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
healthPanel(character: CompiledCharacter)
|
edit()
|
||||||
|
{
|
||||||
|
const editingData = reactive({
|
||||||
|
1: ["Peuple", "Entrainement", "Compétences", "Aspect", "Spécialisations"],
|
||||||
|
2: ["Entrainement"],
|
||||||
|
3: ["Entrainement"],
|
||||||
|
4: ["Entrainement"],
|
||||||
|
5: ["Entrainement"],
|
||||||
|
6: ["Entrainement"],
|
||||||
|
7: ["Entrainement"],
|
||||||
|
8: ["Entrainement"],
|
||||||
|
9: ["Entrainement"],
|
||||||
|
10: ["Entrainement"],
|
||||||
|
11: ["Entrainement"],
|
||||||
|
12: ["Entrainement"],
|
||||||
|
13: ["Entrainement"],
|
||||||
|
14: ["Entrainement"],
|
||||||
|
15: ["Entrainement"],
|
||||||
|
16: ["Entrainement"],
|
||||||
|
17: ["Entrainement"],
|
||||||
|
18: ["Entrainement"],
|
||||||
|
19: ["Entrainement"],
|
||||||
|
20: ["Entrainement"],
|
||||||
|
})
|
||||||
|
this.editingDOM = div('flex flex-1 max-w-full flex-col align-center relative', [
|
||||||
|
div('h-full w-0 border-l-2 border-light-35 dark:border-dark-35 absolute z-0 left-[89px]'),
|
||||||
|
...Array(20).fill(0).map((_, i) => div(() => ['flex flex-row gap-4', { 'opacity-30': (this.compiler.character?.level ?? 0) < (i + 1) }], [
|
||||||
|
div('flex flex-row gap-2 justify-end items-start w-24 py-4', [
|
||||||
|
div('flex flex-row items-center gap-2', [ span('italic text-xs tracking-tight text-end', `Niveau ${i + 1}`), span('w-3 h-3 rounded-full items-center justify-center bg-light-50 dark:bg-dark-50 z-10') ]),
|
||||||
|
]),
|
||||||
|
div('flex flex-col px-2 items-center justify-center divide-y divide-light-35 dark:divide-dark-35', { list: [], render: (e, _c) => _c ?? span('flex px-2 w-full py-2 items-center justify-center cursor-pointer hover:bg-light-20 dark:hover:bg-dark-20', e) }),
|
||||||
|
]))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
async saveEdits()
|
||||||
|
{
|
||||||
|
if(!this.compiler.character)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(!this.state.exists)
|
||||||
|
{
|
||||||
|
const result = await useRequestFetch()(`/api/character`, {
|
||||||
|
method: 'post',
|
||||||
|
body: this.compiler.character,
|
||||||
|
onResponseError: (e) => {
|
||||||
|
Toaster.add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', closeable: true, duration: 25000, timer: true });
|
||||||
|
this.compiler.character!.id = -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(result !== undefined) this.compiler.character.id = this.compiler.compiled.id = result as number;
|
||||||
|
|
||||||
|
Toaster.add({ content: 'Personnage créé', type: 'success', duration: 25000, timer: true });
|
||||||
|
useRouter().push({ name: 'character-id', params: { id: this.compiler.compiled.id } });
|
||||||
|
this.state.edit = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await useRequestFetch()(`/api/character/${this.compiler!.character.id}`, {
|
||||||
|
method: 'post',
|
||||||
|
body: this.compiler!.character,
|
||||||
|
onResponseError: (e) => {
|
||||||
|
Toaster.add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', closeable: true, duration: 25000, timer: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Toaster.add({ content: 'Personnage enregistré', type: 'success', duration: 25000, timer: true });
|
||||||
|
this.state.edit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
healthPanel(character: CompiledCharacter | undefined)
|
||||||
{
|
{
|
||||||
const inputs = reactive({
|
const inputs = reactive({
|
||||||
health: {
|
health: {
|
||||||
|
|
@ -1808,10 +1902,10 @@ export class CharacterSheet
|
||||||
},
|
},
|
||||||
mana: 0,
|
mana: 0,
|
||||||
});
|
});
|
||||||
const armor = this.character?.armor;
|
const armor = this.compiler.armor;
|
||||||
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-[480px] flex flex-col gap-4 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]", [
|
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-[480px] 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', [
|
div('flex flex-row justify-between items-center', [
|
||||||
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Edititon de vie'), div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (character.health - character.variables.health)), text('/'), text(() => character.health) ]) ]),
|
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Edititon de vie'), () => !character ? span('text-xl font-bold', '-') : div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (character.health - character.variables.health)), text('/'), text(() => character.health) ]) ]),
|
||||||
tooltip(button(icon("radix-icons:cross-1", { width: 24, height: 24 }), () => {
|
tooltip(button(icon("radix-icons:cross-1", { width: 24, height: 24 }), () => {
|
||||||
setTimeout(blocker.close, 150);
|
setTimeout(blocker.close, 150);
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
|
|
@ -1823,39 +1917,39 @@ export class CharacterSheet
|
||||||
])))
|
])))
|
||||||
], [
|
], [
|
||||||
div('flex flex-row justify-between items-center', [
|
div('flex flex-row justify-between items-center', [
|
||||||
span('text-lg', 'Total'), div('flex flew-row gap-4 justify-end', [
|
() => !character ? undefined : span('text-lg', 'Total'), div('flex flew-row gap-4 justify-end', [
|
||||||
() => armor ? tooltip(button(div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${armor.current}/${armor.max} (${[armor.flat > 0 ? '-' + armor.flat : undefined, armor.percent > 0 ? armor.percent + '%' : undefined].filter(e => !!e).join('/')})`) ]), () => {
|
() => armor ? tooltip(button(div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${armor.current}/${armor.max} (${[armor.flat > 0 ? '-' + armor.flat : undefined, armor.percent > 0 ? armor.percent + '%' : undefined].filter(e => !!e).join('/')})`) ]), () => {
|
||||||
//TODO
|
//TODO
|
||||||
}, 'px-2 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left') : undefined,
|
}, 'px-2 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left') : undefined,
|
||||||
tooltip(button(icon('radix-icons:minus', { width: 16, height: 16 }), () => {
|
tooltip(button(icon('radix-icons:minus', { width: 16, height: 16 }), () => {
|
||||||
character.variables.health += inputs.health.sum;
|
character!.variables.health += inputs.health.sum;
|
||||||
inputs.health.sum = 0;
|
inputs.health.sum = 0;
|
||||||
DAMAGE_TYPES.forEach(e => inputs.health[e] = 0);
|
DAMAGE_TYPES.forEach(e => inputs.health[e] = 0);
|
||||||
this.character?.saveVariables();
|
this.compiler.saveVariables();
|
||||||
}, 'w-8 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left'),
|
}, 'w-8 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left'),
|
||||||
numberpicker({ defaultValue: () => inputs.health.sum, input: v => { inputs.health.sum = v }, min: 0, disabled: () => inputs.health.open, class: 'h-8 !m-0' }),
|
numberpicker({ defaultValue: () => inputs.health.sum, input: v => { inputs.health.sum = v }, min: 0, disabled: () => inputs.health.open, class: 'h-8 !m-0' }),
|
||||||
tooltip(button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
tooltip(button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
||||||
character.variables.health = Math.max(character.variables.health - inputs.health.sum, 0);
|
character!.variables.health = Math.max(character!.variables.health - inputs.health.sum, 0);
|
||||||
inputs.health.sum = 0;
|
inputs.health.sum = 0;
|
||||||
DAMAGE_TYPES.forEach(e => inputs.health[e] = 0);
|
DAMAGE_TYPES.forEach(e => inputs.health[e] = 0);
|
||||||
this.character?.saveVariables();
|
this.compiler.saveVariables();
|
||||||
}, 'w-8 h-8 border-light-green dark:border-dark-green hover:border-light-green dark:hover:border-dark-green focus:border-light-green dark:focus:border-dark-green focus:shadow-light-green dark:focus:shadow-dark-green'), 'Soin', 'left'),
|
}, 'w-8 h-8 border-light-green dark:border-dark-green hover:border-light-green dark:hover:border-dark-green focus:border-light-green dark:focus:border-dark-green focus:shadow-light-green dark:focus:shadow-dark-green'), 'Soin', 'left'),
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
], { class: { container: 'gap-2', title: 'ps-2' }, open: false, onFold: v => { inputs.health.open = v; if(v) { inputs.health.sum = 0; }} }),
|
], { class: { container: 'gap-2', title: 'ps-2' }, open: false, onFold: v => { inputs.health.open = v; if(v) { inputs.health.sum = 0; }} }),
|
||||||
div('flex flex-row justify-between items-center', [
|
() => !character ? undefined : div('flex flex-row justify-between items-center', [
|
||||||
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Mana'), div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (character.mana - character.variables.mana)), text('/'), text(() => character.mana) ]) ]),
|
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Mana'), div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (character.mana - character.variables.mana)), text('/'), text(() => character.mana) ]) ]),
|
||||||
div('flex flex-row gap-4 justify-end', [
|
div('flex flex-row gap-4 justify-end', [
|
||||||
tooltip(button(icon('radix-icons:minus', { width: 16, height: 16 }), () => {
|
tooltip(button(icon('radix-icons:minus', { width: 16, height: 16 }), () => {
|
||||||
character.variables.mana += inputs.mana;
|
character.variables.mana += inputs.mana;
|
||||||
inputs.mana = 0;
|
inputs.mana = 0;
|
||||||
this.character?.saveVariables();
|
this.compiler.saveVariables();
|
||||||
}, 'w-8 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left'),
|
}, 'w-8 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left'),
|
||||||
numberpicker({ defaultValue: () => inputs.mana, input: v => { inputs.mana = v }, min: 0, class: 'h-8 !m-0' }),
|
numberpicker({ defaultValue: () => inputs.mana, input: v => { inputs.mana = v }, min: 0, class: 'h-8 !m-0' }),
|
||||||
tooltip(button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
tooltip(button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
||||||
character.variables.mana = Math.max(character.variables.mana - inputs.mana, 0);
|
character.variables.mana = Math.max(character.variables.mana - inputs.mana, 0);
|
||||||
inputs.mana = 0;
|
inputs.mana = 0;
|
||||||
this.character?.saveVariables();
|
this.compiler.saveVariables();
|
||||||
}, 'w-8 h-8 border-light-green dark:border-dark-green hover:border-light-green dark:hover:border-dark-green focus:border-light-green dark:focus:border-dark-green focus:shadow-light-green dark:focus:shadow-dark-green'), 'Soin', 'left'),
|
}, 'w-8 h-8 border-light-green dark:border-dark-green hover:border-light-green dark:hover:border-dark-green focus:border-light-green dark:focus:border-dark-green focus:shadow-light-green dark:focus:shadow-dark-green'), 'Soin', 'left'),
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
@ -1870,8 +1964,10 @@ export class CharacterSheet
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
actionsTab(character: CompiledCharacter)
|
actionsTab()
|
||||||
{
|
{
|
||||||
|
const character = this.compiler.compiled;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
div('flex flex-col gap-8', [
|
div('flex flex-col gap-8', [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
|
|
@ -1886,7 +1982,7 @@ export class CharacterSheet
|
||||||
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.action[e]?.name }), config.action[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.action[e]?.cost?.toString() }), text(`point${config.action[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.action[e]?.name }), config.action[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.action[e]?.cost?.toString() }), text(`point${config.action[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
||||||
markdown(getText(config.action[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.action[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
]), list: character.lists.action }),
|
]), list: () => character ? character.lists.action ?? [] : [] }),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
|
|
@ -1901,7 +1997,7 @@ export class CharacterSheet
|
||||||
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.reaction[e]?.name }), config.reaction[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.reaction[e]?.cost?.toString() }), text(`point${config.reaction[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.reaction[e]?.name }), config.reaction[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.reaction[e]?.cost?.toString() }), text(`point${config.reaction[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
||||||
markdown(getText(config.reaction[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.reaction[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
]), list: character.lists.reaction }),
|
]), list: () => character ? character.lists.reaction ?? [] : [] }),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
|
|
@ -1915,13 +2011,13 @@ export class CharacterSheet
|
||||||
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.freeaction[e]?.name }) ]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.freeaction[e]?.name }) ]),
|
||||||
markdown(getText(config.freeaction[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.freeaction[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
]), list: character.lists.reaction })
|
]), list: () => character ? character.lists.freeaction ?? [] : [] })
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
abilitiesTab(character: CompiledCharacter)
|
abilitiesTab(character: CompiledCharacter | undefined)
|
||||||
{
|
{
|
||||||
return [ div('flex flex-col gap-4', [
|
return [ div('flex flex-col gap-4', [
|
||||||
foldable(() => [div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
foldable(() => [div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
||||||
|
|
@ -1944,7 +2040,7 @@ export class CharacterSheet
|
||||||
], { open: false }),
|
], { open: false }),
|
||||||
]) ];
|
]) ];
|
||||||
}
|
}
|
||||||
spellTab(character: CompiledCharacter)
|
spellTab(character: CompiledCharacter | undefined)
|
||||||
{
|
{
|
||||||
const preference = reactive({
|
const preference = reactive({
|
||||||
sort: localStorage.getItem('character-sort') ?? 'rank-asc',
|
sort: localStorage.getItem('character-sort') ?? 'rank-asc',
|
||||||
|
|
@ -2015,7 +2111,7 @@ export class CharacterSheet
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
spellPanel(character: CompiledCharacter)
|
spellPanel(character: CompiledCharacter | undefined)
|
||||||
{
|
{
|
||||||
const spells = character.variables.spells;
|
const spells = character.variables.spells;
|
||||||
const filters = reactive<{ tag: Array<string>, rank: Array<SpellConfig['rank']>, type: Array<SpellConfig['type']>, element: Array<SpellConfig['elements'][number]>, cost: { min: number, max: number }, range: Array<SpellConfig['range']>, speed: Array<SpellConfig['speed']> }>({
|
const filters = reactive<{ tag: Array<string>, rank: Array<SpellConfig['rank']>, type: Array<SpellConfig['type']>, element: Array<SpellConfig['elements'][number]>, cost: { min: number, max: number }, range: Array<SpellConfig['range']>, speed: Array<SpellConfig['speed']> }>({
|
||||||
|
|
@ -2086,7 +2182,7 @@ export class CharacterSheet
|
||||||
if(idx !== -1) spells.splice(idx, 1);
|
if(idx !== -1) spells.splice(idx, 1);
|
||||||
else spells.push(spell.id);
|
else spells.push(spell.id);
|
||||||
|
|
||||||
this.character?.saveVariables();
|
this.compiler.saveVariables();
|
||||||
}, "px-2 py-1 text-sm font-normal"),
|
}, "px-2 py-1 text-sm font-normal"),
|
||||||
]),
|
]),
|
||||||
]) ], { open: false, class: { container: "px-2 flex flex-col border-light-35 dark:border-dark-35", content: 'py-2' } })
|
]) ], { open: false, class: { container: "px-2 flex flex-col border-light-35 dark:border-dark-35", content: 'py-2' } })
|
||||||
|
|
@ -2102,7 +2198,7 @@ export class CharacterSheet
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
itemsTab(character: CompiledCharacter)
|
itemsTab(character: CompiledCharacter | undefined)
|
||||||
{
|
{
|
||||||
const items = character.variables.items;
|
const items = character.variables.items;
|
||||||
const panel = this.itemsPanel(character);
|
const panel = this.itemsPanel(character);
|
||||||
|
|
@ -2110,7 +2206,7 @@ export class CharacterSheet
|
||||||
|
|
||||||
const money = {
|
const money = {
|
||||||
readonly: dom('div', { listeners: { click: () => { money.readonly.replaceWith(money.edit); money.edit.focus(); } }, class: 'cursor-pointer border border-transparent hover:border-light-40 dark:hover:border-dark-40 px-2 py-px flex flex-row gap-1 items-center' }, [ span('text-lg font-bold', () => character.variables.money.toLocaleString(undefined, { useGrouping: true })), icon('ph:coin', { width: 16, height: 16 }) ]),
|
readonly: dom('div', { listeners: { click: () => { money.readonly.replaceWith(money.edit); money.edit.focus(); } }, class: 'cursor-pointer border border-transparent hover:border-light-40 dark:hover:border-dark-40 px-2 py-px flex flex-row gap-1 items-center' }, [ span('text-lg font-bold', () => character.variables.money.toLocaleString(undefined, { useGrouping: true })), icon('ph:coin', { width: 16, height: 16 }) ]),
|
||||||
edit: numberpicker({ defaultValue: character.variables.money, change: v => { character.variables.money = v; this.character?.saveVariables(); money.edit.replaceWith(money.readonly); }, blur: v => { character.variables.money = v; this.character?.saveVariables(); money.edit.replaceWith(money.readonly); }, min: 0, class: 'w-24' }),
|
edit: numberpicker({ defaultValue: character.variables.money, change: v => { character.variables.money = v; this.compiler.saveVariables(); money.edit.replaceWith(money.readonly); }, blur: v => { character.variables.money = v; this.compiler.saveVariables(); money.edit.replaceWith(money.readonly); }, min: 0, class: 'w-24' }),
|
||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
@ -2120,8 +2216,8 @@ export class CharacterSheet
|
||||||
div('flex flex-row gap-1 items-center', [ span('italic text-sm', 'Argent'), money.readonly ]),
|
div('flex flex-row gap-1 items-center', [ span('italic text-sm', 'Argent'), money.readonly ]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-row justify-end items-center gap-8', [
|
div('flex flex-row justify-end items-center gap-8', [
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character!.power > character.itempower }], text: () => `Puissance magique: ${this.character!.power}/${character.itempower}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.compiler!.power > character.itempower }], text: () => `Puissance magique: ${this.compiler!.power}/${character.itempower}` }),
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character!.weight > (character.capacity === false ? 0 : character.capacity) }], text: () => `Poids total: ${this.character!.weight}/${character.capacity}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.compiler!.weight > (character.capacity === false ? 0 : character.capacity) }], text: () => `Poids total: ${this.compiler!.weight}/${character.capacity}` }),
|
||||||
button(text('Modifier'), () => panel.show(), 'py-1 px-4'),
|
button(text('Modifier'), () => panel.show(), 'py-1 px-4'),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
@ -2140,7 +2236,7 @@ export class CharacterSheet
|
||||||
markdown(getText(item.description)),
|
markdown(getText(item.description)),
|
||||||
div('flex flex-row gap-1', { list: () => e.enchantments!.map(e => config.enchantments[e]).filter(e => !!e), render: (e, _c) => _c ?? floater(div(() => ['flex flex-row gap-2 border px-2 rounded-full py-px !bg-opacity-20', { 'border-accent-blue bg-accent-blue': !e.cursed, 'border-light-purple bg-light-purple dark:border-dark-purple dark:bg-dark-purple': e.cursed }], [ span('text-sm font-semibold tracking-tight', e.name), div('flex flex-row gap-1 items-center', [icon('game-icons:bolt-drop', { width: 12, height: 12 }), span('text-sm font-light', e.power)]) ]), () => [markdown(getText(e.description), undefined, { tags: { a: preview } })], { class: 'max-w-96 max-h-48 p-2', position: "right" }) }),
|
div('flex flex-row gap-1', { list: () => e.enchantments!.map(e => config.enchantments[e]).filter(e => !!e), render: (e, _c) => _c ?? floater(div(() => ['flex flex-row gap-2 border px-2 rounded-full py-px !bg-opacity-20', { 'border-accent-blue bg-accent-blue': !e.cursed, 'border-light-purple bg-light-purple dark:border-dark-purple dark:bg-dark-purple': e.cursed }], [ span('text-sm font-semibold tracking-tight', e.name), div('flex flex-row gap-1 items-center', [icon('game-icons:bolt-drop', { width: 12, height: 12 }), span('text-sm font-light', e.power)]) ]), () => [markdown(getText(e.description), undefined, { tags: { a: preview } })], { class: 'max-w-96 max-h-48 p-2', position: "right" }) }),
|
||||||
div('flex flex-row justify-center gap-1', [
|
div('flex flex-row justify-center gap-1', [
|
||||||
this.character?.character.campaign ? button(text('Partager'), () => {
|
this.compiler.character?.campaign ? button(text('Partager'), () => {
|
||||||
|
|
||||||
}, 'px-2 text-sm h-5 box-content') : undefined,
|
}, 'px-2 text-sm h-5 box-content') : undefined,
|
||||||
button(icon(() => e.amount === 1 ? 'radix-icons:trash' : 'radix-icons:minus', { width: 12, height: 12 }), () => {
|
button(icon(() => e.amount === 1 ? 'radix-icons:trash' : 'radix-icons:minus', { width: 12, height: 12 }), () => {
|
||||||
|
|
@ -2150,7 +2246,7 @@ export class CharacterSheet
|
||||||
items[idx]!.amount--;
|
items[idx]!.amount--;
|
||||||
if(items[idx]!.amount <= 0) items.splice(idx, 1);
|
if(items[idx]!.amount <= 0) items.splice(idx, 1);
|
||||||
|
|
||||||
this.character?.saveVariables();
|
this.compiler.saveVariables();
|
||||||
}, 'p-1'),
|
}, 'p-1'),
|
||||||
button(icon('radix-icons:plus', { width: 12, height: 12 }), () => {
|
button(icon('radix-icons:plus', { width: 12, height: 12 }), () => {
|
||||||
const idx = items.findIndex(_e => _e === e);
|
const idx = items.findIndex(_e => _e === e);
|
||||||
|
|
@ -2160,7 +2256,7 @@ export class CharacterSheet
|
||||||
else if(items.find(_e => _e === e)) items.find(_e => _e === e)!.amount++;
|
else if(items.find(_e => _e === e)) items.find(_e => _e === e)!.amount++;
|
||||||
else items.push(stateFactory(item));
|
else items.push(stateFactory(item));
|
||||||
|
|
||||||
this.character?.saveVariables();
|
this.compiler.saveVariables();
|
||||||
}, 'p-1'),
|
}, 'p-1'),
|
||||||
() => !item.capacity ? undefined : button(text("Enchanter"), () => {
|
() => !item.capacity ? undefined : button(text("Enchanter"), () => {
|
||||||
enchant.show(e);
|
enchant.show(e);
|
||||||
|
|
@ -2170,7 +2266,7 @@ export class CharacterSheet
|
||||||
div('flex flex-row items-center gap-y-1 gap-x-4 flex-wrap', [
|
div('flex flex-row items-center gap-y-1 gap-x-4 flex-wrap', [
|
||||||
item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
||||||
e.equipped = v;
|
e.equipped = v;
|
||||||
this.character?.enchant(e);
|
this.compiler.enchant(e);
|
||||||
}, class: { container: '!w-5 !h-5' } }) : undefined,
|
}, class: { container: '!w-5 !h-5' } }) : undefined,
|
||||||
div('flex flex-row items-center gap-4', [ span([colorByRarity[item.rarity], 'text-lg'], item.name), div('flex flex-row gap-2 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(item).map(e => span('', e))) ]),
|
div('flex flex-row items-center gap-4', [ span([colorByRarity[item.rarity], 'text-lg'], item.name), div('flex flex-row gap-2 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(item).map(e => span('', e))) ]),
|
||||||
item.category === 'armor' ? div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${item.health + ((e.state as ArmorState)?.health ?? 0) - ((e.state as ArmorState)?.loss ?? 0)}/${item.health + ((e.state as ArmorState)?.health ?? 0)} (${[item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0) > 0 ? '-' + (item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0)) : undefined, item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0) > 0 ? '-' + (item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0)) + '%' : undefined].filter(e => !!e).join('/')})`) ]) :
|
item.category === 'armor' ? div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${item.health + ((e.state as ArmorState)?.health ?? 0) - ((e.state as ArmorState)?.loss ?? 0)}/${item.health + ((e.state as ArmorState)?.health ?? 0)} (${[item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0) > 0 ? '-' + (item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0)) : undefined, item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0) > 0 ? '-' + (item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0)) + '%' : undefined].filter(e => !!e).join('/')})`) ]) :
|
||||||
|
|
@ -2189,7 +2285,7 @@ export class CharacterSheet
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
itemsPanel(character: CompiledCharacter)
|
itemsPanel(character: CompiledCharacter | undefined)
|
||||||
{
|
{
|
||||||
const filters: { category: Category[], rarity: Rarity[], name: string, power: { min: number, max: number } } = reactive({
|
const filters: { category: Category[], rarity: Rarity[], name: string, power: { min: number, max: number } } = reactive({
|
||||||
category: [],
|
category: [],
|
||||||
|
|
@ -2202,7 +2298,7 @@ export class CharacterSheet
|
||||||
div("flex flex-row justify-between items-center mb-4", [
|
div("flex flex-row justify-between items-center mb-4", [
|
||||||
dom("h2", { class: "text-xl font-bold", text: "Gestion de l'inventaire" }),
|
dom("h2", { class: "text-xl font-bold", text: "Gestion de l'inventaire" }),
|
||||||
div('flex flex-row gap-8 items-center justify-end', [
|
div('flex flex-row gap-8 items-center justify-end', [
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character!.weight > (character.capacity === false ? 0 : character.capacity) }], text: () => `Poids total: ${this.character!.weight}/${character.capacity}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.compiler!.weight > (character.capacity === false ? 0 : character.capacity) }], text: () => `Poids total: ${this.compiler!.weight}/${character.capacity}` }),
|
||||||
tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
|
tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
|
||||||
setTimeout(blocker.close, 150);
|
setTimeout(blocker.close, 150);
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
|
|
@ -2235,12 +2331,12 @@ export class CharacterSheet
|
||||||
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.charge ? `${item.charge}` : '-') ]),
|
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.charge ? `${item.charge}` : '-') ]),
|
||||||
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('ph:coin', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.price ? `${item.price}` : '-') ]),
|
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('ph:coin', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.price ? `${item.price}` : '-') ]),
|
||||||
button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
||||||
const list = this.character!.character.variables.items;
|
const list = this.compiler.character!.variables.items;
|
||||||
if(item.equippable) list.push(stateFactory(item));
|
if(item.equippable) list.push(stateFactory(item));
|
||||||
else if(list.find(e => e.id === item.id)) list.find(e => e.id === item.id)!.amount++;
|
else if(list.find(e => e.id === item.id)) list.find(e => e.id === item.id)!.amount++;
|
||||||
else list.push(stateFactory(item));
|
else list.push(stateFactory(item));
|
||||||
|
|
||||||
this.character?.saveVariables();
|
this.compiler.saveVariables();
|
||||||
}, 'p-1 !border-solid !border-r'),
|
}, 'p-1 !border-solid !border-r'),
|
||||||
]),
|
]),
|
||||||
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } })
|
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } })
|
||||||
|
|
@ -2256,7 +2352,7 @@ export class CharacterSheet
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
enchantPanel(character: CompiledCharacter)
|
enchantPanel(character: CompiledCharacter | undefined)
|
||||||
{
|
{
|
||||||
const current = reactive({
|
const current = reactive({
|
||||||
item: undefined as ItemState | undefined,
|
item: undefined as ItemState | undefined,
|
||||||
|
|
@ -2284,7 +2380,7 @@ export class CharacterSheet
|
||||||
dom("h2", { class: "text-xl font-bold", text: "Enchantements" }),
|
dom("h2", { class: "text-xl font-bold", text: "Enchantements" }),
|
||||||
div('flex flex-row gap-8 items-center justify-end', [
|
div('flex flex-row gap-8 items-center justify-end', [
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': current.item && config.items[current.item.id] !== undefined ? itempower() > (config.items[current.item.id]!.capacity ?? 0) : false }], text: () => `Puissance de l'objet: ${current.item && config.items[current.item.id] !== undefined ? itempower() : false}/${current.item ? (config.items[current.item.id]!.capacity ?? 0) : 0}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': current.item && config.items[current.item.id] !== undefined ? itempower() > (config.items[current.item.id]!.capacity ?? 0) : false }], text: () => `Puissance de l'objet: ${current.item && config.items[current.item.id] !== undefined ? itempower() : false}/${current.item ? (config.items[current.item.id]!.capacity ?? 0) : 0}` }),
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character!.power > character!.itempower }], text: () => `Puissance du personnage: ${this.character!.power}/${character.itempower}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.compiler!.power > character!.itempower }], text: () => `Puissance du personnage: ${this.compiler!.power}/${character.itempower}` }),
|
||||||
tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
|
tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
|
||||||
setTimeout(blocker.close, 150);
|
setTimeout(blocker.close, 150);
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
|
|
@ -2304,7 +2400,7 @@ export class CharacterSheet
|
||||||
else
|
else
|
||||||
current.item!.enchantments?.splice(idx, 1);
|
current.item!.enchantments?.splice(idx, 1);
|
||||||
|
|
||||||
this.character?.enchant(current.item!);
|
this.compiler.enchant(current.item!);
|
||||||
}, 'p-1 !border-solid !border-r'),
|
}, 'p-1 !border-solid !border-r'),
|
||||||
]),
|
]),
|
||||||
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } })
|
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } })
|
||||||
|
|
@ -2321,7 +2417,7 @@ export class CharacterSheet
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
aspectTab(character: CompiledCharacter)
|
aspectTab(character: CompiledCharacter | undefined)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
|
|
@ -2337,7 +2433,7 @@ export class CharacterSheet
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
effectsTab(character: CompiledCharacter)
|
effectsTab(character: CompiledCharacter | undefined)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { RouteLocationAsRelativeTyped, RouteLocationRaw, RouteMapGeneric } from "vue-router";
|
import type { RouteLocationAsRelativeTyped, RouteLocationRaw, RouteMapGeneric } from "vue-router";
|
||||||
import { type NodeProperties, type Class, type NodeChildren, dom, mergeClasses, text, div, icon, type Node } from "#shared/dom";
|
import { type NodeProperties, type Class, type NodeChildren, dom, mergeClasses, text, div, icon, type Node, span } from "#shared/dom";
|
||||||
import { contextmenu, followermenu, minimizeBox, popper, teleport, tooltip, type FloatState } from "#shared/floating";
|
import { contextmenu, followermenu, minimizeBox, popper, teleport, tooltip, type FloatState } from "#shared/floating";
|
||||||
import { clamp } from "#shared/general";
|
import { clamp, deepEquals, shallowEquals } from "#shared/general";
|
||||||
import { Tree } from "#shared/tree";
|
import { Tree } from "#shared/tree";
|
||||||
import type { Placement } from "@floating-ui/dom";
|
import type { Placement } from "@floating-ui/dom";
|
||||||
import { reactivity, type Reactive } from '#shared/reactive';
|
import { reactivity, type Reactive } from '#shared/reactive';
|
||||||
|
|
@ -10,7 +10,7 @@ export function link(children: NodeChildren, properties?: NodeProperties & { act
|
||||||
{
|
{
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const nav = link ? router.resolve(link) : undefined;
|
const nav = link ? router.resolve(link) : undefined;
|
||||||
return dom('a', { ...properties, class: [properties?.class, properties?.active && router.currentRoute.value.fullPath === nav?.fullPath ? properties.active : undefined], attributes: { href: nav?.href, 'data-active': properties?.active ? mergeClasses(properties?.active) : undefined }, listeners: link ? {
|
return dom('a', { ...properties, class: [properties?.class, properties?.active && router.currentRoute.value.fullPath === nav?.fullPath ? properties.active : undefined], attributes: { href: nav?.href, 'data-active': !!properties?.active }, listeners: link ? {
|
||||||
click: function(e)
|
click: function(e)
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -83,12 +83,10 @@ export function optionmenu(options: Array<{ title: string, click: () => void }>,
|
||||||
}
|
}
|
||||||
export type Option<T> = { text: string, render?: () => HTMLElement, value: T | Option<T>[] } | undefined;
|
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>> };
|
type StoredOption<T> = { item: Option<T>, dom: HTMLElement, container?: HTMLElement, children?: Array<StoredOption<T>> };
|
||||||
export function select<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLElement
|
export function select<T extends NonNullable<any>>(options: Reactive<Array<{ text: string, value: T } | undefined>>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLDivElement & { disabled: boolean, value: T | undefined }
|
||||||
{
|
{
|
||||||
let context: { close: Function };
|
let context: { close: Function }, _options: Array<{ text: string, value: T }> = [], optionElements: HTMLElement[] = [];
|
||||||
let focused: number | undefined;
|
let focused: number | undefined, value: T | undefined, valueText: Text = text(''), disabled: boolean, change: ((v: T) => void) | undefined = undefined;
|
||||||
|
|
||||||
options = options.filter(e => !!e);
|
|
||||||
|
|
||||||
const focus = (i?: number) => {
|
const focus = (i?: number) => {
|
||||||
focused !== undefined && optionElements[focused]?.toggleAttribute('data-focused', false);
|
focused !== undefined && optionElements[focused]?.toggleAttribute('data-focused', false);
|
||||||
|
|
@ -96,51 +94,36 @@ export function select<T extends NonNullable<any>>(options: Array<{ text: string
|
||||||
focused = i;
|
focused = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
let disabled = settings?.disabled ?? false;
|
const handleKeys = (e: KeyboardEvent) => {
|
||||||
const textValue = text(options.find(e => Array.isArray(e) ? false : e?.value === settings?.defaultValue)?.text ?? '');
|
switch(e.key.toLocaleLowerCase())
|
||||||
const optionElements = options.map((e, i) => {
|
{
|
||||||
if(e === undefined)
|
case 'arrowdown':
|
||||||
return;
|
focus(clamp((focused ?? -1) + 1, 0, _options.length - 1));
|
||||||
|
return;
|
||||||
return dom('div', { listeners: { click: (_e) => {
|
case 'arrowup':
|
||||||
textValue.textContent = e.text;
|
focus(clamp((focused ?? 1) - 1, 0, _options.length - 1));
|
||||||
settings?.change && settings?.change(e.value);
|
return;
|
||||||
context && context.close && !_e.ctrlKey && context.close();
|
case 'pageup':
|
||||||
}, mouseenter: (e) => focus(i) }, class: ['data-[focused]:bg-light-30 dark:data-[focused]:bg-dark-30 text-light-70 dark:text-dark-70 data-[focused]:text-light-100 dark:data-[focused]:text-dark-100 py-1 px-2 cursor-pointer', settings?.class?.option] }, [ text(e.text) ]);
|
focus(0);
|
||||||
});
|
return;
|
||||||
const select = dom('div', { listeners: { click: () => {
|
case 'pagedown':
|
||||||
if(disabled)
|
focus(optionElements.length - 1);
|
||||||
return;
|
return;
|
||||||
|
case 'enter':
|
||||||
const handleKeys = (e: KeyboardEvent) => {
|
focused && optionElements[focused]?.click();
|
||||||
switch(e.key.toLocaleLowerCase())
|
return;
|
||||||
{
|
case 'escape':
|
||||||
case 'arrowdown':
|
context?.close();
|
||||||
focus(clamp((focused ?? -1) + 1, 0, options.length - 1));
|
return;
|
||||||
return;
|
default: return;
|
||||||
case 'arrowup':
|
|
||||||
focus(clamp((focused ?? 1) - 1, 0, options.length - 1));
|
|
||||||
return;
|
|
||||||
case 'pageup':
|
|
||||||
focus(0);
|
|
||||||
return;
|
|
||||||
case 'pagedown':
|
|
||||||
focus(optionElements.length - 1);
|
|
||||||
return;
|
|
||||||
case 'enter':
|
|
||||||
focused && optionElements[focused]?.click();
|
|
||||||
return;
|
|
||||||
case 'escape':
|
|
||||||
context?.close();
|
|
||||||
return;
|
|
||||||
default: return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
window.addEventListener('keydown', handleKeys);
|
}
|
||||||
|
const select = dom('div', { listeners: { focus: () => {
|
||||||
|
if(disabled) return;
|
||||||
|
|
||||||
const box = select.getBoundingClientRect();
|
window.addEventListener('keydown', handleKeys);
|
||||||
context = contextmenu(box.x, box.y + box.height, optionElements.filter(e => !!e).length > 0 ? optionElements : [ div('text-light-60 dark:text-dark-60 italic text-center px-2 py-1', [ text('Aucune option') ]) ], { placement: "bottom-start", class: ['flex flex-col max-h-[320px] overflow-auto', settings?.class?.popup], style: { "min-width": `${box.width}px` }, blur: () => window.removeEventListener('keydown', handleKeys) });
|
context = followermenu(select, optionElements.filter(e => !!e).length > 0 ? optionElements : [ div('text-light-60 dark:text-dark-60 italic text-center px-2 py-1', [ text('Aucune option') ]) ], { placement: "bottom-start", class: ['flex flex-col max-h-[320px] overflow-auto', settings?.class?.popup], style: { 'min-width': `${select.clientWidth}px` }, blur: () => window.removeEventListener('keydown', handleKeys) });
|
||||||
} }, class: ['mx-4 inline-flex items-center justify-between px-3 text-sm font-semibold leading-none h-8 gap-1 bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:border-light-25 dark: data-[disabled]:border-dark-25 data-[disabled]:bg-light-20 dark: data-[disabled]:bg-dark-20', settings?.class?.container] }, [ dom('span', {}, [ textValue ]), icon('radix-icons:caret-down') ]);
|
} }, class: ['mx-4 inline-flex items-center justify-between px-3 text-sm font-semibold leading-none outline-none cursor-default h-8 gap-1 bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:border-light-25 dark: data-[disabled]:border-dark-25 data-[disabled]:bg-light-20 dark: data-[disabled]:bg-dark-20', settings?.class?.container], attributes: { tabindex: '0' } }, [ span('', valueText), icon('radix-icons:caret-down') ]) as HTMLDivElement & { value: T | undefined, disabled: boolean };
|
||||||
|
|
||||||
Object.defineProperty(select, 'disabled', {
|
Object.defineProperty(select, 'disabled', {
|
||||||
get: () => disabled,
|
get: () => disabled,
|
||||||
|
|
@ -148,12 +131,131 @@ export function select<T extends NonNullable<any>>(options: Array<{ text: string
|
||||||
disabled = !!v;
|
disabled = !!v;
|
||||||
select.toggleAttribute('data-disabled', disabled);
|
select.toggleAttribute('data-disabled', disabled);
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
Object.defineProperty(select, 'value', {
|
||||||
|
get: () => value,
|
||||||
|
set: (v) => {
|
||||||
|
if(v === value) return;
|
||||||
|
if(v === undefined)
|
||||||
|
{
|
||||||
|
valueText.textContent = '';
|
||||||
|
focus();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const idx = _options.findIndex(e => e?.value === v);
|
||||||
|
if(idx !== -1)
|
||||||
|
{
|
||||||
|
valueText.textContent = _options[idx]?.text ?? '';
|
||||||
|
focus(idx);
|
||||||
|
}
|
||||||
|
else return select.value = undefined;
|
||||||
|
}
|
||||||
|
value = v;
|
||||||
|
change && change(v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reactivity(options, (o) => {
|
||||||
|
_options = o.filter(e => !!e);
|
||||||
|
if(!_options.find(e => e.value === value)) select.value = undefined;
|
||||||
|
optionElements = _options.map((e, i) => dom('div', { listeners: { click: (_e) => { select.value = e.value ; context.close(); }, mouseenter: (e) => focus(i) }, class: ['data-[focused]:bg-light-30 dark:data-[focused]:bg-dark-30 text-light-70 dark:text-dark-70 data-[focused]:text-light-100 dark:data-[focused]:text-dark-100 py-1 px-2 cursor-pointer', settings?.class?.option], text: e.text }));
|
||||||
|
});
|
||||||
|
|
||||||
|
select.disabled = settings?.disabled ?? false;
|
||||||
|
select.value = settings?.defaultValue;
|
||||||
|
|
||||||
|
change = settings?.change;
|
||||||
return select;
|
return select;
|
||||||
}
|
}
|
||||||
export function multiselect<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T[], change?: (value: T[]) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLElement
|
export function multiselect<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T[], change?: (value: T[]) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLDivElement & { disabled: boolean, value: T[] | undefined }
|
||||||
{
|
{
|
||||||
let context: { close: Function };
|
let context: { close: Function }, _options: Array<{ text: string, value: T }> = [], optionElements: HTMLElement[] = [];
|
||||||
|
let focused: number | undefined, value: T[] | undefined, valueText: Text = text(''), disabled: boolean, change: ((v: T[]) => void) | undefined = undefined;
|
||||||
|
|
||||||
|
const focus = (i?: number) => {
|
||||||
|
focused !== undefined && optionElements[focused]?.toggleAttribute('data-focused', false);
|
||||||
|
i !== undefined && optionElements[i]?.toggleAttribute('data-focused', true) && optionElements[i]?.scrollIntoView({ behavior: 'instant', block: 'nearest' });
|
||||||
|
focused = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeys = (e: KeyboardEvent) => {
|
||||||
|
switch(e.key.toLocaleLowerCase())
|
||||||
|
{
|
||||||
|
case 'arrowdown':
|
||||||
|
focus(clamp((focused ?? -1) + 1, 0, _options.length - 1));
|
||||||
|
return;
|
||||||
|
case 'arrowup':
|
||||||
|
focus(clamp((focused ?? 1) - 1, 0, _options.length - 1));
|
||||||
|
return;
|
||||||
|
case 'pageup':
|
||||||
|
focus(0);
|
||||||
|
return;
|
||||||
|
case 'pagedown':
|
||||||
|
focus(optionElements.length - 1);
|
||||||
|
return;
|
||||||
|
case 'enter':
|
||||||
|
focused && optionElements[focused]?.click();
|
||||||
|
return;
|
||||||
|
case 'escape':
|
||||||
|
context?.close();
|
||||||
|
return;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const select = dom('div', { listeners: { focus: () => {
|
||||||
|
if(disabled) return;
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeys);
|
||||||
|
context = followermenu(select, optionElements.filter(e => !!e).length > 0 ? optionElements : [ div('text-light-60 dark:text-dark-60 italic text-center px-2 py-1', [ text('Aucune option') ]) ], { placement: "bottom-start", class: ['flex flex-col max-h-[320px] overflow-auto', settings?.class?.popup], style: { 'min-width': `${select.clientWidth}px` }, blur: () => window.removeEventListener('keydown', handleKeys) });
|
||||||
|
} }, class: ['mx-4 inline-flex items-center justify-between px-3 text-sm font-semibold leading-none outline-none cursor-default h-8 gap-1 bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:border-light-25 dark: data-[disabled]:border-dark-25 data-[disabled]:bg-light-20 dark: data-[disabled]:bg-dark-20', settings?.class?.container], attributes: { tabindex: '0' } }, [ span('', valueText), icon('radix-icons:caret-down') ]) as HTMLDivElement & { value: T[] | undefined, disabled: boolean };
|
||||||
|
|
||||||
|
Object.defineProperty(select, 'disabled', {
|
||||||
|
get: () => disabled,
|
||||||
|
set: (v) => {
|
||||||
|
disabled = !!v;
|
||||||
|
select.toggleAttribute('data-disabled', disabled);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Object.defineProperty(select, 'value', {
|
||||||
|
get: () => value,
|
||||||
|
set: (v) => {
|
||||||
|
if(Array.isArray(v)) v = v.filter(e => _options.find(f => f.value === e));
|
||||||
|
if(shallowEquals(v, value)) return;
|
||||||
|
|
||||||
|
if(v === undefined || (Array.isArray(v) && v.length === 0))
|
||||||
|
{
|
||||||
|
valueText.textContent = '';
|
||||||
|
focus();
|
||||||
|
}
|
||||||
|
else if(Array.isArray(v)) valueText.textContent = `${_options.find(e => e.value === v[0])?.text ?? ''}${v.length > 1 ? ' +' + (v.length - 1) : ''}`;
|
||||||
|
else throw new Error('Invalid value type');
|
||||||
|
|
||||||
|
value = v;
|
||||||
|
change && change(v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reactivity(options, (o) => {
|
||||||
|
_options = o.filter(e => !!e);
|
||||||
|
if(!_options.find(e => e.value === value)) select.value = undefined;
|
||||||
|
optionElements = _options.map((e, i) => dom('div', { listeners: { click: function(_e) {
|
||||||
|
const v = [];
|
||||||
|
if(select.value) v.push(...select.value);
|
||||||
|
const idx = select.value?.indexOf(e.value) ?? -1;
|
||||||
|
idx === -1 ? v.push(e.value) : v.splice(idx, 1);
|
||||||
|
this.toggleAttribute('data-selected', idx === -1);
|
||||||
|
select.value = v;
|
||||||
|
_e.ctrlKey || context.close();
|
||||||
|
}, mouseenter: (e) => focus(i) }, class: ['group flex flex-row justify-between items-center data-[focused]:bg-light-30 dark:data-[focused]:bg-dark-30 text-light-70 dark:text-dark-70 data-[focused]:text-light-100 dark:data-[focused]:text-dark-100 py-1 px-2 cursor-pointer', settings?.class?.option], attributes: { 'data-selected': settings?.defaultValue?.includes(e.value) ?? false } }, [ text(e.text), icon('radix-icons:check', { class: 'hidden group-data-[selected]:block' }) ]));
|
||||||
|
});
|
||||||
|
|
||||||
|
select.disabled = settings?.disabled ?? false;
|
||||||
|
select.value = settings?.defaultValue;
|
||||||
|
|
||||||
|
change = settings?.change;
|
||||||
|
return select;
|
||||||
|
/* let context: { close: Function };
|
||||||
let focused: number | undefined;
|
let focused: number | undefined;
|
||||||
let selection: T[] = settings?.defaultValue ?? [];
|
let selection: T[] = settings?.defaultValue ?? [];
|
||||||
|
|
||||||
|
|
@ -221,7 +323,7 @@ export function multiselect<T extends NonNullable<any>>(options: Array<{ text: s
|
||||||
select.toggleAttribute('data-disabled', disabled);
|
select.toggleAttribute('data-disabled', disabled);
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return select;
|
return select; */
|
||||||
}
|
}
|
||||||
export function combobox<T extends NonNullable<any>>(options: Option<T>[], settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean, fill?: 'contain' | 'cover' })
|
export function combobox<T extends NonNullable<any>>(options: Option<T>[], settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean, fill?: 'contain' | 'cover' })
|
||||||
{
|
{
|
||||||
|
|
@ -483,7 +585,7 @@ export function table(content: TableRow[], headers: TableRow, properties?: { cla
|
||||||
const render = (item: (() => HTMLElement) | HTMLElement | string) => typeof item === 'string' ? text(item) : typeof item === 'function' ? item() : item;
|
const render = (item: (() => HTMLElement) | HTMLElement | string) => typeof item === 'string' ? text(item) : typeof item === 'function' ? item() : item;
|
||||||
return dom('table', { class: ['', properties?.class?.table] }, [ dom('thead', { class: ['', properties?.class?.header] }, [ dom('tr', { class: '' }, Object.values(headers).map(e => dom('th', {}, [ render(e) ]))) ]), dom('tbody', { class: ['', properties?.class?.body] }, content.map(e => dom('tr', { class: ['', properties?.class?.row] }, Object.keys(headers).map(f => e.hasOwnProperty(f) ? dom('td', { class: ['', properties?.class?.cell] }, [ render(e[f]!) ]) : undefined)))) ]);
|
return dom('table', { class: ['', properties?.class?.table] }, [ dom('thead', { class: ['', properties?.class?.header] }, [ dom('tr', { class: '' }, Object.values(headers).map(e => dom('th', {}, [ render(e) ]))) ]), dom('tbody', { class: ['', properties?.class?.body] }, content.map(e => dom('tr', { class: ['', properties?.class?.row] }, Object.keys(headers).map(f => e.hasOwnProperty(f) ? dom('td', { class: ['', properties?.class?.cell] }, [ render(e[f]!) ]) : undefined)))) ]);
|
||||||
}
|
}
|
||||||
export function toggle(settings?: { defaultValue?: boolean, change?: (value: boolean) => void, disabled?: boolean, class?: { container?: Class } })
|
export function toggle(settings?: { defaultValue?: boolean, change?: (value: boolean) => void, disabled?: Reactive<boolean>, class?: { container?: Class } })
|
||||||
{
|
{
|
||||||
let state = settings?.defaultValue ?? false;
|
let state = settings?.defaultValue ?? false;
|
||||||
const element = dom("div", { class: [`group mx-3 w-12 h-6 select-none transition-all border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 outline-none
|
const element = dom("div", { class: [`group mx-3 w-12 h-6 select-none transition-all border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 outline-none
|
||||||
|
|
@ -501,7 +603,7 @@ export function toggle(settings?: { defaultValue?: boolean, change?: (value: boo
|
||||||
}, [ div('block w-[18px] h-[18px] translate-x-[2px] will-change-transform transition-transform bg-light-50 dark:bg-dark-50 group-data-[state=checked]:translate-x-[26px] group-data-[disabled]:bg-light-30 dark:group-data-[disabled]:bg-dark-30 group-data-[disabled]:border-light-30 dark:group-data-[disabled]:border-dark-30') ]);
|
}, [ div('block w-[18px] h-[18px] translate-x-[2px] will-change-transform transition-transform bg-light-50 dark:bg-dark-50 group-data-[state=checked]:translate-x-[26px] group-data-[disabled]:bg-light-30 dark:group-data-[disabled]:bg-dark-30 group-data-[disabled]:border-light-30 dark:group-data-[disabled]:border-dark-30') ]);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
export function checkbox(settings?: { defaultValue?: boolean, change?: (this: HTMLElement, value: boolean) => void, disabled?: boolean, class?: { container?: Class, icon?: Class } })
|
export function checkbox(settings?: { defaultValue?: boolean, change?: (this: HTMLElement, value: boolean) => void, disabled?: Reactive<boolean>, class?: { container?: Class, icon?: Class } })
|
||||||
{
|
{
|
||||||
let state = settings?.defaultValue ?? false;
|
let state = settings?.defaultValue ?? false;
|
||||||
const element = dom("div", { class: [`group w-6 h-6 box-content flex items-center justify-center border border-light-50 dark:border-dark-50 bg-light-20 dark:bg-dark-20
|
const element = dom("div", { class: [`group w-6 h-6 box-content flex items-center justify-center border border-light-50 dark:border-dark-50 bg-light-20 dark:bg-dark-20
|
||||||
|
|
|
||||||
|
|
@ -226,17 +226,20 @@ const iconLoadingRegistry: Map<string, Promise<Required<IconifyIcon>> | null | u
|
||||||
export function icon(name: Reactive<string>, properties?: IconProperties)
|
export function icon(name: Reactive<string>, properties?: IconProperties)
|
||||||
{
|
{
|
||||||
const element = dom('div', { class: properties?.class, style: properties?.style });
|
const element = dom('div', { class: properties?.class, style: properties?.style });
|
||||||
|
let timeout: NodeJS.Timeout = setTimeout(() => {}, 0);
|
||||||
|
|
||||||
const build = (icon: IconifyIcon | null | undefined) => {
|
const build = (icon: IconifyIcon | null | undefined) => {
|
||||||
if(!icon) return element.replaceChildren();
|
if(!icon) return clearTimeout(timeout) ?? element.replaceChildren();
|
||||||
const built = buildIcon(icon, properties);
|
const built = buildIcon(icon, properties);
|
||||||
const dom = svg('svg', { attributes: built.attributes });
|
const dom = svg('svg', { attributes: built.attributes });
|
||||||
dom.innerHTML = built.body;
|
dom.innerHTML = built.body;
|
||||||
|
clearTimeout(timeout);
|
||||||
element.replaceChildren(dom);
|
element.replaceChildren(dom);
|
||||||
}
|
}
|
||||||
reactivity(name, (name) => {
|
reactivity(name, (name) => {
|
||||||
if(!iconLoaded(name))
|
if(!iconLoaded(name))
|
||||||
{
|
{
|
||||||
element.replaceChildren(loading('small'));
|
timeout = setTimeout(() => { element.replaceChildren(loading('small')); }, 100);
|
||||||
if(!iconLoadingRegistry.has(name)) iconLoadingRegistry.set(name, loadIcon(name));
|
if(!iconLoadingRegistry.has(name)) iconLoadingRegistry.set(name, loadIcon(name));
|
||||||
iconLoadingRegistry.get(name)?.then(build);
|
iconLoadingRegistry.get(name)?.then(build);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,15 @@ export class HomebrewBuilder
|
||||||
}
|
}
|
||||||
spells()
|
spells()
|
||||||
{
|
{
|
||||||
|
const filters = reactive<{ tag: Array<string>, rank: Array<SpellConfig['rank']>, type: Array<SpellConfig['type']>, element: Array<SpellConfig['elements'][number]>, cost: { min: number, max: number }, range: Array<SpellConfig['range']>, speed: Array<SpellConfig['speed']> }>({
|
||||||
|
tag: [],
|
||||||
|
type: [],
|
||||||
|
rank: [],
|
||||||
|
element: [],
|
||||||
|
cost: { min: 0, max: Infinity },
|
||||||
|
range: [],
|
||||||
|
speed: [],
|
||||||
|
});
|
||||||
const spellTagTexts = {
|
const spellTagTexts = {
|
||||||
'damage': 'Dégâts',
|
'damage': 'Dégâts',
|
||||||
'buff': 'Buff',
|
'buff': 'Buff',
|
||||||
|
|
@ -231,23 +240,23 @@ export class HomebrewBuilder
|
||||||
markdown(spell.description, undefined, { tags: { a: preview } }),
|
markdown(spell.description, undefined, { tags: { a: preview } }),
|
||||||
], [
|
], [
|
||||||
div('gap-4 px-4 flex', [
|
div('gap-4 px-4 flex', [
|
||||||
input('text', { input: (value) => { spell.name = value }, defaultValue: spell.name, class: '!m-0 w-64' }),
|
|
||||||
div('flex flex-1 flex-row gap-2 items-center', [
|
div('flex flex-1 flex-row gap-2 items-center', [
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(`Rang ${spell.rank}`) ]),
|
span('w-64', spell.name),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(spellTypeTexts[spell.type]) ]),
|
span('flex-1', `Rang ${spell.rank}`),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(`${spell.cost} mana`) ]),
|
span('flex-1', spellTypeTexts[spell.type]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(spell.speed === 'action' ? 'Action' : spell.speed === 'reaction' ? 'Réaction' : `${spell.speed} minutes`) ]),
|
span('flex-1', `${spell.cost} mana`),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(`${elementTexts[spell.elements[0]!].text}${spell.elements.length > 1 ? ` (+${spell.elements.length - 1})` : ''}`) ]),
|
span('flex-1', spell.speed === 'action' ? 'Action' : spell.speed === 'reaction' ? 'Réaction' : `${spell.speed} minutes`),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(spell.range === 'personnal' ? 'Personnel' : spell.range === 0 ? 'Toucher' : `${spell.range} cases`) ]),
|
span('flex-1', spell.elements.length === 0 ? '' : `${elementTexts[spell.elements[0]!].text}${spell.elements.length > 1 ? ` (+${spell.elements.length - 1})` : ''}`),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(`${spell.tags && spell.tags.length > 0 ? spellTagTexts[spell.tags[0]!] : ''}${spell.tags && spell.tags.length > 1 ? ` (+${spell.tags.length - 1})` : ''}`) ]),
|
span('flex-1', spell.range === 'personnal' ? 'Personnel' : spell.range === 0 ? 'Toucher' : `${spell.range} cases`),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text(spell.concentration ? 'Concentration' : '') ]),
|
span('flex-1', `${spell.tags && spell.tags.length > 0 ? spellTagTexts[spell.tags[0]!] : ''}${spell.tags && spell.tags.length > 1 ? ` (+${spell.tags.length - 1})` : ''}`),
|
||||||
|
span('flex-1', spell.concentration ? 'Concentration' : ''),
|
||||||
]),
|
]),
|
||||||
div('flex flex-row justify-center gap-2', [ button(icon('radix-icons:pencil-1'), () => editing.id = spell.id, 'p-1'), button(icon('radix-icons:trash'), () => remove(spell), 'p-1') ])
|
div('flex flex-row justify-center gap-2', [ button(icon('radix-icons:pencil-1'), () => editing.id = spell.id, 'p-1'), button(icon('radix-icons:trash'), () => remove(spell), 'p-1') ])
|
||||||
])
|
])
|
||||||
], { class: { container: 'border-light-35 dark:border-dark-35 py-1', content: 'gap-2 px-4 py-1 flex items-center *:flex-1' }, open: false });
|
], { class: { container: 'border-light-35 dark:border-dark-35 py-1', content: 'gap-2 px-4 py-1 flex items-center *:flex-1' }, open: false });
|
||||||
};
|
};
|
||||||
const edit = (id: string) => {
|
const edit = (id: string) => {
|
||||||
const spell = config.spells[id] ? { ...config.spells[id] } : undefined;
|
const spell = config.spells[id] ? reactive({ ...config.spells[id] }) : undefined;
|
||||||
if(!spell) return;
|
if(!spell) return;
|
||||||
|
|
||||||
MarkdownEditor.singleton.onChange = v => {};
|
MarkdownEditor.singleton.onChange = v => {};
|
||||||
|
|
@ -256,18 +265,18 @@ export class HomebrewBuilder
|
||||||
MarkdownEditor.singleton.dom
|
MarkdownEditor.singleton.dom
|
||||||
], [
|
], [
|
||||||
div('gap-4 px-4 flex', [
|
div('gap-4 px-4 flex', [
|
||||||
input('text', { input: (value) => { spell.name = value }, defaultValue: spell.name, class: '!m-0 w-64' }),
|
div('flex flex-row gap-2 items-center flex-1', [
|
||||||
div('flex flex-row gap-2 items-center', [
|
div('flex flex-col items-start justify-between gap-2 flex-1', [ text('Nom'), input('text', { input: (value) => { spell.name = value }, defaultValue: spell.name, class: '!m-0 w-64' }) ]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Rang'), select([{ text: 'Rang 1', value: 1 }, { text: 'Rang 2', value: 2 }, { text: 'Rang 3', value: 3 }, { text: 'Spécial', value: 4 }], { change: (value: 1 | 2 | 3 | 4) => spell.rank = value, defaultValue: spell.rank, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Rang'), select([{ text: 'Rang 1', value: 1 }, { text: 'Rang 2', value: 2 }, { text: 'Rang 3', value: 3 }, { text: 'Spécial', value: 4 }], { change: (value: 1 | 2 | 3 | 4) => spell.rank = value, defaultValue: spell.rank, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Type'), select(SPELL_TYPES.map(f => ({ text: spellTypeTexts[f], value: f })), { change: (value) => spell.type = value, defaultValue: spell.type, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Type'), select(SPELL_TYPES.map(f => ({ text: spellTypeTexts[f], value: f })), { change: (value) => spell.type = value, defaultValue: spell.type, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Coût'), numberpicker({ defaultValue: spell.cost, input: (value) => spell.cost = value, class: '!m-0 w-full' }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Coût'), numberpicker({ defaultValue: spell.cost, input: (value) => spell.cost = value, class: '!m-0 w-full' }), ]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Incantation'), select<'action' | 'reaction' | number>([{ text: 'Action', value: 'action' }, { text: 'Reaction', value: 'reaction' }, { text: '1 minute', value: 1 }, { text: '10 minutes', value: 10 }], { change: (value) => spell.speed = value, defaultValue: spell.speed, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Incantation'), select<'action' | 'reaction' | number>(() => [{ text: 'Action', value: 'action' }, spell.type === 'instinct' ? { text: 'Reaction', value: 'reaction' } : undefined, { text: '1 minute', value: 1 }, { text: '10 minutes', value: 10 }], { change: (value) => spell.speed = value, defaultValue: spell.speed, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Elements'), multiselect(SPELL_ELEMENTS.map(f => ({ text: elementTexts[f].text, value: f })), { change: (value) => spell.elements = value, defaultValue: spell.elements, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Elements'), multiselect(SPELL_ELEMENTS.map(f => ({ text: elementTexts[f].text, value: f })), { change: (value) => spell.elements = value, defaultValue: spell.elements, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Portée'), select<'personnal' | number>([{ text: 'Toucher', value: 0 }, { text: 'Personnel', value: 'personnal' }, { text: '3 cases', value: 3 }, { text: '6 cases', value: 6 }, { text: '9 cases', value: 9 }, { text: '12 cases', value: 12 }, { text: '18 cases', value: 18 }], { change: (value) => spell.range = value, defaultValue: spell.range, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Portée'), select<'personnal' | number>([{ text: 'Toucher', value: 0 }, { text: 'Personnel', value: 'personnal' }, { text: '3 cases', value: 3 }, { text: '6 cases', value: 6 }, { text: '9 cases', value: 9 }, { text: '12 cases', value: 12 }, { text: '18 cases', value: 18 }], { change: (value) => spell.range = value, defaultValue: spell.range, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Tags'), multiselect([{ text: 'Dégâts', value: 'damage' }, { text: 'Buff', value: 'buff' }, { text: 'Debuff', value: 'debuff' }, { text: 'Support', value: 'support' }, { text: 'Tank', value: 'tank' }, { text: 'Mouvement', value: 'movement' }, { text: 'Utilitaire', value: 'utilitary' }], { change: (value) => spell.tags = value, defaultValue: spell.tags, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Tags'), multiselect([{ text: 'Dégâts', value: 'damage' }, { text: 'Buff', value: 'buff' }, { text: 'Debuff', value: 'debuff' }, { text: 'Support', value: 'support' }, { text: 'Tank', value: 'tank' }, { text: 'Mouvement', value: 'movement' }, { text: 'Utilitaire', value: 'utilitary' }], { change: (value) => spell.tags = value, defaultValue: spell.tags, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Concentration'), toggle({ change: (value) => spell.concentration = value, defaultValue: spell.concentration, class: { container: '!m-0 !flex-none' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Concentration'), toggle({ change: (value) => spell.concentration = value, defaultValue: spell.concentration, class: { container: '!m-0 !flex-none' }, disabled: () => spell.type !== 'knowledge' }), ]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-row gap-2', [ tooltip(button(icon('radix-icons:check'), () => {
|
div('flex flex-row gap-2 justify-end items-end', [ tooltip(button(icon('radix-icons:check'), () => {
|
||||||
spell.description = MarkdownEditor.singleton.content;
|
spell.description = MarkdownEditor.singleton.content;
|
||||||
Object.assign(config.spells[spell.id]!, spell);
|
Object.assign(config.spells[spell.id]!, spell);
|
||||||
editing.id = '';
|
editing.id = '';
|
||||||
|
|
@ -292,6 +301,13 @@ export class HomebrewBuilder
|
||||||
range: 0,
|
range: 0,
|
||||||
tags: [],
|
tags: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
filters.tag = [];
|
||||||
|
filters.type = [];
|
||||||
|
filters.rank = [];
|
||||||
|
filters.element = [];
|
||||||
|
filters.range = [];
|
||||||
|
filters.speed = [];
|
||||||
};
|
};
|
||||||
const remove = (spell: SpellConfig) => {
|
const remove = (spell: SpellConfig) => {
|
||||||
confirm('Voulez vous vraiment supprimer ce sort ?').then(e => {
|
confirm('Voulez vous vraiment supprimer ce sort ?').then(e => {
|
||||||
|
|
@ -301,7 +317,28 @@ export class HomebrewBuilder
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), add, 'p-1') ]), div('flex flex-col divide-y', { list: () => Object.values(config.spells), render: (e, _c) => editing.id === e.id ? edit(e.id) : render(e)}) ] ) ];
|
return [ div('flex px-8 py-4 flex-col gap-4', [
|
||||||
|
div('flex flew-row justify-between items-end', [
|
||||||
|
div('flex flex-row gap-2', [
|
||||||
|
div('flex flex-col gap-1 items-center', [ text('Tags'), multiselect<typeof filters.tag[number]>([{ text: 'Dégâts', value: 'damage' }, { text: 'Buff', value: 'buff' }, { text: 'Debuff', value: 'debuff' }, { text: 'Support', value: 'support' }, { text: 'Tank', value: 'tank' }, { text: 'Mouvement', value: 'movement' }, { text: 'Utilitaire', value: 'utilitary' }], { defaultValue: filters.tag, change: v => filters.tag = v, class: { container: 'w-40 !mx-0 text-sm', option: 'p-1' } }) ]),
|
||||||
|
div('flex flex-col gap-1 items-center', [ text('Types'), multiselect<typeof filters.type[number]>(SPELL_TYPES.map(f => ({ text: spellTypeTexts[f], value: f })), { defaultValue: filters.type, change: v => filters.type = v, class: { container: 'w-36 !mx-0 text-sm', option: 'p-1' } }) ]),
|
||||||
|
div('flex flex-col gap-1 items-center', [ text('Rangs'), multiselect<typeof filters.rank[number]>([{ text: 'Rang 1', value: 1 }, { text: 'Rang 2', value: 2 }, { text: 'Rang 3', value: 3 }], { defaultValue: filters.rank, change: v => filters.rank = v, class: { container: 'w-32 !mx-0 text-sm', option: 'p-1' } }) ]),
|
||||||
|
div('flex flex-col gap-1 items-center', [ text('Elements'), multiselect<typeof filters.element[number]>(SPELL_ELEMENTS.map(f => ({ text: elementTexts[f].text, value: f })), { defaultValue: filters.element, change: v => filters.element = v, class: { container: 'w-36 !mx-0 text-sm', option: 'p-1' } }) ]),
|
||||||
|
div('flex flex-col gap-1 items-center', [ text('Portée'), multiselect<typeof filters.range[number]>([{ text: 'Toucher', value: 0 }, { text: 'Personnel', value: 'personnal' }, { text: '3 cases', value: 3 }, { text: '6 cases', value: 6 }, { text: '9 cases', value: 9 }, { text: '12 cases', value: 12 }, { text: '18 cases', value: 18 }], { defaultValue: filters.range, change: v => filters.range = v, class: { container: 'w-36 !mx-0 text-sm', option: 'p-1' } }) ]),
|
||||||
|
div('flex flex-col gap-1 items-center', [ text('Incantation'), multiselect<typeof filters.speed[number]>([{ text: 'Action', value: 'action' }, { text: 'Reaction', value: 'reaction' }, { text: '1 minute', value: 1 }, { text: '10 minutes', value: 10 }], { defaultValue: filters.speed, change: v => filters.speed = v, class: { container: 'w-40 !mx-0 text-sm', option: 'p-1' } }) ]),
|
||||||
|
]), button(icon('radix-icons:plus'), add, 'p-1')
|
||||||
|
]), div('flex flex-col divide-y', { list: () => Object.values(config.spells).filter(spell => {
|
||||||
|
|
||||||
|
if(filters.cost.min > spell.cost || spell.cost > filters.cost.max) return false;
|
||||||
|
if(filters.element.length > 0 && !filters.element.some(e => spell.elements.includes(e))) return false;
|
||||||
|
if(filters.range.length > 0 && !filters.range.includes(spell.range)) return false;
|
||||||
|
if(filters.rank.length > 0 && !filters.rank.includes(spell.rank)) return false;
|
||||||
|
if(filters.type.length > 0 && !filters.type.includes(spell.type)) return false;
|
||||||
|
if(filters.speed.length > 0 && !filters.speed.includes(spell.speed)) return false;
|
||||||
|
if(filters.tag.length > 0 && !filters.tag.some(e => spell.tags?.includes(e))) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}) as SpellConfig[], render: (e, _c) => editing.id === e.id ? edit(e.id) : render(e) }) ] ) ];
|
||||||
}
|
}
|
||||||
actions()
|
actions()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,38 @@ export function padRight(text: string, pad: string, length: number): string
|
||||||
{
|
{
|
||||||
return pad.repeat(length - text.length).concat(text);
|
return pad.repeat(length - text.length).concat(text);
|
||||||
}
|
}
|
||||||
|
export function shallowEquals(a: any, b: any): boolean
|
||||||
|
{
|
||||||
|
if(a === b) return true;
|
||||||
|
|
||||||
|
if(a && b && typeof a == 'object' && typeof b == 'object')
|
||||||
|
{
|
||||||
|
if (a.constructor !== b.constructor) return false;
|
||||||
|
|
||||||
|
let i, keys;
|
||||||
|
if (Array.isArray(a))
|
||||||
|
{
|
||||||
|
if (a.length != b.length) return false;
|
||||||
|
for (i = a.length; i-- !== 0;)
|
||||||
|
if (!b.includes(a[i])) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
|
||||||
|
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
|
||||||
|
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
||||||
|
|
||||||
|
keys = Object.keys(a) as Array<keyof typeof a>;
|
||||||
|
if (keys.length !== Object.keys(b).length) return false;
|
||||||
|
|
||||||
|
for (i = keys.length; i-- !== 0;)
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(b, keys[i]!) || a[keys[i]!] !== b[keys[i]!]) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a !== a && b !== b;
|
||||||
|
}
|
||||||
export function deepEquals(a: any, b: any): boolean
|
export function deepEquals(a: any, b: any): boolean
|
||||||
{
|
{
|
||||||
if(a === b) return true;
|
if(a === b) return true;
|
||||||
|
|
@ -81,12 +113,11 @@ export function deepEquals(a: any, b: any): boolean
|
||||||
{
|
{
|
||||||
if (a.constructor !== b.constructor) return false;
|
if (a.constructor !== b.constructor) return false;
|
||||||
|
|
||||||
let length, i, keys;
|
let i, keys;
|
||||||
if (Array.isArray(a))
|
if (Array.isArray(a))
|
||||||
{
|
{
|
||||||
length = a.length;
|
if (a.length != b.length) return false;
|
||||||
if (length != b.length) return false;
|
for (i = a.length; i-- !== 0;)
|
||||||
for (i = length; i-- !== 0;)
|
|
||||||
if (!deepEquals(a[i], b[i])) return false;
|
if (!deepEquals(a[i], b[i])) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -96,13 +127,12 @@ export function deepEquals(a: any, b: any): boolean
|
||||||
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
||||||
|
|
||||||
keys = Object.keys(a) as Array<keyof typeof a>;
|
keys = Object.keys(a) as Array<keyof typeof a>;
|
||||||
length = keys.length;
|
if (keys.length !== Object.keys(b).length) return false;
|
||||||
if (length !== Object.keys(b).length) return false;
|
|
||||||
|
|
||||||
for (i = length; i-- !== 0;)
|
for (i = keys.length; i-- !== 0;)
|
||||||
if (!Object.prototype.hasOwnProperty.call(b, keys[i]!)) return false;
|
if (!Object.prototype.hasOwnProperty.call(b, keys[i]!)) return false;
|
||||||
|
|
||||||
for (i = length; i-- !== 0;)
|
for (i = keys.length; i-- !== 0;)
|
||||||
if(!deepEquals(a[keys[i]!], b[keys[i]!])) return false;
|
if(!deepEquals(a[keys[i]!], b[keys[i]!])) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue