diff --git a/db.sqlite b/db.sqlite index 1e2c1ea..4129b45 100644 Binary files a/db.sqlite and b/db.sqlite differ diff --git a/db.sqlite-shm b/db.sqlite-shm index 6c7c368..ca5b01f 100644 Binary files a/db.sqlite-shm and b/db.sqlite-shm differ diff --git a/db.sqlite-wal b/db.sqlite-wal index 9f33f59..0444164 100644 Binary files a/db.sqlite-wal and b/db.sqlite-wal differ diff --git a/layouts/default.vue b/layouts/default.vue index d5ba617..8371dbc 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -67,7 +67,7 @@ import { TreeDOM } from '#shared/tree'; import { Content, iconByType } from '#shared/content.util'; import { dom, icon, text } from '#shared/dom.util'; import { unifySlug } from '#shared/general.util'; -import { popper } from '#shared/floating.util'; +import { popper, tooltip } from '#shared/floating.util'; import { link } from '#shared/components.util'; const options = ref([{ @@ -94,13 +94,13 @@ const tree = new TreeDOM((item, depth) => { return dom('div', { class: 'group flex items-center ps-2 outline-none relative cursor-pointer', style: { 'padding-inline-start': `${depth / 1.5}em` } }, [dom('div', { class: ['flex flex-1 items-center hover:border-accent-blue hover:text-accent-purple max-w-full cursor-pointer font-medium'], attributes: { 'data-private': item.private } }, [ icon('radix-icons:chevron-right', { class: 'h-4 w-4 transition-transform absolute group-data-[state=open]:rotate-90', style: { 'left': `${depth / 1.5 - 1}em` } }), dom('div', { class: 'pl-1.5 py-1.5 flex-1 truncate', text: item.title, attributes: { title: item.title } }), - item.private ? popper(dom('span', { class: 'flex' }, [icon('radix-icons:lock-closed', { class: 'mx-1' })]), { delay: 150, offset: 8, placement: 'right', arrow: true, content: [text('Privé')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' }) : undefined, + item.private ? tooltip(icon('radix-icons:lock-closed', { class: 'mx-1' }), 'Privé', 'right') : undefined, ])]); }, (item, depth) => { return dom('div', { class: 'group flex items-center ps-2 outline-none relative cursor-pointer', style: { 'padding-inline-start': `${depth / 1.5}em` } }, [link({ class: ['flex flex-1 items-center hover:border-accent-blue hover:text-accent-purple max-w-full'], attributes: { 'data-private': item.private }, active: 'text-accent-blue' }, item.path ? { name: 'explore-path', params: { path: item.path } } : undefined, [ icon(iconByType[item.type], { class: 'w-5 h-5', width: 20, height: 20 }), dom('div', { class: 'pl-1.5 py-1.5 flex-1 truncate', text: item.title, attributes: { title: item.title } }), - item.private ? popper(dom('span', { class: 'flex' }, [icon('radix-icons:lock-closed', { class: 'mx-1' })]), { delay: 150, offset: 8, placement: 'right', arrow: true, content: [text('Privé')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' }) : undefined, + item.private ? tooltip(icon('radix-icons:lock-closed', { class: 'mx-1' }), 'Privé', 'right') : undefined, ])]); }, (item) => item.navigable); (path.value?.split('/').map((e, i, a) => a.slice(0, i).join('/')) ?? []).forEach(e => tree.toggle(tree.tree.search('path', e)[0], true)); diff --git a/pages/character/[id]/index.client.vue b/pages/character/[id]/index.client.vue index cff061d..ece2372 100644 --- a/pages/character/[id]/index.client.vue +++ b/pages/character/[id]/index.client.vue @@ -84,10 +84,16 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur
-
+
- Maitrise d'arme -
+ Compétences +
+
+{{ value }}{{ characterConfig.abilities[ability].name }}
+
+
+
+ Maitrises +
@@ -101,52 +107,46 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur
-
-
- Maitrise d'armure -
+
-
-
- Maitrise de sorts - Sorts de précision: {{ character.spellranks.precision }} - Sorts de savoir: {{ character.spellranks.knowledge }} - Sorts d'instinct: {{ character.spellranks.instinct }} -
-
- Compétences -
-
+{{ value }}{{ characterConfig.abilities[ability].name }}
+
+ Précision: {{ character.spellranks.precision }} + Savoir: {{ character.spellranks.knowledge }} + Instinct: {{ character.spellranks.instinct }} + Oeuvres: {{ character.spellranks.arts }}
- + Aptitudes Sorts + Inventaire Notes - +
-
+
Actions Attaquer - Saisir - Faire chuter - Déplacer - Courir - Pas de coté - Lancer un sort - S'interposer - Se transformer - Utiliser un objet - Anticiper une action - Improviser
-
- Réactions - Parade - Esquive - Saisir une opportunité - Prendre en tenaille - Intercepter - Désarmer - -
-
- Actions libre - Analyser une situation - Communiquer - +
+
+ Réactions + Parade - Esquive - Saisir une opportunité - Prendre en tenaille - Intercepter - Désarmer + +
+
+ Actions libre + Analyser une situation - Communiquer + +
@@ -155,7 +155,7 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur
- +
{{ character.variables.spells.length }} / {{ character.spellslots }} sorts maitrisés
@@ -179,7 +179,12 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur
- + +
+ +
+
+
diff --git a/pages/explore/edit/index.vue b/pages/explore/edit/index.vue index 1c132e8..8fdc645 100644 --- a/pages/explore/edit/index.vue +++ b/pages/explore/edit/index.vue @@ -38,7 +38,7 @@ import { Content, Editor } from '#shared/content.util'; import { button, loading } from '#shared/components.util'; import { dom, icon, text } from '#shared/dom.util'; -import { modal, popper } from '#shared/floating.util'; +import { modal, popper, tooltip } from '#shared/floating.util'; definePageMeta({ rights: ['admin', 'editor'], @@ -79,8 +79,8 @@ onMounted(async () => { tree.value.appendChild(load); const content = dom('div', { class: 'flex flex-row justify-start items-center gap-4 p-2' }, [ - popper(button(icon('ph:cloud-arrow-down', { height: 20, width: 20 }), pull, 'p-1'), { placement: 'top', offset: 4, delay: 120, arrow: true, content: [text('Actualiser')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' }), - popper(button(icon('ph:cloud-arrow-up', { height: 20, width: 20 }), push, 'p-1'), { placement: 'top', offset: 4, delay: 120, arrow: true, content: [text('Enregistrer')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' }), + tooltip(button(icon('ph:cloud-arrow-down', { height: 20, width: 20 }), pull, 'p-1'), 'Actualiser', 'top'), + tooltip(button(icon('ph:cloud-arrow-up', { height: 20, width: 20 }), push, 'p-1'), 'Enregistrer', 'top'), ]) tree.value.insertBefore(content, load); diff --git a/server/tasks/pull.ts b/server/tasks/pull.ts index 612717c..adca089 100644 --- a/server/tasks/pull.ts +++ b/server/tasks/pull.ts @@ -2,7 +2,7 @@ import useDatabase from "~/composables/useDatabase"; import { extname, basename } from 'node:path'; import type { CanvasColor, CanvasContent } from "~/types/canvas"; import type { FileType, ProjectContent } from "#shared/content.util"; -import { getID, ID_SIZE, parsePath } from "#shared/general.util"; +import { getID, parsePath } from "#shared/general.util"; import { projectContentTable, projectFilesTable } from "~/db/schema"; const typeMapping: Record = { @@ -35,7 +35,7 @@ export default defineTask({ const title = basename(e.path); const order = /(\d+)\. ?(.+)/gsmi.exec(title); return { - id: getID(ID_SIZE), + id: getID(), path: parsePath(e.path), order: i, title: title, @@ -54,7 +54,7 @@ export default defineTask({ const content = (await $fetch(`https://git.peaceultime.com/api/v1/repos/peaceultime/system-aspect/raw/${encodeURIComponent(e.path)}`)); return { - id: getID(ID_SIZE), + id: getID(), path: parsePath(extension === '.md' ? e.path.replace(extension, '') : e.path), order: i, title: title, diff --git a/shared/canvas.util.ts b/shared/canvas.util.ts index b862915..def2532 100644 --- a/shared/canvas.util.ts +++ b/shared/canvas.util.ts @@ -2,7 +2,7 @@ import type { CanvasContent, CanvasEdge, CanvasNode } from "~/types/canvas"; import { clamp, lerp } from "#shared/general.util"; import { dom, icon, svg, text } from "#shared/dom.util"; import render from "#shared/markdown.util"; -import { popper } from "#shared/floating.util"; +import { popper, tooltip } from "#shared/floating.util"; import { History } from "#shared/history.util"; import { fakeA } from "#shared/proses"; import { SpatialGrid } from "#shared/physics.util"; @@ -412,15 +412,9 @@ export class Canvas this.container = dom('div', { class: 'absolute top-0 left-0 overflow-hidden w-full h-full touch-none' }, [ dom('div', { class: 'flex flex-col absolute sm:top-2 top-10 left-2 z-[35] overflow-hidden gap-4' }, [ dom('div', { class: 'border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10' }, [ - popper(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.zoomTo(this._x, this._y, clamp(this._zoom * 1.1, this.containZoom, Canvas.maxZoom)) } }, [icon('radix-icons:plus')]), { - delay: 120, arrow: true, placement: 'right', offset: 8, content: [text('Zoom avant')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' - }), - popper(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: (e: MouseEvent) => { this.reset(); } } }, [icon('radix-icons:corners')]), { - delay: 120, arrow: true, placement: 'right', offset: 8, content: [text('Tout contenir')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' - }), - popper(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.zoomTo(this._x, this._y, clamp(this._zoom / 1.1, this.containZoom, Canvas.maxZoom)) } }, [icon('radix-icons:minus')]), { - delay: 120, arrow: true, placement: 'right', offset: 8, content: [text('Zoom arrière')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' - }), + tooltip(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.zoomTo(this._x, this._y, clamp(this._zoom * 1.1, this.containZoom, Canvas.maxZoom)) } }, [icon('radix-icons:plus')]), 'Zoom avant', 'right'), + tooltip(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: (e: MouseEvent) => { this.reset(); } } }, [icon('radix-icons:corners')]), 'Tout contenir', 'right'), + tooltip(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.zoomTo(this._x, this._y, clamp(this._zoom / 1.1, this.containZoom, Canvas.maxZoom)) } }, [icon('radix-icons:minus')]), 'Zoom arrière', 'right'), ]), ]), this.transform, ]); @@ -707,31 +701,17 @@ export class CanvasEditor extends Canvas this.container = dom('div', { class: 'absolute top-0 left-0 overflow-hidden w-full h-full touch-none', listeners: { mousedown: () => { this.selection.clear(); this.updateSelection() } } }, [ dom('div', { class: 'flex flex-col absolute sm:top-2 top-10 left-2 z-[35] overflow-hidden gap-4' }, [ dom('div', { class: 'border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10' }, [ - popper(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.zoomTo(this._x, this._y, clamp(this._zoom * 1.1, this.containZoom, Canvas.maxZoom)) } }, [icon('radix-icons:plus')]), { - delay: 120, arrow: true, placement: 'right', offset: 8, content: [text('Zoom avant')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' - }), - popper(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.reset() } }, [icon('radix-icons:corners')]), { - delay: 120, arrow: true, placement: 'right', offset: 8, content: [text('Tout contenir')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' - }), - popper(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.zoomTo(this._x, this._y, clamp(this._zoom / 1.1, this.containZoom, Canvas.maxZoom)) } }, [icon('radix-icons:minus')]), { - delay: 120, arrow: true, placement: 'right', offset: 8, content: [text('Zoom arrière')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' - }), + tooltip(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.zoomTo(this._x, this._y, clamp(this._zoom * 1.1, this.containZoom, Canvas.maxZoom)) } }, [icon('radix-icons:plus')]), 'Zoom avant', 'right'), + tooltip(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.reset() } }, [icon('radix-icons:corners')]), 'Tout contenir', 'right'), + tooltip(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.zoomTo(this._x, this._y, clamp(this._zoom / 1.1, this.containZoom, Canvas.maxZoom)) } }, [icon('radix-icons:minus')]), 'Zoom arrière', 'right'), ]), dom('div', { class: 'border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10' }, [ - popper(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.history.undo() } }, [icon('ph:arrow-bend-up-left')]), { - delay: 120, arrow: true, placement: 'right', offset: 8, content: [text('Annuler (Ctrl+Z)')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' - }), - popper(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.history.redo() } }, [icon('ph:arrow-bend-up-right')]), { - delay: 120, arrow: true, placement: 'right', offset: 8, content: [text('Rétablir (Ctrl+Y)')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' - }), + tooltip(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.history.undo() } }, [icon('ph:arrow-bend-up-left')]), 'Annuler (Ctrl+Z)', 'right'), + tooltip(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => this.history.redo() } }, [icon('ph:arrow-bend-up-right')]), 'Rétablir (Ctrl+Y)', 'right'), ]), dom('div', { class: 'border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10' }, [ - popper(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => {} } }, [icon('radix-icons:gear')]), { - delay: 120, arrow: true, placement: 'right', offset: 8, content: [text('Préférences')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' - }), - popper(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => {} } }, [icon('radix-icons:question-mark-circled')]), { - delay: 120, arrow: true, placement: 'right', offset: 8, content: [text('Aide')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' - }), + tooltip(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => {} } }, [icon('radix-icons:gear')]), 'Préférences', 'right'), + tooltip(dom('span', { class: 'w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer', listeners: { click: () => {} } }, [icon('radix-icons:question-mark-circled')]), 'Aide', 'right'), ]), ]), this.pattern, this.transform ]); diff --git a/shared/character-config.json b/shared/character-config.json index 8d099a8..624660e 100644 --- a/shared/character-config.json +++ b/shared/character-config.json @@ -850,7 +850,7 @@ "5d7u2jvi4u0nnrzesderha3uo8kb3zjq" ], "4": [ - "8w4jthjrn3l8u4trmj46z6t6ab5rbgk3" + "4cm4mz365yupl9h8nfet7orbapeg2fzn" ], "5": [ "z9lux6nlhl8pjhcwst6bnhpn6cq6c77w" @@ -6082,7 +6082,7 @@ ] }, "3ugv3ym7bswjhz0drbx6v3932q7w3qsy": { - "description": "Vous apprenez le sort unique [[1. Règles/4. La magie/2. Liste des sorts#^068b55|Soin]]. #todo\n+3 mana max.", + "description": "Vous apprenez le sort unique [[1. Règles/4. La magie/2. Liste des sorts#^068b55|Soin]].\n+3 mana max.", "id": "3ugv3ym7bswjhz0drbx6v3932q7w3qsy", "effect": [ { @@ -6091,6 +6091,13 @@ "property": "mana", "operation": "add", "value": 3 + }, + { + "id": "mxshb0udl4zahch8l9v1rtpm1d5fv4au", + "category": "list", + "list": "spells", + "action": "add", + "item": "zltvtru98sm0ad9whiw5tty0gy4q2jur" } ] }, @@ -6193,9 +6200,17 @@ ] }, "iaaoqrn6kgvovzfpk4ygyd8yjwrxkml3": { - "description": "Vous apprenez le sort unique [[2. Liste des sorts#Sorts unique|Focalisation destructrice]]. #todo", + "description": "Vous apprenez le sort unique [[2. Liste des sorts#Sorts unique|Focalisation destructrice]].", "id": "iaaoqrn6kgvovzfpk4ygyd8yjwrxkml3", - "effect": [] + "effect": [ + { + "id": "96xafpi1s4baffwht7gvfx7eqx3tzocq", + "category": "list", + "list": "spells", + "action": "add", + "item": "kd84l3gujh4evsyriti4g9sk1zwbxu8d" + } + ] }, "syj4q2o1qfh5vezi2d8bzgs9fn9ok273": { "description": "Lorsque vous voyez un sort être lancé, vous pouvez [[2. Actions en combat#Saisir une opportunité|saisir l'opportunité]] et dépenser jusqu'à 5 points de mana pour imposer un malus de égal au mana dépensé.", @@ -6434,7 +6449,7 @@ ] }, "yn70y3tmxdo1w0zvm7at1i7uretcgkvk": { - "description": "Le maximum de toutes les compétences augmente de 1 point. #todo\n+2 points de compétence.", + "description": "Le maximum de toutes les compétences augmente de 1 point.\n+2 points de compétence.", "id": "yn70y3tmxdo1w0zvm7at1i7uretcgkvk", "effect": [ { @@ -6450,11 +6465,130 @@ "property": "modifier/curiosity", "operation": "add", "value": 1 + }, + { + "id": "ujohx9nf79xp2pu7db3m96cq7u6whhom", + "category": "value", + "property": "bonus/abilities/athletics", + "operation": "add", + "value": 1 + }, + { + "id": "52lwifstredyutwzev0aospih6yqfkk0", + "category": "value", + "property": "bonus/abilities/acrobatics", + "operation": "add", + "value": 1 + }, + { + "id": "tpp9gxgsxji4tqhvz9wt2qw4jbqi0vsj", + "category": "value", + "property": "bonus/abilities/intimidation", + "operation": "add", + "value": 1 + }, + { + "id": "tautma08r6qh6kd7g2x9qye04ke6jezu", + "category": "value", + "property": "bonus/abilities/sleightofhand", + "operation": "add", + "value": 1 + }, + { + "id": "v2tmzc6w5ol1sxktqu16qegw7net3gsa", + "category": "value", + "property": "bonus/abilities/stealth", + "operation": "add", + "value": 1 + }, + { + "id": "gdefm1aqperjd6jbzjdj9njudiuehpt8", + "category": "value", + "property": "bonus/abilities/survival", + "operation": "add", + "value": 1 + }, + { + "id": "lqfs22jlrc0iq6vqy4nrart3og7vdbzq", + "category": "value", + "property": "bonus/abilities/investigation", + "operation": "add", + "value": 1 + }, + { + "id": "hhjsudmrlx346avj8kqbz8w1qd78xtu4", + "category": "value", + "property": "bonus/abilities/history", + "operation": "add", + "value": 1 + }, + { + "id": "0ryjnvdv7ube7stshuf7z6ecqfmgbxn5", + "category": "value", + "property": "bonus/abilities/religion", + "operation": "add", + "value": 1 + }, + { + "id": "je0ly1u7ztoyhpik3nabnpm8c26brial", + "category": "value", + "property": "bonus/abilities/arcana", + "operation": "add", + "value": 1 + }, + { + "id": "3jfe6nk5b1feghtar4xjaqqb3680kgv1", + "category": "value", + "property": "bonus/abilities/understanding", + "operation": "add", + "value": 1 + }, + { + "id": "t8wlpfa84jt90gs156dq37fk3bvjxb1r", + "category": "value", + "property": "bonus/abilities/perception", + "operation": "add", + "value": 1 + }, + { + "id": "fje9o5wskdqm5xhq53tgtm1os8sowktx", + "category": "value", + "property": "bonus/abilities/performance", + "operation": "add", + "value": 1 + }, + { + "id": "v5aptp9kp274mstx58fwtnddkig92la1", + "category": "value", + "property": "bonus/abilities/medecine", + "operation": "add", + "value": 1 + }, + { + "id": "opcasr4pbwgetjqwslhlhxn3d8qxxqvi", + "category": "value", + "property": "bonus/abilities/persuasion", + "operation": "add", + "value": 1 + }, + { + "id": "gg30nbm489tf4850r7fwwykb97y8ly8j", + "category": "value", + "property": "bonus/abilities/animalhandling", + "operation": "add", + "value": 1 + }, + { + "id": "lzi81y8vkdztgcv9tjja8klx2mpq8os2", + "category": "value", + "property": "bonus/abilities/deception", + "operation": "add", + "value": 1 } ] }, "nqvexvg3ui6w2hqknwrw4pvf5axvu4go": { - "description": "Le maximum de toutes les compétences est de 6 points, sauf s'il est déjà supérieur. #todo\n+2 points de compétence.", + "description": "Le maximum de toutes les compétences est de 6 points, sauf s'il est déjà supérieur.\n+2 points de compétence.", "id": "nqvexvg3ui6w2hqknwrw4pvf5axvu4go", "effect": [ { @@ -6470,6 +6604,125 @@ "property": "modifier/curiosity", "operation": "add", "value": 1 + }, + { + "id": "2fbjzp4cg6mobxhycvq5ljnah1f1dz7y", + "category": "value", + "property": "bonus/abilities/athletics", + "operation": "min", + "value": 6 + }, + { + "id": "jrm0xt17xbpkg6fgp0d54qu7qmfp5ftr", + "category": "value", + "property": "bonus/abilities/acrobatics", + "operation": "min", + "value": 6 + }, + { + "id": "lra7cbvx5tqj5m5ql4to05x6slknf1wm", + "category": "value", + "property": "bonus/abilities/intimidation", + "operation": "min", + "value": 6 + }, + { + "id": "cik0xa4k7gcepj4wcab710ixhfwaxd3h", + "category": "value", + "property": "bonus/abilities/sleightofhand", + "operation": "min", + "value": 6 + }, + { + "id": "4vieiuimshc3nyvr95fnktuu6n09yjho", + "category": "value", + "property": "bonus/abilities/stealth", + "operation": "min", + "value": 6 + }, + { + "id": "6gb2ax95ig3yzmgabpbpc5ogv8pct76t", + "category": "value", + "property": "bonus/abilities/survival", + "operation": "min", + "value": 6 + }, + { + "id": "zoxh7gpp693hvfokwnfdu3d6dii4rpr8", + "category": "value", + "property": "bonus/abilities/investigation", + "operation": "min", + "value": 6 + }, + { + "id": "h2ebi472yx3zn8bdhli0ydkoc3kj80wh", + "category": "value", + "property": "bonus/abilities/history", + "operation": "min", + "value": 6 + }, + { + "id": "cmnafy5kt7w0w7e3lj1zgyg65oubb9d3", + "category": "value", + "property": "bonus/abilities/religion", + "operation": "min", + "value": 6 + }, + { + "id": "gniqvw47aukyd2tl3p4vzlabopdb1gu9", + "category": "value", + "property": "bonus/abilities/arcana", + "operation": "min", + "value": 6 + }, + { + "id": "39l4ho7y3m9quzwqtasfmbz9k4xsiwvl", + "category": "value", + "property": "bonus/abilities/understanding", + "operation": "min", + "value": 6 + }, + { + "id": "z8kw81jtizyw6m57adyl2vat6h7c86r5", + "category": "value", + "property": "bonus/abilities/perception", + "operation": "min", + "value": 6 + }, + { + "id": "5xsg3w22xai41q5lf5akxt3u0xnxi54b", + "category": "value", + "property": "bonus/abilities/performance", + "operation": "min", + "value": 6 + }, + { + "id": "9k1l0wo3k9qymghh1ghf74gc1xw95y4x", + "category": "value", + "property": "bonus/abilities/medecine", + "operation": "min", + "value": 6 + }, + { + "id": "rj0icbs2tqt2hnxddtr6zv0z7uls1eq2", + "category": "value", + "property": "bonus/abilities/persuasion", + "operation": "min", + "value": 6 + }, + { + "id": "tjq16rz76nh56i9636dygqvk81906goe", + "category": "value", + "property": "bonus/abilities/animalhandling", + "operation": "min", + "value": 6 + }, + { + "id": "ts20cerlj4qior7mzzbtg2k882qymrsl", + "category": "value", + "property": "bonus/abilities/deception", + "operation": "min", + "value": 6 } ] }, @@ -7903,7 +8156,7 @@ ] }, "s5kidncgfzw85ffubl718lx2f68suhqf": { - "description": "Votre connexion innée avec la magie vous a bénie d'un don pour cet art. Choisissez une branche de l'[[1. Les évolutions de valeur.canvas#L'arbre de magie|arbre de magie]]. Vous gagnez le premier niveau de cette branche. #todo", + "description": "Votre connexion innée avec la magie vous a bénie d'un don pour cet art. Choisissez une branche de l'[[1. Les évolutions de valeur.canvas#L'arbre de magie|arbre de magie]]. Vous gagnez le premier niveau de cette branche.", "id": "s5kidncgfzw85ffubl718lx2f68suhqf", "effect": [ { @@ -8072,7 +8325,7 @@ ] }, "qf3eru17f8u3hysq56k246mlq7p2rbc9": { - "description": "Vous gagnez un niveau dans une branche de l'[[1. Les évolutions de valeur.canvas#L'arbre de magie|arbre de magie]] dans laquelle vous avez déjà au moins un niveau. #todo", + "description": "Vous gagnez un niveau dans une branche de l'[[1. Les évolutions de valeur.canvas#L'arbre de magie|arbre de magie]] dans laquelle vous avez déjà au moins un niveau.", "id": "qf3eru17f8u3hysq56k246mlq7p2rbc9", "effect": [ { @@ -8358,9 +8611,17 @@ ] }, "sq43lzc8bdftrbbfwaq5l6nx1h5jx0eh": { - "description": "Vous apprenez le sort unique [[2. Liste des sorts#^5b38b6|Domination mentale]]. #todo", + "description": "Vous apprenez le sort unique [[2. Liste des sorts#^5b38b6|Domination mentale]].", "id": "sq43lzc8bdftrbbfwaq5l6nx1h5jx0eh", - "effect": [] + "effect": [ + { + "id": "y7gnxzaf9puprd4ij56wk7bq8xjnt21d", + "category": "list", + "list": "spells", + "action": "add", + "item": "wj2rxkbw85zd9st8k2w3eezqc1naoy5g" + } + ] }, "c4nptbfb5uoyz98ovsqjxwlssgui9h9p": { "description": "Vous êtes capable d'utiliser les particularités magiques de votre Aspect sans vous transformer.", @@ -9619,11 +9880,6 @@ "description": "suis", "effect": [] }, - "8w4jthjrn3l8u4trmj46z6t6ab5rbgk3": { - "id": "8w4jthjrn3l8u4trmj46z6t6ab5rbgk3", - "description": "Nicolas", - "effect": [] - }, "z9lux6nlhl8pjhcwst6bnhpn6cq6c77w": { "id": "z9lux6nlhl8pjhcwst6bnhpn6cq6c77w", "description": "Sarkozy", @@ -9703,6 +9959,11 @@ "id": "qcp28eysi3l3n438v41kowisdpq4ht61", "description": "", "effect": [] + }, + "4cm4mz365yupl9h8nfet7orbapeg2fzn": { + "id": "4cm4mz365yupl9h8nfet7orbapeg2fzn", + "description": "Nicolas", + "effect": [] } } } \ No newline at end of file diff --git a/shared/character.util.ts b/shared/character.util.ts index 3e2bbb9..e0a2fe8 100644 --- a/shared/character.util.ts +++ b/shared/character.util.ts @@ -4,7 +4,7 @@ import characterConfig from '#shared/character-config.json'; import { fakeA } from "#shared/proses"; import { button, input, loading, numberpicker, select, toggle } from "#shared/components.util"; import { div, dom, icon, mergeClasses, text, type Class } from "#shared/dom.util"; -import { followermenu, popper } from "#shared/floating.util"; +import { followermenu, popper, tooltip } from "#shared/floating.util"; import { clamp } from "#shared/general.util"; import markdownUtil from "#shared/markdown.util"; @@ -34,7 +34,7 @@ export const defaultCharacter: Character = { health: 0, mana: 0, spells: [], - equipment: [], + items: [], exhaustion: 0, sickness: [], }, @@ -198,8 +198,8 @@ export const CharacterValidation = z.object({ thumbnail: z.any(), }); -type Property = { value: number | string | false, id: string, operation: "set" | "add" }; -type PropertySum = { list: Array, value: number, _dirty: boolean }; +type Property = { value: number | string | false, id: string, operation: "set" | "add" | "min" }; +type PropertySum = { list: Array, min: number, value: number, _dirty: boolean }; export class CharacterCompiler { protected _character!: Character; @@ -285,7 +285,7 @@ export class CharacterCompiler return; case "value": - this._buffer[feature.property] ??= { list: [], value: 0, _dirty: true }; + this._buffer[feature.property] ??= { list: [], value: 0, _dirty: true, min: -Infinity }; this._buffer[feature.property]!.list.push({ operation: feature.operation, id: feature.id, value: feature.value }); @@ -318,7 +318,7 @@ export class CharacterCompiler return; case "value": - this._buffer[feature.property] ??= { list: [], value: 0, _dirty: true }; + this._buffer[feature.property] ??= { list: [], value: 0, _dirty: true, min: -Infinity }; this._buffer[feature.property]!.list.splice(this._buffer[feature.property]!.list.findIndex(e => e.id === feature.id), 1); @@ -375,6 +375,8 @@ export class CharacterCompiler sum += modifier.value; else if(buffer.list[i]?.operation === 'set') sum = modifier.value; + else if(buffer.list[i]?.operation === 'min') + this._buffer[property]!.min = modifier.value; } } else @@ -383,6 +385,8 @@ export class CharacterCompiler sum += buffer.list[i]!.value as number; else if(buffer.list[i]?.operation === 'set') sum = buffer.list[i]!.value as number; + else if(buffer.list[i]?.operation === 'min') + this._buffer[property]!.min = buffer.list[i]!.value as number; } } @@ -393,9 +397,9 @@ export class CharacterCompiler const object = path.length === 1 ? this._result : path.slice(0, -1).reduce((p, v) => { p[v] ??= {}; return p[v]; }, this._result as any); if(object.hasOwnProperty(path.slice(-1)[0]!)) - object[path.slice(-1)[0]!] = sum; + object[path.slice(-1)[0]!] = Math.max(sum, this._buffer[property]!.min); - this._buffer[property]!.value = sum; + this._buffer[property]!.value = Math.max(sum, this._buffer[property]!.min); this._buffer[property]!._dirty = false; } } @@ -423,7 +427,7 @@ export class CharacterBuilder extends CharacterCompiler useRequestFetch()(`/api/character/${id}`).then(character => { if(character) { - this._character = character; + this.character = character; document.title = `d[any] - Edition de ${character.name ?? 'nouveau personnage'}`; load.remove(); @@ -459,13 +463,7 @@ export class CharacterBuilder extends CharacterCompiler this._content = dom('div', { class: 'flex-1 outline-none max-w-full w-full overflow-y-auto', attributes: { id: 'characterEditorContainer' } }); this._container.appendChild(div('flex flex-1 flex-col justify-start items-center px-8 w-full h-full overflow-y-hidden', [ div("flex w-full flex-row gap-4 items-center justify-between px-4 bg-light-0 dark:bg-dark-0 z-20", [ - div(), div("flex w-full flex-row gap-4 items-center justify-center relative", this._stepsHeader), div(undefined, [ popper(icon("radix-icons:question-mark-circled", { height: 20, width: 20 }), { - arrow: true, - offset: 8, - content: [ this._helperText ], - placement: "bottom-end", - class: "max-w-96 fixed hidden TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50" - }) ]), + div(), div("flex w-full flex-row gap-4 items-center justify-center relative", this._stepsHeader), div(undefined, [ tooltip(icon("radix-icons:question-mark-circled", { height: 20, width: 20 }), this._helperText, "bottom-end") ]), ]), this._content, ])); @@ -683,6 +681,13 @@ export class PickableFeature return this._content; } } +class FeatureTable +{ + constructor(table: Record) + { + + } +} abstract class BuilderTab { protected _builder: CharacterBuilder; protected _content!: Array; @@ -741,6 +746,8 @@ class PeoplePicker extends BuilderTab ]), button(text('Suivant'), () => this._builder.display(1), 'h-[35px] px-[15px]'), ]), div('flex flex-1 gap-4 p-2 overflow-x-auto justify-center', this._options)]; + + this.update(); } override update() { @@ -798,6 +805,8 @@ class LevelPicker extends BuilderTab ]), button(text('Suivant'), () => this._builder.display(2), 'h-[35px] px-[15px]'), ]), div('flex flex-col flex-1 gap-4 mx-8 my-4', this._options.flatMap(e => [...e]))]; + + this.update(); } override update() { @@ -883,6 +892,8 @@ class TrainingPicker extends BuilderTab ]), div('flex flex-1 px-6 overflow-hidden max-w-full', [ this._statContainer ])]; this.switchTab(0); + + this.update(); } switchTab(tab: number) { @@ -962,7 +973,7 @@ class AbilityPicker extends BuilderTab this._pointsInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }}); this._options = ABILITIES.map((e, i) => div('flex flex-col border border-light-50 dark:border-dark-50 p-4 gap-2 w-[200px] relative', [ - div('flex justify-between', [ numberInput(this._builder.character.abilities[e], (value) => { + div('flex justify-between', [ numberpicker({ defaultValue: this._builder.character.abilities[e], input: (value) => { const values = this._builder.values; const max = (values[`abilities/${e}/max`] ?? 0) + (values[`modifier/${config.abilities[e].max[0]}`] ?? 0) + (values[`modifier/${config.abilities[e].max[1]}`] ?? 0); @@ -975,13 +986,7 @@ class AbilityPicker extends BuilderTab this._pointsInput.value = ((values.ability ?? 0) - abilities).toString(); return this._builder.character.abilities[e]; - }), popper(pushAndReturn(this._maxs, dom('span', { class: 'text-lg text-end cursor-pointer', text: '' })), { - arrow: true, - offset: 6, - placement: 'bottom-end', - class: 'max-w-96 fixed hidden TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50', - content: [ pushAndReturn(this._tooltips, text('')) ] - })]), + }}), tooltip(pushAndReturn(this._maxs, dom('span', { class: 'text-lg text-end cursor-pointer', text: '' })), pushAndReturn(this._tooltips, text('')), 'bottom-end')]), dom('span', { class: "text-xl text-center font-bold", text: config.abilities[e].name }), dom('span', { class: "absolute -bottom-px -left-px h-[3px] bg-accent-blue" }), ])); @@ -993,6 +998,8 @@ class AbilityPicker extends BuilderTab ]), button(text('Suivant'), () => this._builder.display(4), 'h-[35px] px-[15px]'), ]), div('flex flex-row flex-wrap justify-center items-center flex-1 gap-12 mx-8 my-4 px-48', this._options)]; + + this.update(); } override update() { @@ -1002,13 +1009,11 @@ class AbilityPicker extends BuilderTab this._pointsInput.value = ((values.ability ?? 0) - abilities).toString(); ABILITIES.forEach((e, i) => { - const max = (values[`abilities/${e}/max`] ?? 0) + (values[`modifier/${config.abilities[e].max[0]}`] ?? 0) + (values[`modifier/${config.abilities[e].max[1]}`] ?? 0); + const max = (values[`bonus/abilities/${e}`] ?? 0) + (values[`modifier/${config.abilities[e].max[0]}`] ?? 0) + (values[`modifier/${config.abilities[e].max[1]}`] ?? 0); Object.assign((this._options[i]?.lastElementChild as HTMLSpanElement | undefined)?.style ?? {}, { width: `${(max === 0 ? 0 : (this._builder.character.abilities[e] ?? 0) / max) * 100}%` }); - this._tooltips[i]!.textContent = `${mainStatTexts[config.abilities[e].max[0]]} (${values[`modifier/${config.abilities[e].max[0]}`] ?? 0}) + ${mainStatTexts[config.abilities[e].max[1]]} (${values[`modifier/${config.abilities[e].max[1]}`] ?? 0}) + ${values[`abilities/${e}/max`] ?? 0}`; + this._tooltips[i]!.textContent = `${mainStatTexts[config.abilities[e].max[0]]} (${values[`modifier/${config.abilities[e].max[0]}`] ?? 0}) + ${mainStatTexts[config.abilities[e].max[1]]} (${values[`modifier/${config.abilities[e].max[1]}`] ?? 0}) + ${values[`bonus/abilities/${e}`] ?? 0}`; this._maxs[i]!.textContent = `/ ${max ?? 0}`; - - return this._builder.character.abilities[e]; }) } static override validate(builder: CharacterBuilder): boolean @@ -1091,6 +1096,8 @@ class AspectPicker extends BuilderTab ]), button(text('Enregistrer'), () => this._builder.save(), 'h-[35px] px-[15px]'), ]), div('flex flex-row flex-wrap justify-center items-center flex-1 gap-8 mx-8 my-4 px-8', this._options)]; + + this.update(); } override update() { diff --git a/shared/components.util.ts b/shared/components.util.ts index 078d271..45ac9da 100644 --- a/shared/components.util.ts +++ b/shared/components.util.ts @@ -20,6 +20,19 @@ export function loading(size: 'small' | 'normal' | 'large' = 'normal'): HTMLElem { return dom('span', { class: ["after:block after:relative after:rounded-full after:border-transparent after:border-t-accent-purple after:animate-spin", {'w-6 h-6 border-4 border-transparent after:-top-[4px] after:-left-[4px] after:w-6 after:h-6 after:border-4': size === 'normal', 'w-4 h-4 after:-top-[2px] after:-left-[2px] after:w-4 after:h-4 after:border-2': size === 'small', 'w-12 h-12 after:-top-[6px] after:-left-[6px] after:w-12 after:h-12 after:border-[6px]': size === 'large'}] }) } +export function async(size: 'small' | 'normal' | 'large' = 'normal', fn: Promise): HTMLElement +{ + const load = loading(size); + + fn.then((element) => { + load.replaceWith(element); + }).catch(e => { + console.error(e); + load.remove(); + }) + + return load; +} export function button(content: Node, onClick?: () => void, cls?: Class) { return dom('button', { class: [`text-light-100 dark:text-dark-100 font-semibold hover:bg-light-30 dark:hover:bg-dark-30 inline-flex items-center justify-center bg-light-25 dark:bg-dark-25 leading-none outline-none diff --git a/shared/content.util.ts b/shared/content.util.ts index 6e7b22e..560c975 100644 --- a/shared/content.util.ts +++ b/shared/content.util.ts @@ -1,11 +1,11 @@ import { safeDestr as parse } from 'destr'; import { Canvas, CanvasEditor } from "#shared/canvas.util"; import render from "#shared/markdown.util"; -import { confirm, contextmenu, popper } from "#shared/floating.util"; +import { confirm, contextmenu, tooltip } from "#shared/floating.util"; import { cancelPropagation, dom, icon, text, type Node } from "#shared/dom.util"; import { loading } from "#shared/components.util"; import prose, { h1, h2 } from "#shared/proses"; -import { getID, ID_SIZE, parsePath } from '#shared/general.util'; +import { getID, parsePath } from '#shared/general.util'; import { TreeDOM, type Recursive } from '#shared/tree'; import { History } from '#shared/history.util'; import { MarkdownEditor } from '#shared/editor.util'; @@ -681,15 +681,15 @@ export class Editor return dom('div', { class: 'group flex items-center ps-2 outline-none relative cursor-pointer', style: { 'padding-left': `${depth / 2 - 0.5}em` } }, [dom('div', { class: ['flex flex-1 items-center hover:border-accent-blue hover:text-accent-purple max-w-full cursor-pointer font-medium'], attributes: { 'data-private': item.private }, listeners: { contextmenu: (e) => this.contextmenu(e, item as LocalContent)} }, [ icon('radix-icons:chevron-right', { class: 'h-4 w-4 transition-transform absolute group-data-[state=open]:rotate-90', style: { 'left': `${depth / 2 - 1.5}em` } }), dom('div', { class: 'pl-1.5 py-1.5 flex-1 truncate', text: item.title, attributes: { title: item.title } }), - popper(dom('span', { class: 'flex', listeners: { click: e => this.toggleNavigable(e, item as LocalContent) } }, [icon(item.navigable ? 'radix-icons:eye-open' : 'radix-icons:eye-none', { class: ['mx-1', { 'opacity-50': !item.navigable }] })]), { delay: 150, offset: 8, placement: 'left', arrow: true, content: [text('Navigable')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' }), - popper(dom('span', { class: 'flex', listeners: { click: e => this.togglePrivate(e, item as LocalContent) } }, [icon(item.private ? 'radix-icons:lock-closed' : 'radix-icons:lock-open-2', { class: ['mx-1', { 'opacity-50': !item.private }] })]), { delay: 150, offset: 8, placement: 'right', arrow: true, content: [text('Privé')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' }), + tooltip(dom('span', { class: 'flex', listeners: { click: e => this.toggleNavigable(e, item as LocalContent) } }, [icon(item.navigable ? 'radix-icons:eye-open' : 'radix-icons:eye-none', { class: ['mx-1', { 'opacity-50': !item.navigable }] })]), 'Navigable', 'left'), + tooltip(dom('span', { class: 'flex', listeners: { click: e => this.togglePrivate(e, item as LocalContent) } }, [icon(item.private ? 'radix-icons:lock-closed' : 'radix-icons:lock-open-2', { class: ['mx-1', { 'opacity-50': !item.private }] })]), 'Privé', 'right'), ])]); }, (item, depth) => { return dom('div', { class: 'group flex items-center ps-2 outline-none relative cursor-pointer', style: { 'padding-left': `${depth / 2 - 0.5}em` } }, [dom('div', { class: ['flex flex-1 items-center hover:border-accent-blue hover:text-accent-purple max-w-full'], attributes: { 'data-private': item.private }, listeners: { contextmenu: (e) => this.contextmenu(e, item as LocalContent), click: () => this.select(item as LocalContent) } }, [ icon(iconByType[item.type], { class: 'w-5 h-5', width: 20, height: 20 }), dom('div', { class: 'pl-1.5 py-1.5 flex-1 truncate', text: item.title, attributes: { title: item.title } }), - popper(dom('span', { class: 'flex', listeners: { click: e => this.toggleNavigable(e, item as LocalContent) } }, [icon(item.navigable ? 'radix-icons:eye-open' : 'radix-icons:eye-none', { class: ['mx-1', { 'opacity-50': !item.navigable }] })]), { delay: 150, offset: 8, placement: 'left', arrow: true, content: [text('Navigable')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' }), - popper(dom('span', { class: 'flex', listeners: { click: e => this.togglePrivate(e, item as LocalContent) } }, [icon(item.private ? 'radix-icons:lock-closed' : 'radix-icons:lock-open-2', { class: ['mx-1', { 'opacity-50': !item.private }] })]), { delay: 150, offset: 8, placement: 'right', arrow: true, content: [text('Privé')], class: 'TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50' }), + tooltip(dom('span', { class: 'flex', listeners: { click: e => this.toggleNavigable(e, item as LocalContent) } }, [icon(item.navigable ? 'radix-icons:eye-open' : 'radix-icons:eye-none', { class: ['mx-1', { 'opacity-50': !item.navigable }] })]), 'Navigable', 'left'), + tooltip(dom('span', { class: 'flex', listeners: { click: e => this.togglePrivate(e, item as LocalContent) } }, [icon(item.private ? 'radix-icons:lock-closed' : 'radix-icons:lock-open-2', { class: ['mx-1', { 'opacity-50': !item.private }] })]), 'Privé', 'right'), ])]); }); @@ -714,7 +714,7 @@ export class Editor private add(type: FileType, nextTo: Recursive) { const count = Object.values(Content.files).filter(e => e.title.match(/^Nouveau( \(\d+\))?$/)).length; - const item: Recursive & { element?: HTMLElement }> = { id: getID(ID_SIZE), navigable: true, private: false, owner: 0, order: nextTo.order + 1, timestamp: new Date(), title: count === 0 ? 'Nouveau' : `Nouveau (${count})`, type: type, parent: nextTo.parent }; + const item: Recursive & { element?: HTMLElement }> = { id: getID(), navigable: true, private: false, owner: 0, order: nextTo.order + 1, timestamp: new Date(), title: count === 0 ? 'Nouveau' : `Nouveau (${count})`, type: type, parent: nextTo.parent }; this.history.add('overview', 'add', [{ element: item, from: undefined, to: nextTo.order + 1 }]); } private remove(item: LocalContent & { element?: HTMLElement }) diff --git a/shared/feature.util.ts b/shared/feature.util.ts index c08be15..f7d2bff 100644 --- a/shared/feature.util.ts +++ b/shared/feature.util.ts @@ -6,7 +6,7 @@ import { button, combobox, foldable, input, multiselect, numberpicker, select, t import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating.util"; import { ALIGNMENTS, alignmentTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts } from "#shared/character.util"; import characterConfig from "#shared/character-config.json"; -import { getID, ID_SIZE } from "#shared/general.util"; +import { getID } from "#shared/general.util"; import renderMarkdown, { renderText } from "#shared/markdown.util"; import { Tree } from "#shared/tree"; import markdownUtil from "#shared/markdown.util"; @@ -106,12 +106,12 @@ class PeopleEditor extends BuilderTab const add = () => { const people: RaceConfig = { - id: getID(ID_SIZE), + id: getID(), name: '', description: '', options: LEVELS.map(e => { const feature: Feature = { - id: getID(ID_SIZE), + id: getID(), description: '', effect: [], } @@ -143,7 +143,7 @@ class PeopleEditor extends BuilderTab const context = contextmenu(e.clientX, e.clientY, [ dom('div', { class: 'px-2 py-1 border-bottom border-light-35 dark:border-dark-35 cursor-pointer hover:bg-light-40 dark:hover:bg-dark-40 text-light-100 dark:text-dark-100', listeners: { click: () => { context.close(); - const _feature: Feature = { id: getID(ID_SIZE), description: '', effect: [] }; + const _feature: Feature = { id: getID(), description: '', effect: [] }; config.features[_feature.id] = _feature; config.peoples[people]!.options[level]!.push(_feature.id); element.parentElement?.appendChild(render(people, level, _feature.id)); @@ -190,7 +190,7 @@ class TrainingEditor extends BuilderTab const context = contextmenu(e.clientX, e.clientY, [ dom('div', { class: 'px-2 py-1 border-bottom border-light-35 dark:border-dark-35 cursor-pointer hover:bg-light-40 dark:hover:bg-dark-40 text-light-100 dark:text-dark-100', listeners: { click: () => { context.close(); - const _feature: Feature = { id: getID(ID_SIZE), description: '', effect: [] }; + const _feature: Feature = { id: getID(), description: '', effect: [] }; config.features[_feature.id] = _feature; config.training[stat][level].push(_feature.id); element.parentElement?.appendChild(render(stat, level, _feature.id)); @@ -326,7 +326,7 @@ class SpellEditor extends BuilderTab } const add = () => { config.spells.push({ - id: getID(ID_SIZE), + id: getID(), name: '', rank: 1, type: 'precision', @@ -404,7 +404,7 @@ export class FeatureEditor 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(ID_SIZE) })); + this._table.appendChild(this._edit({ id: getID() })); }, 'p-1'), 'Ajouter', 'left'), ]), this._table, @@ -430,7 +430,7 @@ export class FeatureEditor 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('flex', [ tooltip(button(icon('radix-icons:pencil-1'), () => { - this._table.replaceChild(this._edit(effect), content); + 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._feature!.effect = this._feature!.effect.filter(e => e.id !== effect.id); content.remove(); @@ -484,7 +484,7 @@ export class FeatureEditor tooltip(button(icon('radix-icons:update'), () => { (buffer as Extract).value = (typeof (buffer as Extract).value === 'number' ? '' as any as false : 0); const newValueSelection = valueVariable(); - valueSelection?.parentElement?.replaceChild(newValueSelection, valueSelection); + 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'), @@ -514,13 +514,13 @@ export class FeatureEditor top = [ select([ { text: 'Ajouter', value: 'add' }, { text: 'Supprimer', value: 'remove' } ], { defaultValue: buffer.action, change: (value) => { (buffer as Extract).action = value as 'add' | 'remove'; const element = redraw(); - content?.parentElement?.replaceChild(element, content); + content.replaceWith(element); 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 = () => { - const option: Extract["options"][number] = { id: getID(ID_SIZE), category: 'value', text: '', operation: 'add', property: '', value: 0 }; + const option: Extract["options"][number] = { id: getID(), category: 'value', text: '', operation: 'add', property: '', value: 0 }; (buffer as Extract).options.push(option); list.appendChild(render(option, true)); }; @@ -529,7 +529,7 @@ export class FeatureEditor const combo = combobox([...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; }), { defaultValue: match(option), class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, fill: 'cover', change: (e) => { option = { id: option.id, ...e } as FeatureEffect & { text: string }; const element = render(option, true); - _content?.parentElement?.replaceChild(element, _content); + _content.replaceWith(element); _content = element; } }); let _content: HTMLElement = foldable(_bottom, [ div('flex flex-1 justify-between', [ div('flex flex-1 flex-row',[ combo, ..._top, input('text', { defaultValue: option.text, input: (value) => option.text = value, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] flex-shrink-1', placeholder: 'Description' }) ]), tooltip(button(icon('radix-icons:trash'), () => { @@ -553,7 +553,7 @@ export class FeatureEditor 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?.parentElement?.replaceChild(element, content); + content.replaceWith(element); content = element; } }), ...top, @@ -605,7 +605,10 @@ const featureChoices: Option>[] = [ { 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: 1 } })) }, + { text: 'Compétences', 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 } })) as Option>[], + { text: 'Max de compétence', value: Object.keys(config.abilities).map((e) => ({ text: config.abilities[e as keyof typeof config.abilities].name, value: { category: 'value', property: `bonus/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 } }, @@ -614,7 +617,6 @@ const featureChoices: Option>[] = [ { 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: '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 }, @@ -623,7 +625,7 @@ const featureChoices: Option>[] = [ { 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 } - ]}} + ]} as Partial} ] }, { text: 'Jet de résistance', value: [ { text: 'Force', value: { category: 'value', property: 'bonus/defense/strength', operation: 'add', value: 1 } }, @@ -633,7 +635,6 @@ const featureChoices: Option>[] = [ { 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 }, { text: 'Dextérité', category: 'value', property: 'bonus/defense/dexterity', operation: 'add', value: 1 }, @@ -642,7 +643,7 @@ const featureChoices: Option>[] = [ { text: 'Curiosité', category: 'value', property: 'bonus/defense/curiosity', operation: 'add', value: 1 }, { text: 'Charisme', category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 1 }, { text: 'Psyché', egory: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 } - ]}} + ]} as Partial} ] }, { 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: [ @@ -760,12 +761,14 @@ function textFromEffect(effect: Partial): string { case 'resistance': return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: 'Maitrise des armes (for.) ', positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : textFromValue(effect.value, { prefix: { truely: 'Maitrise des armes (for.) fixée à ' }, suffix: { truely: '.' }, falsely: 'Opération interdite (Maitrise for = interdit).' }); - + case 'abilities': + return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `Max de ${config.abilities[splited[2] as Ability].name} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : effect.operation === 'set' ? textFromValue(effect.value, { prefix: { truely: `Max de ${config.abilities[splited[2] as Ability].name} fixé à ` }, suffix: { truely: '.' }, falsely: `Opération interdite ( ${config.abilities[splited[2] as Ability].name} max = interdit).` }) : textFromValue(effect.value, { prefix: { truely: `Max de ${config.abilities[splited[2] as Ability].name} min à ` }, suffix: { truely: '.' }, falsely: `Opération interdite ( ${config.abilities[splited[2] as Ability].name} max = interdit).` }); + default: return 'Bonus inconnu'; } 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}.`}` }); + 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': return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+' }, suffix: { truely: ` au mod. de ${mainStatTexts[splited[1] as MainStat]}.` } }) : textFromValue(effect.value, { prefix: { truely: `Mod. de ${mainStatTexts[splited[1] as MainStat]} fixé à ` }, suffix: { truely: '.' }, falsely: `Opération interdite (Mod. de ${mainStatShortTexts[splited[1] as MainStat]} = interdit).` }); default: break; diff --git a/shared/floating.util.ts b/shared/floating.util.ts index 81d7d09..308a794 100644 --- a/shared/floating.util.ts +++ b/shared/floating.util.ts @@ -19,11 +19,11 @@ export interface FollowerProperties extends FloatingProperties } export interface PopperProperties extends FloatingProperties { - content?: NodeChildren; + content?: NodeChildren | (() => NodeChildren); delay?: number; - onShow?: (element: HTMLDivElement) => boolean | void; - onHide?: (element: HTMLDivElement) => boolean | void; + onShow?: () => boolean | void; + onHide?: () => boolean | void; } export interface ModalProperties @@ -43,7 +43,7 @@ export function popper(container: HTMLElement, properties?: PopperProperties): H { let shown = false, timeout: Timer; const arrow = svg('svg', { class: 'absolute fill-light-35 dark:fill-dark-35', attributes: { width: "12", height: "8", viewBox: "0 0 20 10" } }, [svg('polygon', { attributes: { points: "0,0 20,0 10,10" } })]); - const content = dom('div', { class: ['fixed hidden', properties?.class], style: properties?.style, attributes: { 'data-state': 'closed' } }, [...(properties?.content ?? []), arrow]); + const content = dom('div', { class: ['fixed hidden', properties?.class], style: properties?.style, attributes: { 'data-state': 'closed' } }); const rect = properties?.viewport?.getBoundingClientRect() ?? 'viewport'; function update() @@ -53,7 +53,6 @@ export function popper(container: HTMLElement, properties?: PopperProperties): H strategy: 'fixed', middleware: [ properties?.offset ? FloatingUI.offset(properties?.offset) : undefined, - FloatingUI.hide({ rootBoundary: rect, strategy: "escaped" }), FloatingUI.hide({ rootBoundary: rect }), FloatingUI.shift({ rootBoundary: rect }), FloatingUI.flip({ rootBoundary: rect }), @@ -109,8 +108,17 @@ export function popper(container: HTMLElement, properties?: PopperProperties): H let stop: () => void | undefined; function show() { - if(shown || !properties?.onShow || properties?.onShow(content) !== false) + if(shown || !properties?.onShow || properties?.onShow() !== false) { + if(typeof properties?.content === 'function') + { + properties.content = properties.content(); + } + if(content.children.length === 0 && (properties?.content && properties.content.length > 0 || properties?.arrow)) + { + content.replaceChildren(...(properties!.content as Node[]), arrow); + } + clearTimeout(timeout); timeout = setTimeout(() => { @@ -137,7 +145,7 @@ export function popper(container: HTMLElement, properties?: PopperProperties): H function hide() { - if(!properties?.onHide || properties?.onHide(content) !== false) + if(!properties?.onHide || properties?.onHide() !== false) { clearTimeout(timeout); @@ -274,13 +282,13 @@ export function contextmenu(x: number, y: number, content: NodeChildren, propert }, }, content, properties); } -export function tooltip(container: HTMLElement, txt: string, placement: FloatingUI.Placement, delay?: number): HTMLElement +export function tooltip(container: HTMLElement, txt: string | Text, placement: FloatingUI.Placement, delay?: number): HTMLElement { return popper(container, { arrow: true, offset: 8, delay: delay, - content: [ text(txt) ], + content: [ typeof txt === 'string' ? text(txt) : txt ], placement: placement, class: "fixed hidden TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50" }); diff --git a/shared/general.util.ts b/shared/general.util.ts index a801c47..123ec23 100644 --- a/shared/general.util.ts +++ b/shared/general.util.ts @@ -1,12 +1,12 @@ -export const ID_SIZE = 32; +const ID_SIZE = 32; export function unifySlug(slug: string | string[]): string { return (Array.isArray(slug) ? slug.join('/') : slug); } -export function getID(length: number) +export function getID() { - for (var id = [], i = 0; i < length; i++) + for (var id = [], i = 0; i < ID_SIZE; i++) id.push((36 * Math.random() | 0).toString(36)); return id.join(""); } diff --git a/shared/i18n.ts b/shared/i18n.ts index dd27c7f..847c279 100644 --- a/shared/i18n.ts +++ b/shared/i18n.ts @@ -1,9 +1,9 @@ -import type { CharacterConfig } from "~/types/character"; +import type { CharacterConfig, i18nID } from "~/types/character"; import characterConfig from '#shared/character-config.json'; const config = characterConfig as CharacterConfig; -export function getText(id?: string, lang?: string) +export function getText(id?: i18nID, lang?: string) { return id ? (config.texts.hasOwnProperty(id) ? config.texts[id][lang ?? "default"] : '') : undefined; } \ No newline at end of file diff --git a/shared/markdown.util.ts b/shared/markdown.util.ts index 1248be8..d575c32 100644 --- a/shared/markdown.util.ts +++ b/shared/markdown.util.ts @@ -4,7 +4,7 @@ import prose, { a, blockquote, tag, h1, h2, h3, h4, h5, hr, li, small, table, td import { heading } from "hast-util-heading"; import { headingRank } from "hast-util-heading-rank"; import { parseId } from "#shared/general.util"; -import { loading } from "#shared/components.util"; +import { async, loading } from "#shared/components.util"; export function renderMarkdown(markdown: Root, proses: Record): HTMLDivElement { @@ -45,35 +45,29 @@ export interface MDProperties } export default function(content: string, filter?: string, properties?: MDProperties): HTMLElement { - const load = loading('normal'); - - queueMicrotask(() => { - useMarkdown().parse(content).then(data => { - if(filter) - { - const start = data?.children.findIndex(e => heading(e) && parseId(e.properties.id as string | undefined) === filter) ?? -1; - - if(start !== -1) - { - let end = start; - const rank = headingRank(data.children[start])!; - while(end < data.children.length) - { - end++; - if(heading(data.children[end]) && headingRank(data.children[end])! <= rank) - break; - } - data = { ...data, children: data.children.slice(start, end) }; - } - } - - const el = renderMarkdown(data, { a, blockquote, tag, callout, h1, h2, h3, h4, h5, hr, li, small, table, td, th, ...properties?.tags }); - - if(properties) styling(el, properties); + return async('large', useMarkdown().parse(content).then(data => { + if(filter) + { + const start = data?.children.findIndex(e => heading(e) && parseId(e.properties.id as string | undefined) === filter) ?? -1; - load.parentElement?.replaceChild(el, load); - }); - }) - - return load; + if(start !== -1) + { + let end = start; + const rank = headingRank(data.children[start])!; + while(end < data.children.length) + { + end++; + if(heading(data.children[end]) && headingRank(data.children[end])! <= rank) + break; + } + data = { ...data, children: data.children.slice(start, end) }; + } + } + + const el = renderMarkdown(data, { a, blockquote, tag, callout, h1, h2, h3, h4, h5, hr, li, small, table, td, th, ...properties?.tags }); + + if(properties) styling(el, properties); + + return el; + })); } \ No newline at end of file diff --git a/shared/proses.ts b/shared/proses.ts index 9f7337b..39471d8 100644 --- a/shared/proses.ts +++ b/shared/proses.ts @@ -1,11 +1,11 @@ -import { dom, icon, type NodeChildren, type Node } from "#shared/dom.util"; +import { dom, icon, type NodeChildren, type Node, div } from "#shared/dom.util"; import { parseURL } from 'ufo'; import render from "#shared/markdown.util"; import { popper } from "#shared/floating.util"; import { Canvas } from "#shared/canvas.util"; import { Content, iconByType, type LocalContent } from "#shared/content.util"; import { parsePath, unifySlug } from "#shared/general.util"; -import { loading } from "./components.util"; +import { async, loading } from "./components.util"; export type CustomProse = (properties: any, children: NodeChildren) => Node; @@ -20,8 +20,6 @@ export const a: Prose = { const link = overview ? { name: 'explore-path', params: { path: overview.path }, hash: hash } : href, nav = router.resolve(link); - let rendered = false; - const el = dom('a', { class: 'text-accent-blue inline-flex items-center', attributes: { href: nav.href }, listeners: { 'click': (e) => { e.preventDefault(); @@ -43,26 +41,22 @@ export const a: Prose = { cover: "height", placement: 'bottom-start', class: 'data-[side=bottom]:animate-slideUpAndFade data-[side=right]:animate-slideLeftAndFade data-[side=left]:animate-slideRightAndFade data-[side=top]:animate-slideDownAndFade w-[300px] bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 data-[state=open]:transition-transform text-light-100 dark:text-dark-100 min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] w-full overflow-auto z-[45]', - content: [loading("large")], - viewport: document.getElementById('mainContainer') ?? undefined, - onShow(content: HTMLDivElement) { - if(!rendered) - { - Content.getContent(overview.id).then((_content) => { - if(_content?.type === 'markdown') - { - content.replaceChild(render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'py-4 px-6' }), content.children[0]!); - } - if(_content?.type === 'canvas') - { - const canvas = new Canvas((_content as LocalContent<'canvas'>).content); - content.replaceChild(dom('div', { class: 'w-[600px] h-[600px] relative' }, [canvas.container]), content.children[0]!); - canvas.mount(); - } - }); - rendered = true; - } + content: () => { + return [async('large', Content.getContent(overview.id).then((_content) => { + if(_content?.type === 'markdown') + { + return render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] w-full overflow-auto py-4 px-6' }); + } + if(_content?.type === 'canvas') + { + const canvas = new Canvas((_content as LocalContent<'canvas'>).content); + queueMicrotask(() => canvas.mount()); + return dom('div', { class: 'w-[600px] h-[600px] relative' }, [canvas.container]); + } + return div(''); + }))]; }, + viewport: document.getElementById('mainContainer') ?? undefined }); } @@ -95,24 +89,24 @@ export const fakeA: Prose = { cover: "height", placement: 'bottom-start', class: 'data-[side=bottom]:animate-slideUpAndFade data-[side=right]:animate-slideLeftAndFade data-[side=left]:animate-slideRightAndFade data-[side=top]:animate-slideDownAndFade w-[300px] bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 data-[state=open]:transition-transform text-light-100 dark:text-dark-100 min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] w-full z-[45]', - content: [loading("large")], - onShow(content: HTMLDivElement) { - if(!magicKeys.current.has('control') || magicKeys.current.has('meta')) - return false; - - content.replaceChild(loading("large"), content.children[0]!); - Content.getContent(overview.id).then((_content) => { + content: () => { + return [async('large', Content.getContent(overview.id).then((_content) => { if(_content?.type === 'markdown') { - content.replaceChild(render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] w-full overflow-auto py-4 px-6' }), content.children[0]!); + return render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] w-full overflow-auto py-4 px-6' }); } if(_content?.type === 'canvas') { const canvas = new Canvas((_content as LocalContent<'canvas'>).content); - content.replaceChild(dom('div', { class: 'w-[600px] h-[600px] relative' }, [canvas.container]), content.children[0]!); - canvas.mount(); + queueMicrotask(() => canvas.mount()); + return dom('div', { class: 'w-[600px] h-[600px] relative' }, [canvas.container]); } - }); + return div(''); + }))]; + }, + onShow() { + if(!magicKeys.current.has('control') || magicKeys.current.has('meta')) + return false; }, }); } diff --git a/types/character.d.ts b/types/character.d.ts index cf49425..d130321 100644 --- a/types/character.d.ts +++ b/types/character.d.ts @@ -10,11 +10,9 @@ export type SpellElement = typeof SPELL_ELEMENTS[number]; export type Alignment = typeof ALIGNMENTS[number]; export type FeatureID = string; -export type TextID = string; +export type i18nID = string; export type Resistance = string; -export type Dice = `${number}d${4 | 6 | 8 | 10 | 12 | 20}`; - export type Character = { id: number; @@ -41,7 +39,15 @@ export type CharacterVariables = { sickness: Array<{ id: string, state: number | true }>; spells: string[]; //Spell ID - equipment: string[]; //Equipment ID + items: ItemState[]; +}; +type ItemState = { + id: string, + amount: number; + enchantments?: []; + charges?: number; + equipped?: boolean; + state?: any; }; export type CharacterConfig = { peoples: Record; @@ -51,35 +57,44 @@ export type CharacterConfig = { spells: SpellConfig[]; aspects: AspectConfig[]; features: Record; - enchantments: Record; //TODO - items: Record; + enchantments: Record; //TODO + items: Record; lists: Record; - texts: Record; + texts: Record; }; -export type ItemConfig = { id: string, weight?: number, price?: number, power: number } & (ArmorConfig | WeaponConfig | WondrousConfig | MundaneConfig); +export type ItemConfig = CommonItemConfig & (ArmorConfig | WeaponConfig | WondrousConfig | MundaneConfig); +type CommonItemConfig = { + id: string; + 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 + equippable: boolean; +} type ArmorConfig = { category: 'armor'; name: string; //TODO -> TextID - description: TextID; - life: number; - absorb: number; + description: i18nID; + health: number; + absorb: { static: number, percent: number }; }; type WeaponConfig = { - category: 'armor'; + category: 'weapon'; name: string; //TODO -> TextID - description: TextID; - damage: Dice; + description: i18nID; + damage: string; //Dice formula }; type WondrousConfig = { - category: 'armor'; + category: 'wondrous'; name: string; //TODO -> TextID - description: TextID; + description: i18nID; effect: FeatureEffect[]; }; type MundaneConfig = { - category: 'armor'; + category: 'mundane'; name: string; //TODO -> TextID - description: TextID; + description: i18nID; }; export type SpellConfig = { id: string; @@ -120,7 +135,7 @@ export type AspectConfig = { export type FeatureEffect = { id: FeatureID; category: "value"; - operation: "add" | "set"; + operation: "add" | "set" | "min"; property: string; value: number | `modifier/${MainStat}` | false; } | { @@ -140,10 +155,10 @@ export type FeatureItem = FeatureEffect | { exclusive: boolean; //Disallow to pick the same option twice }; options: Array; -} +}; export type Feature = { id: FeatureID; - description: string; + description: i18nID; effect: FeatureItem[]; };