Start implementing ItemEditor
This commit is contained in:
parent
16cc3ee438
commit
d187957915
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
|
|
@ -1381,7 +1381,6 @@ export class CharacterSheet
|
|||
)
|
||||
),
|
||||
|
||||
|
||||
div("flex flex-row items-center justify-center gap-4", [
|
||||
div("flex flex-row items-center justify-center gap-2", [ dom("div", { class: 'text-xl font-semibold', text: "Maitrises" }), proses('a', preview, [ icon('radix-icons:question-mark-circled', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }) ], { href: 'regles/l\'entrainement/competences', label: 'Compétences', class: 'h-4' }) ]),
|
||||
div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50")
|
||||
|
|
|
|||
|
|
@ -594,7 +594,19 @@ export function floater(container: HTMLElement, content: NodeChildren | (() => N
|
|||
placement: settings?.position,
|
||||
class: 'bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 group-data-[pinned]:bg-light-15 dark:group-data-[pinned]:bg-dark-15 group-data-[pinned]:border-light-50 dark:group-data-[pinned]:border-dark-50 text-light-100 dark:text-dark-100 z-[45] relative group-data-[pinned]:h-full',
|
||||
content: () => [
|
||||
settings?.pinned !== undefined ? div('hidden group-data-[pinned]:flex flex-row items-center border-b border-light-35 dark:border-dark-35', [ dom('span', { class: 'flex-1 w-full h-full cursor-move group-data-[minimized]:cursor-default text-xs px-2', listeners: { mousedown: dragstart }, text: (settings?.title?.substring(0, 1)?.toUpperCase() ?? '') + (settings?.title?.substring(1)?.toLowerCase() ?? '') }), settings?.title ? tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { click: minimize } }, [icon('radix-icons:minus', { width: 12, height: 12, class: 'p-1' })]), text('Réduire'), 'top') : undefined, settings?.href ? tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { mousedown: (e) => { useRouter().push(settings.href!); floating.hide(); } } }, [icon('radix-icons:external-link', { width: 12, height: 12, class: 'p-1' })]), 'Ouvrir', 'top') : undefined, tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { mousedown: (e) => { e.stopImmediatePropagation(); floating.hide(); } } }, [icon('radix-icons:cross-1', { width: 12, height: 12, class: 'p-1' })]), 'Fermer', 'top') ]) : undefined,
|
||||
settings?.pinned !== undefined ? div('hidden group-data-[pinned]:flex flex-row items-center border-b border-light-35 dark:border-dark-35', [ dom('span', { class: 'flex-1 w-full h-full cursor-move group-data-[minimized]:cursor-default text-xs px-2', listeners: { mousedown: dragstart }, text: (settings?.title?.substring(0, 1)?.toUpperCase() ?? '') + (settings?.title?.substring(1)?.toLowerCase() ?? '') }), settings?.title ? tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { click: minimize } }, [icon('radix-icons:minus', { width: 12, height: 12, class: 'p-1' })]), text('Réduire'), 'top') : undefined, settings?.href ? tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { mousedown: (e) => { useRouter().push(settings.href!); floating.hide(); } } }, [icon('radix-icons:external-link', { width: 12, height: 12, class: 'p-1' })]), 'Ouvrir', 'top') : undefined, tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { mousedown: (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
floating.hide();
|
||||
|
||||
floating.content.toggleAttribute('data-minimized', false);
|
||||
minimized && Object.assign(floating.content.style, {
|
||||
left: `${minimizeBox.left}px`,
|
||||
top: `${minimizeBox.top}px`,
|
||||
width: `${minimizeBox.width}px`,
|
||||
height: `${minimizeBox.height}px`,
|
||||
});
|
||||
minimized = false;
|
||||
} } }, [icon('radix-icons:cross-1', { width: 12, height: 12, class: 'p-1' })]), 'Fermer', 'top') ]) : undefined,
|
||||
div('group-data-[minimized]:hidden h-full group-data-[pinned]:h-[calc(100%-21px)] w-full min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] group-data-[pinned]:min-h-[initial] group-data-[pinned]:min-w-[initial] group-data-[pinned]:max-h-[initial] group-data-[pinned]:max-w-[initial] overflow-auto box-content', typeof content === 'function' ? content() : content), dom('span', { class: 'hidden group-data-[pinned]:flex group-data-[minimized]:hidden absolute bottom-0 right-0 cursor-nw-resize z-50', listeners: { mousedown: resizestart } }, [ icon('ph:notches', { width: 12, height: 12 }) ])
|
||||
],
|
||||
viewport,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Ability, AspectConfig, CharacterConfig, Feature, FeatureChoice, FeatureEquipment, FeatureItem, FeatureList, FeatureValue, i18nID, Level, MainStat, RaceConfig, Resistance, SpellConfig, TrainingLevel } from "~/types/character";
|
||||
import type { Ability, AspectConfig, CharacterConfig, CommonItemConfig, Feature, FeatureChoice, FeatureEquipment, FeatureItem, FeatureList, FeatureValue, i18nID, ItemConfig, Level, MainStat, RaceConfig, Resistance, SpellConfig, TrainingLevel } from "~/types/character";
|
||||
import { div, dom, icon, span, text, type NodeChildren } from "#shared/dom.util";
|
||||
import { MarkdownEditor } from "#shared/editor.util";
|
||||
import { preview } from "#shared/proses";
|
||||
|
|
@ -18,12 +18,14 @@ export class HomebrewBuilder
|
|||
private _tabs: HTMLElement & { refresh: () => void };
|
||||
|
||||
private _config: CharacterConfig;
|
||||
private _editor: FeatureEditor;
|
||||
private _featureEditor: FeatureEditor;
|
||||
private _itemEditor: ItemEditor;
|
||||
|
||||
constructor(container: HTMLDivElement)
|
||||
{
|
||||
this._config = config as CharacterConfig;
|
||||
this._editor = new FeatureEditor();
|
||||
this._featureEditor = new FeatureEditor();
|
||||
this._itemEditor = new ItemEditor();
|
||||
this._container = container;
|
||||
|
||||
this._tabs = tabgroup([
|
||||
|
|
@ -32,9 +34,10 @@ export class HomebrewBuilder
|
|||
{ id: 'spells', title: [ text("Sorts") ], content: () => this.spells() },
|
||||
{ id: 'aspects', title: [ text("Aspects") ], content: () => this.aspects() },
|
||||
{ id: 'actions', title: [ text("Actions") ], content: () => this.actions() },
|
||||
{ id: 'items', title: [ text("Objets") ], content: () => this.items() },
|
||||
], { focused: 'training', class: { container: 'flex-1 outline-none max-w-full w-full overflow-y-auto', tabbar: 'flex w-full flex-row gap-4 items-center justify-center relative' } });
|
||||
|
||||
this._tabs.children[0]?.appendChild(tooltip(button(icon('radix-icons:clipboard'), () => this.save(), 'p-1'), 'Copier', 'bottom'))
|
||||
this._tabs.children[0]?.appendChild(tooltip(button(icon('radix-icons:clipboard'), () => this.save(), 'p-1'), 'Copier', 'bottom'));
|
||||
this._container.appendChild(div('flex flex-1 flex-col justify-start items-center px-8 w-full h-full overflow-y-hidden', [
|
||||
this._tabs
|
||||
]));
|
||||
|
|
@ -352,19 +355,104 @@ export class HomebrewBuilder
|
|||
const optionHolder = div('flex flex-col gap-4', options.map(e => e.dom));
|
||||
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), optionmenu([{ title: 'Action', click: () => add('action') }, { title: 'Réaction', click: () => add('reaction') }, { title: 'Action libre', click: () => add('freeaction') }, { title: 'Passif', click: () => add('passive') }], { position: 'left-start' }), 'p-1') ]), optionHolder ] ) ];
|
||||
}
|
||||
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(),
|
||||
name: '',
|
||||
description: getID(), // i18nID
|
||||
rarity: 'common',
|
||||
equippable: false,
|
||||
}
|
||||
switch(category)
|
||||
{
|
||||
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':
|
||||
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' }),
|
||||
]),
|
||||
item,
|
||||
};
|
||||
};
|
||||
const add = (category: Category) => {
|
||||
const item = defaultItem(category);
|
||||
this._config.texts[item.description!] = { 'fr_FR': '', default: '' };
|
||||
this._config.items[item.id!] = item;
|
||||
|
||||
const option = render(item);
|
||||
options.push(option);
|
||||
optionHolder.appendChild(option.dom);
|
||||
};
|
||||
const remove = (item: ItemConfig) => {
|
||||
confirm(`Voulez vous vraiment supprimer l'effet "${item.name}" ?`).then(e => {
|
||||
if(e)
|
||||
{
|
||||
delete this._config.texts[item.description];
|
||||
delete this._config.items[item.id];
|
||||
|
||||
const idx = options.findIndex(e => e.item === item);
|
||||
options.splice(idx, 1)[0]?.dom.remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
const edit = (item: ItemConfig) => {
|
||||
const idx = options.findIndex(e => e.item === item);
|
||||
this._itemEditor.edit(item).then(f => {
|
||||
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 options = Object.values(this._config.items).map(e => render(e));
|
||||
const optionHolder = div('grid grid-cols-3 gap-2', options.map(e => e.dom));
|
||||
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), optionmenu([{ title: 'Objet inerte', click: () => add('mundane') }, { title: 'Armure', click: () => add('armor') }, { title: 'Arme', click: () => add('weapon') }, { title: 'Objet magique', click: () => add('wondrous') }], { position: 'left-start' }), 'p-1') ]), optionHolder ] ) ];
|
||||
}
|
||||
edit(feature: Feature): Promise<Feature>
|
||||
{
|
||||
const promise: Promise<Feature> = this._editor.edit(feature).then(f => {
|
||||
const promise: Promise<Feature> = this._featureEditor.edit(feature).then(f => {
|
||||
this._config.features[feature.id] = f;
|
||||
return f;
|
||||
}).catch((e) => { return feature; }).finally(() => {
|
||||
setTimeout(popup.close, 150);
|
||||
this._editor.container.setAttribute('data-state', 'inactive');
|
||||
this._featureEditor.container.setAttribute('data-state', 'inactive');
|
||||
});
|
||||
const popup = fullblocker([this._editor.container], {
|
||||
const popup = fullblocker([this._featureEditor.container], {
|
||||
priority: true, closeWhenOutside: false,
|
||||
});
|
||||
setTimeout(() => this._editor.container.setAttribute('data-state', 'active'), 1);
|
||||
setTimeout(() => this._featureEditor.container.setAttribute('data-state', 'active'), 1);
|
||||
return promise;
|
||||
}
|
||||
private save()
|
||||
|
|
@ -598,6 +686,225 @@ export class FeatureEditor
|
|||
return this._container;
|
||||
}
|
||||
}
|
||||
export class ItemEditor
|
||||
{
|
||||
private _container: HTMLDivElement;
|
||||
|
||||
private _success?: Function;
|
||||
private _failure?: Function;
|
||||
private _item?: ItemConfig;
|
||||
|
||||
private _idInput: HTMLInputElement;
|
||||
private _table: HTMLDivElement;
|
||||
|
||||
constructor()
|
||||
{
|
||||
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]' }, [
|
||||
div('flex flex-row justify-between items-center', [
|
||||
tooltip(button(icon('radix-icons:check', { width: 20, height: 20 }), () => {
|
||||
this._success!(this._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
|
||||
]),
|
||||
tooltip(button(icon('radix-icons:cross-1', { width: 20, height: 20 }), () => {
|
||||
this._failure!(this._item);
|
||||
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 ]),
|
||||
]),
|
||||
div('flex flex-col gap-2 w-full', [
|
||||
div('flex flex-row justify-between', [
|
||||
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'),
|
||||
]),
|
||||
this._table,
|
||||
])
|
||||
]);
|
||||
}
|
||||
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;
|
||||
});
|
||||
}
|
||||
/* private _renderEffect(effect: Partial<FeatureItem>): HTMLDivElement
|
||||
{
|
||||
const content = div('border border-light-30 dark:border-dark-30 col-span-1', [ div('flex justify-between items-center', [
|
||||
div('px-4 flex items-center h-full', [ markdown(textFromEffect(effect), undefined, { tags: { a: preview } }) ]),
|
||||
div('flex', [ tooltip(button(icon('radix-icons:pencil-1'), () => {
|
||||
content.replaceWith(this._edit(effect));
|
||||
}, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Modifieur", "bottom"), tooltip(button(icon('radix-icons:trash'), () => {
|
||||
this._item!.effect = this._item!.effect.filter(e => e.id !== effect.id);
|
||||
content.remove();
|
||||
}, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Supprimer", "bottom") ])
|
||||
]) ]);
|
||||
return content;
|
||||
}
|
||||
private _edit(effect: Partial<FeatureItem>): HTMLDivElement
|
||||
{
|
||||
const match = (effect: FeatureItem): Partial<FeatureItem> | undefined => {
|
||||
switch(effect.category)
|
||||
{
|
||||
case 'value':
|
||||
return flattenFeatureChoices.findLast(e => e.category === 'value' && e.property === effect.property);
|
||||
case 'choice':
|
||||
return flattenFeatureChoices.findLast(e => e.category === 'choice');
|
||||
case 'list':
|
||||
return flattenFeatureChoices.findLast(e => e.category === 'list' && e.list === effect.list);
|
||||
}
|
||||
};
|
||||
const approve = () => {
|
||||
const idx = this._item!.effect.findIndex(e => e.id === _buffer.id);
|
||||
|
||||
if(idx === -1)
|
||||
this._item!.effect.push(_buffer);
|
||||
else
|
||||
this._item!.effect[idx] = _buffer;
|
||||
|
||||
this._table.replaceChild(this._renderEffect(_buffer), content);
|
||||
}, reject = () => {
|
||||
const idx = this._item!.effect.findIndex(e => e.id === _buffer.id);
|
||||
|
||||
if(idx === -1)
|
||||
content.remove();
|
||||
else
|
||||
this._table.replaceChild(this._renderEffect(effect), content);
|
||||
}
|
||||
let _buffer = JSON.parse(JSON.stringify(effect)) as FeatureItem;
|
||||
|
||||
const drawByCategory = (buffer: Partial<FeatureItem>) => {
|
||||
let top: NodeChildren = [], bottom: NodeChildren = [];
|
||||
switch(buffer.category)
|
||||
{
|
||||
case 'value':
|
||||
const valueVariable = () => typeof buffer.value === 'number' ? numberpicker({ defaultValue: buffer.value, input: (value) => { (buffer as FeatureValue).value = value; summaryText.textContent = textFromEffect(buffer); }, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-[80px]' }) : select<`modifier/${MainStat}` | false>([...Object.entries(mainStatShortTexts).map(e => ({ text: 'Mod. de ' + e[1], value: `modifier/${e[0]}` as `modifier/${MainStat}` })), buffer.operation === 'add' ? undefined : { text: 'Interdit', value: false }], { class: { container: 'w-[160px] bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px]' }, defaultValue: buffer.value, change: (value) => { (buffer as FeatureValue).value = value; summaryText.textContent = textFromEffect(buffer); } });
|
||||
const summaryText = text(textFromEffect(buffer));
|
||||
let valueSelection = valueVariable();
|
||||
top = [
|
||||
select([ (['action', 'reaction'].includes(buffer.property ?? '') ? undefined : { text: '+', value: 'add' }), (['speed', 'capacity', 'action', 'reaction'].includes(buffer.property ?? '') || ['defense/'].some(e => (buffer as FeatureValue).property.startsWith(e))) ? { text: '=', value: 'set' } : undefined ], { defaultValue: buffer.operation, change: (value) => { (buffer as FeatureValue).operation = value as 'add' | 'set'; summaryText.textContent = textFromEffect(buffer); }, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-[80px]' } }),
|
||||
valueSelection,
|
||||
tooltip(button(icon('radix-icons:update'), () => {
|
||||
(buffer as FeatureValue).value = (typeof (buffer as FeatureValue).value === 'number' ? '' as any as false : 0);
|
||||
const newValueSelection = valueVariable();
|
||||
valueSelection.replaceWith(newValueSelection);
|
||||
valueSelection = newValueSelection;
|
||||
summaryText.textContent = textFromEffect(buffer);
|
||||
}, 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Changer d\'editeur', 'bottom'),
|
||||
];
|
||||
bottom = [ div('px-2 py-1 flex items-center flex-1', [summaryText]) ];
|
||||
break;
|
||||
case 'list':
|
||||
top = [ select([ { text: 'Ajouter', value: 'add' }, { text: 'Supprimer', value: 'remove' } ], { defaultValue: buffer.action, change: (value) => {
|
||||
(buffer as FeatureList).action = value as 'add' | 'remove';
|
||||
const element = redraw();
|
||||
content.replaceWith(element);
|
||||
content = element;
|
||||
}, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-32' } }) ];
|
||||
if(buffer.action === 'add')
|
||||
{
|
||||
if(buffer.list === 'spells')
|
||||
{
|
||||
bottom = [ combobox(config.spells.map(e => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }), div('flex flex-row gap-8', [ dom('span', { class: 'italic', text: `Rang ${e.rank === 4 ? 'spécial' : e.rank}` }), dom('span', { text: spellTypeTexts[e.type] }) ]) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderMDAsText(e.effect)) ]) ]), value: e.id })), { defaultValue: buffer.item, change: (value) => (buffer as FeatureList).item = value, class: { container: 'bg-light-25 dark:bg-dark-25 hover:z-10 h-[36px] w-full hover:outline-px outline-light-50 dark:outline-dark-50 !border-none' }, fill: 'contain' }) ];
|
||||
}
|
||||
else if(buffer.list)
|
||||
{
|
||||
bottom = [ combobox(Object.values(config[buffer.list]).map(e => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderMDAsText(getText(e.description))) ]) ]), value: e.id })), { defaultValue: buffer.item, change: (value) => (buffer as FeatureList).item = value, class: { container: 'bg-light-25 dark:bg-dark-25 hover:z-10 h-[36px] w-full hover:outline-px outline-light-50 dark:outline-dark-50 !border-none' }, fill: 'contain' }) ];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bottom = [ combobox(Object.values(config.features).flatMap(e => e.effect).filter(e => e.category === 'list' && e.list === buffer.list && e.action === 'add').map((e) => (e as FeatureList).list === 'spells' ? config.spells.find(f => f.id === (e as FeatureList).item) : config[(e as FeatureList).list][(e as FeatureList).item]).map((e) => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderMDAsText(getText(e.description))) ]) ]), value: e.id })), { defaultValue: buffer.item, change: (item) => buffer.item = item, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full overflow-hidden truncate', option: 'max-h-[90px] text-sm' }, fill: 'contain' }) ];
|
||||
}
|
||||
break;
|
||||
case 'choice':
|
||||
const availableChoices: Option<Partial<FeatureValue | FeatureList>>[] = featureChoices.filter(e => (e?.value as FeatureItem)?.category !== 'choice').map(e => { if(e) e.value = Array.isArray(e.value) ? e.value.filter(f => (f?.value as FeatureItem)?.category !== 'choice') : e.value; return e; }) as Option<Partial<FeatureValue | FeatureList>>[];
|
||||
const addChoice = () => {
|
||||
const choice: { text: string; effects: (Partial<FeatureValue | FeatureList>)[]; } = { effects: [{ id: getID() }], text: '' };
|
||||
(buffer as FeatureChoice).options.push(choice as FeatureChoice["options"][number]);
|
||||
return choice;
|
||||
};
|
||||
const addEffect = (choice: { text: string; effects: (Partial<FeatureValue | FeatureList>)[] }) => {
|
||||
const effect: (Partial<FeatureValue | FeatureList>) = { id: getID() };
|
||||
choice.effects.push(effect);
|
||||
return effect;
|
||||
};
|
||||
const renderEffect = (option: { text: string; effects: (Partial<FeatureValue | FeatureList>)[] }, effect: Partial<FeatureValue | FeatureList>) => {
|
||||
const { top: _top, bottom: _bottom } = drawByCategory(effect);
|
||||
let element = div('border border-light-30 dark:border-dark-30 col-span-2 row-span-2', [ div('flex justify-between items-stretch', [
|
||||
div('flex flex-row flex-1', [
|
||||
combobox(availableChoices, { defaultValue: match(effect as FeatureItem) as Partial<FeatureValue | FeatureList> | undefined, class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, fill: 'cover', change: (e: Partial<FeatureValue | FeatureList>) => {
|
||||
const idx = option.effects.findIndex(e => e === effect);
|
||||
option.effects[idx] = effect = { ...e, id: effect.id };
|
||||
|
||||
const _element = renderEffect(option, effect);
|
||||
element.replaceWith(_element);
|
||||
element = _element;
|
||||
} }),
|
||||
..._top,
|
||||
]),
|
||||
div('flex', [ tooltip(button(icon('radix-icons:trash'), () => { option.effects = option.effects.filter(e => e === effect); element.remove(); }, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Supprimer", "bottom") ])
|
||||
]), div('flex border-t border-light-35 dark:border-dark-35 max-h-[300px] min-h-[36px] overflow-y-auto overflow-x-hidden', _bottom) ]);
|
||||
|
||||
return element;
|
||||
}
|
||||
const renderOption = (option: { text: string; effects: (Partial<FeatureValue | FeatureList>)[] }, state: boolean) => {
|
||||
const effects = div('flex flex-col -m-px flex flex-col ms-px ps-8 w-full', option.effects.map(e => renderEffect(option, e)));
|
||||
let _content = foldable([ effects ], [ div('flex flex-row flex-1 justify-between', [ input('text', { defaultValue: option.text, input: (value) => option.text = value, placeholder: 'Nom de l\'option', class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] flex-shrink-1' }), div('flex flex-row flex-shrink-1', [ tooltip(button(icon('radix-icons:plus'), () => effects.appendChild(renderEffect(option, addEffect(option))), 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Nouvel effet', 'bottom'), , tooltip(button(icon('radix-icons:trash'), () => {
|
||||
_content.remove();
|
||||
(buffer as FeatureChoice).options = (buffer as FeatureChoice).options.filter(e => e !== option);
|
||||
}, 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Supprimer', 'bottom') ]) ]) ], { class: { title: 'border-b border-light-35 dark:border-dark-35', icon: 'w-[34px] h-[34px]', content: 'border-b border-light-35 dark:border-dark-35' }, open: state });
|
||||
return _content;
|
||||
}
|
||||
const list = div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35 gap-2', buffer.options?.map(e => renderOption(e, false)) ?? []);
|
||||
top = [ input('text', { defaultValue: buffer.text, input: (value) => (buffer as FeatureChoice).text = value, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full', placeholder: 'Description' }), tooltip(button(icon('radix-icons:plus'), () => list.appendChild(renderOption(addChoice(), true)), 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Nouvelle option', 'bottom') ];
|
||||
bottom = [ list ];
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return { top, bottom };
|
||||
}
|
||||
const redraw = () => {
|
||||
const { top, bottom } = drawByCategory(_buffer);
|
||||
return div('border border-light-30 dark:border-dark-30 col-span-2 row-span-2', [ div('flex justify-between items-stretch', [
|
||||
div('flex flex-row flex-1', [
|
||||
combobox(featureChoices, { defaultValue: match(_buffer), class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, fill: 'cover', change: (e) => {
|
||||
_buffer = { id: _buffer.id, ...e } as FeatureItem;
|
||||
const element = redraw();
|
||||
content.replaceWith(element);
|
||||
content = element;
|
||||
} }),
|
||||
...top,
|
||||
]),
|
||||
div('flex', [ tooltip(button(icon('radix-icons:check'), approve, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Valider", "bottom"), tooltip(button(icon('radix-icons:cross-1'), reject, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Annuler", "bottom") ])
|
||||
]), div('flex border-t border-light-35 dark:border-dark-35 max-h-[300px] min-h-[36px] overflow-y-auto overflow-x-hidden', bottom) ]);
|
||||
}
|
||||
|
||||
let content = redraw();
|
||||
return content;
|
||||
} */
|
||||
get container()
|
||||
{
|
||||
return this._container;
|
||||
}
|
||||
}
|
||||
|
||||
const featureChoices: Option<Partial<FeatureItem>>[] = [
|
||||
{ text: 'PV max', value: { category: 'value', property: 'health', operation: 'add', value: 1 }, },
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export const a: Prose = {
|
|||
}
|
||||
}
|
||||
export const preview: Prose = {
|
||||
custom(properties: { href: string, class?: Class, label: string }, children) {
|
||||
custom(properties: { href: string, class?: Class, label: string, events? }, children) {
|
||||
const href = properties.href as string;
|
||||
const { hash, pathname } = parseURL(href);
|
||||
const router = useRouter();
|
||||
|
|
@ -72,7 +72,7 @@ export const preview: Prose = {
|
|||
queueMicrotask(() => canvas.mount());
|
||||
return dom('div', { class: 'w-[600px] h-[600px] group-data-[pinned]:h-full group-data-[pinned]:w-full h-[600px] relative w-[600px] relative' }, [canvas.container]);
|
||||
}
|
||||
return div('');
|
||||
return div();
|
||||
})).current], { position: 'bottom-start', pinned: false,
|
||||
events: {
|
||||
show: ['mouseenter', 'mousemove'],
|
||||
|
|
|
|||
|
|
@ -82,36 +82,33 @@ export type EnchantementConfig = {
|
|||
export type ItemConfig = CommonItemConfig & (ArmorConfig | WeaponConfig | WondrousConfig | MundaneConfig);
|
||||
type CommonItemConfig = {
|
||||
id: string;
|
||||
name: string; //TODO -> TextID
|
||||
description: i18nID;
|
||||
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...)
|
||||
charge?: number //Max amount of charges
|
||||
enchantments?: string[]; //Enchantment ID
|
||||
effects?: Array<FeatureValue | FeatureEquipment | FeatureList>;
|
||||
equippable: boolean;
|
||||
}
|
||||
type ArmorConfig = {
|
||||
category: 'armor';
|
||||
name: string; //TODO -> TextID
|
||||
description: i18nID;
|
||||
health: number;
|
||||
type: 'light' | 'medium' | 'heavy';
|
||||
absorb: { static: number, percent: number };
|
||||
};
|
||||
type WeaponConfig = {
|
||||
category: 'weapon';
|
||||
name: string; //TODO -> TextID
|
||||
description: i18nID;
|
||||
type: Array<'classic' | 'light' | 'throw' | 'natural' | 'heavy' | 'twohanded' | 'finesse' | 'reach' | 'projectile' | 'shield'>;
|
||||
damage: string; //Dice formula
|
||||
};
|
||||
type WondrousConfig = {
|
||||
category: 'wondrous';
|
||||
name: string; //TODO -> TextID
|
||||
description: i18nID;
|
||||
effect: FeatureItem[];
|
||||
};
|
||||
type MundaneConfig = {
|
||||
category: 'mundane';
|
||||
name: string; //TODO -> TextID
|
||||
description: i18nID;
|
||||
};
|
||||
export type SpellConfig = {
|
||||
id: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue