Persistant item/spell panel to avoid filing the reactive tracker.
This commit is contained in:
parent
78a101b79d
commit
e9ffdd58a5
|
|
@ -3,7 +3,7 @@ import { z } from "zod/v4";
|
||||||
import characterConfig from '#shared/character-config.json';
|
import characterConfig from '#shared/character-config.json';
|
||||||
import proses, { preview } from "#shared/proses";
|
import proses, { preview } from "#shared/proses";
|
||||||
import { button, buttongroup, checkbox, floater, foldable, input, loading, multiselect, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components.util";
|
import { button, buttongroup, checkbox, floater, foldable, input, loading, multiselect, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components.util";
|
||||||
import { div, dom, icon, span, text, reactive, type DOMList, type RedrawableHTML } from "#shared/dom.util";
|
import { div, dom, icon, span, text, type RedrawableHTML } from "#shared/dom.util";
|
||||||
import { followermenu, fullblocker, tooltip } from "#shared/floating.util";
|
import { followermenu, fullblocker, tooltip } from "#shared/floating.util";
|
||||||
import { clamp, deepEquals } from "#shared/general.util";
|
import { clamp, deepEquals } from "#shared/general.util";
|
||||||
import markdown from "#shared/markdown.util";
|
import markdown from "#shared/markdown.util";
|
||||||
|
|
@ -11,7 +11,7 @@ import { getText } from "#shared/i18n";
|
||||||
import type { User } from "~/types/auth";
|
import type { User } from "~/types/auth";
|
||||||
import { MarkdownEditor } from "#shared/editor.util";
|
import { MarkdownEditor } from "#shared/editor.util";
|
||||||
import { Socket } from "#shared/websocket.util";
|
import { Socket } from "#shared/websocket.util";
|
||||||
import { raw, reactive, reactivity, type Reactive } from '#shared/reactive';
|
import { raw, reactive } from '#shared/reactive';
|
||||||
|
|
||||||
const config = characterConfig as CharacterConfig;
|
const config = characterConfig as CharacterConfig;
|
||||||
|
|
||||||
|
|
@ -286,7 +286,6 @@ export class CharacterCompiler
|
||||||
'modifier/charisma': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
'modifier/charisma': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
'modifier/psyche': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
'modifier/psyche': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
};
|
};
|
||||||
private _variableDirty: boolean = false;
|
|
||||||
private _variableDebounce: NodeJS.Timeout = setTimeout(() => {});
|
private _variableDebounce: NodeJS.Timeout = setTimeout(() => {});
|
||||||
|
|
||||||
constructor(character: Character)
|
constructor(character: Character)
|
||||||
|
|
@ -307,11 +306,6 @@ export class CharacterCompiler
|
||||||
'modifier/charisma': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
'modifier/charisma': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
'modifier/psyche': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
'modifier/psyche': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
};
|
};
|
||||||
reactivity(() => this.character.variables, () => {
|
|
||||||
console.log("Saving variables");
|
|
||||||
clearTimeout(this._variableDebounce);
|
|
||||||
this._variableDebounce = setTimeout(() => this.saveVariables(), 2000);
|
|
||||||
})
|
|
||||||
|
|
||||||
if(value.people !== undefined)
|
if(value.people !== undefined)
|
||||||
{
|
{
|
||||||
|
|
@ -350,7 +344,7 @@ export class CharacterCompiler
|
||||||
get armor()
|
get armor()
|
||||||
{
|
{
|
||||||
const armors = this._character.variables.items.filter(e => e.equipped && config.items[e.id]?.category === 'armor');
|
const armors = this._character.variables.items.filter(e => e.equipped && config.items[e.id]?.category === 'armor');
|
||||||
return armors.length > 0 ? armors.map(e => ({ max: (config.items[e.id] as ArmorConfig).health, current: (config.items[e.id] as ArmorConfig).health - e.state.health })).reduce((p, v) => { p.max += v.max; p.current += v.current; return p; }, { max: 0, current: 0 }) : undefined;
|
return armors.length > 0 ? armors.map(e => ({ max: (config.items[e.id] as ArmorConfig).health, current: (config.items[e.id] as ArmorConfig).health - e.state })).reduce((p, v) => { p.max += v.max; p.current += v.current; return p; }, { max: 0, current: 0 }) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(text: string): string
|
parse(text: string): string
|
||||||
|
|
@ -362,12 +356,15 @@ export class CharacterCompiler
|
||||||
}
|
}
|
||||||
saveVariables()
|
saveVariables()
|
||||||
{
|
{
|
||||||
useRequestFetch()(`/api/character/${this.character.id}/variables`, {
|
clearTimeout(this._variableDebounce);
|
||||||
method: 'POST',
|
this._variableDebounce = setTimeout(() => {
|
||||||
body: raw(this._character.variables),
|
useRequestFetch()(`/api/character/${this.character.id}/variables`, {
|
||||||
}).then(() => {}).catch(() => {
|
method: 'POST',
|
||||||
Toaster.add({ type: 'error', content: 'Impossible de mettre à jour les données', duration: 5000, timer: true });
|
body: raw(this._character.variables),
|
||||||
})
|
}).then(() => {}).catch(() => {
|
||||||
|
Toaster.add({ type: 'error', content: 'Impossible de mettre à jour les données', duration: 5000, timer: true });
|
||||||
|
})
|
||||||
|
}, 2000);
|
||||||
}
|
}
|
||||||
saveNotes()
|
saveNotes()
|
||||||
{
|
{
|
||||||
|
|
@ -1284,6 +1281,17 @@ const subnameFactory = (item: ItemConfig, state?: ItemState): string[] => {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
const stateFactory = (item: ItemConfig) => {
|
||||||
|
const state = { id: item.id, amount: 1, charges: item.charge, enchantments: [], equipped: item.equippable ? false : undefined } as ItemState;
|
||||||
|
switch(item.category)
|
||||||
|
{
|
||||||
|
case 'armor':
|
||||||
|
state.state = 0;
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
export class CharacterSheet
|
export class CharacterSheet
|
||||||
{
|
{
|
||||||
private user: ComputedRef<User | null>;
|
private user: ComputedRef<User | null>;
|
||||||
|
|
@ -1660,6 +1668,8 @@ export class CharacterSheet
|
||||||
default: return spells;
|
default: return spells;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const panel = this.spellPanel(character);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
|
|
@ -1670,7 +1680,7 @@ export class CharacterSheet
|
||||||
]),
|
]),
|
||||||
div('flex flex-row gap-2 items-center', [
|
div('flex flex-row gap-2 items-center', [
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': character.variables.spells.length + (character.lists.spells?.length ?? 0) !== character.spellslots }], text: () => `${character.variables.spells.length + (character.lists.spells?.length ?? 0)}/${character.spellslots} sort(s) maitrisé(s)`.replaceAll('(s)', character.variables.spells.length + (character.lists.spells?.length ?? 0) > 1 ? 's' : '') }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': character.variables.spells.length + (character.lists.spells?.length ?? 0) !== character.spellslots }], text: () => `${character.variables.spells.length + (character.lists.spells?.length ?? 0)}/${character.spellslots} sort(s) maitrisé(s)`.replaceAll('(s)', character.variables.spells.length + (character.lists.spells?.length ?? 0) > 1 ? 's' : '') }),
|
||||||
button(text('Modifier'), () => this.spellPanel(character), 'py-1 px-4'),
|
button(text('Modifier'), () => panel.show(), 'py-1 px-4'),
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', { render: e => {
|
div('flex flex-col gap-2', { render: e => {
|
||||||
|
|
@ -1736,13 +1746,22 @@ export class CharacterSheet
|
||||||
const idx = spells.findIndex(e => e === spell.id);
|
const idx = spells.findIndex(e => e === spell.id);
|
||||||
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();
|
||||||
}, "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' } })
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
const blocker = fullblocker([ container ], { closeWhenOutside: true, onClose: () => this.character?.saveVariables() });
|
const blocker = fullblocker([ container ], { closeWhenOutside: true, open: false });
|
||||||
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
|
|
||||||
|
return { show: () => {
|
||||||
|
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
|
||||||
|
blocker.open();
|
||||||
|
}, hide: () => {
|
||||||
|
setTimeout(blocker.close, 150);
|
||||||
|
container.setAttribute('data-state', 'inactive');
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
itemsTab(character: CompiledCharacter)
|
itemsTab(character: CompiledCharacter)
|
||||||
{
|
{
|
||||||
|
|
@ -1750,12 +1769,14 @@ export class CharacterSheet
|
||||||
const power = () => 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);
|
const power = () => 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);
|
||||||
const weight = () => items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0) * v.amount, 0);
|
const weight = () => items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0) * v.amount, 0);
|
||||||
|
|
||||||
|
const panel = this.itemsPanel(character);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
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': weight() > character.itempower }], text: () => `Poids total: ${weight()}/${character.itempower}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': weight() > character.itempower }], text: () => `Poids total: ${weight()}/${character.itempower}` }),
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': power() > (character.capacity === false ? 0 : character.capacity) }], text: () => `Puissance magique: ${power()}/${character.capacity}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': power() > (character.capacity === false ? 0 : character.capacity) }], text: () => `Puissance magique: ${power()}/${character.capacity}` }),
|
||||||
button(text('Modifier'), () => this.itemsPanel(character), 'py-1 px-4'),
|
button(text('Modifier'), () => panel.show(), 'py-1 px-4'),
|
||||||
]),
|
]),
|
||||||
div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35', { list: character.variables.items, render: e => {
|
div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35', { list: character.variables.items, render: e => {
|
||||||
const item = config.items[e.id];
|
const item = config.items[e.id];
|
||||||
|
|
@ -1776,19 +1797,25 @@ 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();
|
||||||
}, '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);
|
||||||
if(idx === -1) return;
|
if(idx === -1) return;
|
||||||
|
|
||||||
if(item.equippable) items.push({ id: item.id, amount: 1, charges: item.charge, enchantments: [], equipped: false });
|
if(item.equippable) items.push(stateFactory(item));
|
||||||
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({ id: item.id, amount: 1, charges: item.charge, enchantments: [] });
|
else items.push(stateFactory(item));
|
||||||
|
|
||||||
|
this.character?.saveVariables();
|
||||||
}, 'p-1'),
|
}, 'p-1'),
|
||||||
]) ], [div('flex flex-row justify-between', [
|
]) ], [div('flex flex-row justify-between', [
|
||||||
div('flex flex-row items-center gap-4', [
|
div('flex flex-row items-center gap-4', [
|
||||||
item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
||||||
e.equipped = v;
|
e.equipped = v;
|
||||||
|
|
||||||
|
this.character?.saveVariables();
|
||||||
}, 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))) ]),
|
||||||
]),
|
]),
|
||||||
|
|
@ -1847,15 +1874,24 @@ export class CharacterSheet
|
||||||
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.character!.character.variables.items;
|
||||||
if(item.equippable) list.push({ id: item.id, amount: 1, charges: item.charge, enchantments: [], equipped: false });
|
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({ id: item.id, amount: 1, charges: item.charge, enchantments: [] });
|
else list.push(stateFactory(item));
|
||||||
|
|
||||||
|
this.character?.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' } })
|
||||||
} }),
|
} }),
|
||||||
]);
|
]);
|
||||||
const blocker = fullblocker([ container ], { closeWhenOutside: true, onClose: () => this.character?.saveVariables() });
|
const blocker = fullblocker([ container ], { closeWhenOutside: true, open: false });
|
||||||
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
|
|
||||||
|
return { show: () => {
|
||||||
|
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
|
||||||
|
blocker.open();
|
||||||
|
}, hide: () => {
|
||||||
|
setTimeout(blocker.close, 150);
|
||||||
|
container.setAttribute('data-state', 'inactive');
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ export interface PopperProperties extends FloatingProperties
|
||||||
export interface ModalProperties
|
export interface ModalProperties
|
||||||
{
|
{
|
||||||
priority?: boolean;
|
priority?: boolean;
|
||||||
|
open?: boolean;
|
||||||
class?: { blocker?: Class, popup?: Class },
|
class?: { blocker?: Class, popup?: Class },
|
||||||
closeWhenOutside?: boolean;
|
closeWhenOutside?: boolean;
|
||||||
onClose?: () => boolean | void;
|
onClose?: () => boolean | void;
|
||||||
|
|
@ -390,15 +391,16 @@ export function tooltip(container: RedrawableHTML, txt: string | Text, placement
|
||||||
export function fullblocker(content: NodeChildren, properties?: ModalProperties)
|
export function fullblocker(content: NodeChildren, properties?: ModalProperties)
|
||||||
{
|
{
|
||||||
if(!content)
|
if(!content)
|
||||||
return { close: () => {} };
|
return { close: () => {}, open: () => {} };
|
||||||
|
|
||||||
const close = () => (!properties?.onClose || properties.onClose() !== false) && _modal.remove();
|
const open = () => { _modal.parentElement === null && teleport.appendChild(_modal) };
|
||||||
|
const close = () => { _modal.parentElement !== null && (!properties?.onClose || properties.onClose() !== false) && _modal.remove() };
|
||||||
const _modalBlocker = dom('div', { class: [' absolute top-0 left-0 bottom-0 right-0 z-0', { 'bg-light-0 dark:bg-dark-0 opacity-70': properties?.priority ?? false }, properties?.class?.blocker], listeners: { click: properties?.closeWhenOutside ? (close) : undefined } });
|
const _modalBlocker = dom('div', { class: [' absolute top-0 left-0 bottom-0 right-0 z-0', { 'bg-light-0 dark:bg-dark-0 opacity-70': properties?.priority ?? false }, properties?.class?.blocker], listeners: { click: properties?.closeWhenOutside ? (close) : undefined } });
|
||||||
const _modal = dom('div', { class: ['fixed flex justify-center items-center top-0 left-0 bottom-0 right-0 inset-0 z-40', properties?.class?.blocker] }, [ _modalBlocker, ...content]);
|
const _modal = dom('div', { class: ['fixed flex justify-center items-center top-0 left-0 bottom-0 right-0 inset-0 z-40', properties?.class?.blocker] }, [ _modalBlocker, ...content]);
|
||||||
|
|
||||||
teleport.appendChild(_modal);
|
(properties?.open ?? true) && open();
|
||||||
|
|
||||||
return { close };
|
return { close, open };
|
||||||
}
|
}
|
||||||
export function modal(content: NodeChildren, properties?: ModalProperties & { class?: { container?: Class } })
|
export function modal(content: NodeChildren, properties?: ModalProperties & { class?: { container?: Class } })
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue