Checkbox and item panel improvements
This commit is contained in:
parent
48e767944a
commit
a577e3ccfc
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -400,9 +400,12 @@ export function numberpicker(settings?: { defaultValue?: number, change?: (value
|
|||
}
|
||||
return false;
|
||||
}
|
||||
const field = dom("input", { attributes: { disabled: settings?.disabled }, class: [`w-14 mx-4 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20`, settings?.class], listeners: {
|
||||
const field = dom("input", { attributes: { disabled: settings?.disabled }, class: [`w-14 mx-4 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 disabled:shadow-none disabled:bg-light-20 dark:disabled:bg-dark-20 disabled:border-light-20 dark:disabled:border-dark-20`, settings?.class], listeners: {
|
||||
input: () => validateAndChange(parseInt(field.value.trim().toLowerCase().normalize().replace(/[a-z,.]/g, ""), 10)) && settings?.input && settings.input(storedValue),
|
||||
keydown: (e: KeyboardEvent) => {
|
||||
if(field.disabled)
|
||||
return;
|
||||
|
||||
switch(e.key)
|
||||
{
|
||||
case "ArrowUp":
|
||||
|
|
@ -441,7 +444,7 @@ export function foldable(content: NodeChildren | (() => NodeChildren), title: No
|
|||
}
|
||||
}
|
||||
const contentContainer = div(['hidden group-data-[active]:flex', settings?.class?.content]);
|
||||
const fold = div(['group flex flex-1 w-full flex-col', settings?.class?.container], [
|
||||
const fold = div(['group flex w-full flex-col', settings?.class?.container], [
|
||||
div('flex', [ dom('div', { listeners: { click: () => { display(fold.toggleAttribute('data-active')) } }, class: ['flex justify-center items-center', settings?.class?.icon] }, [ icon('radix-icons:caret-right', { class: 'group-data-[active]:rotate-90 origin-center', noobserver: true }) ]), div(['flex-1', settings?.class?.title], title) ]),
|
||||
contentContainer
|
||||
]);
|
||||
|
|
@ -460,8 +463,11 @@ export function toggle(settings?: { defaultValue?: boolean, change?: (value: boo
|
|||
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
|
||||
data-[state=checked]:bg-light-35 dark:data-[state=checked]:bg-dark-35 hover:border-light-50 dark:hover:border-dark-50 focus:shadow-raw focus:shadow-light-40 dark:focus:shadow-dark-40
|
||||
data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 relative py-[2px]`, settings?.class?.container], attributes: { "data-state": state ? "checked" : "unchecked" }, listeners: {
|
||||
click: (e: Event) => {
|
||||
data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 relative py-[2px]`, settings?.class?.container], attributes: { "data-state": state ? "checked" : "unchecked", "data-disabled": settings?.disabled }, listeners: {
|
||||
click: function(e: Event) {
|
||||
if(this.hasAttribute('data-disabled'))
|
||||
return;
|
||||
|
||||
state = !state;
|
||||
element.setAttribute('data-state', state ? "checked" : "unchecked");
|
||||
settings?.change && settings.change(state);
|
||||
|
|
@ -470,6 +476,24 @@ 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') ]);
|
||||
return element;
|
||||
}
|
||||
export function checkbox(settings?: { defaultValue?: boolean, change?: (this: HTMLElement, value: boolean) => void, disabled?: boolean, class?: { container?: Class, icon?: Class } })
|
||||
{
|
||||
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
|
||||
cursor-pointer hover:bg-light-30 dark:hover:bg-dark-30 hover:border-light-60 dark:hover:border-dark-60
|
||||
data-[disabled]:cursor-default data-[disabled]:border-dashed data-[disabled]:border-light-40 dark:data-[disabled]:border-dark-40 data-[disabled]:bg-0 dark:data-[disabled]:bg-0`, settings?.class?.container], attributes: { "data-state": state ? "checked" : "unchecked", "data-disabled": settings?.disabled }, listeners: {
|
||||
click: function(e: Event) {
|
||||
if(this.hasAttribute('data-disabled'))
|
||||
return;
|
||||
|
||||
state = !state;
|
||||
element.setAttribute('data-state', state ? "checked" : "unchecked");
|
||||
settings?.change && settings.change.bind(this)(state);
|
||||
}
|
||||
}
|
||||
}, [ icon('radix-icons:check', { width: 14, height: 14, class: ['hidden group-data-[state="checked"]:block data-[disabled]:text-light-50 dark:data-[disabled]:text-dark-50', settings?.class?.icon] }), ]);
|
||||
return element;
|
||||
}
|
||||
export function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content: NodeChildren | (() => NodeChildren) }>, settings?: { focused?: string, class?: { container?: Class, tabbar?: Class, title?: Class, content?: Class } }): HTMLDivElement & { refresh: () => void }
|
||||
{
|
||||
let focus = settings?.focused ?? tabs[0]?.id;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { Ability, AspectConfig, CharacterConfig, CommonItemConfig, Feature,
|
|||
import { div, dom, icon, span, text, type NodeChildren } from "#shared/dom.util";
|
||||
import { MarkdownEditor } from "#shared/editor.util";
|
||||
import { preview } from "#shared/proses";
|
||||
import { button, combobox, foldable, input, multiselect, numberpicker, optionmenu, select, tabgroup, table, toggle, type Option } from "#shared/components.util";
|
||||
import { button, checkbox, combobox, foldable, input, multiselect, numberpicker, optionmenu, select, tabgroup, table, toggle, type Option } from "#shared/components.util";
|
||||
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating.util";
|
||||
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts } from "#shared/character.util";
|
||||
import characterConfig from "#shared/character-config.json";
|
||||
|
|
@ -33,13 +33,13 @@ export class HomebrewBuilder
|
|||
private _tabs: HTMLElement & { refresh: () => void };
|
||||
|
||||
private _config: CharacterConfig;
|
||||
private _featureEditor: FeatureEditor;
|
||||
private _featureEditor: FeaturePanel;
|
||||
|
||||
constructor(container: HTMLDivElement)
|
||||
{
|
||||
this._config = config as CharacterConfig;
|
||||
this._featureEditor = new FeatureEditor();
|
||||
ItemEditor.config = this._config;
|
||||
this._featureEditor = new FeaturePanel();
|
||||
ItemPanel.config = this._config;
|
||||
this._container = container;
|
||||
|
||||
this._tabs = tabgroup([
|
||||
|
|
@ -378,6 +378,7 @@ export class HomebrewBuilder
|
|||
description: getID(), // i18nID
|
||||
rarity: 'common',
|
||||
equippable: false,
|
||||
consummable: false,
|
||||
}
|
||||
switch(category)
|
||||
{
|
||||
|
|
@ -422,7 +423,7 @@ export class HomebrewBuilder
|
|||
});
|
||||
};
|
||||
const edit = (item: ItemConfig) => {
|
||||
ItemEditor.edit(item).then(f => {
|
||||
ItemPanel.edit(item).then(f => {
|
||||
const idx = options.findIndex(e => e.item === item);
|
||||
this._config.items[item.id] = f;
|
||||
const element = render(f);
|
||||
|
|
@ -455,7 +456,7 @@ export class HomebrewBuilder
|
|||
}
|
||||
}
|
||||
|
||||
export class FeatureEditor
|
||||
export class FeaturePanel
|
||||
{
|
||||
private _container: HTMLDivElement;
|
||||
|
||||
|
|
@ -680,14 +681,14 @@ export class FeatureEditor
|
|||
return this._container;
|
||||
}
|
||||
}
|
||||
export class ItemEditor
|
||||
export class ItemPanel
|
||||
{
|
||||
static config: CharacterConfig;
|
||||
static render(item: ItemConfig, success: (item: ItemConfig) => void, failure: (item: ItemConfig) => void)
|
||||
{
|
||||
const _item = JSON.parse(JSON.stringify(item)) as ItemConfig;
|
||||
MarkdownEditor.singleton.content = getText(_item.description);
|
||||
MarkdownEditor.singleton.onChange = (value) => ItemEditor.config.texts[_item.description]!.default = value;
|
||||
MarkdownEditor.singleton.onChange = (value) => ItemPanel.config.texts[_item.description]!.default = value;
|
||||
return dom('div', { attributes: { 'data-state': 'inactive' }, class: 'border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-1/2 flex flex-col gap-2 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]' }, [
|
||||
div('flex flex-row justify-between items-center', [
|
||||
tooltip(button(icon('radix-icons:check', { width: 20, height: 20 }), () => {
|
||||
|
|
@ -703,37 +704,27 @@ export class ItemEditor
|
|||
MarkdownEditor.singleton.onChange = undefined;
|
||||
}, 'p-1'), 'Annuler', 'left'),
|
||||
]),
|
||||
dom('div', { class: 'flex flex-col justify-start items-start my-2 gap-4' }, [
|
||||
dom('span', { class: 'pb-1 md:p-0 w-full', text: "Description" }),
|
||||
div('p-1 border border-light-40 dark:border-dark-40 w-full bg-light-25 dark:bg-dark-25 min-h-48 max-h-[32rem]', [ MarkdownEditor.singleton.dom ]),
|
||||
]),
|
||||
dom('div', { class: 'flex flex-col justify-start items-start my-2 gap-4' }, [
|
||||
dom('span', { class: 'pb-1 md:p-0 w-full', text: "Propriétés" }),
|
||||
div('flex flex-row gap-4 px-2 py-1 items-center justify-center w-full', [
|
||||
div('flex flex-col gap-2 items-center px-1', [ span('', 'Rareté'), select(Object.keys(rarityText).map(e => ({ text: rarityText[e as Rarity], value: e as Rarity })), { defaultValue: _item.rarity, change: (v) => _item.rarity = v }), ]),
|
||||
div('flex flex-col gap-2 items-center px-1', [ span('', 'Poids'), numberpicker({ defaultValue: _item.weight, input: (v) => _item.weight = v }), ]),
|
||||
div('flex flex-col gap-2 items-center px-1', [ span('', 'Prix'), numberpicker({ defaultValue: _item.price, input: (v) => _item.price = v }), ]),
|
||||
div('flex flex-col gap-2 items-center px-1', [ span('', 'Puissance magique'), numberpicker({ defaultValue: _item.power, input: (v) => _item.power = v }), ]),
|
||||
div('flex flex-col gap-2 items-center px-1', [ span('', 'Charges'), numberpicker({ defaultValue: _item.charge, input: (v) => _item.charge = v }), ]),
|
||||
div('flex flex-col gap-2 items-center px-1', [ span('', 'Equippable ?'), toggle({ defaultValue: _item.equippable, change: (v) => _item.equippable = v }), ]),
|
||||
]),
|
||||
]),
|
||||
div('flex flex-col gap-2 w-full', [
|
||||
div('flex flex-row justify-between', [
|
||||
dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
||||
foldable([
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.weight !== undefined, change: function(value) { _item.weight = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Poids'), ]), numberpicker({ defaultValue: _item.weight, disabled: _item.weight === undefined, input: (v) => _item.weight = v, class: '!w-1/3' }), ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.price !== undefined, change: function(value) { _item.price = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Prix'), ]), numberpicker({ defaultValue: _item.price, disabled: _item.price === undefined, input: (v) => _item.price = v, class: '!w-1/3' }), ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.capacity !== undefined, change: function(value) { _item.capacity = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Capacité magique'), ]), numberpicker({ defaultValue: _item.capacity, disabled: _item.capacity === undefined, input: (v) => _item.capacity = v, class: '!w-1/3' }), ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.powercost !== undefined, change: function(value) { _item.powercost = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Puissance magique'), ]), numberpicker({ defaultValue: _item.powercost, disabled: _item.powercost === undefined, input: (v) => _item.powercost = v, class: '!w-1/3' }), ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.equippable, change: function(value) { _item.equippable = value; this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Equipable'), ]), div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.consummable, change: function(value) { _item.consummable = value; this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Consommable'), ]) ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.charge !== undefined, change: function(value) { _item.charge = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Charges'), ]), numberpicker({ defaultValue: _item.charge, disabled: _item.charge === undefined, input: (v) => _item.charge = v, class: '!w-1/3' }), ]),
|
||||
], [ span('text-lg font-bold', "Propriétés"), div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Rareté'), ]), select(Object.keys(rarityText).map(e => ({ text: rarityText[e as Rarity], value: e as Rarity })), { defaultValue: _item.rarity, change: (v) => _item.rarity = v }), ]) ], { class: { content: 'group-data-[active]:grid grid-cols-2 my-2 gap-4', title: 'grid grid-cols-2 gap-4 mx-2', container: 'pb-2 border-b border-light-35 dark:border-dark-35' }, open: true } ),
|
||||
foldable([div('p-1 border border-light-40 dark:border-dark-40 w-full bg-light-25 dark:bg-dark-25 min-h-48 max-h-[32rem]', [ MarkdownEditor.singleton.dom ])], [ span('text-lg font-bold px-2', "Description") ], { class: { container: 'gap-4 pb-2 border-b border-light-35 dark:border-dark-35' }, open: true, }),
|
||||
foldable([ div('grid grid-cols-2 gap-4 px-2'), ], [ dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
||||
tooltip(button(icon('radix-icons:plus', { width: 20, height: 20 }), () => {
|
||||
//this._table.appendChild(this._edit({ id: getID() }));
|
||||
}, 'p-1'), 'Ajouter', 'left'),
|
||||
]),
|
||||
div('grid grid-cols-2 gap-4 px-2'),
|
||||
])
|
||||
])
|
||||
], { class: { container: 'flex flex-col gap-2 w-full', title: 'flex flex-row justify-between px-2' } })
|
||||
]);
|
||||
}
|
||||
static edit(item: ItemConfig): Promise<ItemConfig>
|
||||
{
|
||||
let container: HTMLElement, close: Function;
|
||||
return new Promise<ItemConfig>((success, failure) => {
|
||||
container = ItemEditor.render(item, success, failure);
|
||||
container = ItemPanel.render(item, success, failure);
|
||||
close = fullblocker([container], {
|
||||
priority: true, closeWhenOutside: false,
|
||||
}).close;
|
||||
|
|
|
|||
|
|
@ -87,11 +87,13 @@ type CommonItemConfig = {
|
|||
rarity: 'common' | 'uncommon' | 'rare' | 'legendary';
|
||||
weight?: number; //Optionnal but highly recommended
|
||||
price?: number; //Optionnal but highly recommended
|
||||
power?: number; //Optionnal as most mundane items should not receive enchantments (potions, herbal heals, etc...)
|
||||
capacity?: number; //Optionnal as most mundane items should not receive enchantments (potions, herbal heals, etc...)
|
||||
powercost?: number; //Optionnal
|
||||
charge?: number //Max amount of charges
|
||||
enchantments?: string[]; //Enchantment ID
|
||||
effects?: Array<FeatureValue | FeatureEquipment | FeatureList>;
|
||||
equippable: boolean;
|
||||
consummable: boolean;
|
||||
}
|
||||
type ArmorConfig = {
|
||||
category: 'armor';
|
||||
|
|
|
|||
Loading…
Reference in New Issue