diff --git a/db.sqlite b/db.sqlite index decab8c..e5b04e6 100644 Binary files a/db.sqlite and b/db.sqlite differ diff --git a/db.sqlite-shm b/db.sqlite-shm index c92b1c6..7e94629 100644 Binary files a/db.sqlite-shm and b/db.sqlite-shm differ diff --git a/db.sqlite-wal b/db.sqlite-wal index fcf177c..af3ed57 100644 Binary files a/db.sqlite-wal and b/db.sqlite-wal differ diff --git a/shared/feature.util.ts b/shared/feature.util.ts index e9f06d8..b8e2af2 100644 --- a/shared/feature.util.ts +++ b/shared/feature.util.ts @@ -1,7 +1,7 @@ import type { Ability, CharacterConfig, Feature, FeatureEffect, FeatureItem, MainStat } from "~/types/character"; import { div, dom, icon, text, type NodeChildren } from "./dom.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 { MAIN_STATS, mainStatShortTexts, mainStatTexts } from "./character.util"; import config from "#shared/character-config.json"; @@ -338,7 +338,7 @@ export class FeatureEditor MarkdownEditor.singleton.content = this._feature.description; }); } - private _renderEffect(effect: FeatureItem): HTMLDivElement + private _renderEffect(effect: Partial): 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', [ renderMarkdown(textFromEffect(effect), undefined, { tags: { a: fakeA } }) ]), @@ -351,7 +351,7 @@ export class FeatureEditor ]) ]); return content; } - private _edit(effect: FeatureItem): HTMLDivElement + private _edit(effect: Partial): HTMLDivElement { const match = (effect: FeatureItem): Partial | undefined => { switch(effect.category) @@ -383,14 +383,14 @@ export class FeatureEditor } let buffer = JSON.parse(JSON.stringify(effect)) as FeatureItem; - const redraw = () => { + const drawByCategory = (buffer: Partial) => { let top: NodeChildren = [], bottom: NodeChildren = []; switch(buffer.category) { case 'value': const summaryText = text(textFromEffect(buffer)); top = [ - select([ { text: '+', value: 'add' }, (['speed', 'capacity'].includes(buffer.property) || ['defense/'].some(e => (buffer as Extract).property.startsWith(e))) ? { text: '=', value: 'set' } : undefined ], { defaultValue: buffer.operation, change: (value) => { (buffer as Extract).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).property.startsWith(e))) ? { text: '=', value: 'set' } : undefined ], { defaultValue: buffer.operation, change: (value) => { (buffer as Extract).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).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).value = value; summaryText.textContent = textFromEffect(buffer); } }), button(icon('radix-icons:update'), () => { (buffer as Extract).value = (typeof (buffer as Extract).value === 'number' ? '' as any as false : 0); @@ -400,7 +400,7 @@ export class FeatureEditor 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'), ]; - bottom = [div('px-2 py-1', [summaryText])]; + bottom = [ div('px-2 py-1 flex items-center flex-1', [summaryText]) ]; break; case 'list': if(buffer.action === 'add') @@ -412,10 +412,10 @@ export class FeatureEditor else { const editor = new MarkdownEditor(); - editor.content = buffer.item; + editor.content = buffer.item ?? ''; editor.onChange = (item) => (buffer as Extract).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).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 option: Extract["options"][number] = { id: getID(ID_SIZE), category: 'value', text: '', operation: 'add', property: '', value: 0 }; (buffer as Extract).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'); - top = [ input('text', { defaultValue: buffer.text, input: (value) => (buffer as Extract).text = value, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full', placeholder: 'Description' }) ]; + 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).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; 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]' }, change: (e) => { @@ -445,7 +461,7 @@ export class FeatureEditor ...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-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(); @@ -504,10 +520,13 @@ const featureChoices: Option>[] = [ { 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[]; -function textFromEffect(effect: FeatureItem) +function textFromEffect(effect: Partial): string { if(effect.category === 'value') { + if(effect.property === undefined) + return ''; + switch(effect.property) { case 'health': @@ -595,7 +614,7 @@ function textFromEffect(effect: FeatureItem) case 'reaction': case 'freeaction': case 'passive': - return effect.action === 'add' ? effect.item : 'Suppression d\'effet.'; + return effect.action === 'add' ? effect.item ?? '' : 'Suppression d\'effet.'; 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'}".`; case 'sickness': @@ -604,14 +623,12 @@ function textFromEffect(effect: FeatureItem) } else if(effect.category === 'choice') { - return `${effect.text} (${effect.options.length} options)`; - } - else - { - return `Inconnu`; + return `${effect.text} (${effect.options?.length ?? 0} options)`; } + + 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 }, suffix?: { text?: string, positive?: string, negative?: string, truely?: string }, falsely?: string @@ -619,7 +636,7 @@ function textFromValue(value: `modifier/${MainStat}` | number | false, settings? { 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') ?? ''}`; - else if(value === false) + else if(value === false || value === undefined) return settings?.falsely ?? '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' : '') ?? ''}`; diff --git a/shared/proses.ts b/shared/proses.ts index e04bb2d..d2240ff 100644 --- a/shared/proses.ts +++ b/shared/proses.ts @@ -422,4 +422,14 @@ export function numberpicker(settings?: { defaultValue?: number, change?: (value if(settings?.defaultValue) field.value = storedValue.toString(10); 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; } \ No newline at end of file