Progress on option rendering
This commit is contained in:
parent
72982a4ea9
commit
06276b3fbc
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Ability, CharacterConfig, Feature, FeatureEffect, FeatureItem, MainStat } from "~/types/character";
|
import type { Ability, CharacterConfig, Feature, FeatureEffect, FeatureItem, MainStat } from "~/types/character";
|
||||||
import { div, dom, icon, text, type NodeChildren } from "./dom.util";
|
import { div, dom, icon, text, type NodeChildren } from "./dom.util";
|
||||||
import { MarkdownEditor } from "./editor.util";
|
import { MarkdownEditor } from "./editor.util";
|
||||||
import { button, combobox, fakeA, input, numberpicker, select, type Option } from "./proses";
|
import { button, combobox, fakeA, foldable, input, numberpicker, select, type Option } from "./proses";
|
||||||
import { fullblocker, tooltip } from "./floating.util";
|
import { fullblocker, tooltip } from "./floating.util";
|
||||||
import { MAIN_STATS, mainStatShortTexts, mainStatTexts } from "./character.util";
|
import { MAIN_STATS, mainStatShortTexts, mainStatTexts } from "./character.util";
|
||||||
import config from "#shared/character-config.json";
|
import config from "#shared/character-config.json";
|
||||||
|
|
@ -338,7 +338,7 @@ export class FeatureEditor
|
||||||
MarkdownEditor.singleton.content = this._feature.description;
|
MarkdownEditor.singleton.content = this._feature.description;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private _renderEffect(effect: FeatureItem): HTMLDivElement
|
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', [
|
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', [ renderMarkdown(textFromEffect(effect), undefined, { tags: { a: fakeA } }) ]),
|
div('px-4 flex items-center h-full', [ renderMarkdown(textFromEffect(effect), undefined, { tags: { a: fakeA } }) ]),
|
||||||
|
|
@ -351,7 +351,7 @@ export class FeatureEditor
|
||||||
]) ]);
|
]) ]);
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
private _edit(effect: FeatureItem): HTMLDivElement
|
private _edit(effect: Partial<FeatureItem>): HTMLDivElement
|
||||||
{
|
{
|
||||||
const match = (effect: FeatureItem): Partial<FeatureItem> | undefined => {
|
const match = (effect: FeatureItem): Partial<FeatureItem> | undefined => {
|
||||||
switch(effect.category)
|
switch(effect.category)
|
||||||
|
|
@ -383,14 +383,14 @@ export class FeatureEditor
|
||||||
}
|
}
|
||||||
let buffer = JSON.parse(JSON.stringify(effect)) as FeatureItem;
|
let buffer = JSON.parse(JSON.stringify(effect)) as FeatureItem;
|
||||||
|
|
||||||
const redraw = () => {
|
const drawByCategory = (buffer: Partial<FeatureItem>) => {
|
||||||
let top: NodeChildren = [], bottom: NodeChildren = [];
|
let top: NodeChildren = [], bottom: NodeChildren = [];
|
||||||
switch(buffer.category)
|
switch(buffer.category)
|
||||||
{
|
{
|
||||||
case 'value':
|
case 'value':
|
||||||
const summaryText = text(textFromEffect(buffer));
|
const summaryText = text(textFromEffect(buffer));
|
||||||
top = [
|
top = [
|
||||||
select([ { text: '+', value: 'add' }, (['speed', 'capacity'].includes(buffer.property) || ['defense/'].some(e => (buffer as Extract<FeatureEffect, { category: "value" }>).property.startsWith(e))) ? { text: '=', value: 'set' } : undefined ], { defaultValue: buffer.operation, change: (value) => { (buffer as Extract<FeatureEffect, { category: "value" }>).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]' } }),
|
select([ { text: '+', value: 'add' }, (['speed', 'capacity'].includes(buffer.property ?? '') || ['defense/'].some(e => (buffer as Extract<FeatureEffect, { category: "value" }>).property.startsWith(e))) ? { text: '=', value: 'set' } : undefined ], { defaultValue: buffer.operation, change: (value) => { (buffer as Extract<FeatureEffect, { category: "value" }>).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]' } }),
|
||||||
typeof buffer.value === 'number' ? numberpicker({ defaultValue: buffer.value, input: (value) => { (buffer as Extract<FeatureEffect, { category: "value" }>).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 Extract<FeatureEffect, { category: "value" }>).value = value; summaryText.textContent = textFromEffect(buffer); } }),
|
typeof buffer.value === 'number' ? numberpicker({ defaultValue: buffer.value, input: (value) => { (buffer as Extract<FeatureEffect, { category: "value" }>).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 Extract<FeatureEffect, { category: "value" }>).value = value; summaryText.textContent = textFromEffect(buffer); } }),
|
||||||
button(icon('radix-icons:update'), () => {
|
button(icon('radix-icons:update'), () => {
|
||||||
(buffer as Extract<FeatureEffect, { category: "value" }>).value = (typeof (buffer as Extract<FeatureEffect, { category: "value" }>).value === 'number' ? '' as any as false : 0);
|
(buffer as Extract<FeatureEffect, { category: "value" }>).value = (typeof (buffer as Extract<FeatureEffect, { category: "value" }>).value === 'number' ? '' as any as false : 0);
|
||||||
|
|
@ -400,7 +400,7 @@ export class FeatureEditor
|
||||||
summaryText.textContent = textFromEffect(buffer);
|
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'),
|
}, 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'),
|
||||||
];
|
];
|
||||||
bottom = [div('px-2 py-1', [summaryText])];
|
bottom = [ div('px-2 py-1 flex items-center flex-1', [summaryText]) ];
|
||||||
break;
|
break;
|
||||||
case 'list':
|
case 'list':
|
||||||
if(buffer.action === 'add')
|
if(buffer.action === 'add')
|
||||||
|
|
@ -412,10 +412,10 @@ export class FeatureEditor
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const editor = new MarkdownEditor();
|
const editor = new MarkdownEditor();
|
||||||
editor.content = buffer.item;
|
editor.content = buffer.item ?? '';
|
||||||
editor.onChange = (item) => (buffer as Extract<FeatureEffect, { category: "list" }>).item = item;
|
editor.onChange = (item) => (buffer as Extract<FeatureEffect, { category: "list" }>).item = item;
|
||||||
|
|
||||||
bottom = [ div('px-2 py-1 bg-light-25 dark:bg-dark-25 flex-1', [ editor.dom ]) ];
|
bottom = [ div('px-2 py-1 bg-light-25 dark:bg-dark-25 flex-1 flex items-center', [ editor.dom ]) ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
top = [ select([ { text: 'Ajouter', value: 'add' }, { text: 'Supprimer', value: 'remove' } ], { defaultValue: buffer.action, change: (value) => (buffer as Extract<FeatureEffect, { category: "list" }>).action = value as 'add' | 'remove', class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-32' } }) ];
|
top = [ select([ { text: 'Ajouter', value: 'add' }, { text: 'Supprimer', value: 'remove' } ], { defaultValue: buffer.action, change: (value) => (buffer as Extract<FeatureEffect, { category: "list" }>).action = value as 'add' | 'remove', class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-32' } }) ];
|
||||||
|
|
@ -424,16 +424,32 @@ export class FeatureEditor
|
||||||
const add = () => {
|
const add = () => {
|
||||||
const option: Extract<FeatureItem, { category: 'choice' }>["options"][number] = { id: getID(ID_SIZE), category: 'value', text: '', operation: 'add', property: '', value: 0 };
|
const option: Extract<FeatureItem, { category: 'choice' }>["options"][number] = { id: getID(ID_SIZE), category: 'value', text: '', operation: 'add', property: '', value: 0 };
|
||||||
(buffer as Extract<FeatureItem, { category: 'choice' }>).options.push(option);
|
(buffer as Extract<FeatureItem, { category: 'choice' }>).options.push(option);
|
||||||
render(option);
|
list.appendChild(render(option));
|
||||||
};
|
};
|
||||||
const render = (option: FeatureEffect) => {
|
const remove = (option: FeatureEffect) => {
|
||||||
|
|
||||||
|
};
|
||||||
|
const render = (option: FeatureEffect): HTMLElement => {
|
||||||
|
const { top: _top, bottom: _bottom } = drawByCategory(option);
|
||||||
|
const combo = combobox(featureChoices.filter(e => (e?.value as FeatureItem)?.category !== 'choice'), { defaultValue: match(option), class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, change: (e) => {
|
||||||
|
option = { id: option.id, ...e } as FeatureEffect;
|
||||||
|
const element = render(option);
|
||||||
|
_content?.parentElement?.replaceChild(element, _content);
|
||||||
|
_content = element;
|
||||||
|
} });
|
||||||
|
let _content: HTMLElement = foldable(_bottom, [ div('flex flex-1 justify-between', [ div('flex flex-row',[ combo, ..._top ]), tooltip(button(icon('radix-icons:trash'), () => remove(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' } });
|
||||||
|
return _content;
|
||||||
}
|
}
|
||||||
const list = div('flex flex-row');
|
const list = div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35 gap-2', buffer.options?.map(e => render(e)) ?? []);
|
||||||
top = [ input('text', { defaultValue: buffer.text, input: (value) => (buffer as Extract<FeatureItem, { category: 'choice' }>).text = value, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full', placeholder: 'Description' }) ];
|
top = [ input('text', { defaultValue: buffer.text, input: (value) => (buffer as Extract<FeatureItem, { category: 'choice' }>).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'), () => add(), 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Ajouter une option', 'bottom') ];
|
||||||
|
bottom = [ list ];
|
||||||
break;
|
break;
|
||||||
default: 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', [
|
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', [
|
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]' }, change: (e) => {
|
combobox(featureChoices, { defaultValue: match(buffer), class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, change: (e) => {
|
||||||
|
|
@ -445,7 +461,7 @@ export class FeatureEditor
|
||||||
...top,
|
...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', [ 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-auto items-center', 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();
|
let content = redraw();
|
||||||
|
|
@ -504,10 +520,13 @@ const featureChoices: Option<Partial<FeatureItem>>[] = [
|
||||||
{ text: 'Choix', value: { category: 'choice', text: '', options: [] }, },
|
{ text: 'Choix', value: { category: 'choice', text: '', options: [] }, },
|
||||||
];
|
];
|
||||||
const flattenFeatureChoices = Tree.accumulate(featureChoices, 'value', (item) => Array.isArray(item.value) ? undefined : item.value).filter(e => !!e) as Partial<FeatureItem>[];
|
const flattenFeatureChoices = Tree.accumulate(featureChoices, 'value', (item) => Array.isArray(item.value) ? undefined : item.value).filter(e => !!e) as Partial<FeatureItem>[];
|
||||||
function textFromEffect(effect: FeatureItem)
|
function textFromEffect(effect: Partial<FeatureItem>): string
|
||||||
{
|
{
|
||||||
if(effect.category === 'value')
|
if(effect.category === 'value')
|
||||||
{
|
{
|
||||||
|
if(effect.property === undefined)
|
||||||
|
return '';
|
||||||
|
|
||||||
switch(effect.property)
|
switch(effect.property)
|
||||||
{
|
{
|
||||||
case 'health':
|
case 'health':
|
||||||
|
|
@ -595,7 +614,7 @@ function textFromEffect(effect: FeatureItem)
|
||||||
case 'reaction':
|
case 'reaction':
|
||||||
case 'freeaction':
|
case 'freeaction':
|
||||||
case 'passive':
|
case 'passive':
|
||||||
return effect.action === 'add' ? effect.item : 'Suppression d\'effet.';
|
return effect.action === 'add' ? effect.item ?? '' : 'Suppression d\'effet.';
|
||||||
case 'spells':
|
case 'spells':
|
||||||
return effect.action === 'add' ? `Maitrise du sort "${config.spells.find(e => e.id === effect.item)?.name ?? 'Sort inconnu'}".` : `Perte de maitrise du sort "${config.spells.find(e => e.id === effect.item)?.name ?? 'Sort inconnu'}".`;
|
return effect.action === 'add' ? `Maitrise du sort "${config.spells.find(e => e.id === effect.item)?.name ?? 'Sort inconnu'}".` : `Perte de maitrise du sort "${config.spells.find(e => e.id === effect.item)?.name ?? 'Sort inconnu'}".`;
|
||||||
case 'sickness':
|
case 'sickness':
|
||||||
|
|
@ -604,14 +623,12 @@ function textFromEffect(effect: FeatureItem)
|
||||||
}
|
}
|
||||||
else if(effect.category === 'choice')
|
else if(effect.category === 'choice')
|
||||||
{
|
{
|
||||||
return `${effect.text} (${effect.options.length} options)`;
|
return `${effect.text} (${effect.options?.length ?? 0} options)`;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return `Inconnu`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return `Inconnu`;
|
||||||
}
|
}
|
||||||
function textFromValue(value: `modifier/${MainStat}` | number | false, settings?: {
|
function textFromValue(value?: `modifier/${MainStat}` | number | false, settings?: {
|
||||||
prefix?: { text?: string, positive?: string, negative?: string, truely?: string },
|
prefix?: { text?: string, positive?: string, negative?: string, truely?: string },
|
||||||
suffix?: { text?: string, positive?: string, negative?: string, truely?: string },
|
suffix?: { text?: string, positive?: string, negative?: string, truely?: string },
|
||||||
falsely?: string
|
falsely?: string
|
||||||
|
|
@ -619,7 +636,7 @@ function textFromValue(value: `modifier/${MainStat}` | number | false, settings?
|
||||||
{
|
{
|
||||||
if(typeof value === 'string')
|
if(typeof value === 'string')
|
||||||
return `${settings?.prefix?.truely?.replaceAll('(s)', 's') ?? ''}${settings?.prefix?.text?.replaceAll('(s)', 's') ?? ''}${mainStatShortTexts[value.split('/')[1] as MainStat] ?? 'inconnu'}${settings?.suffix?.text?.replaceAll('(s)', 's') ?? ''}${settings?.suffix?.truely?.replaceAll('(s)', 's') ?? ''}`;
|
return `${settings?.prefix?.truely?.replaceAll('(s)', 's') ?? ''}${settings?.prefix?.text?.replaceAll('(s)', 's') ?? ''}${mainStatShortTexts[value.split('/')[1] as MainStat] ?? 'inconnu'}${settings?.suffix?.text?.replaceAll('(s)', 's') ?? ''}${settings?.suffix?.truely?.replaceAll('(s)', 's') ?? ''}`;
|
||||||
else if(value === false)
|
else if(value === false || value === undefined)
|
||||||
return settings?.falsely ?? '0';
|
return settings?.falsely ?? '0';
|
||||||
else if(value >= 0)
|
else if(value >= 0)
|
||||||
return `${settings?.prefix?.truely?.replaceAll('(s)', value > 1 ? 's' : '') ?? ''}${settings?.prefix?.positive?.replaceAll('(s)', value > 1 ? 's' : '') ?? ''}${value.toString(10)}${settings?.suffix?.positive?.replaceAll('(s)', value > 1 ? 's' : '') ?? ''}${settings?.suffix?.truely?.replaceAll('(s)', value > 1 ? 's' : '') ?? ''}`;
|
return `${settings?.prefix?.truely?.replaceAll('(s)', value > 1 ? 's' : '') ?? ''}${settings?.prefix?.positive?.replaceAll('(s)', value > 1 ? 's' : '') ?? ''}${value.toString(10)}${settings?.suffix?.positive?.replaceAll('(s)', value > 1 ? 's' : '') ?? ''}${settings?.suffix?.truely?.replaceAll('(s)', value > 1 ? 's' : '') ?? ''}`;
|
||||||
|
|
|
||||||
|
|
@ -422,4 +422,14 @@ export function numberpicker(settings?: { defaultValue?: number, change?: (value
|
||||||
if(settings?.defaultValue) field.value = storedValue.toString(10);
|
if(settings?.defaultValue) field.value = storedValue.toString(10);
|
||||||
|
|
||||||
return field;
|
return field;
|
||||||
|
}
|
||||||
|
// Open by default
|
||||||
|
export function foldable(content: NodeChildren, title: NodeChildren, settings?: { open?: boolean, class?: { container?: Class, title?: Class, content?: Class, icon?: Class } })
|
||||||
|
{
|
||||||
|
const fold = div(['group flex flex-1 w-full flex-col', settings?.class?.container], [
|
||||||
|
div('flex', [ dom('div', { listeners: { click: () => 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' }) ]), div(['flex-1', settings?.class?.title], title) ]),
|
||||||
|
div(['hidden group-data-[active]:flex', settings?.class?.content], content),
|
||||||
|
]);
|
||||||
|
fold.toggleAttribute('data-active', settings?.open ?? true);
|
||||||
|
return fold;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue