Progress on ItemEditor interface and rendering

This commit is contained in:
Clément Pons
2025-10-13 17:56:22 +02:00
parent d187957915
commit 48e767944a
6 changed files with 64 additions and 70 deletions

File diff suppressed because one or more lines are too long

View File

@@ -11,6 +11,21 @@ import markdown, { markdownReference, renderMDAsText } from "#shared/markdown.ut
import { Tree } from "#shared/tree";
import { getText } from "#shared/i18n";
type Category = ItemConfig['category'];
type Rarity = ItemConfig['rarity'];
const categoryText: Record<Category, string> = {
'mundane': 'Objet inerte',
'armor': 'Armure',
'weapon': 'Arme',
'wondrous': 'Objet magique'
};
const rarityText: Record<Rarity, string> = {
'common': 'Commun',
'uncommon': 'Peu commun',
'rare': 'Rare',
'legendary': 'Légendaire'
};
const config = characterConfig as CharacterConfig;
export class HomebrewBuilder
{
@@ -19,13 +34,12 @@ export class HomebrewBuilder
private _config: CharacterConfig;
private _featureEditor: FeatureEditor;
private _itemEditor: ItemEditor;
constructor(container: HTMLDivElement)
{
this._config = config as CharacterConfig;
this._featureEditor = new FeatureEditor();
this._itemEditor = new ItemEditor();
ItemEditor.config = this._config;
this._container = container;
this._tabs = tabgroup([
@@ -317,8 +331,8 @@ export class HomebrewBuilder
editing = { id, type };
const buttons = div('flex flex-row items-center gap-2', [ span('text-sm text-light-70 dark:text-dark-70', type), tooltip(button(icon('radix-icons:check'), () => {
this._config.texts[feature.description].default = editor.content;
this._config.texts[feature.description]['fr_FR'] = editor.content;
this._config.texts[feature.description]!.default = editor.content;
this._config.texts[feature.description]!['fr_FR'] = editor.content;
const rerender = render(type, feature);
option!.buttons.replaceWith(rerender.buttons);
@@ -357,21 +371,6 @@ export class HomebrewBuilder
}
items()
{
type Category = ItemConfig['category'];
type Rarity = ItemConfig['rarity'];
const categoryText: Record<Category, string> = {
'mundane': 'Objet inerte',
'armor': 'Armure',
'weapon': 'Arme',
'wondrous': 'Objet magique'
};
const rarityText: Record<Rarity, string> = {
'common': 'Commun',
'uncommon': 'Peu commun',
'rare': 'Rare',
'legendary': 'Légendaire'
};
const defaultItem = (category: Category): ItemConfig => {
const common: CommonItemConfig = {
id: getID(),
@@ -384,19 +383,19 @@ export class HomebrewBuilder
{
case 'armor':
return { ...common, category: category, health: 0, absorb: { percent: 0, static: 0 }, type: 'light' };
case 'mundane':
return { ...common, category: category };
case 'weapon':
return { ...common, category: category, damage: '0', type: ['classic'] };
case 'wondrous':
case 'mundane':
return { ...common, category: category };
}
};
const render = (item: ItemConfig) => {
return {
dom: div('flex flex-col gap-2 border border-light-35 dark:border-dark-35 p-1', [
div('flex flex-row justify-between', [ span('text-xl font-bold px-4', item.name), div('flex flex-row gap-2 items-center', [ div('flex flex-row items-center gap-2', [ span('text-sm text-light-70 dark:text-dark-70', categoryText[item.category]), text('-'), span('text-sm text-light-70 dark:text-dark-70', rarityText[item.rarity]), tooltip(button(icon('radix-icons:pencil-1'), () => edit(item), 'p-1'), 'Modifier', 'top'), tooltip(button(icon('radix-icons:trash'), () => remove(item), 'p-1'), 'Supprimer', 'top') ]) ])]),
markdown(getText(item.description), undefined, { tags: { a: preview }, class: 'ms-2 px-2 py-1 border-l-4 border-light-30 dark:border-dark-30' }),
div('flex flex-row justify-between', [ span('text-xl font-bold ps-2', item.name), div('flex flex-row gap-2 items-center', [ div('flex flex-row items-center gap-2', [ tooltip(button(icon('radix-icons:pencil-1'), () => edit(item), 'p-1'), 'Modifier', 'top'), tooltip(button(icon('radix-icons:trash'), () => remove(item), 'p-1'), 'Supprimer', 'top') ]) ])]),
div('flex flex-row gap-2 px-4 items-center', [ span('text-sm text-light-70 dark:text-dark-70', categoryText[item.category]), text('-'), span('text-sm text-light-70 dark:text-dark-70', rarityText[item.rarity]), ]),
markdown(getText(item.description), undefined, { tags: { a: preview }, class: 'px-2 py-1 border-l-4 border-light-30 dark:border-dark-30 h-full' }),
]),
item,
};
@@ -423,18 +422,13 @@ export class HomebrewBuilder
});
};
const edit = (item: ItemConfig) => {
const idx = options.findIndex(e => e.item === item);
this._itemEditor.edit(item).then(f => {
ItemEditor.edit(item).then(f => {
const idx = options.findIndex(e => e.item === item);
this._config.items[item.id] = f;
options[idx] = render(f);
}).catch((e) => {}).finally(() => {
setTimeout(popup.close, 150);
this._itemEditor.container.setAttribute('data-state', 'inactive');
});
const popup = fullblocker([this._itemEditor.container], {
priority: true, closeWhenOutside: false,
});
setTimeout(() => this._itemEditor.container.setAttribute('data-state', 'active'), 1);
const element = render(f);
options[idx]?.dom.replaceWith(element.dom);
options[idx] = element;
}).catch((e) => {});
}
const options = Object.values(this._config.items).map(e => render(e));
const optionHolder = div('grid grid-cols-3 gap-2', options.map(e => e.dom));
@@ -688,31 +682,24 @@ export class FeatureEditor
}
export class ItemEditor
{
private _container: HTMLDivElement;
private _success?: Function;
private _failure?: Function;
private _item?: ItemConfig;
private _idInput: HTMLInputElement;
private _table: HTMLDivElement;
constructor()
static config: CharacterConfig;
static render(item: ItemConfig, success: (item: ItemConfig) => void, failure: (item: ItemConfig) => void)
{
this._idInput = dom("input", { attributes: { 'disabled': true }, class: `mx-4 text-light-70 dark:text-dark-70 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-25 dark:bg-dark-25 border-light-30 dark:border-dark-30` });
this._table = div('grid grid-cols-2 gap-4 px-2');
this._container = 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]' }, [
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;
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 }), () => {
this._success!(this._item);
success!(_item);
MarkdownEditor.singleton.onChange = undefined;
}, 'p-1'), 'Valider', 'left'),
dom('label', { class: 'flex justify-center items-center my-2' }, [
dom('span', { class: 'pb-1 md:p-0', text: "ID" }),
this._idInput
dom('span', { class: 'pb-1 md:p-0', text: "Nom" }),
input('text', { defaultValue: _item.name, input: (v) => _item.name = v })
]),
tooltip(button(icon('radix-icons:cross-1', { width: 20, height: 20 }), () => {
this._failure!(this._item);
failure!(item);
MarkdownEditor.singleton.onChange = undefined;
}, 'p-1'), 'Annuler', 'left'),
]),
@@ -720,6 +707,17 @@ export class ItemEditor
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' }),
@@ -727,22 +725,22 @@ export class ItemEditor
//this._table.appendChild(this._edit({ id: getID() }));
}, 'p-1'), 'Ajouter', 'left'),
]),
this._table,
div('grid grid-cols-2 gap-4 px-2'),
])
]);
])
}
edit(item: ItemConfig): Promise<ItemConfig>
static edit(item: ItemConfig): Promise<ItemConfig>
{
return new Promise((success, failure) => {
this._success = success;
this._failure = failure;
this._item = JSON.parse(JSON.stringify(item)) as ItemConfig;
//this._table.replaceChildren(...this._item.effect.map(this._renderEffect.bind(this)));
this._idInput.value = this._item.id;
MarkdownEditor.singleton.onChange = (e) => this._item!.description = e;
MarkdownEditor.singleton.content = this._item.description;
let container: HTMLElement, close: Function;
return new Promise<ItemConfig>((success, failure) => {
container = ItemEditor.render(item, success, failure);
close = fullblocker([container], {
priority: true, closeWhenOutside: false,
}).close;
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
}).finally(() => {
setTimeout(close, 150);
container.setAttribute('data-state', 'inactive');
});
}
/* private _renderEffect(effect: Partial<FeatureItem>): HTMLDivElement
@@ -900,10 +898,6 @@ export class ItemEditor
let content = redraw();
return content;
} */
get container()
{
return this._container;
}
}
const featureChoices: Option<Partial<FeatureItem>>[] = [