Various fixes to select, combobox and feature editor.
This commit is contained in:
parent
658499749d
commit
247b14b2c8
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
|
|
@ -1,18 +1,16 @@
|
|||
import type { Ability, CharacterConfig, Feature, FeatureEffect, FeatureItem, MainStat } from "~/types/character";
|
||||
import type { Ability, CharacterConfig, Feature, FeatureEffect, FeatureItem, MainStat, Resistance } from "~/types/character";
|
||||
import { div, dom, icon, text, type NodeChildren } from "./dom.util";
|
||||
import { MarkdownEditor } from "./editor.util";
|
||||
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";
|
||||
import characterConfig from "#shared/character-config.json";
|
||||
import { clamp, getID, ID_SIZE } from "./general.util";
|
||||
import renderMarkdown from "./markdown.util";
|
||||
import { Tree } from "./tree";
|
||||
import markdownUtil from "./markdown.util";
|
||||
|
||||
const tabTexts: Record<string, string> = {
|
||||
|
||||
};
|
||||
const config = characterConfig as CharacterConfig;
|
||||
export class HomebrewBuilder
|
||||
{
|
||||
private _container: HTMLDivElement;
|
||||
|
|
@ -350,7 +348,7 @@ export class FeatureEditor
|
|||
div('px-4 flex items-center h-full', [ renderMarkdown(textFromEffect(effect), undefined, { tags: { a: fakeA } }) ]),
|
||||
div('flex', [ tooltip(button(icon('radix-icons:pencil-1'), () => {
|
||||
this._table.replaceChild(this._edit(effect), content);
|
||||
}, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Modifier", "bottom"), tooltip(button(icon('radix-icons:trash'), () => {
|
||||
}, '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._feature!.effect = this._feature!.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") ])
|
||||
|
|
@ -398,7 +396,7 @@ export class FeatureEditor
|
|||
const summaryText = text(textFromEffect(buffer));
|
||||
let valueSelection = valueVariable();
|
||||
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([ (['action', 'reaction'].includes(buffer.property ?? '') ? undefined : { text: '+', value: 'add' }), (['speed', 'capacity', 'action', 'reaction'].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]' } }),
|
||||
valueSelection,
|
||||
tooltip(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);
|
||||
|
|
@ -426,7 +424,16 @@ export class FeatureEditor
|
|||
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' } }) ];
|
||||
else
|
||||
{
|
||||
bottom = [ select(Object.values(config.features).flatMap(e => e.effect).filter(e => e.category === 'list' && e.list === buffer.list && e.action === 'add').map(e => ({ text: buffer.list !== 'spells' ? (e as Extract<FeatureItem, { category: 'list' }>).item : config.spells.find(f => f.id === (e as Extract<FeatureItem, { category: 'list' }>).item)?.name ?? '', value: e.id })), { defaultValue: buffer.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' } }) ];
|
||||
}
|
||||
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';
|
||||
const element = redraw();
|
||||
content?.parentElement?.replaceChild(element, content);
|
||||
content = element;
|
||||
}, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-32' } }) ];
|
||||
break;
|
||||
case 'choice':
|
||||
const add = () => {
|
||||
|
|
@ -482,63 +489,67 @@ export class FeatureEditor
|
|||
}
|
||||
|
||||
const featureChoices: Option<Partial<FeatureItem>>[] = [
|
||||
{ text: 'PV max', value: { category: 'value', property: 'health', operation: 'add', value: 0 }, },
|
||||
{ text: 'Mana max', value: { category: 'value', property: 'mana', operation: 'add', value: 0 }, },
|
||||
{ text: 'Nombre de sorts maitrisés', value: { category: 'value', property: 'spellslots', operation: 'add', value: 0 }, },
|
||||
{ text: 'Nombre d\'œuvres maitrisés', value: { category: 'value', property: 'artslots', operation: 'add', value: 0 }, },
|
||||
{ text: 'Vitesse de course', value: { category: 'value', property: 'speed', operation: 'add', value: 0 }, },
|
||||
{ text: 'Poids max', value: { category: 'value', property: 'capacity', operation: 'add', value: 0 }, },
|
||||
{ text: 'Initiative', value: { category: 'value', property: 'initiative', operation: 'add', value: 0 }, },
|
||||
{ text: 'Points d\'entrainement', value: { category: 'value', property: 'training', operation: 'add', value: 0 }, },
|
||||
{ text: 'Points de compétence', value: { category: 'value', property: 'ability', operation: 'add', value: 0 }, },
|
||||
{ text: 'PV max', value: { category: 'value', property: 'health', operation: 'add', value: 1 }, },
|
||||
{ text: 'Mana max', value: { category: 'value', property: 'mana', operation: 'add', value: 1 }, },
|
||||
{ text: 'Nombre de sorts maitrisés', value: { category: 'value', property: 'spellslots', operation: 'add', value: 1 }, },
|
||||
{ text: 'Nombre d\'œuvres maitrisés', value: { category: 'value', property: 'artslots', operation: 'add', value: 1 }, },
|
||||
{ text: 'Vitesse de course', value: { category: 'value', property: 'speed', operation: 'add', value: 1 }, },
|
||||
{ text: 'Poids max', value: { category: 'value', property: 'capacity', operation: 'add', value: 1 }, },
|
||||
{ text: 'Initiative', value: { category: 'value', property: 'initiative', operation: 'add', value: 1 }, },
|
||||
{ text: 'Points d\'entrainement', value: { category: 'value', property: 'training', operation: 'add', value: 1 }, },
|
||||
{ text: 'Points de compétence', value: { category: 'value', property: 'ability', operation: 'add', value: 1 }, },
|
||||
{ text: 'Sort bonus', value: { category: 'list', list: 'spells', action: 'add' }, },
|
||||
{ text: 'Point d\'action', value: { category: 'value', property: 'action', operation: 'set', value: 1 }, },
|
||||
{ text: 'Point de réaction', value: { category: 'value', property: 'reaction', operation: 'set', value: 1 }, },
|
||||
{ text: 'Puissance magique', value: { category: 'value', property: 'itempower', operation: 'add', value: 1 }, },
|
||||
{ text: 'Spécialisation', value: { category: 'value', property: 'spec', operation: 'add', value: 1 }, },
|
||||
{ text: 'Défense', value: [
|
||||
{ text: 'Défense max', value: { category: 'value', property: 'defense/hardcap', operation: 'add', value: 0 } },
|
||||
{ text: 'Défense fixe', value: { category: 'value', property: 'defense/static', operation: 'add', value: 0 } },
|
||||
{ text: 'Parade active', value: { category: 'value', property: 'defense/activeparry', operation: 'add', value: 0 } },
|
||||
{ text: 'Parade passive', value: { category: 'value', property: 'defense/passiveparry', operation: 'add', value: 0 } },
|
||||
{ text: 'Esquive active', value: { category: 'value', property: 'defense/activedodge', operation: 'add', value: 0 } },
|
||||
{ text: 'Esquive passive', value: { category: 'value', property: 'defense/passivedodge', operation: 'add', value: 0 } }
|
||||
{ text: 'Défense max', value: { category: 'value', property: 'defense/hardcap', operation: 'add', value: 1 } },
|
||||
{ text: 'Défense fixe', value: { category: 'value', property: 'defense/static', operation: 'add', value: 1 } },
|
||||
{ text: 'Parade active', value: { category: 'value', property: 'defense/activeparry', operation: 'add', value: 1 } },
|
||||
{ text: 'Parade passive', value: { category: 'value', property: 'defense/passiveparry', operation: 'add', value: 1 } },
|
||||
{ text: 'Esquive active', value: { category: 'value', property: 'defense/activedodge', operation: 'add', value: 1 } },
|
||||
{ text: 'Esquive passive', value: { category: 'value', property: 'defense/passivedodge', operation: 'add', value: 1 } }
|
||||
] },
|
||||
{ text: 'Maitrise', value: [
|
||||
{ text: 'Maitrise des armes (for.)', value: { category: 'value', property: 'mastery/strength', operation: 'add', value: 0 } },
|
||||
{ text: 'Maitrise des armes (dex.)', value: { category: 'value', property: 'mastery/dexterity', operation: 'add', value: 0 } },
|
||||
{ text: 'Maitrise des boucliers', value: { category: 'value', property: 'mastery/shield', operation: 'add', value: 0 } },
|
||||
{ text: 'Maitrise des armure', value: { category: 'value', property: 'mastery/armor', operation: 'add', value: 0 } },
|
||||
{ text: 'Attaque multiple', value: { category: 'value', property: 'mastery/multiattack', operation: 'add', value: 0 } },
|
||||
{ text: 'Arbre de magie (Puissance)', value: { category: 'value', property: 'mastery/magicpower', operation: 'add', value: 0 } },
|
||||
{ text: 'Arbre de magie (Rapidité)', value: { category: 'value', property: 'mastery/magicspeed', operation: 'add', value: 0 } },
|
||||
{ text: 'Arbre de magie (Elements)', value: { category: 'value', property: 'mastery/magicelement', operation: 'add', value: 0 } },
|
||||
{ text: 'Arbre de magie (Instinct)', value: { category: 'value', property: 'mastery/magicinstinct', operation: 'add', value: 0 } }
|
||||
{ text: 'Maitrise des armes (for.)', value: { category: 'value', property: 'mastery/strength', operation: 'add', value: 1 } },
|
||||
{ text: 'Maitrise des armes (dex.)', value: { category: 'value', property: 'mastery/dexterity', operation: 'add', value: 1 } },
|
||||
{ text: 'Maitrise des boucliers', value: { category: 'value', property: 'mastery/shield', operation: 'add', value: 1 } },
|
||||
{ text: 'Maitrise des armure', value: { category: 'value', property: 'mastery/armor', operation: 'add', value: 1 } },
|
||||
{ text: 'Attaque multiple', value: { category: 'value', property: 'mastery/multiattack', operation: 'add', value: 1 } },
|
||||
{ text: 'Arbre de magie (Puissance)', value: { category: 'value', property: 'mastery/magicpower', operation: 'add', value: 1 } },
|
||||
{ text: 'Arbre de magie (Rapidité)', value: { category: 'value', property: 'mastery/magicspeed', operation: 'add', value: 1 } },
|
||||
{ text: 'Arbre de magie (Elements)', value: { category: 'value', property: 'mastery/magicelement', operation: 'add', value: 1 } },
|
||||
{ text: 'Arbre de magie (Instinct)', value: { category: 'value', property: 'mastery/magicinstinct', operation: 'add', value: 1 } }
|
||||
] },
|
||||
{ text: 'Compétence', value: Object.keys(config.abilities).map((e) => ({ text: config.abilities[e as keyof typeof config.abilities].name, value: { category: 'value', property: `abilities/${e}`, operation: 'add', value: 0 } })) },
|
||||
{ text: 'Modifier', value: [
|
||||
{ text: 'Modifier de force', value: { category: 'value', property: 'modifier/strength', operation: 'add', value: 0 } },
|
||||
{ text: 'Modifier de dextérité', value: { category: 'value', property: 'modifier/dexterity', operation: 'add', value: 0 } },
|
||||
{ text: 'Modifier de constitution', value: { category: 'value', property: 'modifier/constitution', operation: 'add', value: 0 } },
|
||||
{ text: 'Modifier d\'intelligence', value: { category: 'value', property: 'modifier/intelligence', operation: 'add', value: 0 } },
|
||||
{ text: 'Modifier de curiosité', value: { category: 'value', property: 'modifier/curiosity', operation: 'add', value: 0 } },
|
||||
{ text: 'Modifier de charisme', value: { category: 'value', property: 'modifier/charisma', operation: 'add', value: 0 } },
|
||||
{ text: 'Modifier de psyché', value: { category: 'value', property: 'modifier/psyche', operation: 'add', value: 0 } },
|
||||
{ text: 'Compétence', value: Object.keys(config.abilities).map((e) => ({ text: config.abilities[e as keyof typeof config.abilities].name, value: { category: 'value', property: `abilities/${e}`, operation: 'add', value: 1 } })) },
|
||||
{ text: 'Modifieur', value: [
|
||||
{ text: 'Modifieur de force', value: { category: 'value', property: 'modifier/strength', operation: 'add', value: 1 } },
|
||||
{ text: 'Modifieur de dextérité', value: { category: 'value', property: 'modifier/dexterity', operation: 'add', value: 1 } },
|
||||
{ text: 'Modifieur de constitution', value: { category: 'value', property: 'modifier/constitution', operation: 'add', value: 1 } },
|
||||
{ text: 'Modifieur d\'intelligence', value: { category: 'value', property: 'modifier/intelligence', operation: 'add', value: 1 } },
|
||||
{ text: 'Modifieur de curiosité', value: { category: 'value', property: 'modifier/curiosity', operation: 'add', value: 1 } },
|
||||
{ text: 'Modifieur de charisme', value: { category: 'value', property: 'modifier/charisma', operation: 'add', value: 1 } },
|
||||
{ text: 'Modifieur de psyché', value: { category: 'value', property: 'modifier/psyche', operation: 'add', value: 1 } },
|
||||
//@ts-ignore
|
||||
{ text: 'Modifier au choix', value: { category: 'choice', text: '+1 au modifier de ', options: [
|
||||
{ text: 'Modifier de force', category: 'value', property: 'modifier/strength', operation: 'add', value: 1 },
|
||||
{ text: 'Modifier de dextérité', category: 'value', property: 'modifier/dexterity', operation: 'add', value: 1 },
|
||||
{ text: 'Modifier de constitution', category: 'value', property: 'modifier/constitution', operation: 'add', value: 1 },
|
||||
{ text: 'Modifier d\'intelligence', category: 'value', property: 'modifier/intelligence', operation: 'add', value: 1 },
|
||||
{ text: 'Modifier de curiosité', category: 'value', property: 'modifier/curiosity', operation: 'add', value: 1 },
|
||||
{ text: 'Modifier de charisme', category: 'value', property: 'modifier/charisma', operation: 'add', value: 1 },
|
||||
{ text: 'Modifier de psyché', egory: 'value', property: 'modifier/psyche', operation: 'add', value: 1 }
|
||||
{ text: 'Modifieur au choix', value: { category: 'choice', text: '+1 au modifieur de ', options: [
|
||||
{ text: 'Modifieur de force', category: 'value', property: 'modifier/strength', operation: 'add', value: 1 },
|
||||
{ text: 'Modifieur de dextérité', category: 'value', property: 'modifier/dexterity', operation: 'add', value: 1 },
|
||||
{ text: 'Modifieur de constitution', category: 'value', property: 'modifier/constitution', operation: 'add', value: 1 },
|
||||
{ text: 'Modifieur d\'intelligence', category: 'value', property: 'modifier/intelligence', operation: 'add', value: 1 },
|
||||
{ text: 'Modifieur de curiosité', category: 'value', property: 'modifier/curiosity', operation: 'add', value: 1 },
|
||||
{ text: 'Modifieur de charisme', category: 'value', property: 'modifier/charisma', operation: 'add', value: 1 },
|
||||
{ text: 'Modifieur de psyché', egory: 'value', property: 'modifier/psyche', operation: 'add', value: 1 }
|
||||
]}}
|
||||
] },
|
||||
{ text: 'Jet de résistance', value: [
|
||||
{ text: 'Force', value: { category: 'value', property: 'bonus/defense/strength', operation: 'add', value: 0 } },
|
||||
{ text: 'Dextérité', value: { category: 'value', property: 'bonus/defense/dexterity', operation: 'add', value: 0 } },
|
||||
{ text: 'Constitution', value: { category: 'value', property: 'bonus/defense/constitution', operation: 'add', value: 0 } },
|
||||
{ text: 'Intelligence', value: { category: 'value', property: 'bonus/defense/intelligence', operation: 'add', value: 0 } },
|
||||
{ text: 'Curiosité', value: { category: 'value', property: 'bonus/defense/curiosity', operation: 'add', value: 0 } },
|
||||
{ text: 'Charisme', value: { category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 0 } },
|
||||
{ text: 'Psyché', value: { category: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 0 } },
|
||||
{ text: 'Force', value: { category: 'value', property: 'bonus/defense/strength', operation: 'add', value: 1 } },
|
||||
{ text: 'Dextérité', value: { category: 'value', property: 'bonus/defense/dexterity', operation: 'add', value: 1 } },
|
||||
{ text: 'Constitution', value: { category: 'value', property: 'bonus/defense/constitution', operation: 'add', value: 1 } },
|
||||
{ text: 'Intelligence', value: { category: 'value', property: 'bonus/defense/intelligence', operation: 'add', value: 1 } },
|
||||
{ text: 'Curiosité', value: { category: 'value', property: 'bonus/defense/curiosity', operation: 'add', value: 1 } },
|
||||
{ text: 'Charisme', value: { category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 1 } },
|
||||
{ text: 'Psyché', value: { category: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 } },
|
||||
//@ts-ignore
|
||||
{ text: 'Résistance au choix', value: { category: 'choice', text: '+1 au jet de résistance de ', options: [
|
||||
{ text: 'Force', category: 'value', property: 'bonus/defense/strength', operation: 'add', value: 1 },
|
||||
|
|
@ -550,6 +561,14 @@ const featureChoices: Option<Partial<FeatureItem>>[] = [
|
|||
{ text: 'Psyché', egory: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 }
|
||||
]}}
|
||||
] },
|
||||
{ text: 'Bonus', value: Object.keys(config.resistances).map((e: Resistance) => ({ text: config.resistances[e]!.name, value: { category: 'value', property: `resistance/${e}`, operation: 'add', value: 1 } })) },
|
||||
{ text: 'Rang', value: [
|
||||
{ text: 'Sorts de précision', value: { category: 'value', property: 'spellranks/precision', operation: 'add', value: 1 } },
|
||||
{ text: 'Sorts de savoir', value: { category: 'value', property: 'spellranks/knowledge', operation: 'add', value: 1 } },
|
||||
{ text: 'Sorts d\'instinct', value: { category: 'value', property: 'spellranks/instinct', operation: 'add', value: 1 } },
|
||||
{ text: 'Œuvres', value: { category: 'value', property: 'spellranks/arts', operation: 'add', value: 1 } },
|
||||
] },
|
||||
{ text: 'Fatigue supportable', value: { category: 'value', property: 'exhaust', operation: 'add', value: 1 } },
|
||||
{ text: 'Action', value: { category: 'list', list: 'action', action: 'add' }, },
|
||||
{ text: 'Réaction', value: { category: 'list', list: 'reaction', action: 'add' }, },
|
||||
{ text: 'Action libre', value: { category: 'list', list: 'freeaction', action: 'add' }, },
|
||||
|
|
@ -584,6 +603,16 @@ function textFromEffect(effect: Partial<FeatureItem>): string
|
|||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' point(s) d\'entrainement.' } }) : `Opération interdite (Entrainement fixe).`;
|
||||
case 'ability':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' point(s) de compétence.' } }) : `Opération interdite (Compétences fixe).`;
|
||||
case 'spec':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' spécialisation(s).' } }) : `Opération interdite (Spécialisation fixe).`;
|
||||
case 'itempower':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' puissance magique supportable.' } }) : `Opération interdite (Puissance magique fixe).`;
|
||||
case 'action':
|
||||
return effect.operation === 'add' ? `Opération interdite (Point d'action bonus).` : textFromValue(effect.value, { suffix: { truely: ' point(s) d\'action par tour.' }, falsely: 'Opération interdite (Action = interdit).' });
|
||||
case 'reaction':
|
||||
return effect.operation === 'add' ? `Opération interdite (Point de réaction bonus).` : textFromValue(effect.value, { suffix: { truely: ' point(s) de réaction par tour.' }, falsely: 'Opération interdite (Réaction = interdit).' });
|
||||
case 'exhaust':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: 'Vous êtes capable de supporter ', positive: '+', text: '+Mod. de ' }, suffix: { truely: ' point(s) de fatigue avant de subir les effets de la fatigue.' } }) : `Opération interdite (Fatigue fixe).`;
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
|
@ -591,7 +620,18 @@ function textFromEffect(effect: Partial<FeatureItem>): string
|
|||
switch(splited[0])
|
||||
{
|
||||
case 'spellranks':
|
||||
return '';
|
||||
switch(splited[1])
|
||||
{
|
||||
case 'precision':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' rang de sort de précision.' } }) : `Opération interdite (Rang de sorts de précision fixe).`;
|
||||
case 'knowledge':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' rang de sort de savoir.' } }) : `Opération interdite (Rang de sorts de savoir fixe).`;
|
||||
case 'instinct':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' rang de sort d\'instinct.' } }) : `Opération interdite (Rang de sorts d\'instinct fixe).`;
|
||||
case 'arts':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' rang d\'œuvres.' } }) : `Opération interdite (Rang d\'œuvres fixe).`;
|
||||
default: return 'Type de sort inconnu.';
|
||||
}
|
||||
case 'defense':
|
||||
switch(splited[1])
|
||||
{
|
||||
|
|
@ -632,8 +672,8 @@ function textFromEffect(effect: Partial<FeatureItem>): string
|
|||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: 'Arbre de magie (Instinct) ', positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : textFromValue(effect.value, { prefix: { truely: 'Arbre de magie (Instinct) fixée à ' }, suffix: { truely: '.' }, falsely: 'Opération interdite (Maitrise instinct = interdit).' });
|
||||
default: return 'Maitrise inconnue.';
|
||||
}
|
||||
/* case 'resistance':
|
||||
return splited[1] ? config.resistances[splited[1] as string].name : 'résistance inconnue'; */
|
||||
case 'resistance':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `Difficulté des jets de résistance de ${config.resistances[splited[1] as Resistance]!.name} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : textFromValue(effect.value, { prefix: { truely: `Difficulté des jets de résistance de ${config.resistances[splited[1] as Resistance]!.name} fixé à ` }, suffix: { truely: '.' }, falsely: `Opération interdite (${config.resistances[splited[1] as Resistance]!.name} = interdit).` });
|
||||
case 'abilities':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `${config.abilities[splited[1] as Ability].name} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : textFromValue(effect.value, { prefix: { truely: `${config.abilities[splited[1] as Ability].name} fixé à ` }, suffix: { truely: '.' }, falsely: `Echec automatique de ${`${config.abilities[splited[1] as Ability].name}.`}` });
|
||||
case 'modifier':
|
||||
|
|
@ -655,7 +695,7 @@ function textFromEffect(effect: Partial<FeatureItem>): string
|
|||
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':
|
||||
return effect.action === 'add' ? `Vous subisez en permanence la maladie "${config.lists.sickness.find(e => e.id === effect.item)?.name ?? 'Maladie inconnue'}".` : `Vous ne subisez plus la maladie "${config.lists.sickness.find(e => e.id === effect.item)?.name ?? 'Maladie inconnue'}".`;
|
||||
return effect.action === 'add' ? `Vous subisez en permanence la maladie "${config.lists.sickness?.find(e => e.id === effect.item)?.name ?? 'Maladie inconnue'}".` : `Vous ne subisez plus la maladie "${config.lists.sickness?.find(e => e.id === effect.item)?.name ?? 'Maladie inconnue'}".`;
|
||||
}
|
||||
}
|
||||
else if(effect.category === 'choice')
|
||||
|
|
|
|||
|
|
@ -235,14 +235,23 @@ export function button(content: Node, onClick?: () => void, cls?: Class)
|
|||
disabled:bg-light-10 dark:disabled:bg-dark-10 disabled:border-none disabled:text-light-50 dark:disabled:text-dark-50`, cls], listeners: { click: onClick } }, [ content ]);
|
||||
}
|
||||
export type Option<T> = { text: string, value: T | Option<T>[] } | undefined;
|
||||
type StoredOption<T> = { item: Option<T>, dom: HTMLElement, container?: HTMLElement, children?: Array<StoredOption<T> | undefined> };
|
||||
type StoredOption<T> = { item: Option<T>, dom: HTMLElement, container?: HTMLElement, children?: Array<StoredOption<T> | undefined>, index: number };
|
||||
export function select<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLElement
|
||||
{
|
||||
let close: Function | undefined;
|
||||
let context: { close: Function };
|
||||
let focused: number | undefined;
|
||||
|
||||
options = options.filter(e => !!e);
|
||||
|
||||
const focus = (i?: number) => {
|
||||
focused !== undefined && optionElements[focused]?.toggleAttribute('data-focused', false);
|
||||
i !== undefined && optionElements[i]?.toggleAttribute('data-focused', true) && optionElements[i]?.scrollIntoView({ behavior: 'instant', block: 'nearest' });
|
||||
focused = i;
|
||||
}
|
||||
|
||||
let disabled = settings?.disabled ?? false;
|
||||
const textValue = text(options.find(e => Array.isArray(e) ? false : e?.value === settings?.defaultValue)?.text ?? '');
|
||||
const optionElements = options.map(e => {
|
||||
const optionElements = options.map((e, i) => {
|
||||
if(e === undefined)
|
||||
return;
|
||||
|
||||
|
|
@ -250,14 +259,40 @@ export function select<T extends NonNullable<any>>(options: Array<{ text: string
|
|||
textValue.textContent = e.text;
|
||||
settings?.change && settings?.change(e.value);
|
||||
close && close();
|
||||
} }, class: ['hover:bg-light-30 dark:hover:bg-dark-30 text-light-70 dark:text-dark-70 hover:text-light-100 dark:hover:text-dark-100 py-1 px-2 cursor-pointer', settings?.class?.option] }, [ text(e.text) ]);
|
||||
}, mouseenter: (e) => focus(i) }, class: ['data-[focused]:bg-light-30 dark:data-[focused]:bg-dark-30 text-light-70 dark:text-dark-70 data-[focused]:text-light-100 dark:data-[focused]:text-dark-100 py-1 px-2 cursor-pointer', settings?.class?.option] }, [ text(e.text) ]);
|
||||
});
|
||||
const select = dom('div', { listeners: { click: () => {
|
||||
if(disabled)
|
||||
return;
|
||||
|
||||
const handleKeys = (e: KeyboardEvent) => {
|
||||
switch(e.key.toLocaleLowerCase())
|
||||
{
|
||||
case 'arrowdown':
|
||||
focus(clamp((focused ?? -1) + 1, 0, options.length - 1));
|
||||
return;
|
||||
case 'arrowup':
|
||||
focus(clamp((focused ?? 1) - 1, 0, options.length - 1));
|
||||
return;
|
||||
case 'pageup':
|
||||
focus(0);
|
||||
return;
|
||||
case 'pagedown':
|
||||
focus(optionElements.length - 1);
|
||||
return;
|
||||
case 'enter':
|
||||
focused && optionElements[focused]?.click();
|
||||
return;
|
||||
case 'escape':
|
||||
context?.close();
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', handleKeys);
|
||||
|
||||
const box = select.getBoundingClientRect();
|
||||
close = contextmenu(box.x, box.y + box.height, optionElements.filter(e => !!e).length > 0 ? optionElements : [ div('text-light-60 dark:text-dark-60 italic text-center px-2 py-1', [ text('Aucune option') ]) ], { placement: "bottom-start", class: ['flex flex-col max-h-[320px] overflow-auto', settings?.class?.popup], style: { "min-width": `${box.width}px` } }).close;
|
||||
context = contextmenu(box.x, box.y + box.height, optionElements.filter(e => !!e).length > 0 ? optionElements : [ div('text-light-60 dark:text-dark-60 italic text-center px-2 py-1', [ text('Aucune option') ]) ], { placement: "bottom-start", class: ['flex flex-col max-h-[320px] overflow-auto', settings?.class?.popup], style: { "min-width": `${box.width}px` }, blur: () => window.removeEventListener('keydown', handleKeys) });
|
||||
} }, class: ['mx-4 inline-flex items-center justify-between px-3 text-sm font-semibold leading-none h-8 gap-1 bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:border-light-25 dark: data-[disabled]:border-dark-25 data-[disabled]:bg-light-20 dark: data-[disabled]:bg-dark-20', settings?.class?.container] }, [ dom('span', {}, [ textValue ]), icon('radix-icons:caret-down') ]);
|
||||
|
||||
Object.defineProperty(select, 'disabled', {
|
||||
|
|
@ -273,12 +308,19 @@ export function combobox<T extends NonNullable<any>>(options: Option<T>[], setti
|
|||
{
|
||||
let context: { container: HTMLElement, content: NodeChildren, close: () => void };
|
||||
let selected = true, tree: StoredOption<T>[] = [];
|
||||
let focused: number | undefined;
|
||||
|
||||
const focus = (i?: number) => {
|
||||
focused !== undefined && (tree.slice(-1)[0]?.children ?? optionElements)[focused]?.dom.toggleAttribute('data-focused', false);
|
||||
i !== undefined && (tree.slice(-1)[0]?.children ?? optionElements)[i]?.dom.toggleAttribute('data-focused', true) && (tree.slice(-1)[0]?.children ?? optionElements)[i]?.dom.scrollIntoView({ behavior: 'instant', block: 'nearest' });
|
||||
focused = i;
|
||||
}
|
||||
const show = () => {
|
||||
if(disabled || (context && context.container.parentElement))
|
||||
return;
|
||||
|
||||
const box = container.getBoundingClientRect();
|
||||
focus();
|
||||
context = contextmenu(box.x, box.y + box.height, optionElements.filter(e => !!e).length > 0 ? optionElements.map(e => e?.dom) : [ div('text-light-60 dark:text-dark-60 italic text-center px-2 py-1', [ text('Aucune option') ]) ], { placement: "bottom-start", class: ['flex flex-col max-h-[320px] overflow-y-auto overflow-x-hidden', settings?.class?.popup], style: { "min-width": `${box.width}px` }, blur: hide });
|
||||
if(!selected) container.classList.remove('!border-light-red', 'dark:!border-dark-red');
|
||||
};
|
||||
|
|
@ -292,12 +334,9 @@ export function combobox<T extends NonNullable<any>>(options: Option<T>[], setti
|
|||
if(!context || !context.container.parentElement || option.container === undefined)
|
||||
return;
|
||||
|
||||
const redrawn = render(option.item)?.container;
|
||||
if(redrawn)
|
||||
{
|
||||
context.container.replaceChildren(redrawn);
|
||||
context.container.replaceChildren(option.container);
|
||||
tree.push(option);
|
||||
}
|
||||
focus();
|
||||
};
|
||||
const back = () => {
|
||||
tree.pop();
|
||||
|
|
@ -305,7 +344,7 @@ export function combobox<T extends NonNullable<any>>(options: Option<T>[], setti
|
|||
|
||||
last ? context.container.replaceChildren(last.container ?? last.dom) : context.container.replaceChildren(...optionElements.filter(e => !!e).map(e => e.dom));
|
||||
};
|
||||
const render = (option: Option<T>): StoredOption<T> | undefined => {
|
||||
const render = (option: Option<T>, i: number): StoredOption<T> | undefined => {
|
||||
if(option === undefined)
|
||||
return;
|
||||
|
||||
|
|
@ -313,17 +352,17 @@ export function combobox<T extends NonNullable<any>>(options: Option<T>[], setti
|
|||
{
|
||||
const children = option.value.map(render);
|
||||
|
||||
const stored = { item: option, dom: dom('div', { listeners: { click: () => progress(stored) }, class: ['hover:bg-light-30 dark:hover:bg-dark-30 text-light-70 dark:text-dark-70 hover:text-light-100 dark:hover:text-dark-100 py-1 px-2 cursor-pointer flex justify-between items-center', settings?.class?.option] }, [ text(option.text), icon('radix-icons:caret-right', { width: 20, height: 20 }) ]), container: div('flex flex-1 flex-col', [div('flex flex-row justify-between items-center text-light-100 dark:text-dark-100 py-1 px-2 text-sm select-none sticky top-0 bg-light-20 dark:bg-dark-20 font-semibold', [button(icon('radix-icons:caret-left', { width: 16, height: 16 }), back, 'p-px'), text(option.text), div()]), div('flex flex-col flex-1', children.map(e => e?.dom))]), children };
|
||||
const stored = { index: i, item: option, dom: dom('div', { listeners: { click: () => progress(stored), mouseenter: () => focus(i) }, class: ['data-[focused]:bg-light-30 dark:data-[focused]:bg-dark-30 text-light-70 dark:text-dark-70 data-[focused]:text-light-100 dark:data-[focused]:text-dark-100 py-1 px-2 cursor-pointer flex justify-between items-center', settings?.class?.option] }, [ text(option.text), icon('radix-icons:caret-right', { width: 20, height: 20 }) ]), container: div('flex flex-1 flex-col', [div('flex flex-row justify-between items-center text-light-100 dark:text-dark-100 py-1 px-2 text-sm select-none sticky top-0 bg-light-20 dark:bg-dark-20 font-semibold', [button(icon('radix-icons:caret-left', { width: 16, height: 16 }), back, 'p-px'), text(option.text), div()]), div('flex flex-col flex-1', children.map(e => e?.dom))]), children };
|
||||
return stored;
|
||||
}
|
||||
else
|
||||
{
|
||||
return { item: option, dom: dom('div', { listeners: { click: () => {
|
||||
return { index: i, item: option, dom: dom('div', { listeners: { click: () => {
|
||||
select.value = option.text;
|
||||
settings?.change && settings?.change(option.value as T);
|
||||
selected = true;
|
||||
hide();
|
||||
} }, class: ['hover:bg-light-30 dark:hover:bg-dark-30 text-light-70 dark:text-dark-70 hover:text-light-100 dark:hover:text-dark-100 py-1 px-2 cursor-pointer', settings?.class?.option] }, [ text(option.text) ]) };
|
||||
}, mouseenter: () => focus(i) }, class: ['data-[focused]:bg-light-30 dark:data-[focused]:bg-dark-30 text-light-70 dark:text-dark-70 data-[focused]:text-light-100 dark:data-[focused]:text-dark-100 py-1 px-2 cursor-pointer', settings?.class?.option] }, [ text(option.text) ]) };
|
||||
}
|
||||
}
|
||||
const filter = (value: string, option?: StoredOption<T>): HTMLElement[] => {
|
||||
|
|
@ -347,6 +386,31 @@ export function combobox<T extends NonNullable<any>>(options: Option<T>[], setti
|
|||
context && select.value ? context.container.replaceChildren(...optionElements.flatMap(e => filter(select.value.toLowerCase().trim().normalize(), e))) : context.container.replaceChildren(...optionElements.filter(e => !!e).map(e => e.dom));
|
||||
selected = false;
|
||||
if(!context || !context.container.parentElement) container.classList.add('!border-light-red', 'dark:!border-dark-red')
|
||||
}, keydown: (e) => {
|
||||
|
||||
const opt = (tree.slice(-1)[0]?.item?.value as Option<T>[] ?? options.filter(e => !!e)).filter(e => !!e), elements = (tree.slice(-1)[0]?.children ?? optionElements);
|
||||
switch(e.key.toLocaleLowerCase())
|
||||
{
|
||||
case 'arrowdown':
|
||||
focus(clamp((focused ?? -1) + 1, 0, opt.length - 1));
|
||||
return;
|
||||
case 'arrowup':
|
||||
focus(clamp((focused ?? 1) - 1, 0, opt.length - 1));
|
||||
return;
|
||||
case 'pageup':
|
||||
focus(0);
|
||||
return;
|
||||
case 'pagedown':
|
||||
focus(opt.length - 1);
|
||||
return;
|
||||
case 'enter':
|
||||
focused && elements[focused]?.dom.click();
|
||||
return;
|
||||
case 'escape':
|
||||
context?.close();
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
} }, attributes: { type: 'text', }, class: 'flex-1 outline-none px-3 leading-none appearance-none py-1 bg-light-25 dark:bg-dark-25 disabled:bg-light-20 dark:disabled:bg-dark-20' });
|
||||
settings?.defaultValue && Tree.each(options, 'value', (item) => { if(item.value === settings?.defaultValue) select.value = item.text });
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export type Category = typeof CATEGORIES[number];
|
|||
export type SpellElement = typeof SPELL_ELEMENTS[number];
|
||||
|
||||
export type FeatureID = string;
|
||||
export type Resistance = string;
|
||||
export type Alignment = { loyalty: 'loyal' | 'neutral' | 'chaotic', kindness: 'good' | 'neutral' | 'evil' };
|
||||
|
||||
export type Character = {
|
||||
|
|
@ -37,12 +38,14 @@ export type Character = {
|
|||
export type CharacterVariables = {
|
||||
health: number;
|
||||
mana: number;
|
||||
exhaustion: number;
|
||||
|
||||
sickness: Array<{ id: string, progress: number | true }>;
|
||||
equipment: Array<string>;
|
||||
};
|
||||
export type CharacterConfig = {
|
||||
peoples: RaceConfig[],
|
||||
peoples: RaceConfig[];
|
||||
resistances: Record<Resistance, { name: string, statistic: MainStat }>;
|
||||
training: Record<MainStat, Record<TrainingLevel, FeatureID[]>>;
|
||||
abilities: Record<Ability, AbilityConfig>;
|
||||
spells: SpellConfig[];
|
||||
|
|
@ -129,6 +132,11 @@ export type CompiledCharacter = {
|
|||
speed: number | false;
|
||||
capacity: number | false;
|
||||
initiative: number;
|
||||
exhaust: number;
|
||||
itempower: number;
|
||||
|
||||
action: number;
|
||||
reaction: number;
|
||||
|
||||
variables: CharacterVariables,
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue