From 06276b3fbc4b581a07540247cde70e26a17ca1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pons?= Date: Mon, 18 Aug 2025 17:42:07 +0200 Subject: [PATCH] Progress on option rendering --- db.sqlite | Bin 761856 -> 761856 bytes db.sqlite-shm | Bin 32768 -> 32768 bytes db.sqlite-wal | Bin 123632 -> 8272 bytes shared/feature.util.ts | 61 ++++++++++++++++++++++++++--------------- shared/proses.ts | 10 +++++++ 5 files changed, 49 insertions(+), 22 deletions(-) diff --git a/db.sqlite b/db.sqlite index decab8c14b144e8b2f0197ac2369225c24738841..e5b04e6a4a0fc96c29e47adc13829930ccd64176 100644 GIT binary patch delta 198 zcmZoTpx1CfZv#sJGe582WcC1ACSJeIh6)FbECR5hv#MQM0ul}livSMW57rJd z4+Rf{4Ydzhvlw7W4U<3+EVGMkauNfau%Uv0hJXQvfC7eq1BQSEhJXczfCh$u2Zn$M NhJXo%fC{F73m|;pBlrLS diff --git a/db.sqlite-shm b/db.sqlite-shm index c92b1c6139c404b559974a4c6fe379101904b87d..7e946291066d884205c5f5fcf795c406da7188d8 100644 GIT binary patch delta 160 zcmZo@U}|V!s+V}A%K!pQK+MR%AONCw0rBR1sa*5wQ`2smhY42oK8#_QlgsgX;rBhH pss|bc1|W0)BLS%J#CpNaEQ}ulCJQi@FfwjtWcB>}D?qIhx`-MiC%DfWYhs%w?hcv0G&&Ygtb> zy=-MCb@p>O{sVY+Hnj->0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N M0t5&UAn=>O7v$F|J^%m! diff --git a/db.sqlite-wal b/db.sqlite-wal index fcf177c0eda9f576fbd825513ee37c2a915f7eec..af3ed57a352a076b115b1da40b695fbcaf89f34f 100644 GIT binary patch delta 158 zcmexxl>LH(y?H%b6NBy}2?hoM1`xPu9wu1T`!MF2t<{^~Yzw%7LTo_13nu>O!9ioY z)fRTVKtX<9zlrv;OuT*@6E%4CbOc#KIa!iSQc{vkQ_^(JOe_s`O%l@*brTKD%yi8y tQVlJP&CHX`Q_V9Ldo0@?a)GgjNdRVSu2lZ!X{V~qHwOs(3$^&8(#L$}g^@{(+gLi*DBQ*Ef~FF+9=6W|kqUS(UBpqTLhralp(n zL^n&-W%+D_Eq9({Gs_6oEQ>AoCbun{YXURN2;D6CW7mY+fa z`g4PG1@V5FZ`T&F6T2gvl39v za=j6?`oe384=}S5(9PPn{(tu7H#+&)%t}NxtGdSj@twzC@4(DTL^taogX`n6uJ7I0 z%*sYJtBIvk`UC&27cjH3(aqYM+w2mwA7;kC~6nta4Pd&J}Dw%w%eD24+?{x>=`% joDQGs5V1xxYx5S~9z+RYJh|$(llhVeuqn)QHdX=v04S&t 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