You've already forked obsidian-visualiser
Rollback CharacterEditor to the previous version
This commit is contained in:
11
app/app.vue
11
app/app.vue
@@ -61,7 +61,6 @@ iconify-icon
|
|||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
@apply bg-light-40;
|
@apply bg-light-40;
|
||||||
@apply dark:bg-dark-40;
|
@apply dark:bg-dark-40;
|
||||||
@@ -71,11 +70,19 @@ iconify-icon
|
|||||||
@apply border-transparent;
|
@apply border-transparent;
|
||||||
@apply bg-clip-padding;
|
@apply bg-clip-padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
@apply bg-light-50;
|
@apply bg-light-50;
|
||||||
@apply dark:bg-dark-50;
|
@apply dark:bg-dark-50;
|
||||||
}
|
}
|
||||||
|
.no-scroll::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.no-scroll::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-transparent;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.callout[data-type="abstract"],
|
.callout[data-type="abstract"],
|
||||||
.callout[data-type="summary"],
|
.callout[data-type="summary"],
|
||||||
.callout[data-type="tldr"]
|
.callout[data-type="tldr"]
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
</NavigationMenuTrigger>
|
</NavigationMenuTrigger>
|
||||||
<NavigationMenuContent class="absolute top-0 w-full sm:w-auto bg-light-0 dark:bg-dark-0 border border-light-30 dark:border-dark-30 py-2 z-20 flex flex-col">
|
<NavigationMenuContent class="absolute top-0 w-full sm:w-auto bg-light-0 dark:bg-dark-0 border border-light-30 dark:border-dark-30 py-2 z-20 flex flex-col">
|
||||||
<NuxtLink :href="{ name: 'character-list' }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Personnages publics</span></NuxtLink>
|
<NuxtLink :href="{ name: 'character-list' }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Personnages publics</span></NuxtLink>
|
||||||
<NuxtLink :href="{ name: 'character-id', params: { id: 'new' } }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Nouveau personnage</span></NuxtLink>
|
<NuxtLink :href="{ name: 'character-id-edit', params: { id: 'new' } }" class="hover:bg-light-30 dark:hover:bg-dark-30 px-4 py-2 select-none" active-class="!text-accent-blue"><span class="flex-1 truncate">Nouveau personnage</span></NuxtLink>
|
||||||
</NavigationMenuContent>
|
</NavigationMenuContent>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
</NavigationMenuList>
|
</NavigationMenuList>
|
||||||
|
|||||||
28
app/pages/character/[id]/edit.client.vue
Normal file
28
app/pages/character/[id]/edit.client.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { CharacterBuilder } from '~~/shared/character';
|
||||||
|
import { unifySlug } from '~~/shared/general';
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
requiresAuth: true,
|
||||||
|
validState: true,
|
||||||
|
});
|
||||||
|
const id = unifySlug(useRouter().currentRoute.value.params.id ?? "new");
|
||||||
|
const container = useTemplateRef('container');
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if(container.value)
|
||||||
|
{
|
||||||
|
const builder = new CharacterBuilder(container.value, id === 'new' ? undefined : id);
|
||||||
|
|
||||||
|
useShortcuts({
|
||||||
|
"Meta_S": () => builder.save(false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-1 max-w-full flex-col align-center" ref="container"></div>
|
||||||
|
</template>
|
||||||
@@ -83,7 +83,7 @@ async function duplicateCharacter(id: number)
|
|||||||
<NuxtLink v-if="user && user.state === 1" class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
<NuxtLink v-if="user && user.state === 1" class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
||||||
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
||||||
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
||||||
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-id', params: { id: 'new' } }">Nouveau personnage</NuxtLink>
|
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-id-edit', params: { id: 'new' } }">Nouveau personnage</NuxtLink>
|
||||||
<div v-else>Veuillez validez votre adresse mail pour pouvoir créer des personnages.</div>
|
<div v-else>Veuillez validez votre adresse mail pour pouvoir créer des personnages.</div>
|
||||||
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
||||||
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const { user } = useUserSession();
|
|||||||
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
||||||
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
||||||
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
||||||
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-id', params: { id: 'new' } }">Nouveau personnage</NuxtLink>
|
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-id-edit', params: { id: 'new' } }">Nouveau personnage</NuxtLink>
|
||||||
</template>
|
</template>
|
||||||
<div v-else>Veuillez valider votre adresse mail pour pouvoir créer des personnages.</div>
|
<div v-else>Veuillez valider votre adresse mail pour pouvoir créer des personnages.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
2
app/types/character.d.ts
vendored
2
app/types/character.d.ts
vendored
@@ -165,7 +165,7 @@ export type SpellConfig = {
|
|||||||
rank: 1 | 2 | 3 | 4;
|
rank: 1 | 2 | 3 | 4;
|
||||||
type: SpellType;
|
type: SpellType;
|
||||||
cost: number;
|
cost: number;
|
||||||
speed: "action" | "reaction" | number;
|
speed: "action" | "reaction" | "channeling" | number;
|
||||||
elements: Array<SpellElement>;
|
elements: Array<SpellElement>;
|
||||||
description: string; //TODO -> TextID
|
description: string; //TODO -> TextID
|
||||||
concentration: boolean;
|
concentration: boolean;
|
||||||
|
|||||||
@@ -234,6 +234,43 @@ export default defineNuxtConfig({
|
|||||||
path: '/ws'
|
path: '/ws'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
include: [
|
||||||
|
'@atlaskit/pragmatic-drag-and-drop-auto-scroll/element',
|
||||||
|
'@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item',
|
||||||
|
'@atlaskit/pragmatic-drag-and-drop/combine',
|
||||||
|
'@atlaskit/pragmatic-drag-and-drop/element/adapter',
|
||||||
|
'@codemirror/autocomplete',
|
||||||
|
'@codemirror/commands',
|
||||||
|
'@codemirror/lang-markdown',
|
||||||
|
'@codemirror/language',
|
||||||
|
'@codemirror/search',
|
||||||
|
'@codemirror/state',
|
||||||
|
'@codemirror/view',
|
||||||
|
'@floating-ui/dom',
|
||||||
|
'@iconify/vue',
|
||||||
|
'@lezer/common',
|
||||||
|
'@lezer/highlight',
|
||||||
|
'hast-util-heading',
|
||||||
|
'hast-util-heading-rank',
|
||||||
|
'hast-util-select',
|
||||||
|
'iconify-icon',
|
||||||
|
'lodash.capitalize',
|
||||||
|
'mdast-util-find-and-replace',
|
||||||
|
'mdast-util-to-string',
|
||||||
|
'remark-breaks',
|
||||||
|
'remark-frontmatter',
|
||||||
|
'remark-gfm',
|
||||||
|
'remark-parse',
|
||||||
|
'remark-rehype',
|
||||||
|
'remark-stringify',
|
||||||
|
'strip-markdown',
|
||||||
|
'unified',
|
||||||
|
'unist-util-visit',
|
||||||
|
'zod',
|
||||||
|
'zod/v4',
|
||||||
|
]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
vue: {
|
vue: {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
|
|||||||
42
package.json
42
package.json
@@ -9,22 +9,22 @@
|
|||||||
"migrate": "bun migrate.ts"
|
"migrate": "bun migrate.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
"@atlaskit/pragmatic-drag-and-drop": "^1.8.1",
|
||||||
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.2",
|
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.5",
|
||||||
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.2.0",
|
||||||
"@codemirror/lang-markdown": "^6.5.0",
|
"@codemirror/lang-markdown": "^6.5.0",
|
||||||
"@floating-ui/dom": "^1.7.4",
|
"@floating-ui/dom": "^1.7.6",
|
||||||
"@iconify/vue": "^5.0.0",
|
"@iconify/vue": "^5.0.1",
|
||||||
"@lezer/highlight": "^1.2.3",
|
"@lezer/highlight": "^1.2.3",
|
||||||
"@markdoc/markdoc": "^0.5.4",
|
"@markdoc/markdoc": "^0.5.7",
|
||||||
"@nuxtjs/color-mode": "^4.0.0",
|
"@nuxtjs/color-mode": "^4.0.0",
|
||||||
"@nuxtjs/sitemap": "^7.4.9",
|
"@nuxtjs/sitemap": "^8.1.0",
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
"@vueuse/gesture": "^2.0.0",
|
"@vueuse/gesture": "^2.0.0",
|
||||||
"@vueuse/math": "^14.1.0",
|
"@vueuse/math": "^14.3.0",
|
||||||
"@vueuse/nuxt": "^14.1.0",
|
"@vueuse/nuxt": "^14.3.0",
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"drizzle-orm": "^0.45.0",
|
"drizzle-orm": "^0.45.2",
|
||||||
"hast": "^1.0.0",
|
"hast": "^1.0.0",
|
||||||
"hast-util-heading": "^3.0.0",
|
"hast-util-heading": "^3.0.0",
|
||||||
"hast-util-heading-rank": "^3.0.0",
|
"hast-util-heading-rank": "^3.0.0",
|
||||||
@@ -32,9 +32,9 @@
|
|||||||
"iconify-icon": "^3.0.2",
|
"iconify-icon": "^3.0.2",
|
||||||
"lodash.capitalize": "^4.2.1",
|
"lodash.capitalize": "^4.2.1",
|
||||||
"mdast-util-find-and-replace": "^3.0.2",
|
"mdast-util-find-and-replace": "^3.0.2",
|
||||||
"nodemailer": "^7.0.11",
|
"nodemailer": "^8.0.10",
|
||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.4.8",
|
||||||
"nuxt-security": "^2.5.0",
|
"nuxt-security": "^2.6.0",
|
||||||
"radix-vue": "^1.9.17",
|
"radix-vue": "^1.9.17",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"remark-breaks": "^4.0.0",
|
"remark-breaks": "^4.0.0",
|
||||||
@@ -47,18 +47,18 @@
|
|||||||
"rollup-plugin-vue": "^6.0.0",
|
"rollup-plugin-vue": "^6.0.0",
|
||||||
"strip-markdown": "^6.0.0",
|
"strip-markdown": "^6.0.0",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.1.0",
|
||||||
"vue": "^3.5.25",
|
"vue": "^3.5.35",
|
||||||
"vue-router": "^4.6.3",
|
"vue-router": "^5.1.0",
|
||||||
"zod": "^4.1.13"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.3.4",
|
"@types/bun": "^1.3.14",
|
||||||
"@types/lodash.capitalize": "^4.2.9",
|
"@types/lodash.capitalize": "^4.2.9",
|
||||||
"@types/nodemailer": "^7.0.4",
|
"@types/nodemailer": "^8.0.0",
|
||||||
"@types/unist": "^3.0.3",
|
"@types/unist": "^3.0.3",
|
||||||
"bun-types": "^1.3.4",
|
"bun-types": "^1.3.14",
|
||||||
"drizzle-kit": "^0.31.8",
|
"drizzle-kit": "^0.31.10",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"rehype-stringify": "^10.0.1"
|
"rehype-stringify": "^10.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
|||||||
import type { Ability, Alignment, ArmorConfig, ArmorState, Character, CharacterConfig, CompiledCharacter, DamageType, ImprovementConfig, FeatureEquipment, FeatureID, FeatureItem, FeatureList, FeatureState, FeatureValue, ItemConfig, ItemState, Level, MainStat, MundaneState, Resistance, SpellConfig, SpellElement, SpellType, TrainingLevel, TreeStructure, WeaponConfig, WeaponState, WeaponType, WondrousState, CraftingType } from "~/types/character";
|
import type { Ability, Alignment, ArmorConfig, ArmorState, Character, CharacterConfig, CompiledCharacter, DamageType, ImprovementConfig, FeatureEquipment, FeatureID, FeatureItem, FeatureList, FeatureState, FeatureValue, ItemConfig, ItemState, Level, MainStat, MundaneState, Resistance, SpellConfig, SpellElement, SpellType, TrainingLevel, TreeStructure, WeaponConfig, WeaponState, WeaponType, WondrousState, CraftingType, CharacterVariables } from "~/types/character";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import characterConfig from '#shared/character-config.json';
|
import characterConfig from '#shared/character-config.json';
|
||||||
import proses, { preview } from "#shared/proses";
|
import proses, { preview } from "#shared/proses";
|
||||||
import { button, checkbox, floater, foldable, input, loading, multiselect, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components";
|
import { button, checkbox, floater, foldable, input, loading, multiselect, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components";
|
||||||
import { div, dom, icon, span, text } from "#shared/dom";
|
import { div, dom, icon, span, text } from "#shared/dom";
|
||||||
import { followermenu, fullblocker, tooltip } from "#shared/floating";
|
import { followermenu, fullblocker, tooltip } from "#shared/floating";
|
||||||
import { clamp } from "#shared/general";
|
import { clamp, deepEquals } from "#shared/general";
|
||||||
import markdown from "#shared/markdown";
|
import markdown from "#shared/markdown";
|
||||||
import { getText } from "#shared/i18n";
|
import { getText } from "#shared/i18n";
|
||||||
import type { User } from "~/types/auth";
|
import type { User } from "~/types/auth";
|
||||||
@@ -48,7 +48,7 @@ export const defaultCharacter: Character = {
|
|||||||
exhaustion: 0,
|
exhaustion: 0,
|
||||||
sickness: [],
|
sickness: [],
|
||||||
poisons: [],
|
poisons: [],
|
||||||
money: 0,
|
components: { money: 0, natural: 0, mineral: 0, processed: 0, magical: 0 },
|
||||||
transformed: false,
|
transformed: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ export const CharacterVariablesValidation = z.object({
|
|||||||
spells: z.array(z.string()),
|
spells: z.array(z.string()),
|
||||||
items: z.array(ItemStateValidation),
|
items: z.array(ItemStateValidation),
|
||||||
|
|
||||||
money: z.number(),
|
components: z.object({ natural: z.number(), mineral: z.number(), processed: z.number(), magical: z.number(), money: z.number() }),
|
||||||
transformed: z.boolean(),
|
transformed: z.boolean(),
|
||||||
});
|
});
|
||||||
export const CharacterValidation = z.object({
|
export const CharacterValidation = z.object({
|
||||||
@@ -286,7 +286,7 @@ export const CharacterValidation = z.object({
|
|||||||
owner: z.number(),
|
owner: z.number(),
|
||||||
username: z.string().optional(),
|
username: z.string().optional(),
|
||||||
visibility: z.enum(["public", "private"]),
|
visibility: z.enum(["public", "private"]),
|
||||||
thumbnail: z.any(),
|
thumbnail: z.any().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Property = { value: number | string | false, id: string, operation: "set" | "add" | "min" };
|
type Property = { value: number | string | false, id: string, operation: "set" | "add" | "min" };
|
||||||
@@ -720,7 +720,7 @@ function setProperty<T>(root: any, path: string, value: T | ((old: T) => T), for
|
|||||||
if(force || object.hasOwnProperty(arr.slice(-1)[0]!))
|
if(force || object.hasOwnProperty(arr.slice(-1)[0]!))
|
||||||
object[arr.slice(-1)[0]!] = typeof value === 'function' ? (value as (old: T) => T)(object[arr.slice(-1)[0]!]) : value;
|
object[arr.slice(-1)[0]!] = typeof value === 'function' ? (value as (old: T) => T)(object[arr.slice(-1)[0]!]) : value;
|
||||||
}
|
}
|
||||||
/* export class CharacterBuilder extends CharacterCompiler
|
export class CharacterBuilder extends CharacterCompiler
|
||||||
{
|
{
|
||||||
private _container: HTMLElement;
|
private _container: HTMLElement;
|
||||||
private _content?: HTMLElement;
|
private _content?: HTMLElement;
|
||||||
@@ -1074,10 +1074,7 @@ class LevelPicker extends BuilderTab
|
|||||||
{
|
{
|
||||||
super(builder);
|
super(builder);
|
||||||
|
|
||||||
this._levelInput = numberpicker({ defaultValue: this._builder.character.level, min: 1, max: 20, input: (value) => {
|
this._levelInput = 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._builder.character.level = value;
|
|
||||||
this.updateLevel();
|
|
||||||
} });
|
|
||||||
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._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 = Object.entries(config.peoples[this._builder.character.people!]!.options).map(
|
this._options = Object.entries(config.peoples[this._builder.character.people!]!.options).map(
|
||||||
@@ -1417,7 +1414,7 @@ class AspectPicker extends BuilderTab
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} */
|
}
|
||||||
|
|
||||||
export const masteryTexts: Record<CompiledCharacter['mastery'][number], { text: string, href: string }> = {
|
export const masteryTexts: Record<CompiledCharacter['mastery'][number], { text: string, href: string }> = {
|
||||||
"armor/light": { text: "Armure légère", href: "regles/annexes/equipement#Les armures légères" },
|
"armor/light": { text: "Armure légère", href: "regles/annexes/equipement#Les armures légères" },
|
||||||
@@ -1523,99 +1520,74 @@ export const stateFactory = (item: ItemConfig) => {
|
|||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
export class CharacterSheet extends CharacterCompiler
|
export class CharacterSheet
|
||||||
{
|
{
|
||||||
private user: ComputedRef<User | null>;
|
private user: ComputedRef<User | null>;
|
||||||
container: HTMLElement = div('flex flex-1 h-full w-full items-start justify-between');
|
private character: CharacterCompiler = reactive(new CharacterCompiler(defaultCharacter));
|
||||||
private editingDOM: HTMLElement = div();
|
container: HTMLElement = div('flex flex-1 h-full w-full items-start justify-center');
|
||||||
private readingDOM: HTMLElement = div();
|
|
||||||
private state: { exists: boolean, edit: boolean };
|
|
||||||
private tabs?: HTMLElement;
|
private tabs?: HTMLElement;
|
||||||
private tab: string = localStorage.getItem('character-tab') ?? 'actions';
|
private tab: string = localStorage.getItem('character-tab') ?? 'actions';
|
||||||
|
|
||||||
private _variableDebounce: NodeJS.Timeout = setTimeout(() => {});
|
private _variableDebounce: NodeJS.Timeout = setTimeout(() => {});
|
||||||
|
|
||||||
ws?: Socket;
|
ws?: Socket;
|
||||||
constructor(id: string, user: ComputedRef<User | null>, editing: boolean = false)
|
constructor(id: string, user: ComputedRef<User | null>)
|
||||||
{
|
{
|
||||||
super(defaultCharacter);
|
|
||||||
|
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
|
||||||
const load = div("flex justify-center items-center w-full h-full", [ loading('large') ]);
|
const load = div("flex justify-center items-center w-full h-full", [ loading('large') ]);
|
||||||
this.container.replaceChildren(load);
|
this.container.replaceChildren(load);
|
||||||
this.state = reactive({ exists: id !== 'new', edit: id === 'new' || editing });
|
|
||||||
|
|
||||||
this.render();
|
useRequestFetch()(`/api/character/${id}`).then(character => {
|
||||||
this.edit();
|
if(character)
|
||||||
|
{
|
||||||
|
this.character.character = reactive(character);
|
||||||
|
|
||||||
if(this.state.exists)
|
if(character.campaign)
|
||||||
{
|
|
||||||
useRequestFetch()(`/api/character/${id}`).then(character => {
|
|
||||||
if(character)
|
|
||||||
{
|
{
|
||||||
this.character = reactive(character);
|
this.ws = new Socket(`/ws/campaign/${character.campaign}`, true);
|
||||||
|
|
||||||
if(character.campaign)
|
this.ws.handleMessage('SYNC', () => {
|
||||||
{
|
useRequestFetch()(`/api/character/${id}`).then(character => {
|
||||||
//this.ws = new Socket(`/ws/campaign/${character.campaign}`, true);
|
if(character) this.character!.character = reactive(character);
|
||||||
|
});
|
||||||
/* this.ws.handleMessage('SYNC', () => {
|
})
|
||||||
useRequestFetch()(`/api/character/${id}`).then(character => {
|
this.ws.handleMessage<{ action: 'set' | 'add' | 'remove', key: keyof CharacterVariables, value: any }>('VARIABLE', (variable) => {
|
||||||
if(character)
|
const prop = this.character!.character.variables[variable.key];
|
||||||
{
|
if(variable.action === 'set')
|
||||||
this.character!.character = reactive(character);
|
this.character!.character.variables[variable.key] = variable.value;
|
||||||
this.character!.values;
|
else if(Array.isArray(prop))
|
||||||
'update' in this.container! && this.container!.update!(true);
|
{
|
||||||
}
|
if(variable.action === 'add')
|
||||||
});
|
prop.push(variable.value);
|
||||||
})
|
else if(variable.action === 'remove')
|
||||||
this.ws.handleMessage<{ action: 'set' | 'add' | 'remove', key: keyof CharacterVariables, value: any }>('VARIABLE', (variable) => {
|
|
||||||
const prop = this.character!.character.variables[variable.key];
|
|
||||||
if(variable.action === 'set')
|
|
||||||
this.character!.character.variables[variable.key] = variable.value;
|
|
||||||
else if(Array.isArray(prop))
|
|
||||||
{
|
{
|
||||||
if(variable.action === 'add')
|
const idx = prop.findIndex(e => deepEquals(e, variable.value));
|
||||||
prop.push(variable.value);
|
if(idx !== -1) prop.splice(idx, 1);
|
||||||
else if(variable.action === 'remove')
|
|
||||||
{
|
|
||||||
const idx = prop.findIndex(e => deepEquals(e, variable.value));
|
|
||||||
if(idx !== -1) prop.splice(idx, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}) */
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
document.title = `d[any] - ${character.name}`;
|
|
||||||
load.remove();
|
|
||||||
|
|
||||||
this.container.replaceChildren(this.readingDOM);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
throw new Error('Cannot find the requested character from its ID');
|
|
||||||
}).catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
this.container.replaceChildren(div('flex flex-col items-center justify-center flex-1 h-full gap-4', [
|
|
||||||
span('text-2xl font-bold tracking-wider', 'Personnage introuvable'),
|
|
||||||
span(undefined, 'Ce personnage n\'existe pas ou est privé.'),
|
|
||||||
div('flex flex-row gap-4 justify-center items-center', [
|
|
||||||
button(text('Personnages publics'), () => useRouter().push({ name: 'character-list' }), 'px-2 py-1'),
|
|
||||||
button(text('Créer un personange'), () => useRouter().push({ name: 'character-id-edit', params: { id: 'new' } }), 'px-2 py-1')
|
|
||||||
]),
|
|
||||||
span('text-sm italic text-light-70 dark:text-dark-70', e.toString())
|
|
||||||
]))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.state.edit = true;
|
|
||||||
|
|
||||||
this.container.replaceChildren(this.editingDOM, this.readingDOM);
|
document.title = `d[any] - ${character.name}`;
|
||||||
}
|
load.remove();
|
||||||
|
|
||||||
reactivity(this.state, (state) => {
|
this.render();
|
||||||
state.edit ? this.container.replaceChildren(this.editingDOM, this.readingDOM) : this.container.replaceChildren(this.readingDOM);
|
}
|
||||||
})
|
else
|
||||||
|
throw new Error('Cannot find the requested character from its ID');
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
this.container.replaceChildren(div('flex flex-col items-center justify-center flex-1 h-full gap-4', [
|
||||||
|
span('text-2xl font-bold tracking-wider', 'Personnage introuvable'),
|
||||||
|
span(undefined, 'Ce personnage n\'existe pas ou est privé.'),
|
||||||
|
div('flex flex-row gap-4 justify-center items-center', [
|
||||||
|
button(text('Personnages publics'), () => useRouter().push({ name: 'character-list' }), 'px-2 py-1'),
|
||||||
|
button(text('Créer un personange'), () => useRouter().push({ name: 'character-id-edit', params: { id: 'new' } }), 'px-2 py-1')
|
||||||
|
]),
|
||||||
|
span('text-sm italic text-light-70 dark:text-dark-70', e.toString())
|
||||||
|
]))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
@@ -1637,7 +1609,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
|
|
||||||
{ id: 'inventory', title: [ text('Inventaire') ], content: () => this.itemsTab() },
|
{ id: 'inventory', title: [ text('Inventaire') ], content: () => this.itemsTab() },
|
||||||
|
|
||||||
{ id: 'aspect', title: [ span(() => ({ 'relative before:absolute before:top-0 before:-right-2 before:w-2 before:h-2 before:rounded-full before:bg-accent-blue': this.compiled?.variables?.transformed ?? false }), 'Aspect') ], content: () => this.aspectTab() },
|
{ id: 'aspect', title: [ span(() => ({ 'relative before:absolute before:top-0 before:-right-2 before:w-2 before:h-2 before:rounded-full before:bg-accent-blue': this.character.compiled?.variables?.transformed ?? false }), 'Aspect') ], content: () => this.aspectTab() },
|
||||||
|
|
||||||
{ id: 'effects', title: [ text('Afflictions') ], content: () => this.effectsTab() },
|
{ id: 'effects', title: [ text('Afflictions') ], content: () => this.effectsTab() },
|
||||||
|
|
||||||
@@ -1653,90 +1625,80 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
] },
|
] },
|
||||||
], { focused: this.tab, class: { container: 'flex-1 gap-4 px-4 max-w-[960px] h-full', content: 'overflow-auto h-full' }, switch: v => { this.tab = v; localStorage.setItem('this.compiled-tab', v); } });
|
], { focused: this.tab, class: { container: 'flex-1 gap-4 px-4 max-w-[960px] h-full', content: 'overflow-auto h-full' }, switch: v => { this.tab = v; localStorage.setItem('this.character.compiled-tab', v); } });
|
||||||
|
|
||||||
this.readingDOM = div('flex flex-col justify-start gap-1 h-full w-full min-w-half', [
|
this.container.replaceChildren(div('flex flex-col justify-start gap-1 h-full w-full min-w-half', [
|
||||||
div("flex flex-row gap-4 justify-between", [
|
div("flex lg:flex-row flex-col gap-6 items-center justify-center", [
|
||||||
div(),
|
div("flex gap-6 items-center", [
|
||||||
|
div('inline-flex select-none items-center justify-center overflow-hidden align-middle h-16', [
|
||||||
div("flex lg:flex-row flex-col gap-6 items-center justify-center", [
|
div('text-light-100 dark:text-dark-100 leading-1 flex p-4 items-center justify-center bg-light-25 dark:bg-dark-25 font-medium', [
|
||||||
div("flex gap-6 items-center", [
|
icon("radix-icons:person", { width: 16, height: 16 }),
|
||||||
div('inline-flex select-none items-center justify-center overflow-hidden align-middle h-16', [
|
|
||||||
div('text-light-100 dark:text-dark-100 leading-1 flex p-4 items-center justify-center bg-light-25 dark:bg-dark-25 font-medium', [
|
|
||||||
icon("radix-icons:person", { width: 16, height: 16 }),
|
|
||||||
])
|
|
||||||
]),
|
|
||||||
|
|
||||||
div("flex flex-col", [
|
|
||||||
span("text-xl font-bold", () => this.compiled.name === '' ? "Inconnu" : this.compiled.name),
|
|
||||||
span("text-sm", () => this.compiled.username ? `De ${this.compiled.username}` : `De ${this.user.value?.username}`)
|
|
||||||
]),
|
|
||||||
|
|
||||||
div("flex flex-col", [
|
|
||||||
span("font-bold", () =>`Niveau ${this.compiled?.level ?? 0}`),
|
|
||||||
span('', () => this.compiled && this.compiled.race ? config.peoples[this.compiled.race]?.name ?? 'Peuple inconnu' : '')
|
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("flex flex-row lg:border-l border-light-35 dark:border-dark-35 py-4 ps-4 gap-8", [
|
div("flex flex-col", [
|
||||||
div("flex flex-row items-center gap-2 text-3xl font-light", [
|
span("text-xl font-bold", () => this.character.compiled.name === '' ? "Inconnu" : this.character.compiled.name),
|
||||||
text("PV: "),
|
span("text-sm", () => this.character.compiled.username ? `De ${this.character.compiled.username}` : `De ${this.user.value?.username}`)
|
||||||
() => this.compiled ? dom("span", {
|
|
||||||
class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35",
|
|
||||||
text: () => `${this.compiled.health - this.compiled.variables.health}`,
|
|
||||||
listeners: { click: healthPanel.show },
|
|
||||||
}) : undefined,
|
|
||||||
() => this.compiled ? text('/') : text('-'),
|
|
||||||
() => this.compiled ? text(() => this.compiled.health) : undefined,
|
|
||||||
]),
|
|
||||||
div("flex flex-row items-center gap-2 text-3xl font-light", [
|
|
||||||
text("Mana: "),
|
|
||||||
() => this.compiled ? dom("span", {
|
|
||||||
class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35",
|
|
||||||
text: () => `${this.compiled.mana - this.compiled.variables.mana}`,
|
|
||||||
listeners: { click: healthPanel.show },
|
|
||||||
}) : undefined,
|
|
||||||
() => this.compiled ? text('/') : text('-'),
|
|
||||||
() => this.compiled ? text(() => this.compiled.mana) : undefined,
|
|
||||||
]),
|
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
div("flex flex-col", [
|
||||||
|
span("font-bold", () =>`Niveau ${this.character.compiled?.level ?? 0}`),
|
||||||
|
span('', () => this.character.compiled && this.character.compiled.race ? config.peoples[this.character.compiled.race]?.name ?? 'Peuple inconnu' : '')
|
||||||
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("self-center", [
|
div("flex flex-row lg:border-l border-light-35 dark:border-dark-35 py-4 ps-4 gap-8", [
|
||||||
() => this.user.value && this.compiled && this.user.value.id === this.compiled.owner ?
|
div("flex flex-row items-center gap-2 text-3xl font-light", [
|
||||||
this.state.edit ? button(icon("ph:floppy-disk"), () => this.saveEdits(), "p-1") : button(icon("radix-icons:pencil-2"), () => this.state.edit = true, "p-1")
|
text("PV: "),
|
||||||
: div(),
|
() => this.character.compiled ? dom("span", {
|
||||||
|
class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35",
|
||||||
|
text: () => `${this.character.compiled.health - this.character.compiled.variables.health}`,
|
||||||
|
listeners: { click: healthPanel.show },
|
||||||
|
}) : undefined,
|
||||||
|
() => this.character.compiled ? text('/') : text('-'),
|
||||||
|
() => this.character.compiled ? text(() => this.character.compiled.health) : undefined,
|
||||||
|
]),
|
||||||
|
div("flex flex-row items-center gap-2 text-3xl font-light", [
|
||||||
|
text("Mana: "),
|
||||||
|
() => this.character.compiled ? dom("span", {
|
||||||
|
class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35",
|
||||||
|
text: () => `${this.character.compiled.mana - this.character.compiled.variables.mana}`,
|
||||||
|
listeners: { click: healthPanel.show },
|
||||||
|
}) : undefined,
|
||||||
|
() => this.character.compiled ? text('/') : text('-'),
|
||||||
|
() => this.character.compiled ? text(() => this.character.compiled.mana) : undefined,
|
||||||
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("flex flex-row justify-center gap-2 p-4 border-b border-light-35 dark:border-dark-35", [
|
div("flex flex-row justify-center gap-2 p-4 border-b border-light-35 dark:border-dark-35", [
|
||||||
div("flex gap-2 flex-row items-center justify-between", [
|
div("flex gap-2 flex-row items-center justify-between", [
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
() => !this.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiled.variables.transformed && config.aspects[this.compiled.aspect.id]?.stat === 'strength' }], () => `+${this.compiled.modifier.strength}`),
|
() => !this.character.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.character.compiled.variables.transformed && config.aspects[this.character.compiled.aspect.id]?.stat === 'strength' }], () => `+${this.character.compiled.modifier.strength}`),
|
||||||
span("text-sm ", "Force")
|
span("text-sm ", "Force")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
() => !this.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiled.variables.transformed && config.aspects[this.compiled.aspect.id]?.stat === 'dexterity' }], () => `+${this.compiled.modifier.dexterity}`),
|
() => !this.character.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.character.compiled.variables.transformed && config.aspects[this.character.compiled.aspect.id]?.stat === 'dexterity' }], () => `+${this.character.compiled.modifier.dexterity}`),
|
||||||
span("text-sm ", "Dextérité")
|
span("text-sm ", "Dextérité")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
() => !this.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiled.variables.transformed && config.aspects[this.compiled.aspect.id]?.stat === 'constitution' }], () => `+${this.compiled.modifier.constitution}`),
|
() => !this.character.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.character.compiled.variables.transformed && config.aspects[this.character.compiled.aspect.id]?.stat === 'constitution' }], () => `+${this.character.compiled.modifier.constitution}`),
|
||||||
span("text-sm ", "Constitution")
|
span("text-sm ", "Constitution")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
() => !this.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiled.variables.transformed && config.aspects[this.compiled.aspect.id]?.stat === 'intelligence' }], () => `+${this.compiled.modifier.intelligence}`),
|
() => !this.character.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.character.compiled.variables.transformed && config.aspects[this.character.compiled.aspect.id]?.stat === 'intelligence' }], () => `+${this.character.compiled.modifier.intelligence}`),
|
||||||
span("text-sm ", "Intelligence")
|
span("text-sm ", "Intelligence")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
() => !this.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiled.variables.transformed && config.aspects[this.compiled.aspect.id]?.stat === 'curiosity' }], () => `+${this.compiled.modifier.curiosity}`),
|
() => !this.character.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.character.compiled.variables.transformed && config.aspects[this.character.compiled.aspect.id]?.stat === 'curiosity' }], () => `+${this.character.compiled.modifier.curiosity}`),
|
||||||
span("text-sm ", "Curiosité")
|
span("text-sm ", "Curiosité")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
() => !this.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiled.variables.transformed && config.aspects[this.compiled.aspect.id]?.stat === 'charisma' }], () => `+${this.compiled.modifier.charisma}`),
|
() => !this.character.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.character.compiled.variables.transformed && config.aspects[this.character.compiled.aspect.id]?.stat === 'charisma' }], () => `+${this.character.compiled.modifier.charisma}`),
|
||||||
span("text-sm ", "Charisme")
|
span("text-sm ", "Charisme")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
() => !this.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiled.variables.transformed && config.aspects[this.compiled.aspect.id]?.stat === 'psyche' }], () => `+${this.compiled.modifier.psyche}`),
|
() => !this.character.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.character.compiled.variables.transformed && config.aspects[this.character.compiled.aspect.id]?.stat === 'psyche' }], () => `+${this.character.compiled.modifier.psyche}`),
|
||||||
span("text-sm ", "Psyché")
|
span("text-sm ", "Psyché")
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
@@ -1745,11 +1707,11 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
|
|
||||||
div("flex gap-2 flex-row items-center justify-between", [
|
div("flex gap-2 flex-row items-center justify-between", [
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
span("text-xl font-bold", () => !this.compiled ? '-' : `+${this.compiled.initiative}`),
|
span("text-xl font-bold", () => !this.character.compiled ? '-' : `+${this.character.compiled.initiative}`),
|
||||||
span("text-sm ", "Initiative")
|
span("text-sm ", "Initiative")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
span("text-xl font-bold", () => !this.compiled ? '-' : this.compiled.speed === false ? "N/A" : `${this.compiled.speed}`),
|
span("text-xl font-bold", () => !this.character.compiled ? '-' : this.character.compiled.speed === false ? "N/A" : `${this.character.compiled.speed}`),
|
||||||
span("text-sm ", "Course")
|
span("text-sm ", "Course")
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
@@ -1759,15 +1721,15 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div("flex gap-2 flex-row items-center justify-between", [
|
div("flex gap-2 flex-row items-center justify-between", [
|
||||||
icon("game-icons:checked-shield", { width: 32, height: 32 }),
|
icon("game-icons:checked-shield", { width: 32, height: 32 }),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
span(" text-xl font-bold", () => !this.compiled ? '-' : clamp(this.compiled.defense.static + this.compiled.defense.passiveparry + this.compiled.defense.passivedodge, 0, this.compiled.defense.hardcap)),
|
span(" text-xl font-bold", () => !this.character.compiled ? '-' : clamp(this.character.compiled.defense.static + this.character.compiled.defense.passiveparry + this.character.compiled.defense.passivedodge, 0, this.character.compiled.defense.hardcap)),
|
||||||
span("text-sm ", "Passive")
|
span("text-sm ", "Passive")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
span(" text-xl font-bold", () => !this.compiled ? '-' : clamp(this.compiled.defense.static + this.compiled.defense.activeparry + this.compiled.defense.passivedodge, 0, this.compiled.defense.hardcap)),
|
span(" text-xl font-bold", () => !this.character.compiled ? '-' : clamp(this.character.compiled.defense.static + this.character.compiled.defense.activeparry + this.character.compiled.defense.passivedodge, 0, this.character.compiled.defense.hardcap)),
|
||||||
span("text-sm ", "Blocage")
|
span("text-sm ", "Blocage")
|
||||||
]),
|
]),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
span(" text-xl font-bold", () => !this.compiled ? '-' : clamp(this.compiled.defense.static + this.compiled.defense.passiveparry + this.compiled.defense.activedodge, 0, this.compiled.defense.hardcap)),
|
span(" text-xl font-bold", () => !this.character.compiled ? '-' : clamp(this.character.compiled.defense.static + this.character.compiled.defense.passiveparry + this.character.compiled.defense.activedodge, 0, this.character.compiled.defense.hardcap)),
|
||||||
span("text-sm ", "Esquive")
|
span("text-sm ", "Esquive")
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
@@ -1785,7 +1747,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
ABILITIES.map((ability) =>
|
ABILITIES.map((ability) =>
|
||||||
div("flex flex-row px-1 justify-between items-center", [
|
div("flex flex-row px-1 justify-between items-center", [
|
||||||
proses('a', preview, [ span("text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline", abilityTexts[ability as Ability] || ability) ], { href: `regles/l'entrainement/competences#${abilityTexts[ability as Ability]}`, label: abilityTexts[ability as Ability], navigate: false }),
|
proses('a', preview, [ span("text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline", abilityTexts[ability as Ability] || ability) ], { href: `regles/l'entrainement/competences#${abilityTexts[ability as Ability]}`, label: abilityTexts[ability as Ability], navigate: false }),
|
||||||
span("font-bold text-base text-light-100 dark:text-dark-100", () => !this.compiled ? '-' : `+${this.compiled.abilities[ability as Ability] ?? 0}`),
|
span("font-bold text-base text-light-100 dark:text-dark-100", () => !this.character.compiled ? '-' : `+${this.character.compiled.abilities[ability as Ability] ?? 0}`),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -1795,11 +1757,11 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50")
|
div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50")
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", { list: () => this.compiled?.mastery ?? [], render: (e, _c) => proses('a', preview, [ text(masteryTexts[e].text) ], { href: masteryTexts[e].href, label: masteryTexts[e].text, class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline', }) }),
|
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", { list: () => this.character.compiled?.mastery ?? [], render: (e, _c) => proses('a', preview, [ text(masteryTexts[e].text) ], { href: masteryTexts[e].href, label: masteryTexts[e].text, class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline', }) }),
|
||||||
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", [
|
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", [
|
||||||
() => (this.compiled?.spellranks?.precision ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Précision') ], { href: 'regles/la-magie/magie#Les sorts de précision', label: 'Précision', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiled?.spellranks?.precision ?? 0) ]) : undefined,
|
() => (this.character.compiled?.spellranks?.precision ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Précision') ], { href: 'regles/la-magie/magie#Les sorts de précision', label: 'Précision', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.character.compiled?.spellranks?.precision ?? 0) ]) : undefined,
|
||||||
() => (this.compiled?.spellranks?.knowledge ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Savoir') ], { href: 'regles/la-magie/magie#Les sorts de savoir', label: 'Savoir', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiled?.spellranks?.knowledge ?? 0) ]) : undefined,
|
() => (this.character.compiled?.spellranks?.knowledge ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Savoir') ], { href: 'regles/la-magie/magie#Les sorts de savoir', label: 'Savoir', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.character.compiled?.spellranks?.knowledge ?? 0) ]) : undefined,
|
||||||
() => (this.compiled?.spellranks?.instinct ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Instinct') ], { href: 'regles/la-magie/magie#Les sorts instinctif', label: 'Instinct', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiled?.spellranks?.instinct ?? 0) ]) : undefined,
|
() => (this.character.compiled?.spellranks?.instinct ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Instinct') ], { href: 'regles/la-magie/magie#Les sorts instinctif', label: 'Instinct', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.character.compiled?.spellranks?.instinct ?? 0) ]) : undefined,
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
@@ -1808,64 +1770,17 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
|
|
||||||
this.tabs,
|
this.tabs,
|
||||||
])
|
])
|
||||||
]);
|
]));
|
||||||
|
|
||||||
reactivity(this.character, (c) => {
|
|
||||||
if(c)
|
|
||||||
{
|
|
||||||
c.notes ??= { public: '', private: '' };
|
|
||||||
|
|
||||||
publicNotes.onChange = (v) => c.notes!.public = v;
|
|
||||||
privateNotes.onChange = (v) => c.notes!.private = v;
|
|
||||||
|
|
||||||
publicNotes.content = c.notes!.public!;
|
|
||||||
privateNotes.content = c.notes!.private!;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
edit()
|
|
||||||
{
|
|
||||||
const editingData: Record<number, string[]> = reactive({
|
|
||||||
1: ["Peuple", "Entrainement", "Compétences", "Aspect", "Spécialisations"],
|
|
||||||
2: ["Entrainement"],
|
|
||||||
3: ["Entrainement"],
|
|
||||||
4: ["Entrainement"],
|
|
||||||
5: ["Entrainement"],
|
|
||||||
6: ["Entrainement"],
|
|
||||||
7: ["Entrainement"],
|
|
||||||
8: ["Entrainement"],
|
|
||||||
9: ["Entrainement"],
|
|
||||||
10: ["Entrainement"],
|
|
||||||
11: ["Entrainement"],
|
|
||||||
12: ["Entrainement"],
|
|
||||||
13: ["Entrainement"],
|
|
||||||
14: ["Entrainement"],
|
|
||||||
15: ["Entrainement"],
|
|
||||||
16: ["Entrainement"],
|
|
||||||
17: ["Entrainement"],
|
|
||||||
18: ["Entrainement"],
|
|
||||||
19: ["Entrainement"],
|
|
||||||
20: ["Entrainement"],
|
|
||||||
})
|
|
||||||
this.editingDOM = div('flex flex-1 max-w-full flex-col align-center relative', [
|
|
||||||
div('h-full w-0 border-l-2 border-light-35 dark:border-dark-35 absolute z-0 left-[89px]'),
|
|
||||||
...Array(20).fill(0).map((_, i) => div(() => ['flex flex-row gap-4', { 'opacity-30': (this.character?.level ?? 0) < (i + 1) }], [
|
|
||||||
div('flex flex-row gap-2 justify-end items-start w-24 py-4', [
|
|
||||||
div('flex flex-row items-center gap-2', [ span('italic text-xs tracking-tight text-end', `Niveau ${i + 1}`), span('w-3 h-3 rounded-full items-center justify-center bg-light-50 dark:bg-dark-50 z-10') ]),
|
|
||||||
]),
|
|
||||||
div('flex flex-col px-2 items-center justify-center divide-y divide-light-35 dark:divide-dark-35', { list: editingData[i + 1], render: (e, _c) => _c ?? span('flex px-2 w-full py-2 items-center justify-center cursor-pointer hover:bg-light-20 dark:hover:bg-dark-20', e) }),
|
|
||||||
]))
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
saveVariables()
|
saveVariables()
|
||||||
{
|
{
|
||||||
if(!this._character) return;
|
if(!this.character.character) return;
|
||||||
clearTimeout(this._variableDebounce);
|
clearTimeout(this._variableDebounce);
|
||||||
this._variableDebounce = setTimeout(() => {
|
this._variableDebounce = setTimeout(() => {
|
||||||
if(!this._character) return;
|
if(!this.character.character) return;
|
||||||
useRequestFetch()(`/api/character/${this._character.id}/variables`, {
|
useRequestFetch()(`/api/character/${this.character.character.id}/variables`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: raw(this._character.variables),
|
body: raw(this.character.character.variables),
|
||||||
}).then(() => {}).catch(() => {
|
}).then(() => {}).catch(() => {
|
||||||
Toaster.add({ type: 'error', content: 'Impossible de mettre à jour les données', duration: 5000, timer: true });
|
Toaster.add({ type: 'error', content: 'Impossible de mettre à jour les données', duration: 5000, timer: true });
|
||||||
})
|
})
|
||||||
@@ -1873,49 +1788,14 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
}
|
}
|
||||||
saveNotes()
|
saveNotes()
|
||||||
{
|
{
|
||||||
if(!this._character) return Promise.resolve();
|
if(!this.character.character) return Promise.resolve();
|
||||||
return useRequestFetch()(`/api/character/${this._character.id}/notes`, {
|
return useRequestFetch()(`/api/character/${this.character.character.id}/notes`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: this._character.notes,
|
body: this.character.character.notes,
|
||||||
}).then(() => {}).catch(() => {
|
}).then(() => {}).catch(() => {
|
||||||
Toaster.add({ type: 'error', content: 'Impossible de mettre à jour les données', duration: 5000, timer: true });
|
Toaster.add({ type: 'error', content: 'Impossible de mettre à jour les données', duration: 5000, timer: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async saveEdits()
|
|
||||||
{
|
|
||||||
if(!this.character)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(!this.state.exists)
|
|
||||||
{
|
|
||||||
const result = await useRequestFetch()(`/api/character`, {
|
|
||||||
method: 'post',
|
|
||||||
body: this.character,
|
|
||||||
onResponseError: (e) => {
|
|
||||||
Toaster.add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', closeable: true, duration: 25000, timer: true });
|
|
||||||
this.character!.id = -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(result !== undefined) this.character.id = this.compiled.id = result as number;
|
|
||||||
|
|
||||||
Toaster.add({ content: 'Personnage créé', type: 'success', duration: 25000, timer: true });
|
|
||||||
useRouter().push({ name: 'character-id', params: { id: this.compiled.id } });
|
|
||||||
this.state.edit = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await useRequestFetch()(`/api/character/${this.character.id}`, {
|
|
||||||
method: 'post',
|
|
||||||
body: this.character,
|
|
||||||
onResponseError: (e) => {
|
|
||||||
Toaster.add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', closeable: true, duration: 25000, timer: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Toaster.add({ content: 'Personnage enregistré', type: 'success', duration: 25000, timer: true });
|
|
||||||
this.state.edit = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
healthPanel()
|
healthPanel()
|
||||||
{
|
{
|
||||||
const inputs = reactive({
|
const inputs = reactive({
|
||||||
@@ -1934,7 +1814,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
});
|
});
|
||||||
const container = div("border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-[480px] flex flex-col gap-4 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]", [
|
const container = div("border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-[480px] flex flex-col gap-4 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]", [
|
||||||
div('flex flex-row justify-between items-center', [
|
div('flex flex-row justify-between items-center', [
|
||||||
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Edititon de vie'), () => !this.compiled ? span('text-xl font-bold', '-') : div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (this.compiled.health - this.compiled.variables.health)), text('/'), text(() => this.compiled.health) ]) ]),
|
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Edititon de vie'), () => !this.character.compiled ? span('text-xl font-bold', '-') : div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (this.character.compiled.health - this.character.compiled.variables.health)), text('/'), text(() => this.character.compiled.health) ]) ]),
|
||||||
tooltip(button(icon("radix-icons:cross-1", { width: 24, height: 24 }), () => {
|
tooltip(button(icon("radix-icons:cross-1", { width: 24, height: 24 }), () => {
|
||||||
setTimeout(blocker.close, 150);
|
setTimeout(blocker.close, 150);
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
@@ -1942,25 +1822,25 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
]),
|
]),
|
||||||
foldable([
|
foldable([
|
||||||
div('flex flex-col w-full gap-2 ms-2 ps-4 border-l border-light-35 dark:border-dark-35', DAMAGE_TYPES.map((e) => div('flex flex-row justify-between items-center', [
|
div('flex flex-col w-full gap-2 ms-2 ps-4 border-l border-light-35 dark:border-dark-35', DAMAGE_TYPES.map((e) => div('flex flex-row justify-between items-center', [
|
||||||
span('text-lg', damageTypeTexts[e]), div('flex flew-row gap-4 items-center justify-end', [ () => this.compiled.bonus.damage[e] ? tooltip(span('w-8 font-bold', protectionTexts[this.compiled.bonus.damage[e] as 'resistance' | 'immunity' | 'vulnerability'].summary), protectionTexts[this.compiled.bonus.damage[e] as 'resistance' | 'immunity' | 'vulnerability'].detail, 'left') : div('w-8'), numberpicker({ defaultValue: () => inputs.health[e], input: v => { inputs.health[e] = v; inputs.health.sum = DAMAGE_TYPES.reduce((p, v) => p + inputs.health[v], 0) }, min: 0, class: 'h-8 !m-0' }), div('w-8') ]),
|
span('text-lg', damageTypeTexts[e]), div('flex flew-row gap-4 items-center justify-end', [ () => this.character.compiled.bonus.damage[e] ? tooltip(span('w-8 font-bold', protectionTexts[this.character.compiled.bonus.damage[e] as 'resistance' | 'immunity' | 'vulnerability'].summary), protectionTexts[this.character.compiled.bonus.damage[e] as 'resistance' | 'immunity' | 'vulnerability'].detail, 'left') : div('w-8'), numberpicker({ defaultValue: () => inputs.health[e], input: v => { inputs.health[e] = v; inputs.health.sum = DAMAGE_TYPES.reduce((p, v) => p + inputs.health[v], 0) }, min: 0, class: 'h-8 !m-0' }), div('w-8') ]),
|
||||||
])))
|
])))
|
||||||
], [
|
], [
|
||||||
div('flex flex-row justify-between items-center', [
|
div('flex flex-row justify-between items-center', [
|
||||||
() => !this.compiled ? undefined : span('text-lg', 'Total'), div('flex flew-row gap-4 justify-end', [
|
() => !this.character.compiled ? undefined : span('text-lg', 'Total'), div('flex flew-row gap-4 justify-end', [
|
||||||
() => this.armor.state ? tooltip(button(div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${this.armor.current}/${this.armor.max} (${[this.armor.flat > 0 ? '-' + this.armor.flat : undefined, this.armor.percent > 0 ? this.armor.percent + '%' : undefined].filter(e => !!e).join('/')})`) ]), () => {
|
() => this.character.armor.state ? tooltip(button(div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${this.character.armor.current}/${this.character.armor.max} (${[this.character.armor.flat > 0 ? '-' + this.character.armor.flat : undefined, this.character.armor.percent > 0 ? this.character.armor.percent + '%' : undefined].filter(e => !!e).join('/')})`) ]), () => {
|
||||||
const value = clamp(this.armor.percent > 0 ? Math.floor(inputs.health.sum * clamp(this.armor.percent / 100, 0, 1)) : clamp(inputs.health.sum, 0, this.armor.flat), 0, this.armor.current);
|
const value = clamp(this.character.armor.percent > 0 ? Math.floor(inputs.health.sum * clamp(this.character.armor.percent / 100, 0, 1)) : clamp(inputs.health.sum, 0, this.character.armor.flat), 0, this.character.armor.current);
|
||||||
(this.armor.state!.state as ArmorState).loss += value;
|
(this.character.armor.state!.state as ArmorState).loss += value;
|
||||||
inputs.health.sum -= value;
|
inputs.health.sum -= value;
|
||||||
}, 'px-2 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red', () => this.armor.current === 0), 'Dégats', 'left') : undefined,
|
}, 'px-2 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red', () => this.character.armor.current === 0), 'Dégats', 'left') : undefined,
|
||||||
tooltip(button(icon('radix-icons:minus', { width: 16, height: 16 }), () => {
|
tooltip(button(icon('radix-icons:minus', { width: 16, height: 16 }), () => {
|
||||||
this.compiled.variables.health += inputs.health.sum;
|
this.character.compiled.variables.health += inputs.health.sum;
|
||||||
inputs.health.sum = 0;
|
inputs.health.sum = 0;
|
||||||
DAMAGE_TYPES.forEach(e => inputs.health[e] = 0);
|
DAMAGE_TYPES.forEach(e => inputs.health[e] = 0);
|
||||||
this.saveVariables();
|
this.saveVariables();
|
||||||
}, 'w-8 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left'),
|
}, 'w-8 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left'),
|
||||||
numberpicker({ defaultValue: () => inputs.health.sum, input: v => { inputs.health.sum = v }, min: 0, disabled: () => inputs.health.open, class: 'h-8 !m-0' }),
|
numberpicker({ defaultValue: () => inputs.health.sum, input: v => { inputs.health.sum = v }, min: 0, disabled: () => inputs.health.open, class: 'h-8 !m-0' }),
|
||||||
tooltip(button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
tooltip(button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
||||||
this.compiled.variables.health = Math.max(this.compiled.variables.health - inputs.health.sum, 0);
|
this.character.compiled.variables.health = Math.max(this.character.compiled.variables.health - inputs.health.sum, 0);
|
||||||
inputs.health.sum = 0;
|
inputs.health.sum = 0;
|
||||||
DAMAGE_TYPES.forEach(e => inputs.health[e] = 0);
|
DAMAGE_TYPES.forEach(e => inputs.health[e] = 0);
|
||||||
this.saveVariables();
|
this.saveVariables();
|
||||||
@@ -1969,16 +1849,16 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
])
|
])
|
||||||
], { class: { container: 'gap-2', title: 'ps-2' }, open: false, onFold: v => { inputs.health.open = v; if(v) { inputs.health.sum = 0; }} }),
|
], { class: { container: 'gap-2', title: 'ps-2' }, open: false, onFold: v => { inputs.health.open = v; if(v) { inputs.health.sum = 0; }} }),
|
||||||
() => div('flex flex-row justify-between items-center', [
|
() => div('flex flex-row justify-between items-center', [
|
||||||
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Mana'), div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (this.compiled.mana - this.compiled.variables.mana)), text('/'), text(() => this.compiled.mana) ]) ]),
|
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Mana'), div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (this.character.compiled.mana - this.character.compiled.variables.mana)), text('/'), text(() => this.character.compiled.mana) ]) ]),
|
||||||
div('flex flex-row gap-4 justify-end', [
|
div('flex flex-row gap-4 justify-end', [
|
||||||
tooltip(button(icon('radix-icons:minus', { width: 16, height: 16 }), () => {
|
tooltip(button(icon('radix-icons:minus', { width: 16, height: 16 }), () => {
|
||||||
this.compiled.variables.mana += inputs.mana;
|
this.character.compiled.variables.mana += inputs.mana;
|
||||||
inputs.mana = 0;
|
inputs.mana = 0;
|
||||||
this.saveVariables();
|
this.saveVariables();
|
||||||
}, 'w-8 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left'),
|
}, 'w-8 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left'),
|
||||||
numberpicker({ defaultValue: () => inputs.mana, input: v => { inputs.mana = v }, min: 0, class: 'h-8 !m-0' }),
|
numberpicker({ defaultValue: () => inputs.mana, input: v => { inputs.mana = v }, min: 0, class: 'h-8 !m-0' }),
|
||||||
tooltip(button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
tooltip(button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
||||||
this.compiled.variables.mana = Math.max(this.compiled.variables.mana - inputs.mana, 0);
|
this.character.compiled.variables.mana = Math.max(this.character.compiled.variables.mana - inputs.mana, 0);
|
||||||
inputs.mana = 0;
|
inputs.mana = 0;
|
||||||
this.saveVariables();
|
this.saveVariables();
|
||||||
}, 'w-8 h-8 border-light-green dark:border-dark-green hover:border-light-green dark:hover:border-dark-green focus:border-light-green dark:focus:border-dark-green focus:shadow-light-green dark:focus:shadow-dark-green'), 'Soin', 'left'),
|
}, 'w-8 h-8 border-light-green dark:border-dark-green hover:border-light-green dark:hover:border-dark-green focus:border-light-green dark:focus:border-dark-green focus:shadow-light-green dark:focus:shadow-dark-green'), 'Soin', 'left'),
|
||||||
@@ -2011,7 +1891,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.action[e]?.name }), config.action[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.action[e]?.cost?.toString() }), text(`point${config.action[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.action[e]?.name }), config.action[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.action[e]?.cost?.toString() }), text(`point${config.action[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
||||||
markdown(getText(config.action[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.action[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
]), list: () => this.compiled ? this.compiled.lists.action ?? [] : [] }),
|
]), list: () => this.character.compiled ? this.character.compiled.lists.action ?? [] : [] }),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
@@ -2026,7 +1906,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.reaction[e]?.name }), config.reaction[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.reaction[e]?.cost?.toString() }), text(`point${config.reaction[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.reaction[e]?.name }), config.reaction[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.reaction[e]?.cost?.toString() }), text(`point${config.reaction[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
||||||
markdown(getText(config.reaction[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.reaction[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
]), list: () => this.compiled ? this.compiled.lists.reaction ?? [] : [] }),
|
]), list: () => this.character.compiled ? this.character.compiled.lists.reaction ?? [] : [] }),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
@@ -2040,7 +1920,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.freeaction[e]?.name }) ]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.freeaction[e]?.name }) ]),
|
||||||
markdown(getText(config.freeaction[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.freeaction[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
]), list: () => this.compiled ? this.compiled.lists.freeaction ?? [] : [] })
|
]), list: () => this.character.compiled ? this.character.compiled.lists.freeaction ?? [] : [] })
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
@@ -2052,7 +1932,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
foldable(() => [div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
foldable(() => [div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.passive[e]?.name }) ]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.passive[e]?.name }) ]),
|
||||||
markdown(getText(config.passive[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.passive[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
]), list: this.compiled.lists.passive })], [
|
]), list: this.character.compiled.lists.passive })], [
|
||||||
div("flex flex-row items-center justify-center gap-4", [
|
div("flex flex-row items-center justify-center gap-4", [
|
||||||
div("flex flex-row items-center justify-center gap-2", [ dom("div", { class: 'text-xl font-bold', text: "Aptitudes" }) ]),
|
div("flex flex-row items-center justify-center gap-2", [ dom("div", { class: 'text-xl font-bold', text: "Aptitudes" }) ]),
|
||||||
div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50"),
|
div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50"),
|
||||||
@@ -2061,7 +1941,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
foldable(() => [div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
foldable(() => [div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.dedication[e]?.name }) ]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.dedication[e]?.name }) ]),
|
||||||
markdown(getText(config.features[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.features[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
]), list: this.compiled.lists.dedication })], [
|
]), list: this.character.compiled.lists.dedication })], [
|
||||||
div("flex flex-row items-center justify-center gap-4", [
|
div("flex flex-row items-center justify-center gap-4", [
|
||||||
div("flex flex-row items-center justify-center gap-2", [ dom("div", { class: 'text-xl font-bold', text: "Spécialisations" }) ]),
|
div("flex flex-row items-center justify-center gap-2", [ dom("div", { class: 'text-xl font-bold', text: "Spécialisations" }) ]),
|
||||||
div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50"),
|
div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50"),
|
||||||
@@ -2072,11 +1952,11 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
spellTab()
|
spellTab()
|
||||||
{
|
{
|
||||||
const preference = reactive({
|
const preference = reactive({
|
||||||
sort: localStorage.getItem('this.compiled-sort') ?? 'rank-asc',
|
sort: localStorage.getItem('this.character.compiled-sort') ?? 'rank-asc',
|
||||||
} as { sort: `${'rank'|'type'|'element'|'cost'|'range'|'speed'}-${'asc'|'desc'}` | '' });
|
} as { sort: `${'rank'|'type'|'element'|'cost'|'range'|'speed'}-${'asc'|'desc'}` | '' });
|
||||||
|
|
||||||
const sort = (spells: string[]) => {
|
const sort = (spells: string[]) => {
|
||||||
localStorage.setItem('this.compiled-sort', preference.sort);
|
localStorage.setItem('this.character.compiled-sort', preference.sort);
|
||||||
const _spells = Object.keys(config.spells);
|
const _spells = Object.keys(config.spells);
|
||||||
|
|
||||||
spells.sort((a, b) => _spells.indexOf(a) - _spells.indexOf(b));
|
spells.sort((a, b) => _spells.indexOf(a) - _spells.indexOf(b));
|
||||||
@@ -2087,14 +1967,14 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
case 'element-asc': return spells.sort((a, b) => SPELL_ELEMENTS.indexOf(config.spells[a]?.elements[0]!) - SPELL_ELEMENTS.indexOf(config.spells[b]?.elements[0]!));
|
case 'element-asc': return spells.sort((a, b) => SPELL_ELEMENTS.indexOf(config.spells[a]?.elements[0]!) - SPELL_ELEMENTS.indexOf(config.spells[b]?.elements[0]!));
|
||||||
case 'cost-asc': return spells.sort((a, b) => (config.spells[a]?.cost ?? 0) - (config.spells[b]?.cost ?? 0));
|
case 'cost-asc': return spells.sort((a, b) => (config.spells[a]?.cost ?? 0) - (config.spells[b]?.cost ?? 0));
|
||||||
case 'range-asc': return spells.sort((a, b) => (config.spells[a]?.range === 'personnal' ? -1 : config.spells[a]?.range ?? 0) - (config.spells[b]?.range === 'personnal' ? -1 : config.spells[b]?.range ?? 0));
|
case 'range-asc': return spells.sort((a, b) => (config.spells[a]?.range === 'personnal' ? -1 : config.spells[a]?.range ?? 0) - (config.spells[b]?.range === 'personnal' ? -1 : config.spells[b]?.range ?? 0));
|
||||||
case 'speed-asc': return spells.sort((a, b) => (config.spells[a]?.speed === 'action' ? -2 : config.spells[a]?.speed === 'reaction' ? -1 : config.spells[a]?.speed ?? 0) - (config.spells[a]?.speed === 'action' ? -2 : config.spells[a]?.speed === 'reaction' ? -1 : config.spells[a]?.speed ?? 0));
|
case 'speed-asc': return spells.sort((a, b) => (config.spells[a]?.speed === 'action' ? -3 : config.spells[a]?.speed === 'reaction' ? -2 : config.spells[a]?.speed === 'channeling' ? -1 : config.spells[a]?.speed ?? 0) - (config.spells[b]?.speed === 'action' ? -3 : config.spells[b]?.speed === 'reaction' ? -2 : config.spells[b]?.speed === 'channeling' ? -1 : config.spells[b]?.speed ?? 0));
|
||||||
|
|
||||||
case 'rank-desc': return spells.sort((b, a) => (config.spells[a]?.rank ?? 0) - (config.spells[b]?.rank ?? 0));
|
case 'rank-desc': return spells.sort((b, a) => (config.spells[a]?.rank ?? 0) - (config.spells[b]?.rank ?? 0));
|
||||||
case 'type-desc': return spells.sort((b, a) => config.spells[a]?.type.localeCompare(config.spells[b]?.type ?? '') ?? 0);
|
case 'type-desc': return spells.sort((b, a) => config.spells[a]?.type.localeCompare(config.spells[b]?.type ?? '') ?? 0);
|
||||||
case 'element-desc': return spells.sort((b, a) => SPELL_ELEMENTS.indexOf(config.spells[a]?.elements[0]!) - SPELL_ELEMENTS.indexOf(config.spells[b]?.elements[0]!));
|
case 'element-desc': return spells.sort((b, a) => SPELL_ELEMENTS.indexOf(config.spells[a]?.elements[0]!) - SPELL_ELEMENTS.indexOf(config.spells[b]?.elements[0]!));
|
||||||
case 'cost-desc': return spells.sort((b, a) => (config.spells[a]?.cost ?? 0) - (config.spells[b]?.cost ?? 0));
|
case 'cost-desc': return spells.sort((b, a) => (config.spells[a]?.cost ?? 0) - (config.spells[b]?.cost ?? 0));
|
||||||
case 'range-desc': return spells.sort((b, a) => (config.spells[a]?.range === 'personnal' ? -1 : config.spells[a]?.range ?? 0) - (config.spells[b]?.range === 'personnal' ? -1 : config.spells[b]?.range ?? 0));
|
case 'range-desc': return spells.sort((b, a) => (config.spells[a]?.range === 'personnal' ? -1 : config.spells[a]?.range ?? 0) - (config.spells[b]?.range === 'personnal' ? -1 : config.spells[b]?.range ?? 0));
|
||||||
case 'speed-desc': return spells.sort((b, a) => (config.spells[a]?.speed === 'action' ? -2 : config.spells[a]?.speed === 'reaction' ? -1 : config.spells[a]?.speed ?? 0) - (config.spells[a]?.speed === 'action' ? -2 : config.spells[a]?.speed === 'reaction' ? -1 : config.spells[a]?.speed ?? 0));
|
case 'speed-desc': return spells.sort((b, a) => (config.spells[a]?.speed === 'action' ? -3 : config.spells[a]?.speed === 'reaction' ? -2 : config.spells[a]?.speed === 'channeling' ? -1 : config.spells[a]?.speed ?? 0) - (config.spells[b]?.speed === 'action' ? -3 : config.spells[b]?.speed === 'reaction' ? -2 : config.spells[b]?.speed === 'channeling' ? -1 : config.spells[b]?.speed ?? 0));
|
||||||
|
|
||||||
default: return spells;
|
default: return spells;
|
||||||
}
|
}
|
||||||
@@ -2115,8 +1995,8 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div('flex flex-col gap-2 h-full', [
|
div('flex flex-col gap-2 h-full', [
|
||||||
div('flex flex-row justify-end items-center', [
|
div('flex flex-row justify-end items-center', [
|
||||||
div('flex flex-row gap-2 items-center', [
|
div('flex flex-row gap-2 items-center', [
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.compiled.variables.spells.length !== this.compiled.spellslots }], text: () => `${this.compiled.variables.spells.length}/${this.compiled.spellslots} sort(s) maitrisé(s)`.replaceAll('(s)', this.compiled.variables.spells.length > 1 ? 's' : '') }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character.compiled.variables.spells.length !== this.character.compiled.spellslots }], text: () => `${this.character.compiled.variables.spells.length}/${this.character.compiled.spellslots} sort(s) maitrisé(s)`.replaceAll('(s)', this.character.compiled.variables.spells.length > 1 ? 's' : '') }),
|
||||||
button(text('Modifier'), () => panel.show(), 'py-1 px-4', () => this.state.edit),
|
button(text('Modifier'), () => panel.show(), 'py-1 px-4'),
|
||||||
tooltip(button(icon('ph:arrows-down-up', { width: 16, height: 16 }), sorter, 'p-1'), 'Trier par', 'right')
|
tooltip(button(icon('ph:arrows-down-up', { width: 16, height: 16 }), sorter, 'p-1'), 'Trier par', 'right')
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
@@ -2134,13 +2014,13 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
]),
|
]),
|
||||||
div('flex flex-row ps-4 p-1 border-l-4 border-light-35 dark:border-dark-35', [ markdown(spell.description) ]),
|
div('flex flex-row ps-4 p-1 border-l-4 border-light-35 dark:border-dark-35', [ markdown(spell.description) ]),
|
||||||
])
|
])
|
||||||
}, list: () => sort([...(this.compiled.lists.spells ?? []), ...this.compiled.variables.spells]) }),
|
}, list: () => sort([...(this.character.compiled.lists.spells ?? []), ...this.character.compiled.variables.spells]) }),
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
spellPanel()
|
spellPanel()
|
||||||
{
|
{
|
||||||
const spells = this.compiled.variables.spells;
|
const spells = this.character.compiled.variables.spells;
|
||||||
const filters = reactive<{ tag: Array<string>, rank: Array<SpellConfig['rank']>, type: Array<SpellConfig['type']>, element: Array<SpellConfig['elements'][number]>, cost: { min: number, max: number }, range: Array<SpellConfig['range']>, speed: Array<SpellConfig['speed']> }>({
|
const filters = reactive<{ tag: Array<string>, rank: Array<SpellConfig['rank']>, type: Array<SpellConfig['type']>, element: Array<SpellConfig['elements'][number]>, cost: { min: number, max: number }, range: Array<SpellConfig['range']>, speed: Array<SpellConfig['speed']> }>({
|
||||||
tag: [],
|
tag: [],
|
||||||
type: [],
|
type: [],
|
||||||
@@ -2154,7 +2034,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
const container = div("border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-1/2 flex flex-col gap-4 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]", [
|
const container = div("border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-1/2 flex flex-col gap-4 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]", [
|
||||||
div("flex flex-row justify-between items-center", [
|
div("flex flex-row justify-between items-center", [
|
||||||
dom("h2", { class: "text-xl font-bold", text: "Ajouter un sort" }),
|
dom("h2", { class: "text-xl font-bold", text: "Ajouter un sort" }),
|
||||||
div('flex flex-row gap-4 items-center', [ dom('span', { class: 'italic text-light-70 dark:text-dark-70 text-sm' }, [ text(() => `${spells.length}/${this.compiled.spellslots} sort(s) maitrisé(s)`.replaceAll('(s)', spells.length > 1 ? 's' : '')) ]), tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
|
div('flex flex-row gap-4 items-center', [ dom('span', { class: 'italic text-light-70 dark:text-dark-70 text-sm' }, [ text(() => `${spells.length}/${this.character.compiled.spellslots} sort(s) maitrisé(s)`.replaceAll('(s)', spells.length > 1 ? 's' : '')) ]), tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
|
||||||
setTimeout(blocker.close, 150);
|
setTimeout(blocker.close, 150);
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
}, "p-1"), "Fermer", "left") ])
|
}, "p-1"), "Fermer", "left") ])
|
||||||
@@ -2165,12 +2045,12 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div('flex flex-col gap-1 items-center', [ text('Rangs'), multiselect<typeof filters.rank[number]>([{ text: 'Rang 1', value: 1 }, { text: 'Rang 2', value: 2 }, { text: 'Rang 3', value: 3 }], { defaultValue: filters.rank, change: v => filters.rank = v, class: { container: 'w-24 !mx-0 text-xs', option: 'text-sm p-1' } }) ]),
|
div('flex flex-col gap-1 items-center', [ text('Rangs'), multiselect<typeof filters.rank[number]>([{ text: 'Rang 1', value: 1 }, { text: 'Rang 2', value: 2 }, { text: 'Rang 3', value: 3 }], { defaultValue: filters.rank, change: v => filters.rank = v, class: { container: 'w-24 !mx-0 text-xs', option: 'text-sm p-1' } }) ]),
|
||||||
div('flex flex-col gap-1 items-center', [ text('Elements'), multiselect<typeof filters.element[number]>(SPELL_ELEMENTS.map(f => ({ text: elementTexts[f].text, value: f })), { defaultValue: filters.element, change: v => filters.element = v, class: { container: 'w-28 !mx-0 text-xs', option: 'text-sm p-1' } }) ]),
|
div('flex flex-col gap-1 items-center', [ text('Elements'), multiselect<typeof filters.element[number]>(SPELL_ELEMENTS.map(f => ({ text: elementTexts[f].text, value: f })), { defaultValue: filters.element, change: v => filters.element = v, class: { container: 'w-28 !mx-0 text-xs', option: 'text-sm p-1' } }) ]),
|
||||||
div('flex flex-col gap-1 items-center', [ text('Portée'), multiselect<typeof filters.range[number]>([{ text: 'Toucher', value: 0 }, { text: 'Personnel', value: 'personnal' }, { text: '3 cases', value: 3 }, { text: '6 cases', value: 6 }, { text: '9 cases', value: 9 }, { text: '12 cases', value: 12 }, { text: '18 cases', value: 18 }], { defaultValue: filters.range, change: v => filters.range = v, class: { container: 'w-28 !mx-0 text-xs', option: 'text-sm p-1' } }) ]),
|
div('flex flex-col gap-1 items-center', [ text('Portée'), multiselect<typeof filters.range[number]>([{ text: 'Toucher', value: 0 }, { text: 'Personnel', value: 'personnal' }, { text: '3 cases', value: 3 }, { text: '6 cases', value: 6 }, { text: '9 cases', value: 9 }, { text: '12 cases', value: 12 }, { text: '18 cases', value: 18 }], { defaultValue: filters.range, change: v => filters.range = v, class: { container: 'w-28 !mx-0 text-xs', option: 'text-sm p-1' } }) ]),
|
||||||
div('flex flex-col gap-1 items-center', [ text('Incantation'), multiselect<typeof filters.speed[number]>([{ text: 'Action', value: 'action' }, { text: 'Reaction', value: 'reaction' }, { text: '1 minute', value: 1 }, { text: '10 minutes', value: 10 }], { defaultValue: filters.speed, change: v => filters.speed = v, class: { container: 'w-32 !mx-0 text-xs', option: 'text-sm p-1' } }) ]),
|
div('flex flex-col gap-1 items-center', [ text('Incantation'), multiselect<typeof filters.speed[number]>([{ text: 'Action', value: 'action' }, { text: 'Reaction', value: 'reaction' }, { text: 'Canalisaton', value: 'channeling' }, { text: '1 minute', value: 1 }, { text: '10 minutes', value: 10 }], { defaultValue: filters.speed, change: v => filters.speed = v, class: { container: 'w-32 !mx-0 text-xs', option: 'text-sm p-1' } }) ]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-col divide-y *:py-2 -my-2 overflow-y-auto', { list: () => Object.values(config.spells).filter(spell => {
|
div('flex flex-col divide-y *:py-2 -my-2 overflow-y-auto', { list: () => Object.values(config.spells).filter(spell => {
|
||||||
//if(spells.includes(spell.id)) return true;
|
//if(spells.includes(spell.id)) return true;
|
||||||
|
|
||||||
if(this.compiled.spellranks[spell.type] < spell.rank) return false;
|
if(this.character.compiled.spellranks[spell.type] < spell.rank) return false;
|
||||||
if(filters.cost.min > spell.cost || spell.cost > filters.cost.max) return false;
|
if(filters.cost.min > spell.cost || spell.cost > filters.cost.max) return false;
|
||||||
if(filters.element.length > 0 && !filters.element.some(e => spell.elements.includes(e))) return false;
|
if(filters.element.length > 0 && !filters.element.some(e => spell.elements.includes(e))) return false;
|
||||||
if(filters.range.length > 0 && !filters.range.includes(spell.range)) return false;
|
if(filters.range.length > 0 && !filters.range.includes(spell.range)) return false;
|
||||||
@@ -2204,7 +2084,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
text("/"),
|
text("/"),
|
||||||
dom("span", { text: typeof spell.speed === "string" ? spell.speed : `${spell.speed} minutes` })
|
dom("span", { text: typeof spell.speed === "string" ? spell.speed : `${spell.speed} minutes` })
|
||||||
]),
|
]),
|
||||||
button(text(() => spells.includes(spell.id) ? 'Supprimer' : this.compiled.lists.spells?.includes(spell.id) ? 'Inné' : 'Ajouter'), () => {
|
button(text(() => spells.includes(spell.id) ? 'Supprimer' : this.character.compiled.lists.spells?.includes(spell.id) ? 'Inné' : 'Ajouter'), () => {
|
||||||
const idx = spells.findIndex(e => e === spell.id);
|
const idx = spells.findIndex(e => e === spell.id);
|
||||||
if(idx !== -1) spells.splice(idx, 1);
|
if(idx !== -1) spells.splice(idx, 1);
|
||||||
else spells.push(spell.id);
|
else spells.push(spell.id);
|
||||||
@@ -2227,7 +2107,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
}
|
}
|
||||||
itemsTab()
|
itemsTab()
|
||||||
{
|
{
|
||||||
const items = this.compiled.variables.items;
|
const items = this.character.compiled.variables.items;
|
||||||
const panel = this.itemsPanel();
|
const panel = this.itemsPanel();
|
||||||
const improve = this.improvePanel();
|
const improve = this.improvePanel();
|
||||||
|
|
||||||
@@ -2238,12 +2118,12 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div('flex flex-row gap-1 items-center', [ span('italic text-sm', 'Argent'), text("TODO") ]),
|
div('flex flex-row gap-1 items-center', [ span('italic text-sm', 'Argent'), text("TODO") ]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-row justify-end items-center gap-8', [
|
div('flex flex-row justify-end items-center gap-8', [
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.power > this.compiled.itempower }], text: () => `Puissance magique: ${this.power}/${this.compiled.itempower}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character.power > this.character.compiled.itempower }], text: () => `Puissance magique: ${this.character.power}/${this.character.compiled.itempower}` }),
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.weight > (this.compiled.capacity === false ? 0 : this.compiled.capacity) }], text: () => `Poids total: ${this.weight}/${this.compiled.capacity}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character.weight > (this.character.compiled.capacity === false ? 0 : this.character.compiled.capacity) }], text: () => `Poids total: ${this.character.weight}/${this.character.compiled.capacity}` }),
|
||||||
button(text('Modifier'), () => panel.show(), 'py-1 px-4'),
|
button(text('Modifier'), () => panel.show(), 'py-1 px-4'),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35', { list: this.compiled.variables.items, render: (e, _c) => {
|
div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35', { list: this.character.compiled.variables.items, render: (e, _c) => {
|
||||||
if(_c) return _c;
|
if(_c) return _c;
|
||||||
|
|
||||||
const item = config.items[e.id];
|
const item = config.items[e.id];
|
||||||
@@ -2256,9 +2136,9 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
const weight = div(() => ['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 && !!item.weight }], [ icon('mdi:weight', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span(() => ({ 'underline decoration-1 decoration-dotted underline-offset-2': e.amount > 1 && !!item.weight }), () => item.weight ? `${item.weight * e.amount}` : '-') ]);
|
const weight = div(() => ['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 && !!item.weight }], [ icon('mdi:weight', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span(() => ({ 'underline decoration-1 decoration-dotted underline-offset-2': e.amount > 1 && !!item.weight }), () => item.weight ? `${item.weight * e.amount}` : '-') ]);
|
||||||
return foldable(() => [
|
return foldable(() => [
|
||||||
markdown(getText(item.description)),
|
markdown(getText(item.description)),
|
||||||
div('flex flex-row gap-1', { list: () => e.improvements!.map(e => config.improvements[e]).filter(e => !!e), render: (e, _c) => _c ?? floater(div(() => ['flex flex-row gap-2 border px-2 rounded-full py-px !bg-opacity-20', { 'border-accent-blue bg-accent-blue': !e.cursed, 'border-light-purple bg-light-purple dark:border-dark-purple dark:bg-dark-purple': e.cursed }], [ span('text-sm font-semibold tracking-tight', e.name), div('flex flex-row gap-1 items-center', [icon('game-icons:bolt-drop', { width: 12, height: 12 }), span('text-sm font-light', e.power)]) ]), () => [markdown(getText(e.description), undefined, { tags: { a: preview } })], { class: 'max-w-96 max-h-48 p-2', position: "right" }) }),
|
div('flex flex-row gap-1', { list: () => e.improvements!.map(e => config.improvements[e]).filter(e => !!e), render: (e, _c) => _c ?? floater(div(() => ['flex flex-row gap-2 border px-2 rounded-full py-px !bg-opacity-20', { 'border-accent-blue bg-accent-blue': !e.cursed, 'border-light-purple bg-light-purple dark:border-dark-purple dark:bg-dark-purple': e.cursed }], [ span('text-sm font-semibold tracking-tight', e.name), div('flex flex-row gap-1 items-center', [icon('game-icons:bolt-drop', { width: 12, height: 12 }), span('text-sm font-light', e.power)]) ]), () => [markdown(getText(e.description), undefined, { tags: { a: preview } })], { class: 'max-w-96 max-h-48 p-2', position: "bottom-start" }) }),
|
||||||
div('flex flex-row justify-center gap-1', [
|
div('flex flex-row justify-center gap-1', [
|
||||||
this.character.campaign ? button(text('Partager'), () => {
|
this.character.character.campaign ? button(text('Partager'), () => {
|
||||||
|
|
||||||
}, 'px-2 text-sm h-5 box-content') : undefined,
|
}, 'px-2 text-sm h-5 box-content') : undefined,
|
||||||
button(icon(() => e.amount === 1 ? 'radix-icons:trash' : 'radix-icons:minus', { width: 12, height: 12 }), () => {
|
button(icon(() => e.amount === 1 ? 'radix-icons:trash' : 'radix-icons:minus', { width: 12, height: 12 }), () => {
|
||||||
@@ -2287,15 +2167,15 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
], [ div('flex flex-row justify-between', [
|
], [ div('flex flex-row justify-between', [
|
||||||
div('flex flex-row items-center gap-y-1 gap-x-4 flex-wrap', [
|
div('flex flex-row items-center gap-y-1 gap-x-4 flex-wrap', [
|
||||||
item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
||||||
if(v && config.items[e.id]?.category === 'armor' && this.compiled.variables.items.find(e => config.items[e.id]?.category === 'armor' && e.equipped))
|
if(v && config.items[e.id]?.category === 'armor' && this.character.compiled.variables.items.find(e => config.items[e.id]?.category === 'armor' && e.equipped))
|
||||||
return Toaster.add({ content: "Vous ne pouvez equipper qu'une seule armure à la fois.", duration: 5000, timer: true, type: 'info' }), false;
|
return Toaster.add({ content: "Vous ne pouvez equipper qu'une seule armure à la fois.", duration: 5000, timer: true, type: 'info' }), false;
|
||||||
|
|
||||||
e.equipped = v;
|
e.equipped = v;
|
||||||
this.improve(e);
|
this.character.improve(e);
|
||||||
}, class: { container: '!w-5 !h-5' } }) : undefined,
|
}, class: { container: '!w-5 !h-5' } }) : undefined,
|
||||||
div('flex flex-row items-center gap-4', [ span([colorByRarity[item.rarity], 'text-lg'], item.name), div('flex flex-row gap-2 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(item).map(e => span('', e))) ]),
|
div('flex flex-row items-center gap-4', [ span([colorByRarity[item.rarity], 'text-lg'], item.name), div('flex flex-row gap-2 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(item).map(e => span('', e))) ]),
|
||||||
item.category === 'armor' ? div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${item.health + ((e.state as ArmorState)?.health ?? 0) - ((e.state as ArmorState)?.loss ?? 0)}/${item.health + ((e.state as ArmorState)?.health ?? 0)} (${[item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0) > 0 ? '-' + (item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0)) : undefined, item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0) > 0 ? '-' + (item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0)) + '%' : undefined].filter(e => !!e).join('/')})`) ]) :
|
item.category === 'armor' ? div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${item.health + ((e.state as ArmorState)?.health ?? 0) - ((e.state as ArmorState)?.loss ?? 0)}/${item.health + ((e.state as ArmorState)?.health ?? 0)} (${[item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0) > 0 ? '-' + (item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0)) : undefined, item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0) > 0 ? '-' + (item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0)) + '%' : undefined].filter(e => !!e).join('/')})`) ]) :
|
||||||
item.category === 'weapon' ? div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:broadsword', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => stringifyRoll(parseDice(`${item.damage.value}${(e.state as WeaponState)?.attack ? '+' + (e.state as WeaponState).attack : ''}`), this.compiled.modifier, true)), proses('a', preview, [ text(damageTypeTexts[item.damage.type].toLowerCase()) ], { href: `regles/le-combat/les-types-de-degats#${damageTypeTexts[item.damage.type]}`, label: damageTypeTexts[item.damage.type], navigate: false }) ]) :
|
item.category === 'weapon' ? div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:broadsword', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => stringifyRoll(parseDice(`${item.damage.value}${(e.state as WeaponState)?.attack ? '+' + (e.state as WeaponState).attack : ''}`), this.character.compiled.modifier, true)), proses('a', preview, [ text(damageTypeTexts[item.damage.type].toLowerCase()) ], { href: `regles/le-combat/les-types-de-degats#${damageTypeTexts[item.damage.type]}`, label: damageTypeTexts[item.damage.type], navigate: false }) ]) :
|
||||||
undefined
|
undefined
|
||||||
]),
|
]),
|
||||||
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2', [
|
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2', [
|
||||||
@@ -2323,7 +2203,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div("flex flex-row justify-between items-center mb-4", [
|
div("flex flex-row justify-between items-center mb-4", [
|
||||||
dom("h2", { class: "text-xl font-bold", text: "Gestion de l'inventaire" }),
|
dom("h2", { class: "text-xl font-bold", text: "Gestion de l'inventaire" }),
|
||||||
div('flex flex-row gap-8 items-center justify-end', [
|
div('flex flex-row gap-8 items-center justify-end', [
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.weight > (this.compiled.capacity === false ? 0 : this.compiled.capacity) }], text: () => `Poids total: ${this.weight}/${this.compiled.capacity}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character.weight > (this.character.compiled.capacity === false ? 0 : this.character.compiled.capacity) }], text: () => `Poids total: ${this.character.weight}/${this.character.compiled.capacity}` }),
|
||||||
tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
|
tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
|
||||||
setTimeout(blocker.close, 150);
|
setTimeout(blocker.close, 150);
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
@@ -2356,7 +2236,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.charge ? `${item.charge}` : '-') ]),
|
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.charge ? `${item.charge}` : '-') ]),
|
||||||
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('ph:coin', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.price ? `${item.price}` : '-') ]),
|
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('ph:coin', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.price ? `${item.price}` : '-') ]),
|
||||||
button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
||||||
const list = this.compiled.variables.items;
|
const list = this.character.compiled.variables.items;
|
||||||
if(item.equippable) list.push(stateFactory(item));
|
if(item.equippable) list.push(stateFactory(item));
|
||||||
else if(list.find(e => e.id === item.id)) list.find(e => e.id === item.id)!.amount++;
|
else if(list.find(e => e.id === item.id)) list.find(e => e.id === item.id)!.amount++;
|
||||||
else list.push(stateFactory(item));
|
else list.push(stateFactory(item));
|
||||||
@@ -2407,7 +2287,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
dom("h2", { class: "text-xl font-bold", text: "Améliorations" }),
|
dom("h2", { class: "text-xl font-bold", text: "Améliorations" }),
|
||||||
div('flex flex-row gap-8 items-center justify-end', [
|
div('flex flex-row gap-8 items-center justify-end', [
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': current.item && config.items[current.item.id] !== undefined ? itempower() > (config.items[current.item.id]!.capacity ?? 0) : false }], text: () => `Puissance de l'objet: ${current.item && config.items[current.item.id] !== undefined ? itempower() : false}/${current.item ? (config.items[current.item.id]!.capacity ?? 0) : 0}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': current.item && config.items[current.item.id] !== undefined ? itempower() > (config.items[current.item.id]!.capacity ?? 0) : false }], text: () => `Puissance de l'objet: ${current.item && config.items[current.item.id] !== undefined ? itempower() : false}/${current.item ? (config.items[current.item.id]!.capacity ?? 0) : 0}` }),
|
||||||
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.power > this.compiled!.itempower }], text: () => `Puissance du personnage: ${this.power}/${this.compiled.itempower}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character.power > this.character.compiled!.itempower }], text: () => `Puissance du personnage: ${this.character.power}/${this.character.compiled.itempower}` }),
|
||||||
tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
|
tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
|
||||||
setTimeout(blocker.close, 150);
|
setTimeout(blocker.close, 150);
|
||||||
container.setAttribute('data-state', 'inactive');
|
container.setAttribute('data-state', 'inactive');
|
||||||
@@ -2427,7 +2307,7 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
else
|
else
|
||||||
current.item!.improvements?.splice(idx, 1);
|
current.item!.improvements?.splice(idx, 1);
|
||||||
|
|
||||||
this.improve(current.item!);
|
this.character.improve(current.item!);
|
||||||
}, 'p-1 !border-solid !border-r'),
|
}, 'p-1 !border-solid !border-r'),
|
||||||
]),
|
]),
|
||||||
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } })
|
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } })
|
||||||
@@ -2450,13 +2330,13 @@ export class CharacterSheet extends CharacterCompiler
|
|||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
div('flex flex-row justify-between items-center', [
|
div('flex flex-row justify-between items-center', [
|
||||||
div('flex flex-row gap-12 items-center', [
|
div('flex flex-row gap-12 items-center', [
|
||||||
span('text-lg font-semibold', config.aspects[this.compiled.aspect.id]?.name), div('flex flex-row items-center gap-2', [ text('Transformé'), checkbox({ defaultValue: this.compiled.variables.transformed, change: v => this.compiled.variables.transformed = v, }) ]),
|
span('text-lg font-semibold', config.aspects[this.character.compiled.aspect.id]?.name), div('flex flex-row items-center gap-2', [ text('Transformé'), checkbox({ defaultValue: this.character.compiled.variables.transformed, change: v => this.character.compiled.variables.transformed = v, }) ]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-row gap-8 items-center', [
|
div('flex flex-row gap-8 items-center', [
|
||||||
text('Difficulté: '), span('text-lg font-semibold', config.aspects[this.compiled.aspect.id]?.difficulty),
|
text('Difficulté: '), span('text-lg font-semibold', config.aspects[this.character.compiled.aspect.id]?.difficulty),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
div(() => ({ 'opacity-20': !this.compiled.variables.transformed }), [ markdown(getText(config.aspects[this.compiled.aspect.id]?.description), undefined, { tags: { a: preview } }), ]),
|
div(() => ({ 'opacity-20': !this.character.compiled.variables.transformed }), [ markdown(getText(config.aspects[this.character.compiled.aspect.id]?.description), undefined, { tags: { a: preview } }), ]),
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { RouteLocationAsRelativeTyped, RouteLocationRaw, RouteMapGeneric } from "vue-router";
|
import type { RouteLocationAsRelativeTyped, RouteLocationRaw, RouteMapGeneric } from "vue-router";
|
||||||
import { type NodeProperties, type Class, type NodeChildren, dom, mergeClasses, text, div, icon, type Node, span } from "#shared/dom";
|
import { type NodeProperties, type Class, type NodeChildren, dom, text, div, icon, type Node, span } from "#shared/dom";
|
||||||
import { contextmenu, followermenu, minimizeBox, popper, teleport, tooltip, type FloatState } from "#shared/floating";
|
import { followermenu, minimizeBox, popper, teleport, tooltip, type FloatState } from "#shared/floating";
|
||||||
import { clamp, deepEquals, shallowEquals } from "#shared/general";
|
import { clamp, shallowEquals } from "#shared/general";
|
||||||
import { Tree } from "#shared/tree";
|
import { Tree } from "#shared/tree";
|
||||||
import type { Placement } from "@floating-ui/dom";
|
import type { Placement } from "@floating-ui/dom";
|
||||||
import { reactivity, type Reactive } from '#shared/reactive';
|
import { reactivity, type Reactive, reactive } from '#shared/reactive';
|
||||||
|
|
||||||
export function link(children: NodeChildren, properties?: NodeProperties & { active?: Class }, link?: RouteLocationAsRelativeTyped<RouteMapGeneric, string>)
|
export function link(children: NodeChildren, properties?: NodeProperties & { active?: Class }, link?: RouteLocationAsRelativeTyped<RouteMapGeneric, string>)
|
||||||
{
|
{
|
||||||
@@ -84,8 +84,10 @@ export function optionmenu(options: Array<{ title: string, click: () => void }>,
|
|||||||
close = followermenu(target ?? this, [ element ], { arrow: true, placement: settings?.position, offset: 8 }).close;
|
close = followermenu(target ?? this, [ element ], { arrow: true, placement: settings?.position, offset: 8 }).close;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export type Option<T> = { text: string, render?: () => HTMLElement, value: T | Option<T>[] } | undefined;
|
export type Option<T> = { text: string, value: T, render?: () => HTMLElement } | undefined;
|
||||||
type StoredOption<T> = { item: Option<T>, dom: HTMLElement, container?: HTMLElement, children?: Array<StoredOption<T>> };
|
export type RecurrentOption<T> = { text: string, render?: () => HTMLElement, value: T | RecurrentOption<T>[] } | undefined;
|
||||||
|
type StoredRecurrentOption<T> = { item: RecurrentOption<T>, dom: HTMLElement, container?: HTMLElement, children?: Array<StoredRecurrentOption<T>> };
|
||||||
|
type StoredOption<T> = { item: Option<T>, dom: HTMLElement, container?: HTMLElement };
|
||||||
export function select<T extends NonNullable<any>>(options: Reactive<Array<{ text: string, value: T } | undefined>>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLDivElement & { disabled: boolean, value: T | undefined }
|
export function select<T extends NonNullable<any>>(options: Reactive<Array<{ text: string, value: T } | undefined>>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLDivElement & { disabled: boolean, value: T | undefined }
|
||||||
{
|
{
|
||||||
let context: { close: Function }, _options: Array<{ text: string, value: T }> = [], optionElements: HTMLElement[] = [];
|
let context: { close: Function }, _options: Array<{ text: string, value: T }> = [], optionElements: HTMLElement[] = [];
|
||||||
@@ -171,7 +173,7 @@ export function select<T extends NonNullable<any>>(options: Reactive<Array<{ tex
|
|||||||
change = settings?.change;
|
change = settings?.change;
|
||||||
return select;
|
return select;
|
||||||
}
|
}
|
||||||
export function multiselect<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 }): HTMLDivElement & { disabled: boolean, value: T[] | undefined }
|
export function multiselect<T extends NonNullable<any>>(options: Option<T>[], settings?: { defaultValue?: T[], change?: (value: T[]) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLDivElement & { disabled: boolean, value: T[] | undefined }
|
||||||
{
|
{
|
||||||
let context: { close: Function }, _options: Array<{ text: string, value: T }> = [], optionElements: HTMLElement[] = [];
|
let context: { close: Function }, _options: Array<{ text: string, value: T }> = [], optionElements: HTMLElement[] = [];
|
||||||
let focused: number | undefined, value: T[] | undefined, valueText: Text = text(''), disabled: boolean, change: ((v: T[]) => void) | undefined = undefined;
|
let focused: number | undefined, value: T[] | undefined, valueText: Text = text(''), disabled: boolean, change: ((v: T[]) => void) | undefined = undefined;
|
||||||
@@ -328,14 +330,14 @@ export function multiselect<T extends NonNullable<any>>(options: Array<{ text: s
|
|||||||
})
|
})
|
||||||
return select; */
|
return select; */
|
||||||
}
|
}
|
||||||
export function combobox<T extends NonNullable<any>>(options: Option<T>[], settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean, fill?: 'contain' | 'cover' })
|
export function combobox<T extends NonNullable<any>>(options: RecurrentOption<T>[], settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean, fill?: 'contain' | 'cover' })
|
||||||
{
|
{
|
||||||
let context: { container: HTMLElement, content: NodeChildren, close: () => void };
|
let context: { container: HTMLElement, content: NodeChildren, close: () => void };
|
||||||
let selected = true, tree: StoredOption<T>[] = [];
|
let selected = true, tree: StoredRecurrentOption<T>[] = [];
|
||||||
let focused: number | undefined;
|
let focused: number | undefined;
|
||||||
let currentOptions: StoredOption<T>[] = [];
|
let currentOptions: StoredRecurrentOption<T>[] = [];
|
||||||
|
|
||||||
const focus = (value?: T | Option<T>[]) => {
|
const focus = (value?: T | RecurrentOption<T>[]) => {
|
||||||
focused !== undefined && currentOptions[focused]?.dom.toggleAttribute('data-focused', false);
|
focused !== undefined && currentOptions[focused]?.dom.toggleAttribute('data-focused', false);
|
||||||
if(value !== undefined)
|
if(value !== undefined)
|
||||||
{
|
{
|
||||||
@@ -371,7 +373,7 @@ export function combobox<T extends NonNullable<any>>(options: Option<T>[], setti
|
|||||||
|
|
||||||
context && context.container.parentElement && context.close();
|
context && context.container.parentElement && context.close();
|
||||||
};
|
};
|
||||||
const progress = (option: StoredOption<T>) => {
|
const progress = (option: StoredRecurrentOption<T>) => {
|
||||||
if(!context || !context.container.parentElement || option.container === undefined)
|
if(!context || !context.container.parentElement || option.container === undefined)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -387,7 +389,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));
|
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: RecurrentOption<T>): StoredRecurrentOption<T> | undefined => {
|
||||||
if(option === undefined)
|
if(option === undefined)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -403,7 +405,7 @@ export function combobox<T extends NonNullable<any>>(options: Option<T>[], setti
|
|||||||
return { item: option, dom: dom('div', { listeners: { click: (_e) => { container.value = option.value as T; !_e.ctrlKey && hide(); }, mouseenter: () => focus(option.value) }, 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] }, [ option?.render ? option?.render() : text(option.text) ]) };
|
return { item: option, dom: dom('div', { listeners: { click: (_e) => { container.value = option.value as T; !_e.ctrlKey && hide(); }, mouseenter: () => focus(option.value) }, 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] }, [ option?.render ? option?.render() : text(option.text) ]) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const filter = (value: string, option?: StoredOption<T>): StoredOption<T>[] => {
|
const filter = (value: string, option?: StoredRecurrentOption<T>): StoredRecurrentOption<T>[] => {
|
||||||
if(option && option.children !== undefined)
|
if(option && option.children !== undefined)
|
||||||
{
|
{
|
||||||
return option.children.flatMap(e => filter(value, e));
|
return option.children.flatMap(e => filter(value, e));
|
||||||
@@ -484,9 +486,124 @@ export function combobox<T extends NonNullable<any>>(options: Option<T>[], setti
|
|||||||
}
|
}
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
export function tagpicker<T extends NonNullable<any>>(options: Reactive<Option<T>[]>, settings?: { defaultValue?: T[], change?: (value: T[]) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean, fill?: 'contain' | 'cover' })
|
||||||
|
{
|
||||||
|
let context: { container: HTMLElement, content: NodeChildren, close: () => void };
|
||||||
|
let focused: number | undefined;
|
||||||
|
let currentOptions: StoredOption<T>[] = [];
|
||||||
|
|
||||||
|
const focus = (value?: T | Option<T>[]) => {
|
||||||
|
focused !== undefined && currentOptions[focused]?.dom.toggleAttribute('data-focused', false);
|
||||||
|
if(value !== undefined)
|
||||||
|
{
|
||||||
|
const i = currentOptions.findIndex(e => e.item?.value === value);
|
||||||
|
if(i !== -1)
|
||||||
|
{
|
||||||
|
currentOptions[i]?.dom.toggleAttribute('data-focused', true);
|
||||||
|
currentOptions[i]?.dom.scrollIntoView({ behavior: 'instant', block: 'nearest' });
|
||||||
|
focused = i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
focused = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
focused = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const show = () => {
|
||||||
|
if(disabled || (context && context.container.parentElement))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const box = container.getBoundingClientRect();
|
||||||
|
focus();
|
||||||
|
currentOptions = optionElements.map(e => filter(select.value.toLowerCase().trim().normalize(), e)).filter(e => !!e);
|
||||||
|
context = followermenu(container, currentOptions.length > 0 ? currentOptions.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": settings?.fill === 'cover' && `${box.width}px`, "max-width": settings?.fill === 'contain' && `${box.width}px` }, blur: hide });
|
||||||
|
};
|
||||||
|
const hide = () => {
|
||||||
|
context && context.container.parentElement && context.close();
|
||||||
|
select.value = "";
|
||||||
|
};
|
||||||
|
const add = (value: T) => {
|
||||||
|
if(container.value.includes(value)) return;
|
||||||
|
container.value = [...container.value, value];
|
||||||
|
};
|
||||||
|
const remove = (value: T) => {
|
||||||
|
container.value = container.value.filter(e => e !== value);
|
||||||
|
}
|
||||||
|
const render = (option: Option<T>): StoredOption<T> | undefined => {
|
||||||
|
if(option === undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return { item: option, dom: dom('div', { listeners: { click: (_e) => { add(option.value); !_e.ctrlKey && hide(); }, mouseenter: () => focus(option.value) }, 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] }, [ option?.render ? option?.render() : text(option.text) ]) };
|
||||||
|
}
|
||||||
|
const filter = (_value: string, option?: StoredOption<T>): StoredOption<T> | undefined => {
|
||||||
|
return option?.item?.text.toLowerCase().normalize().includes(_value) && !value.includes(option.item.value) ? option : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let disabled = settings?.disabled ?? false, optionElements: StoredOption<T>[] = [];
|
||||||
|
reactivity(options, (_options) => {
|
||||||
|
optionElements = currentOptions = _options.map(render).filter(e => !!e);
|
||||||
|
})
|
||||||
|
const select = dom('input', { listeners: { focus: show, input: () => {
|
||||||
|
focus();
|
||||||
|
currentOptions = context && select.value ? optionElements.map(e => filter(select.value.toLowerCase().trim().normalize(), e)).filter(e => !!e) : optionElements.filter(e => !!e);
|
||||||
|
context && context.container.replaceChildren(...currentOptions.map(e => e.dom));
|
||||||
|
}, keydown: (e) => {
|
||||||
|
switch(e.key.toLocaleLowerCase())
|
||||||
|
{
|
||||||
|
case 'arrowdown':
|
||||||
|
focus(currentOptions[clamp((focused ?? -1) + 1, 0, currentOptions.length - 1)]?.item?.value);
|
||||||
|
return;
|
||||||
|
case 'arrowup':
|
||||||
|
focus(currentOptions[clamp((focused ?? 1) - 1, 0, currentOptions.length - 1)]?.item?.value);
|
||||||
|
return;
|
||||||
|
case 'pageup':
|
||||||
|
focus(currentOptions[0]?.item?.value);
|
||||||
|
return;
|
||||||
|
case 'pagedown':
|
||||||
|
focus(currentOptions[currentOptions.length - 1]?.item?.value);
|
||||||
|
return;
|
||||||
|
case 'enter':
|
||||||
|
focused !== undefined ? currentOptions[focused]?.dom.click() : currentOptions[0]?.dom.click();
|
||||||
|
return;
|
||||||
|
case 'escape':
|
||||||
|
context?.close();
|
||||||
|
return;
|
||||||
|
case 'backspace':
|
||||||
|
if(select.value === '')
|
||||||
|
currentOptions[currentOptions.length - 1]?.item?.value && remove(currentOptions[currentOptions.length - 1]!.item!.value);
|
||||||
|
return;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
} }, attributes: { type: 'text', }, class: 'flex-1 outline-none px-3 leading-none appearance-none py-1 bg-light-10 dark:bg-dark-10 disabled:bg-light-20 dark:disabled:bg-dark-20 w-full' });
|
||||||
|
|
||||||
|
let value: T[] = reactive([]);
|
||||||
|
const container = dom('label', { class: ['inline-flex h-10 w-full outline-none px-1 items-center justify-between text-sm font-semibold leading-none 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 overflow-y-hidden overflow-x-auto no-scroll', settings?.class?.container] }, [ div('flex flex-row gap-2 item-center select-none py-1 flex-shrink-0', { list: () => value, render: (v, _c) => _c ?? div('flex flex-row gap-2 items-center border border-light-35 dark:border-dark-35 py-1 ps-2 pe-1', [ span('text-sm', currentOptions.find(e => e.item?.value === v)?.item?.text), dom('span', { listeners: { click: () => remove(v) } }, [ icon('radix-icons:cross-1', { width: 10, height: 10, class: 'cursor-pointer text-light-60 dark:text-dark-60 hover:text-light-100 dark:hover:text-dark-100' }) ]) ]) }), select ]) as HTMLLabelElement & { disabled: boolean, value: T[] };
|
||||||
|
|
||||||
|
Object.defineProperty(container, 'disabled', {
|
||||||
|
get: () => disabled,
|
||||||
|
set: (v) => {
|
||||||
|
disabled = !!v;
|
||||||
|
container.toggleAttribute('data-disabled', disabled);
|
||||||
|
select.toggleAttribute('disabled', disabled);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Object.defineProperty(container, 'value', {
|
||||||
|
get: () => value,
|
||||||
|
set: (v: T[] | undefined) => {
|
||||||
|
settings?.change && value !== v && settings?.change(v as T[]);
|
||||||
|
value.splice(0, value.length, ...(v ?? []));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if(settings?.defaultValue) value.splice(0, value.length, ...settings?.defaultValue);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
export function input(type: 'text' | 'number' | 'email' | 'password' | 'tel', settings?: { defaultValue?: string, change?: (value: string) => void, input?: (value: string) => void | boolean, focus?: () => void, blur?: () => void, class?: Class, disabled?: boolean, placeholder?: string }): HTMLInputElement
|
export function input(type: 'text' | 'number' | 'email' | 'password' | 'tel', settings?: { defaultValue?: string, change?: (value: string) => void, input?: (value: string) => void | boolean, focus?: () => void, blur?: () => void, class?: Class, disabled?: boolean, placeholder?: string }): HTMLInputElement
|
||||||
{
|
{
|
||||||
const input = dom("input", { attributes: { disabled: settings?.disabled, placeholder: settings?.placeholder }, class: [`mx-4 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50
|
const input = dom("input", { attributes: { disabled: settings?.disabled, placeholder: settings?.placeholder, type: type }, class: [`mx-4 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50
|
||||||
bg-light-20 dark:bg-dark-20 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40
|
bg-light-20 dark:bg-dark-20 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40
|
||||||
border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20`, settings?.class], listeners: {
|
border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20`, settings?.class], listeners: {
|
||||||
input: (e) => { if(settings?.input && settings.input(input.value) === false) input.value = value; else value = input.value; },
|
input: (e) => { if(settings?.input && settings.input(input.value) === false) input.value = value; else value = input.value; },
|
||||||
@@ -821,14 +938,6 @@ export function floater(container: HTMLElement, content: NodeChildren | (() => N
|
|||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
export function tagpicker<T extends string>(list?: Reactive<T[]>, settings?: { defaultValue?: T[], class?: { container?: string, tag?: string }, disabled?: Reactive<boolean>, onUpdate?: () => boolean, onAdd?: (pick: T) => boolean, onRemove?: (pick: T) => boolean })
|
|
||||||
{
|
|
||||||
const value = settings?.defaultValue ?? [];
|
|
||||||
const _input = dom('input', { class: 'appearence-none bg-transparent border-none outline-none', attributes: { type: 'text', tabindex: '0' }, listeners: { focus: () => content.toggleAttribute('data-focused', true), blur: () => content.toggleAttribute('data-focused', false) }}), float = followermenu(_input, [ div('flex flex-col', { list: list, render: (item, _c) => _c ?? text(item) }) ]);
|
|
||||||
const content = dom('div', { class: ['border border-light-35 dark:border-dark-35 p-2 gap-2 flex flex-row items-center', settings?.class?.container], attributes: { tabindex: '0' }, listeners: { focus: () => _input.focus(), click: () => _input.focus() } }, [ div('flex flex-row gap-2', { list: () => value, render: (restrict, _c) => _c ?? div('') }), _input ]);
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToastConfig
|
export interface ToastConfig
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Ability, ArmorConfig, AspectConfig, CharacterConfig, CommonItemCon
|
|||||||
import { div, dom, icon, span, text, type NodeChildren } from "#shared/dom";
|
import { div, dom, icon, span, text, type NodeChildren } from "#shared/dom";
|
||||||
import { MarkdownEditor } from "#shared/editor";
|
import { MarkdownEditor } from "#shared/editor";
|
||||||
import { preview } from "#shared/proses";
|
import { preview } from "#shared/proses";
|
||||||
import { button, checkbox, combobox, foldable, input, multiselect, numberpicker, optionmenu, select, tabgroup, table, tagpicker, toggle, type Option } from "#shared/components";
|
import { button, checkbox, combobox, foldable, input, multiselect, numberpicker, optionmenu, select, tabgroup, table, tagpicker, toggle, type Option, type RecurrentOption } from "#shared/components";
|
||||||
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating";
|
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating";
|
||||||
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, colorByRarity, craftingText, DAMAGE_TYPES, damageTypeTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, masteryTexts, rarityText, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts, subnameFactory, weaponTypeTexts } from "#shared/character";
|
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, colorByRarity, craftingText, DAMAGE_TYPES, damageTypeTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, masteryTexts, rarityText, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts, subnameFactory, weaponTypeTexts } from "#shared/character";
|
||||||
import characterConfig from "#shared/character-config.json";
|
import characterConfig from "#shared/character-config.json";
|
||||||
@@ -246,7 +246,7 @@ export class HomebrewBuilder
|
|||||||
span('flex-1', `Rang ${spell.rank}`),
|
span('flex-1', `Rang ${spell.rank}`),
|
||||||
span('flex-1', spellTypeTexts[spell.type]),
|
span('flex-1', spellTypeTexts[spell.type]),
|
||||||
span('flex-1', `${spell.cost} mana`),
|
span('flex-1', `${spell.cost} mana`),
|
||||||
span('flex-1', spell.speed === 'action' ? 'Action' : spell.speed === 'reaction' ? 'Réaction' : `${spell.speed} minutes`),
|
span('flex-1', spell.speed === 'action' ? 'Action' : spell.speed === 'reaction' ? 'Réaction' : spell.speed === 'channeling' ? 'Canalisation' : `${spell.speed} minutes`),
|
||||||
span('flex-1', spell.elements.length === 0 ? '' : `${elementTexts[spell.elements[0]!].text}${spell.elements.length > 1 ? ` (+${spell.elements.length - 1})` : ''}`),
|
span('flex-1', spell.elements.length === 0 ? '' : `${elementTexts[spell.elements[0]!].text}${spell.elements.length > 1 ? ` (+${spell.elements.length - 1})` : ''}`),
|
||||||
span('flex-1', spell.range === 'personnal' ? 'Personnel' : spell.range === 0 ? 'Toucher' : `${spell.range} cases`),
|
span('flex-1', spell.range === 'personnal' ? 'Personnel' : spell.range === 0 ? 'Toucher' : `${spell.range} cases`),
|
||||||
span('flex-1', `${spell.tags && spell.tags.length > 0 ? spellTagTexts[spell.tags[0]!] : ''}${spell.tags && spell.tags.length > 1 ? ` (+${spell.tags.length - 1})` : ''}`),
|
span('flex-1', `${spell.tags && spell.tags.length > 0 ? spellTagTexts[spell.tags[0]!] : ''}${spell.tags && spell.tags.length > 1 ? ` (+${spell.tags.length - 1})` : ''}`),
|
||||||
@@ -271,7 +271,7 @@ export class HomebrewBuilder
|
|||||||
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Rang'), select([{ text: 'Rang 1', value: 1 }, { text: 'Rang 2', value: 2 }, { text: 'Rang 3', value: 3 }, { text: 'Spécial', value: 4 }], { change: (value: 1 | 2 | 3 | 4) => spell.rank = value, defaultValue: spell.rank, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Rang'), select([{ text: 'Rang 1', value: 1 }, { text: 'Rang 2', value: 2 }, { text: 'Rang 3', value: 3 }, { text: 'Spécial', value: 4 }], { change: (value: 1 | 2 | 3 | 4) => spell.rank = value, defaultValue: spell.rank, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Type'), select(SPELL_TYPES.map(f => ({ text: spellTypeTexts[f], value: f })), { change: (value) => spell.type = value, defaultValue: spell.type, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Type'), select(SPELL_TYPES.map(f => ({ text: spellTypeTexts[f], value: f })), { change: (value) => spell.type = value, defaultValue: spell.type, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Coût'), numberpicker({ defaultValue: spell.cost, input: (value) => spell.cost = value, class: '!m-0 w-full' }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Coût'), numberpicker({ defaultValue: spell.cost, input: (value) => spell.cost = value, class: '!m-0 w-full' }), ]),
|
||||||
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Incantation'), select<'action' | 'reaction' | number>(() => [{ text: 'Action', value: 'action' }, spell.type === 'instinct' ? { text: 'Reaction', value: 'reaction' } : undefined, { text: '1 minute', value: 1 }, { text: '10 minutes', value: 10 }], { change: (value) => spell.speed = value, defaultValue: spell.speed, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Incantation'), select<'action' | 'reaction' | 'channeling' | number>(() => [{ text: 'Action', value: 'action' }, spell.type === 'instinct' ? { text: 'Reaction', value: 'reaction' } : undefined, { text: 'Canalisation', value: 'channeling' }, { text: '1 minute', value: 1 }, { text: '10 minutes', value: 10 }], { change: (value) => spell.speed = value, defaultValue: spell.speed, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Elements'), multiselect(SPELL_ELEMENTS.map(f => ({ text: elementTexts[f].text, value: f })), { change: (value) => spell.elements = value, defaultValue: spell.elements, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Elements'), multiselect(SPELL_ELEMENTS.map(f => ({ text: elementTexts[f].text, value: f })), { change: (value) => spell.elements = value, defaultValue: spell.elements, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Portée'), select<'personnal' | number>([{ text: 'Toucher', value: 0 }, { text: 'Personnel', value: 'personnal' }, { text: '3 cases', value: 3 }, { text: '6 cases', value: 6 }, { text: '9 cases', value: 9 }, { text: '12 cases', value: 12 }, { text: '18 cases', value: 18 }], { change: (value) => spell.range = value, defaultValue: spell.range, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Portée'), select<'personnal' | number>([{ text: 'Toucher', value: 0 }, { text: 'Personnel', value: 'personnal' }, { text: '3 cases', value: 3 }, { text: '6 cases', value: 6 }, { text: '9 cases', value: 9 }, { text: '12 cases', value: 12 }, { text: '18 cases', value: 18 }], { change: (value) => spell.range = value, defaultValue: spell.range, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Tags'), multiselect([{ text: 'Dégâts', value: 'damage' }, { text: 'Buff', value: 'buff' }, { text: 'Debuff', value: 'debuff' }, { text: 'Support', value: 'support' }, { text: 'Tank', value: 'tank' }, { text: 'Mouvement', value: 'movement' }, { text: 'Utilitaire', value: 'utilitary' }], { change: (value) => spell.tags = value, defaultValue: spell.tags, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
div('flex flex-col items-center justify-between gap-2 flex-1 *:text-center', [ text('Tags'), multiselect([{ text: 'Dégâts', value: 'damage' }, { text: 'Buff', value: 'buff' }, { text: 'Debuff', value: 'debuff' }, { text: 'Support', value: 'support' }, { text: 'Tank', value: 'tank' }, { text: 'Mouvement', value: 'movement' }, { text: 'Utilitaire', value: 'utilitary' }], { change: (value) => spell.tags = value, defaultValue: spell.tags, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
@@ -800,7 +800,7 @@ class FeatureEditor
|
|||||||
}
|
}
|
||||||
private editList(buffer: Partial<FeatureList>)
|
private editList(buffer: Partial<FeatureList>)
|
||||||
{
|
{
|
||||||
let list: Option<string>[] = [];
|
let list: RecurrentOption<string>[] = [];
|
||||||
switch(buffer.list)
|
switch(buffer.list)
|
||||||
{
|
{
|
||||||
case undefined:
|
case undefined:
|
||||||
@@ -868,7 +868,7 @@ class FeatureEditor
|
|||||||
}
|
}
|
||||||
private editChoice(buffer: Partial<FeatureChoice>)
|
private editChoice(buffer: Partial<FeatureChoice>)
|
||||||
{
|
{
|
||||||
const availableChoices: Option<Partial<FeatureValue | FeatureList | FeatureTree>>[] = featureChoices.filter(e => (e?.value as FeatureOption)?.category !== 'choice').map(e => { if(e) e.value = Array.isArray(e.value) ? e.value.filter(f => (f?.value as FeatureOption)?.category !== 'choice') : e.value; return e; }) as Option<Partial<FeatureValue | FeatureList | FeatureTree>>[];
|
const availableChoices: RecurrentOption<Partial<FeatureValue | FeatureList | FeatureTree>>[] = featureChoices.filter(e => (e?.value as FeatureOption)?.category !== 'choice').map(e => { if(e) e.value = Array.isArray(e.value) ? e.value.filter(f => (f?.value as FeatureOption)?.category !== 'choice') : e.value; return e; }) as RecurrentOption<Partial<FeatureValue | FeatureList | FeatureTree>>[];
|
||||||
const addChoice = () => {
|
const addChoice = () => {
|
||||||
const choice: { text: string; effects: (Partial<FeatureValue | FeatureList | FeatureTree>)[]; } = { effects: [{ id: getID() }], text: '' };
|
const choice: { text: string; effects: (Partial<FeatureValue | FeatureList | FeatureTree>)[]; } = { effects: [{ id: getID() }], text: '' };
|
||||||
buffer.options ??= [];
|
buffer.options ??= [];
|
||||||
@@ -1109,7 +1109,7 @@ export class ImprovementPanel
|
|||||||
div('flex flex-row gap-2 items-center justify-between', [ span('flex flex-row gap-2 items-center', 'Fabrication'), div('flex flex-row items-center gap-2 !w-2/3', [ select<CraftingType>(Object.entries(craftingText).map(e => ({ text: e[1], value: e[0] as CraftingType })), { defaultValue: _improvement.craft.ability ?? 'crafter', change: (v) => _improvement.craft.ability = v, class: { container: '!w-1/2' } }), numberpicker({ defaultValue: _improvement.craft.difficulty ?? 0, input: (v) => _improvement.craft.difficulty = v, class: 'w-12' }) ]) ]),
|
div('flex flex-row gap-2 items-center justify-between', [ span('flex flex-row gap-2 items-center', 'Fabrication'), div('flex flex-row items-center gap-2 !w-2/3', [ select<CraftingType>(Object.entries(craftingText).map(e => ({ text: e[1], value: e[0] as CraftingType })), { defaultValue: _improvement.craft.ability ?? 'crafter', change: (v) => _improvement.craft.ability = v, class: { container: '!w-1/2' } }), numberpicker({ defaultValue: _improvement.craft.difficulty ?? 0, input: (v) => _improvement.craft.difficulty = v, class: 'w-12' }) ]) ]),
|
||||||
], [ span('text-lg font-bold', "Propriétés"), div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Rareté'), ]), select(Object.keys(rarityText).map(e => ({ text: rarityText[e as Rarity], value: e as Rarity })), { defaultValue: _improvement.rarity, change: (v) => _improvement.rarity = v, class: { container: '!w-1/2' } }), ]) ], { class: { content: 'group-data-[active]:grid grid-cols-2 my-2 gap-4', title: 'grid grid-cols-2 gap-4 mx-2', container: 'pb-2 border-b border-light-35 dark:border-dark-35' }, open: true } ),
|
], [ span('text-lg font-bold', "Propriétés"), div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Rareté'), ]), select(Object.keys(rarityText).map(e => ({ text: rarityText[e as Rarity], value: e as Rarity })), { defaultValue: _improvement.rarity, change: (v) => _improvement.rarity = v, class: { container: '!w-1/2' } }), ]) ], { class: { content: 'group-data-[active]:grid grid-cols-2 my-2 gap-4', title: 'grid grid-cols-2 gap-4 mx-2', container: 'pb-2 border-b border-light-35 dark:border-dark-35' }, open: true } ),
|
||||||
foldable([ div('p-1 border border-light-40 dark:border-dark-40 w-full bg-light-25 dark:bg-dark-25 min-h-48 max-h-[32rem]', [ ImprovementPanel.descriptionEditor.dom ])], [ span('text-lg font-bold px-2', "Description des effets") ], { class: { container: 'gap-4 pb-2 border-b border-light-35 dark:border-dark-35' }, open: true, }),
|
foldable([ div('p-1 border border-light-40 dark:border-dark-40 w-full bg-light-25 dark:bg-dark-25 min-h-48 max-h-[32rem]', [ ImprovementPanel.descriptionEditor.dom ])], [ span('text-lg font-bold px-2', "Description des effets") ], { class: { container: 'gap-4 pb-2 border-b border-light-35 dark:border-dark-35' }, open: true, }),
|
||||||
div('flex flex-row gap-4 items-center pb-2 border-b border-light-35 dark:border-dark-35', [ dom('h3', { class: 'text-lg font-bold', text: 'Restrictions' }), tagpicker([], { defaultValue: Object.keys(improvement.restrictions ?? {}), class: { container: 'data-[focused]:shadow-raw transition-[box-shadow] data-[focused]:shadow-light-40 dark:data-[focused]:shadow-dark-40' } }) ]),
|
div('flex flex-row gap-4 items-center pb-2 border-b border-light-35 dark:border-dark-35', [ dom('h3', { class: 'text-lg font-bold', text: 'Restrictions' }), tagpicker(() => [...fixedItemTags, ...Object.values(config.items).filter(e => e.variants === undefined).map(e => ({ text: e.name, value: e.id }))], { defaultValue: Object.keys(_improvement.restrictions ?? {}), change: (v) => _improvement.restrictions = v.length === 0 ? undefined : Object.fromEntries(v.map(e => [e, true])), fill: 'cover' }) ]),
|
||||||
foldable([ effectContainer ], [ dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
foldable([ effectContainer ], [ dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
||||||
tooltip(button(icon('radix-icons:plus', { width: 20, height: 20 }), () => {
|
tooltip(button(icon('radix-icons:plus', { width: 20, height: 20 }), () => {
|
||||||
const f = { id: getID(), };
|
const f = { id: getID(), };
|
||||||
@@ -1136,7 +1136,27 @@ export class ImprovementPanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureChoices: Option<Partial<FeatureOption>>[] = [
|
export const fixedItemTags: Option<string>[] = [
|
||||||
|
{ text: 'Objet inerte', value: 'mundane' },
|
||||||
|
{ text: 'Objet magique', value: 'wondrous' },
|
||||||
|
{ text: 'Armure', value: 'armor' },
|
||||||
|
{ text: 'Armure légère', value: 'armor/light' },
|
||||||
|
{ text: 'Armure standard', value: 'armor/medium' },
|
||||||
|
{ text: 'Armure lourde', value: 'armor/heavy' },
|
||||||
|
{ text: 'Arme', value: 'weapon' },
|
||||||
|
{ text: 'Arme légère', value: 'weapon/light' },
|
||||||
|
{ text: 'Bouclier', value: 'weapon/shield' },
|
||||||
|
{ text: 'Arme lourde', value: 'weapon/heavy' },
|
||||||
|
{ text: 'Arme arme', value: 'weapon/classic' },
|
||||||
|
{ text: 'Arme de jet', value: 'weapon/throw' },
|
||||||
|
{ text: 'Arme naturelle', value: 'weapon/natural' },
|
||||||
|
{ text: 'Arme à deux mains', value: 'weapon/twohanded' },
|
||||||
|
{ text: 'Arme maniable', value: 'weapon/finesse' },
|
||||||
|
{ text: 'Arme longue', value: 'weapon/reach' },
|
||||||
|
{ text: 'Arme à projectile', value: 'weapon/projectile' },
|
||||||
|
{ text: 'Arme improvisée', value: 'weapon/improvised' }
|
||||||
|
]
|
||||||
|
const featureChoices: RecurrentOption<Partial<FeatureOption>>[] = [
|
||||||
{ text: 'PV max', value: { category: 'value', property: 'health', operation: 'add', value: 1 }, },
|
{ 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: '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 de sorts maitrisés', value: { category: 'value', property: 'spellslots', operation: 'add', value: 1 }, },
|
||||||
@@ -1164,7 +1184,7 @@ const featureChoices: Option<Partial<FeatureOption>>[] = [
|
|||||||
{ text: 'Arbre', value: { category: 'tree' } },
|
{ text: 'Arbre', value: { category: 'tree' } },
|
||||||
{ text: 'Maitrise', value: Object.keys(masteryTexts).map(e => ({ text: `Maitrise > ${masteryTexts[e as keyof typeof masteryTexts].text}`, value: { category: 'list', action: 'add', list: 'mastery', item: e } })) },
|
{ text: 'Maitrise', value: Object.keys(masteryTexts).map(e => ({ text: `Maitrise > ${masteryTexts[e as keyof typeof masteryTexts].text}`, value: { category: 'list', action: 'add', list: 'mastery', item: e } })) },
|
||||||
{ text: 'Compétences', value: [
|
{ text: 'Compétences', value: [
|
||||||
...ABILITIES.map((e) => ({ text: abilityTexts[e as Ability], value: { category: 'value', property: `abilities/${e}`, operation: 'add', value: 1 } })) as Option<Partial<FeatureItem>>[],
|
...ABILITIES.map((e) => ({ text: abilityTexts[e as Ability], value: { category: 'value', property: `abilities/${e}`, operation: 'add', value: 1 } })) as RecurrentOption<Partial<FeatureItem>>[],
|
||||||
{ text: 'Max de compétence', value: ABILITIES.map((e) => ({ text: `Max > ${abilityTexts[e as Ability]}`, value: { category: 'value', property: `bonus/abilities/${e}`, operation: 'add', value: 1 } })) }
|
{ text: 'Max de compétence', value: ABILITIES.map((e) => ({ text: `Max > ${abilityTexts[e as Ability]}`, value: { category: 'value', property: `bonus/abilities/${e}`, operation: 'add', value: 1 } })) }
|
||||||
] },
|
] },
|
||||||
{ text: 'Modifieur', value: [
|
{ text: 'Modifieur', value: [
|
||||||
@@ -1201,13 +1221,13 @@ const featureChoices: Option<Partial<FeatureOption>>[] = [
|
|||||||
{ text: 'Résistance > Charisme', value: { category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 1 } },
|
{ text: 'Résistance > Charisme', value: { category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 1 } },
|
||||||
{ text: 'Résistance > Psyché', value: { category: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 } },
|
{ text: 'Résistance > Psyché', value: { category: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 } },
|
||||||
{ text: 'Résistance au choix', value: { category: 'choice', text: '+1 au jet de résistance de ', options: [
|
{ text: 'Résistance au choix', value: { category: 'choice', text: '+1 au jet de résistance de ', options: [
|
||||||
{ text: 'Résistance > Force', effects: [{ category: 'value', property: 'bonus/defense/strength', operation: 'add', value: 1 }] },
|
{ text: 'Résistance > Force', effects: [{ category: 'value', property: 'bonus/defense/strength', operation: 'add', value: 1 } as Partial<FeatureValue>] },
|
||||||
{ text: 'Résistance > Dextérité', effects: [{ category: 'value', property: 'bonus/defense/dexterity', operation: 'add', value: 1 }] },
|
{ text: 'Résistance > Dextérité', effects: [{ category: 'value', property: 'bonus/defense/dexterity', operation: 'add', value: 1 } as Partial<FeatureValue>] },
|
||||||
{ text: 'Résistance > Constitution', effects: [{ category: 'value', property: 'bonus/defense/constitution', operation: 'add', value: 1 }] },
|
{ text: 'Résistance > Constitution', effects: [{ category: 'value', property: 'bonus/defense/constitution', operation: 'add', value: 1 } as Partial<FeatureValue>] },
|
||||||
{ text: 'Résistance > Intelligence', effects: [{ category: 'value', property: 'bonus/defense/intelligence', operation: 'add', value: 1 }] },
|
{ text: 'Résistance > Intelligence', effects: [{ category: 'value', property: 'bonus/defense/intelligence', operation: 'add', value: 1 } as Partial<FeatureValue>] },
|
||||||
{ text: 'Résistance > Curiosité', effects: [{ category: 'value', property: 'bonus/defense/curiosity', operation: 'add', value: 1 }] },
|
{ text: 'Résistance > Curiosité', effects: [{ category: 'value', property: 'bonus/defense/curiosity', operation: 'add', value: 1 } as Partial<FeatureValue>] },
|
||||||
{ text: 'Résistance > Charisme', effects: [{ category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 1 }] },
|
{ text: 'Résistance > Charisme', effects: [{ category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 1 } as Partial<FeatureValue>] },
|
||||||
{ text: 'Résistance > Psyché', effects: [{ category: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 }] }
|
{ text: 'Résistance > Psyché', effects: [{ category: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 } as Partial<FeatureValue>] }
|
||||||
]} as Partial<FeatureChoice>}
|
]} as Partial<FeatureChoice>}
|
||||||
] },
|
] },
|
||||||
{ text: 'Difficulté des chocs', value: RESISTANCES.map(e => ({ text: `Difficulté > ${resistanceTexts[e]}`, value: { category: 'value', property: `bonus/resistance/${e}`, operation: 'add', value: 1 } })) },
|
{ text: 'Difficulté des chocs', value: RESISTANCES.map(e => ({ text: `Difficulté > ${resistanceTexts[e]}`, value: { category: 'value', property: `bonus/resistance/${e}`, operation: 'add', value: 1 } })) },
|
||||||
@@ -1222,9 +1242,9 @@ const featureChoices: Option<Partial<FeatureOption>>[] = [
|
|||||||
{ text: 'Bonus > Savoir', value: { category: 'value', property: 'bonus/spells/type/knowledge', operation: 'add', value: 1 } },
|
{ text: 'Bonus > Savoir', value: { category: 'value', property: 'bonus/spells/type/knowledge', operation: 'add', value: 1 } },
|
||||||
{ text: 'Bonus > Instinct', value: { category: 'value', property: 'bonus/spells/type/instinct', operation: 'add', value: 1 } },
|
{ text: 'Bonus > Instinct', value: { category: 'value', property: 'bonus/spells/type/instinct', operation: 'add', value: 1 } },
|
||||||
{ text: 'Bonus > Choix par type', value: { category: 'choice', options: [
|
{ text: 'Bonus > Choix par type', value: { category: 'choice', options: [
|
||||||
{ text: 'Précision', effects: [{ category: 'value', property: 'bonus/spells/type/precision', operation: 'add', value: 1 }] },
|
{ text: 'Précision', effects: [{ category: 'value', property: 'bonus/spells/type/precision', operation: 'add', value: 1 } as Partial<FeatureValue>] },
|
||||||
{ text: 'Savoir', effects: [{ category: 'value', property: 'bonus/spells/type/knowledge', operation: 'add', value: 1 }] },
|
{ text: 'Savoir', effects: [{ category: 'value', property: 'bonus/spells/type/knowledge', operation: 'add', value: 1 } as Partial<FeatureValue>] },
|
||||||
{ text: 'Instinct', effects: [{ category: 'value', property: 'bonus/spells/type/instinct', operation: 'add', value: 1 }] },
|
{ text: 'Instinct', effects: [{ category: 'value', property: 'bonus/spells/type/instinct', operation: 'add', value: 1 } as Partial<FeatureValue>] },
|
||||||
] } as Partial<FeatureChoice> }
|
] } as Partial<FeatureChoice> }
|
||||||
]},
|
]},
|
||||||
{ text: 'Bonus par rang', value: [
|
{ text: 'Bonus par rang', value: [
|
||||||
@@ -1239,8 +1259,8 @@ const featureChoices: Option<Partial<FeatureOption>>[] = [
|
|||||||
] } }
|
] } }
|
||||||
] as Partial<FeatureChoice> },
|
] as Partial<FeatureChoice> },
|
||||||
{ text: 'Bonus par élément', value: [
|
{ text: 'Bonus par élément', value: [
|
||||||
...SPELL_ELEMENTS.map(e => ({ text: `Bonus > ${elementTexts[e].text}`, value: { category: 'value', property: `bonus/spells/elements/${e}`, operation: 'add', value: 1 } })) as Option<Partial<FeatureItem>>[],
|
...SPELL_ELEMENTS.map(e => ({ text: `Bonus > ${elementTexts[e].text}`, value: { category: 'value', property: `bonus/spells/elements/${e}`, operation: 'add', value: 1 } })) as RecurrentOption<Partial<FeatureItem>>[],
|
||||||
{ text: 'Bonus > Choix par élément', value: { category: 'choice', options: SPELL_ELEMENTS.map(e => ({ text: elementTexts[e].text, effects: [{ category: 'value', property: `bonus/spells/elements/${e}`, operation: 'add', value: 1 }] })) } as Partial<FeatureChoice> }
|
{ text: 'Bonus > Choix par élément', value: { category: 'choice', options: SPELL_ELEMENTS.map(e => ({ text: elementTexts[e].text, effects: [{ category: 'value', property: `bonus/spells/elements/${e}`, operation: 'add', value: 1 } as Partial<FeatureValue>] })) } as Partial<FeatureChoice> }
|
||||||
] },
|
] },
|
||||||
] },
|
] },
|
||||||
{ text: 'Aspect', value: [
|
{ text: 'Aspect', value: [
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
|
|||||||
let state: FloatState = 'hidden', timeout: Timer;
|
let state: FloatState = 'hidden', timeout: Timer;
|
||||||
const arrow = svg('svg', { class: ' group-data-[pinned]:hidden 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 arrow = svg('svg', { class: ' group-data-[pinned]:hidden 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: properties?.class, style: properties?.style });
|
const content = dom('div', { class: properties?.class, style: properties?.style });
|
||||||
const floater = dom('div', { class: 'fixed hidden group', attributes: { 'data-state': 'closed' } }, [ content, properties?.arrow ? arrow : undefined ]);
|
const floater = dom('div', { class: 'fixed hidden group z-50', attributes: { 'data-state': 'closed' } }, [ content, properties?.arrow ? arrow : undefined ]);
|
||||||
const rect = properties?.viewport?.getBoundingClientRect() ?? 'viewport';
|
const rect = properties?.viewport?.getBoundingClientRect() ?? 'viewport';
|
||||||
|
|
||||||
function update()
|
function update()
|
||||||
@@ -346,7 +346,7 @@ export function followermenu(target: FloatingUI.ReferenceElement, content: NodeC
|
|||||||
properties?.priority || document.addEventListener('mousedown', close);
|
properties?.priority || document.addEventListener('mousedown', close);
|
||||||
container.addEventListener('mousedown', cancelPropagation);
|
container.addEventListener('mousedown', cancelPropagation);
|
||||||
|
|
||||||
const stop = FloatingUI.autoUpdate(target, container, update, {
|
let stop = FloatingUI.autoUpdate(target, container, update, {
|
||||||
animationFrame: true,
|
animationFrame: true,
|
||||||
layoutShift: false,
|
layoutShift: false,
|
||||||
elementResize: false,
|
elementResize: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user