Updated legal stuff, added floating popup that can be pin and move. Fix character compiler modifier updates not dirtying all dependents.
This commit is contained in:
parent
89c4476ffb
commit
b19d2d1b41
|
|
@ -1,22 +0,0 @@
|
||||||
<template>
|
|
||||||
<span ref="container"></span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { parseURL } from 'ufo';
|
|
||||||
import proses, { preview } from '#shared/proses';
|
|
||||||
import { text } from '#shared/dom.util';
|
|
||||||
|
|
||||||
const { href, label } = defineProps<{
|
|
||||||
href: string,
|
|
||||||
label: string
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const container = useTemplateRef('container');
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
queueMicrotask(() => {
|
|
||||||
container.value && container.value.appendChild(proses('a', preview, [ text(label) ], { href }) as HTMLElement);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -49,7 +49,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="xl:px-12 px-6 pt-4 pb-2 text-center text-xs text-light-60 dark:text-dark-60">
|
<div class="xl:px-12 px-6 pt-4 pb-2 text-center text-xs text-light-60 dark:text-dark-60">
|
||||||
<NuxtLink class="hover:underline italic" :to="{ name: 'roadmap' }">Roadmap</NuxtLink> - <NuxtLink class="hover:underline italic" :to="{ name: 'legal' }">Mentions légales</NuxtLink>
|
<NuxtLink class="hover:underline italic" :to="{ name: 'legal' }">Mentions légales</NuxtLink> - <NuxtLink class="hover:underline italic" :to="{ name: 'usage' }">Conditions d'utilisations</NuxtLink>
|
||||||
<p>Copyright Peaceultime - 2025</p>
|
<p>Copyright Peaceultime - 2025</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -65,9 +65,9 @@ import type { DropdownOption } from '~/components/base/DropdownMenu.vue';
|
||||||
import { hasPermissions } from '#shared/auth.util';
|
import { hasPermissions } from '#shared/auth.util';
|
||||||
import { TreeDOM } from '#shared/tree';
|
import { TreeDOM } from '#shared/tree';
|
||||||
import { Content, iconByType } from '#shared/content.util';
|
import { Content, iconByType } from '#shared/content.util';
|
||||||
import { dom, icon, text } from '#shared/dom.util';
|
import { dom, icon } from '#shared/dom.util';
|
||||||
import { unifySlug } from '#shared/general.util';
|
import { unifySlug } from '#shared/general.util';
|
||||||
import { popper, tooltip } from '#shared/floating.util';
|
import { tooltip } from '#shared/floating.util';
|
||||||
import { link } from '#shared/components.util';
|
import { link } from '#shared/components.util';
|
||||||
|
|
||||||
const options = ref<DropdownOption[]>([{
|
const options = ref<DropdownOption[]>([{
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import characterConfig from '#shared/character-config.json';
|
import characterConfig from '#shared/character-config.json';
|
||||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
import { unifySlug } from '#shared/general.util';
|
||||||
import PreviewA from '~/components/prose/PreviewA.vue';
|
|
||||||
import { clamp, unifySlug } from '#shared/general.util';
|
|
||||||
import type { CompiledCharacter, SpellConfig } from '~/types/character';
|
|
||||||
import type { CharacterConfig } from '~/types/character';
|
import type { CharacterConfig } from '~/types/character';
|
||||||
import { abilityTexts, CharacterCompiler, CharacterSheet, defaultCharacter, elementTexts, spellTypeTexts } from '#shared/character.util';
|
import { CharacterSheet } from '#shared/character.util';
|
||||||
import { getText } from '#shared/i18n';
|
|
||||||
import { preview } from '#shared/proses';
|
|
||||||
import { div, dom, icon, text } from '#shared/dom.util';
|
|
||||||
import markdown from '#shared/markdown.util';
|
|
||||||
import { button, foldable } from '#shared/components.util';
|
|
||||||
import { fullblocker, tooltip } from '~/shared/floating.util';
|
|
||||||
/*
|
/*
|
||||||
text-light-red dark:text-dark-red border-light-red dark:border-dark-red bg-light-red dark:bg-dark-red
|
text-light-red dark:text-dark-red border-light-red dark:border-dark-red bg-light-red dark:bg-dark-red
|
||||||
text-light-blue dark:text-dark-blue border-light-blue dark:border-dark-blue bg-light-blue dark:bg-dark-blue
|
text-light-blue dark:text-dark-blue border-light-blue dark:border-dark-blue bg-light-blue dark:bg-dark-blue
|
||||||
|
|
@ -43,172 +34,4 @@ onMounted(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="container"></div>
|
<div ref="container"></div>
|
||||||
<!-- <div v-if="status === 'pending'">
|
|
||||||
<Head>
|
|
||||||
<Title>d[any] - Chargement ...</Title>
|
|
||||||
</Head>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="status === 'success' && character && !error">
|
|
||||||
<Head>
|
|
||||||
<Title>d[any] - {{ character.name }}</Title>
|
|
||||||
</Head>
|
|
||||||
<div class="flex flex-row gap-4 justify-between">
|
|
||||||
<div></div>
|
|
||||||
<div class="flex lg:flex-row flex-col gap-6 items-center justify-center">
|
|
||||||
<div class="flex gap-6 items-center">
|
|
||||||
<Avatar src="" icon="radix-icons:person" size="large" />
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="text-xl font-bold">{{ character.name }}</span>
|
|
||||||
<span class="text-sm">De {{ character.username }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="font-bold">Niveau {{ character.level }}</span>
|
|
||||||
<span>{{ config.peoples[character.race]?.name ?? 'Peuple inconnu' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row lg:border-l border-light-30 dark:border-dark-30 py-4 ps-4 gap-8">
|
|
||||||
<span class="flex flex-row items-center gap-2 text-3xl font-light">PV: <span class="font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35">{{ character.health - character.variables.health }}</span>/ {{ character.health }}</span>
|
|
||||||
<span class="flex flex-row items-center gap-2 text-3xl font-light">Mana: <span class="font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35">{{ character.mana - character.variables.mana }}</span>/ {{ character.mana }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="self-center">
|
|
||||||
<Tooltip side="right" message="Modifier" v-if="user && user.id === character.owner"><NuxtLink :to="{ name: 'character-id-edit', params: { id: character.id } }"><Button icon><Icon icon="radix-icons:pencil-2" /></Button></NuxtLink></Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-1 flex-col justify-center gap-4 *:py-2">
|
|
||||||
<div class="flex flex-row gap-4 items-center border-b border-light-30 dark:border-dark-30 me-4 pe-4 divide-x divide-light-30 dark:divide-dark-30">
|
|
||||||
<div class="flex relative justify-between ps-4 gap-2">
|
|
||||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.strength }}</span><span class="text-sm 2xl:text-base">Force</span></div>
|
|
||||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.dexterity }}</span><span class="text-sm 2xl:text-base">Dextérité</span></div>
|
|
||||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.constitution }}</span><span class="text-sm 2xl:text-base">Constitution</span></div>
|
|
||||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.intelligence }}</span><span class="text-sm 2xl:text-base">Intelligence</span></div>
|
|
||||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.curiosity }}</span><span class="text-sm 2xl:text-base">Curiosité</span></div>
|
|
||||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.charisma }}</span><span class="text-sm 2xl:text-base">Charisme</span></div>
|
|
||||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">+{{ character.modifier.psyche }}</span><span class="text-sm 2xl:text-base">Psyché</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-1 relative ps-4 flex-row items-center justify-between">
|
|
||||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.initiative }}</span><span>Initiative</span></div>
|
|
||||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">{{ character.speed === false ? "Aucun déplacement" : `${character.speed} cases` }}</span><span>Course</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-1 relative ps-4 flex-row items-center justify-between">
|
|
||||||
<Icon icon="ph:shield-checkered" class="w-8 h-8" />
|
|
||||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">{{ clamp(character.defense.static + character.defense.passivedodge + character.defense.passiveparry, 0, character.defense.hardcap) }}</span><span class="text-sm 2xl:text-base">Passive</span></div>
|
|
||||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">{{ clamp(character.defense.static + character.defense.passivedodge + character.defense.activeparry, 0, character.defense.hardcap) }}</span><span class="text-sm 2xl:text-base">Blocage</span></div>
|
|
||||||
<div class="flex flex-col items-center"><span class="2xl:text-2xl text-xl font-bold">{{ clamp(character.defense.static + character.defense.activedodge + character.defense.passiveparry, 0, character.defense.hardcap) }}</span><span class="text-sm 2xl:text-base">Esquive</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-1 px-8">
|
|
||||||
<div class="flex flex-col pe-8 gap-4 py-2 w-80 border-r border-light-30 dark:border-dark-30">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30 mb-2">Compétences</span>
|
|
||||||
<div class="grid grid-cols-3 gap-1">
|
|
||||||
<div class="flex flex-col px-2 items-center text-sm text-light-70 dark:text-dark-70" v-for="(value, ability) of character.abilities"><span class="font-bold text-base text-light-100 dark:text-dark-100">+{{ value }}</span><span>{{ abilityTexts[ability] }}</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30">Maitrises</span>
|
|
||||||
<div class="grid grid-cols-2 gap-x-3 gap-y-1 text-sm">
|
|
||||||
<PreviewA v-if="character.mastery.strength + character.mastery.dexterity > 0" href="regles/annexes/equipement#Les armes légères" label="Arme légère" />
|
|
||||||
<PreviewA v-if="character.mastery.strength + character.mastery.dexterity > 0" href="regles/annexes/equipement#Les armes de jet" label="Arme de jet" />
|
|
||||||
<PreviewA v-if="character.mastery.strength + character.mastery.dexterity > 0" href="regles/annexes/equipement#Les armes naturelles" label="Arme naturelle" />
|
|
||||||
<PreviewA v-if="character.mastery.strength > 1" href="regles/annexes/equipement#Les armes" label="Arme standard" />
|
|
||||||
<PreviewA v-if="character.mastery.strength > 1" href="regles/annexes/equipement#Les armes improvisées" label="Arme improvisée" />
|
|
||||||
<PreviewA v-if="character.mastery.strength > 2" href="regles/annexes/equipement#Les armes lourdes" label="Arme lourde" />
|
|
||||||
<PreviewA v-if="character.mastery.strength > 3" href="regles/annexes/equipement#Les armes à deux mains" label="Arme à deux mains" />
|
|
||||||
<PreviewA v-if="character.mastery.dexterity > 0 && character.mastery.strength > 1" href="regles/annexes/equipement#Les armes maniables" label="Arme maniable" />
|
|
||||||
<PreviewA v-if="character.mastery.dexterity > 1 && character.mastery.strength > 1" href="regles/annexes/equipement#Les armes à projectiles" label="Arme à projectiles" />
|
|
||||||
<PreviewA v-if="character.mastery.dexterity > 1 && character.mastery.strength > 2" href="regles/annexes/equipement#Les armes longues" label="Arme longue" />
|
|
||||||
<PreviewA v-if="character.mastery.shield > 0" href="regles/annexes/equipement#Les boucliers" label="Bouclier" />
|
|
||||||
<PreviewA v-if="character.mastery.shield > 0 && character.mastery.strength > 3" href="regles/annexes/equipement#Les boucliers à deux mains" label="Bouclier à deux mains" />
|
|
||||||
</div>
|
|
||||||
<div v-if="character.mastery.armor > 0" class="grid grid-cols-2 gap-x-3 gap-y-1 text-sm">
|
|
||||||
<PreviewA v-if="character.mastery.armor > 0" href="regles/annexes/equipement#Les armures légères" label="Armure légère" />
|
|
||||||
<PreviewA v-if="character.mastery.armor > 1" href="regles/annexes/equipement#Les armures" label="Armure standard" />
|
|
||||||
<PreviewA v-if="character.mastery.armor > 2" href="regles/annexes/equipement#Les armures lourdes" label="Armure lourde" />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-x-3 gap-y-1 text-sm">
|
|
||||||
<span>Précision: <span class="font-bold">{{ character.spellranks.precision }}</span></span>
|
|
||||||
<span>Savoir: <span class="font-bold">{{ character.spellranks.knowledge }}</span></span>
|
|
||||||
<span>Instinct: <span class="font-bold">{{ character.spellranks.instinct }}</span></span>
|
|
||||||
<span>Oeuvres: <span class="font-bold">{{ character.spellranks.arts }}</span></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<TabsRoot default-value="features" class="w-[60rem] max-h-full">
|
|
||||||
<TabsList class="flex flex-row relative px-4 gap-4">
|
|
||||||
<TabsIndicator class="absolute left-0 h-[3px] bottom-0 w-[--radix-tabs-indicator-size] translate-x-[--radix-tabs-indicator-position] transition-[width,transform] duration-300 bg-accent-blue"></TabsIndicator>
|
|
||||||
<TabsTrigger value="features" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Aptitudes</TabsTrigger>
|
|
||||||
<TabsTrigger value="spells" class="px-2 py-1 border-b border-transparent hover:border-accent-blue" v-if="character.spellslots > 0">Sorts</TabsTrigger>
|
|
||||||
<TabsTrigger value="inventory" class="px-2 py-1 border-b border-transparent hover:border-accent-blue" v-if="character.capacity !== false">Inventaire</TabsTrigger>
|
|
||||||
<TabsTrigger value="notes" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Notes</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="features" class="overflow-y-auto max-h-full">
|
|
||||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-4">
|
|
||||||
<div class="grid grid-cols-3 gap-4">
|
|
||||||
<div class="flex flex-col col-span-2">
|
|
||||||
<span class="text-lg font-semibold">Actions</span>
|
|
||||||
<span class="text-sm text-light-70 dark:text-dark-70">Attaquer - Saisir - Faire chuter - Déplacer - Courir - Pas de coté - Lancer un sort - S'interposer - Se transformer - Utiliser un objet - Anticiper une action - Improviser</span>
|
|
||||||
<MarkdownRenderer :content="character.lists.action?.map(e => getText(e))?.join('\n')" :properties="{ tags: { a: preview } }" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="text-lg font-semibold">Réactions</span>
|
|
||||||
<span class="text-sm text-light-70 dark:text-dark-70">Parade - Esquive - Saisir une opportunité - Prendre en tenaille - Intercepter - Désarmer</span>
|
|
||||||
<MarkdownRenderer :content="character.lists.reaction?.map(e => getText(e))?.join('\n')" :properties="{ tags: { a: preview } }" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="text-lg font-semibold">Actions libre</span>
|
|
||||||
<span class="text-sm text-light-70 dark:text-dark-70">Analyser une situation - Communiquer</span>
|
|
||||||
<MarkdownRenderer :content="character.lists.freeaction?.map(e => getText(e))?.join('\n')" :properties="{ tags: { a: preview } }" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="text-lg font-semibold">Aptitudes</span>
|
|
||||||
<MarkdownRenderer :content="character.lists.passive?.map(e => getText(e))?.map(e => `> ${e}`).join('\n\n')" :properties="{ tags: { a: preview } }" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent v-if="character.spellslots > 0" value="spells" class="overflow-y-auto max-h-full">
|
|
||||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-2">
|
|
||||||
<div class="flex flex-1 justify-between items-baseline px-2"><div></div><div class="flex gap-4 items-baseline"><span class="italic text-light-70 dark:text-dark-70 text-sm">{{ character.variables.spells.length }} / {{ character.spellslots }} sorts maitrisés</span><Button class="!font-normal" @click="openSpellPanel">Modifier</Button></div></div>
|
|
||||||
<div class="flex flex-col" v-if="[...(character.lists.spells ?? []), ...character.variables.spells].length > 0">
|
|
||||||
<div class="pb-4 px-2 mt-4 border-b last:border-none border-light-30 dark:border-dark-30 flex flex-col" v-for="spell of [...(character.lists.spells ?? []), ...character.variables.spells].map(e => config.spells.find((f: SpellConfig) => f.id === e)).filter(e => !!e)">
|
|
||||||
<div class="flex flex-row justify-between">
|
|
||||||
<span class="text-lg font-bold">{{ spell.name }}</span>
|
|
||||||
<div class="flex flex-row items-center gap-6">
|
|
||||||
<div class="flex flex-row text-sm gap-2">
|
|
||||||
<span v-for="element of spell.elements" :class="elementTexts[element].class" class="border !border-opacity-50 rounded-full !bg-opacity-20 px-2 py-px">{{ elementTexts[element].text }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row text-sm gap-1">
|
|
||||||
<span class="" v-if="spell.rank !== 4">Rang {{ spell.rank }}</span><span v-if="spell.rank !== 4">/</span>
|
|
||||||
<span class="" v-if="spell.rank !== 4">{{ spellTypeTexts[spell.type] }}</span><span v-if="spell.rank !== 4">/</span>
|
|
||||||
<span class="">{{ spell.cost }} mana</span><span>/</span>
|
|
||||||
<span class="capitalize">{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<MarkdownRenderer :content="spell.effect" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="inventory" v-if="character.capacity !== false" class="overflow-y-auto max-h-full">
|
|
||||||
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="notes" class="overflow-y-auto max-h-full">
|
|
||||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
|
|
||||||
<MarkdownRenderer :content="character.notes" />
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</TabsRoot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<Head>
|
|
||||||
<Title>d[any] - Erreur</Title>
|
|
||||||
</Head>
|
|
||||||
<div>Erreur de chargement</div>
|
|
||||||
</div> -->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -37,8 +37,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Content, Editor } from '#shared/content.util';
|
import { Content, Editor } from '#shared/content.util';
|
||||||
import { button, loading } from '#shared/components.util';
|
import { button, loading } from '#shared/components.util';
|
||||||
import { dom, icon, text } from '#shared/dom.util';
|
import { dom, icon } from '#shared/dom.util';
|
||||||
import { modal, popper, tooltip } from '#shared/floating.util';
|
import { modal, tooltip } from '#shared/floating.util';
|
||||||
import { Toaster } from '#shared/components.util';
|
import { Toaster } from '#shared/components.util';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
<Title>d[any] - Mentions légales</Title>
|
<Title>d[any] - Mentions légales</Title>
|
||||||
</Head>
|
</Head>
|
||||||
<div class="flex flex-col max-w-[1200px] p-16">
|
<div class="flex flex-col max-w-[1200px] p-16">
|
||||||
<ProseH3>Mentions Légales</ProseH3>
|
<h3 class="text-xl font-bold">Mentions Légales</h3>
|
||||||
<ProseH4>Collecte et Traitement des Données Personnelles</ProseH4>
|
<h4 class="text-lg font-semibold">Collecte et Traitement des Données Personnelles</h4>
|
||||||
Ce site collecte des données personnelles durant l'inscription et des données anonymes durant la navigation sur
|
Ce site collecte des données personnelles durant l'inscription et des données anonymes durant la navigation sur
|
||||||
le site dans un but de collecte statistiques.<br />
|
le site dans un but de collecte statistiques.<br />
|
||||||
|
|
||||||
|
|
@ -12,21 +12,21 @@
|
||||||
suppression de vos données personnelles. <br />
|
suppression de vos données personnelles. <br />
|
||||||
|
|
||||||
Pour exercer ces droits, vous pouvez vous rendre dans votre profil et selectionner l'option "Supprimer mon
|
Pour exercer ces droits, vous pouvez vous rendre dans votre profil et selectionner l'option "Supprimer mon
|
||||||
compte" qui garanti une suppression de l'intégralité de vos données personnelles.
|
compte" qui garanti une suppression de l'intégralité de vos données personnelles.<br /><br />
|
||||||
|
|
||||||
<ProseH4>Utilisation des Cookies</ProseH4>
|
<h4 class="text-lg font-semibold">Utilisation des Cookies</h4>
|
||||||
Ce site utilise des cookies uniquement pour maintenir la connexion des utilisateurs et faciliter leur navigation
|
Ce site utilise des cookies uniquement pour maintenir la connexion des utilisateurs et faciliter leur navigation
|
||||||
lors de chaque visite. Aucune information de suivi ou de profilage n'est réalisée. Ces cookies sont essentiels
|
lors de chaque visite. Aucune information de suivi ou de profilage n'est réalisée. Ces cookies sont essentiels
|
||||||
au fonctionnement du site et ne nécessitent pas de consentement préalable. <br />
|
au fonctionnement du site et ne nécessitent pas de consentement préalable. <br />
|
||||||
|
|
||||||
Vous pouvez gérer les cookies en configurant les paramètres de votre navigateur, mais la désactivation de ces
|
Vous pouvez gérer les cookies en configurant les paramètres de votre navigateur, mais la désactivation de ces
|
||||||
cookies pourrait affecter votre expérience de navigation.
|
cookies pourrait affecter votre expérience de navigation.<br /><br />
|
||||||
|
|
||||||
<ProseH4>Limitation de Responsabilité</ProseH4>
|
<h4 class="text-lg font-semibold">Limitation de Responsabilité</h4>
|
||||||
Les informations publiées sur ce site sont fournies à titre indicatif et peuvent contenir des erreurs. <br />
|
Les informations publiées sur ce site sont fournies à titre indicatif et peuvent contenir des erreurs. <br />
|
||||||
L'éditeur décline toute responsabilité quant à l'usage qui pourrait être fait de ces informations.
|
L'éditeur décline toute responsabilité quant à l'usage qui pourrait être fait de ces informations.<br /><br />
|
||||||
|
|
||||||
<ProseH4>Propriété Intellectuelle</ProseH4>
|
<h4 class="text-lg font-semibold">Propriété Intellectuelle</h4>
|
||||||
Tous les contenus présents sur ce site (textes, images, logos, etc.) sont protégés par les lois en vigueur
|
Tous les contenus présents sur ce site (textes, images, logos, etc.) sont protégés par les lois en vigueur
|
||||||
sur la propriété intellectuelle. Toute reproduction ou utilisation de ces contenus sans autorisation préalable
|
sur la propriété intellectuelle. Toute reproduction ou utilisation de ces contenus sans autorisation préalable
|
||||||
est interdite. <br /><br />
|
est interdite. <br /><br />
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
<template>
|
|
||||||
<Head>
|
|
||||||
<Title>d[any] - Roadmap</Title>
|
|
||||||
</Head>
|
|
||||||
<div class="flex flex-col justify-start p-6">
|
|
||||||
<ProseH2>Roadmap</ProseH2>
|
|
||||||
<div class="grid grid-cols-4 gap-x-2 gap-y-4">
|
|
||||||
<div v-if="loggedIn && user && hasPermissions(user.permissions, ['admin'])" class="flex flex-col gap-2 justify-start">
|
|
||||||
<ProseH3>Administration</ProseH3>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" checked disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class="text-light-60 dark:text-dark-60 line-through">Dashboard de statistiques</span></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" checked disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class="text-light-60 dark:text-dark-60 line-through">Editeur de permissions</span><ProseTag>prioritaire</ProseTag></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Synchro project <-> GIT</span><ProseTag>prioritaire</ProseTag></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Versionning automatisé, releases et newsletter</span></Label>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 justify-start">
|
|
||||||
<ProseH3>Editeur</ProseH3>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" checked disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class="text-light-60 dark:text-dark-60 line-through">Edition de page</span></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" checked disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class="text-light-60 dark:text-dark-60 line-through">Edition riche de page</span></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Edition live de page</span></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" checked disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class="text-light-60 dark:text-dark-60 line-through">Raccourcis d'edition</span></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Affichage alternatif par page</span></Label>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 justify-start">
|
|
||||||
<ProseH3>Projet</ProseH3>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" checked disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class="text-light-60 dark:text-dark-60 line-through">Edition du projet</span></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" checked disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class="text-light-60 dark:text-dark-60 line-through">Déplacement des fichiers</span></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Configuration de droit du projet</span><ProseTag>prioritaire</ProseTag></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Theme par projet</span></Label>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 justify-start">
|
|
||||||
<ProseH3>Nouvelles features</ProseH3>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Historique des modifs</span><ProseTag>prioritaire</ProseTag></Label><!-- Objet release: key hash, timestamp, version, name, description?. Objet edit: key hash, key property, value, timestamp -->
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Commentaire par page</span></Label><!-- Object comment: key path, key comment_id, position, content, owner, following? -->
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Type de fichier: Timeline</span></Label><!-- Propriétés: array of (from, (to || ponctual), ((title, content) || dedicated page)) -->
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Type de fichier: Whiteboard</span></Label><!-- Tableau de données SVG -->
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 justify-start">
|
|
||||||
<ProseH3>Utilisateur</ProseH3>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" checked disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class="text-light-60 dark:text-dark-60 line-through">Validation du compte par mail<ProseTag>prioritaire</ProseTag></span></Label>
|
|
||||||
<!-- <Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Modification de profil</span></Label> -->
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Image de profil</span></Label>
|
|
||||||
<Label class="flex flex-row gap-2 items-center"><CheckboxRoot class="border border-light-35 dark:border-dark-35 w-6 h-6 flex justify-center items-center" disabled><CheckboxIndicator><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span class=" ">Préférence d'email</span></Label><!-- New features, newsletter et surveys -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
|
||||||
import { hasPermissions } from '~/shared/auth.util';
|
|
||||||
|
|
||||||
const { loggedIn, user } = useUserSession();
|
|
||||||
</script>
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
<template>
|
||||||
|
<Head>
|
||||||
|
<Title>d[any] - Mentions légales</Title>
|
||||||
|
</Head>
|
||||||
|
<div class="flex flex-col max-w-[1200px] p-16">
|
||||||
|
<h3 class="text-xl font-bold">Conditions Générales d'Utilisation du site d-any.com</h3>
|
||||||
|
<h4 class="text-lg font-semibold py-2">1. Objet</h4>
|
||||||
|
Le site d-any.com offre un service en ligne dédié au jeu de rôle comprenant une section de règles officielles maintenues par l'administrateur, une section permettant la création de personnages
|
||||||
|
publics ou privés et une section de campagnes visant à rassembler plusieurs joueurs pour faire interagir leurs personnages. L'utilisation du site implique l'acceptation pleine et entière des présentes conditions. <br/><br/>
|
||||||
|
|
||||||
|
<h4 class="text-lg font-semibold py-2">2. Accès et fonctionnement</h4>
|
||||||
|
L'accès au site est gratuit. L'interaction entre utilisateurs est strictement limitée aux personnages et joueurs participant à une même campagne partagée. Aucun contact direct ni interaction n'est possible en dehors de cette structure.<br/><br/>
|
||||||
|
|
||||||
|
<h4 class="text-lg font-semibold py-2">3. Création et gestion des personnages</h4>
|
||||||
|
Les utilisateurs peuvent créer des personnages publics, visibles par tous les membres des campagnes partagées, ou privés, visibles uniquement par leur créateur.
|
||||||
|
Les utilisateurs sont responsables du contenu des personnages qu'ils créent. Ils s'engagent à ne pas créer ou publier des personnages portant atteinte à la dignité, contenant des propos discriminatoires, diffamatoires, obscènes ou illicites.
|
||||||
|
L'administrateur du site se réserve le droit de supprimer ou masquer tout personnage en infraction avec ces règles.<br/><br/>
|
||||||
|
|
||||||
|
<h4 class="text-lg font-semibold py-2">4. Règles du jeu</h4>
|
||||||
|
Les règles officielles du jeu, rédigées et entretenues par l'administrateur, doivent être respectées par tous les utilisateurs dans la création et le déroulement des campagnes.<br/><br/>
|
||||||
|
|
||||||
|
<h4 class="text-lg font-semibold py-2">5. Interaction en campagne</h4>
|
||||||
|
Les communications et interactions entre joueurs et personnages sont strictement limitées aux campagnes partagées.
|
||||||
|
Toute interaction dans ces cadres doit respecter les règles de respect, de courtoisie et de fair-play.
|
||||||
|
Tout comportement abusif, harcèlement, propos haineux ou toute forme de contenu illicite est prohibé et pourra entraîner des sanctions, incluant la suppression de comptes ou personnages.<br/><br/>
|
||||||
|
|
||||||
|
<h4 class="text-lg font-semibold py-2">6. Propriété intellectuelle</h4>
|
||||||
|
Les règles, outils, et contenus hébergés sur le site sont la propriété de l'administrateur ou des auteurs respectifs.
|
||||||
|
Les personnages créés appartiennent à leurs auteurs, sous réserve du respect des droits d'auteur liés au jeu original et de la charte du site.<br/><br/>
|
||||||
|
|
||||||
|
<h4 class="text-lg font-semibold py-2">7. Données personnelles</h4>
|
||||||
|
Les données collectées se limitent à celles nécessaires au fonctionnement du site. Toute donnée personnelle est traitée conformément à la réglementation en vigueur et peut être modifiée ou supprimée sur demande.<br/><br/>
|
||||||
|
|
||||||
|
<h4 class="text-lg font-semibold py-2">8. Responsabilité</h4>
|
||||||
|
L'administrateur ne pourra être tenu responsable des usages faits par les utilisateurs des personnages publics ou des interactions au sein des campagnes. L'éditeur décline toute responsabilité en cas d'abus
|
||||||
|
entre joueurs ou de contenu illégal diffusé par un utilisateur.<br/><br/>
|
||||||
|
|
||||||
|
<h4 class="text-lg font-semibold py-2">9. Modification des conditions</h4>
|
||||||
|
Ces conditions peuvent être modifiées à tout moment par l'administrateur. Les utilisateurs seront informés des modifications via le site et l'usage continu vaudra acceptation des nouvelles conditions.<br/><br/>
|
||||||
|
|
||||||
|
<h4 class="text-lg font-semibold py-2">10. Droit applicable</h4>
|
||||||
|
Les présentes conditions sont soumises au droit français. Tout litige sera porté devant les tribunaux compétents.<br/><br/>
|
||||||
|
<div class="py-32"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
<span class="ps-4 flex items-center gap-2" :class="{'text-light-red dark:text-dark-red': !checkedSymbol}"><Icon v-show="!checkedSymbol" icon="radix-icons:cross-2" />Un caractère special</span>
|
<span class="ps-4 flex items-center gap-2" :class="{'text-light-red dark:text-dark-red': !checkedSymbol}"><Icon v-show="!checkedSymbol" icon="radix-icons:cross-2" />Un caractère special</span>
|
||||||
</div>
|
</div>
|
||||||
<TextInput type="password" label="Confirmation du mot de passe" autocomplete="new-password" v-model="confirmPassword" class="w-full md:w-auto"/>
|
<TextInput type="password" label="Confirmation du mot de passe" autocomplete="new-password" v-model="confirmPassword" class="w-full md:w-auto"/>
|
||||||
|
<Label class="pb-2 col-span-2 md:col-span-1 flex flex-row gap-2 items-center"><CheckboxRoot v-model:checked="agreeOnRules" class="border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 w-5 h-5" ><CheckboxIndicator ><Icon icon="radix-icons:check" /></CheckboxIndicator></CheckboxRoot><span>J'ai lu et j'accepte les <NuxtLink class="text-accent-blue cursor-pointer" :to="{ name: 'usage' }" target="_blank">conditions d'utilisation</NuxtLink></span></Label>
|
||||||
<Button type="submit" class="border border-light-35 dark:border-dark-35 max-w-48 w-full order-9 col-span-2 md:col-span-1 m-auto" :loading="status === 'pending'">S'inscrire</Button>
|
<Button type="submit" class="border border-light-35 dark:border-dark-35 max-w-48 w-full order-9 col-span-2 md:col-span-1 m-auto" :loading="status === 'pending'">S'inscrire</Button>
|
||||||
<span class="mt-4 order-10 flex justify-center items-center gap-4 col-span-2 md:col-span-1 m-auto">Vous avez déjà un compte ?<NuxtLink class="text-center block text-sm font-semibold tracking-wide hover:text-accent-blue" :to="{ name: 'user-login' }">Se connecter</NuxtLink></span>
|
<span class="mt-4 order-10 flex justify-center items-center gap-4 col-span-2 md:col-span-1 m-auto">Vous avez déjà un compte ?<NuxtLink class="text-center block text-sm font-semibold tracking-wide hover:text-accent-blue" :to="{ name: 'user-login' }">Se connecter</NuxtLink></span>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -30,7 +31,7 @@
|
||||||
import { ZodError } from 'zod/v4';
|
import { ZodError } from 'zod/v4';
|
||||||
import { schema, type Registration } from '~/schemas/registration';
|
import { schema, type Registration } from '~/schemas/registration';
|
||||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||||
import { Toaster } from '#shared/components.util';
|
import { floater, Toaster } from '#shared/components.util';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'login',
|
layout: 'login',
|
||||||
|
|
@ -50,6 +51,7 @@ const checkedLower = computed(() => state.password.toUpperCase() !== state.passw
|
||||||
const checkedUpper = computed(() => state.password.toLowerCase() !== state.password);
|
const checkedUpper = computed(() => state.password.toLowerCase() !== state.password);
|
||||||
const checkedDigit = computed(() => /[0-9]/.test(state.password));
|
const checkedDigit = computed(() => /[0-9]/.test(state.password));
|
||||||
const checkedSymbol = computed(() => " !\"#$%&'()*+,-./:;<=>?@[]^_`{|}~".split("").some(e => state.password.includes(e)));
|
const checkedSymbol = computed(() => " !\"#$%&'()*+,-./:;<=>?@[]^_`{|}~".split("").some(e => state.password.includes(e)));
|
||||||
|
const agreeOnRules = ref<boolean>(false);
|
||||||
|
|
||||||
const { data: result, status, error, refresh } = await useFetch('/api/auth/register', {
|
const { data: result, status, error, refresh } = await useFetch('/api/auth/register', {
|
||||||
body: state,
|
body: state,
|
||||||
|
|
@ -57,7 +59,7 @@ const { data: result, status, error, refresh } = await useFetch('/api/auth/regis
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
watch: false,
|
watch: false,
|
||||||
ignoreResponseError: true,
|
ignoreResponseError: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
async function submit()
|
async function submit()
|
||||||
{
|
{
|
||||||
|
|
@ -69,6 +71,8 @@ async function submit()
|
||||||
return Toaster.add({ content: 'Veuillez saisir un mot de passe', timer: true, duration: 10000 });
|
return Toaster.add({ content: 'Veuillez saisir un mot de passe', timer: true, duration: 10000 });
|
||||||
if(state.password !== confirmPassword.value)
|
if(state.password !== confirmPassword.value)
|
||||||
return Toaster.add({ content: 'Les deux mots de passe saisis ne correspondent pas', timer: true, duration: 10000 });
|
return Toaster.add({ content: 'Les deux mots de passe saisis ne correspondent pas', timer: true, duration: 10000 });
|
||||||
|
if(agreeOnRules.value !== true)
|
||||||
|
return Toaster.add({ content: 'Veuillez accepter des conditions d\'utilisations pour vous inscrire', timer: true, duration: 10000 });
|
||||||
|
|
||||||
const data = schema.safeParse(state);
|
const data = schema.safeParse(state);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import type { CanvasContent, CanvasEdge, CanvasNode } from "~/types/canvas";
|
import type { CanvasContent, CanvasEdge, CanvasNode } from "~/types/canvas";
|
||||||
import { clamp, lerp } from "#shared/general.util";
|
import { clamp, lerp } from "#shared/general.util";
|
||||||
import { dom, icon, svg, text } from "#shared/dom.util";
|
import { dom, icon, svg } from "#shared/dom.util";
|
||||||
import render from "#shared/markdown.util";
|
import render from "#shared/markdown.util";
|
||||||
import { popper, tooltip } from "#shared/floating.util";
|
import { tooltip } from "#shared/floating.util";
|
||||||
import { History } from "#shared/history.util";
|
import { History } from "#shared/history.util";
|
||||||
import { preview } from "#shared/proses";
|
import { preview } from "#shared/proses";
|
||||||
import { SpatialGrid } from "#shared/physics.util";
|
import { SpatialGrid } from "#shared/physics.util";
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -253,7 +253,15 @@ export class CharacterCompiler
|
||||||
{
|
{
|
||||||
protected _character!: Character;
|
protected _character!: Character;
|
||||||
protected _result!: CompiledCharacter;
|
protected _result!: CompiledCharacter;
|
||||||
protected _buffer: Record<string, PropertySum> = {};
|
protected _buffer: Record<string, PropertySum> = {
|
||||||
|
'modifier/strength': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/dexterity': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/constitution': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/intelligence': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/curiosity': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/charisma': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/psyche': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
};
|
||||||
private _variableDirty: boolean = false;
|
private _variableDirty: boolean = false;
|
||||||
|
|
||||||
constructor(character: Character)
|
constructor(character: Character)
|
||||||
|
|
@ -265,7 +273,15 @@ export class CharacterCompiler
|
||||||
{
|
{
|
||||||
this._character = value;
|
this._character = value;
|
||||||
this._result = defaultCompiledCharacter(value);
|
this._result = defaultCompiledCharacter(value);
|
||||||
this._buffer = {};
|
this._buffer = {
|
||||||
|
'modifier/strength': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/dexterity': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/constitution': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/intelligence': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/curiosity': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/charisma': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
'modifier/psyche': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
|
};
|
||||||
|
|
||||||
if(value.people !== undefined)
|
if(value.people !== undefined)
|
||||||
{
|
{
|
||||||
|
|
@ -352,8 +368,8 @@ export class CharacterCompiler
|
||||||
case "list":
|
case "list":
|
||||||
if(feature.action === 'add' && !this._result.lists[feature.list]!.includes(feature.item))
|
if(feature.action === 'add' && !this._result.lists[feature.list]!.includes(feature.item))
|
||||||
this._result.lists[feature.list]!.push(feature.item);
|
this._result.lists[feature.list]!.push(feature.item);
|
||||||
else
|
else if(feature.action === 'remove')
|
||||||
this._result.lists[feature.list] = this._result.lists[feature.list]!.filter((e: string) => e !== feature.item);
|
this._result.lists[feature.list] = this._result.lists[feature.list]!.splice(this._result.lists[feature.list]!.findIndex((e: string) => e !== feature.item), 1);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case "value":
|
case "value":
|
||||||
|
|
@ -364,6 +380,9 @@ export class CharacterCompiler
|
||||||
this._buffer[feature.property]!.min = -Infinity;
|
this._buffer[feature.property]!.min = -Infinity;
|
||||||
this._buffer[feature.property]!._dirty = true;
|
this._buffer[feature.property]!._dirty = true;
|
||||||
|
|
||||||
|
if(feature.property.startsWith('modifier/'))
|
||||||
|
Object.values(this._buffer).forEach(e => e._dirty = e.list.some(f => f.value === feature.property) ? true : e._dirty);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case "choice":
|
case "choice":
|
||||||
const choice = this._character.choices[feature.id];
|
const choice = this._character.choices[feature.id];
|
||||||
|
|
@ -386,8 +405,8 @@ export class CharacterCompiler
|
||||||
case "list":
|
case "list":
|
||||||
if(feature.action === 'remove' && !this._result.lists[feature.list]!.includes(feature.item))
|
if(feature.action === 'remove' && !this._result.lists[feature.list]!.includes(feature.item))
|
||||||
this._result.lists[feature.list]!.push(feature.item);
|
this._result.lists[feature.list]!.push(feature.item);
|
||||||
else
|
else if(feature.action === 'add')
|
||||||
this._result.lists[feature.list] = this._result.lists[feature.list]!.filter(e => e !== feature.item);
|
this._result.lists[feature.list] = this._result.lists[feature.list]!.splice(this._result.lists[feature.list]!.findIndex((e: string) => e !== feature.item), 1)
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case "value":
|
case "value":
|
||||||
|
|
@ -398,6 +417,9 @@ export class CharacterCompiler
|
||||||
this._buffer[feature.property]!.min = -Infinity;
|
this._buffer[feature.property]!.min = -Infinity;
|
||||||
this._buffer[feature.property]!._dirty = true;
|
this._buffer[feature.property]!._dirty = true;
|
||||||
|
|
||||||
|
if(feature.property.startsWith('modifier/'))
|
||||||
|
Object.values(this._buffer).forEach(e => e._dirty = e.list.some(f => f.value === feature.property) ? true : e._dirty);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case "choice":
|
case "choice":
|
||||||
const choice = this._character.choices[feature.id];
|
const choice = this._character.choices[feature.id];
|
||||||
|
|
@ -410,60 +432,53 @@ export class CharacterCompiler
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected compile(properties: string[])
|
protected compile(queue: string[])
|
||||||
{
|
{
|
||||||
const queue = properties;
|
|
||||||
for(let i = 0; i < queue.length; i++)
|
for(let i = 0; i < queue.length; i++)
|
||||||
{
|
{
|
||||||
|
if(queue[i] === undefined || queue[i] === "") continue;
|
||||||
|
|
||||||
const property = queue[i]!;
|
const property = queue[i]!;
|
||||||
const buffer = this._buffer[property];
|
const buffer = this._buffer[property];
|
||||||
|
|
||||||
if(property === "")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if(buffer && buffer._dirty === true)
|
if(buffer && buffer._dirty === true)
|
||||||
{
|
{
|
||||||
let sum = 0, shortcut = false;
|
let sum = 0, shortcut = false;
|
||||||
for(let j = 0; j < buffer.list.length; j++)
|
for(let j = 0; j < buffer.list.length; j++)
|
||||||
{
|
{
|
||||||
if(typeof buffer.list[j]!.value === 'string') // Add or set a modifier
|
const item = buffer.list[j];
|
||||||
{
|
if(!item)
|
||||||
const modifier = this._buffer[buffer.list[j]!.value as string];
|
continue;
|
||||||
if(!modifier)
|
|
||||||
{
|
|
||||||
if(!queue.includes(buffer.list[j]!.value as string))
|
|
||||||
this._buffer[buffer.list[j]!.value as string] = { _dirty: false, list: [], min: -Infinity, value: 0 };
|
|
||||||
|
|
||||||
queue.push(property);
|
if(typeof item.value === 'string') // Add or set a modifier
|
||||||
shortcut = true;
|
{
|
||||||
break;
|
const modifier = this._buffer[item.value as string]!;
|
||||||
}
|
if(modifier._dirty)
|
||||||
else if(modifier._dirty)
|
|
||||||
{
|
{
|
||||||
//Put it back in queue since its dependencies haven't been resolved yet
|
//Put it back in queue since its dependencies haven't been resolved yet
|
||||||
queue.push(buffer.list[j]!.value as string);
|
queue.push(item.value as string);
|
||||||
queue.push(property);
|
queue.push(property);
|
||||||
shortcut = true;
|
shortcut = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(buffer.list[j]?.operation === 'add')
|
if(item.operation === 'add')
|
||||||
sum += modifier.value;
|
sum += modifier.value;
|
||||||
else if(buffer.list[j]?.operation === 'set')
|
else if(item.operation === 'set')
|
||||||
sum = modifier.value;
|
sum = modifier.value;
|
||||||
else if(buffer.list[j]?.operation === 'min')
|
else if(item.operation === 'min')
|
||||||
this._buffer[property]!.min = modifier.value;
|
buffer.min = modifier.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(buffer.list[j]?.operation === 'add')
|
if(item.operation === 'add')
|
||||||
sum += buffer.list[j]!.value as number;
|
sum += item.value as number;
|
||||||
else if(buffer.list[j]?.operation === 'set')
|
else if(item.operation === 'set')
|
||||||
sum = buffer.list[j]!.value as number;
|
sum = item.value as number;
|
||||||
else if(buffer.list[j]?.operation === 'min')
|
else if(item.operation === 'min')
|
||||||
this._buffer[property]!.min = buffer.list[j]!.value as number;
|
buffer.min = item.value as number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -573,7 +588,7 @@ export class CharacterBuilder extends CharacterCompiler
|
||||||
}
|
}
|
||||||
async save(leave: boolean = true)
|
async save(leave: boolean = true)
|
||||||
{
|
{
|
||||||
if(this.id === 'new')
|
if(this.id === 'new' || this.id === '-1')
|
||||||
{
|
{
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
this.id = this._character.id = this._result.id = await useRequestFetch()(`/api/character`, {
|
this.id = this._character.id = this._result.id = await useRequestFetch()(`/api/character`, {
|
||||||
|
|
@ -1052,8 +1067,6 @@ class AbilityPicker extends BuilderTab
|
||||||
const values = builder.values, compiled = builder.compiled;
|
const values = builder.values, compiled = builder.compiled;
|
||||||
const abilities = Object.values(builder.character.abilities).reduce((p, v) => p + v, 0);
|
const abilities = Object.values(builder.character.abilities).reduce((p, v) => p + v, 0);
|
||||||
|
|
||||||
console.log(ABILITIES.map(e => (values[`bonus/abilities/${e}`] ?? 0) >= (compiled.abilities[e] ?? 0)));
|
|
||||||
|
|
||||||
return ABILITIES.map(e => (values[`bonus/abilities/${e}`] ?? 0) >= (compiled.abilities[e] ?? 0)).every(e => e) && (values.ability ?? 0) - abilities >= 0;
|
return ABILITIES.map(e => (values[`bonus/abilities/${e}`] ?? 0) >= (compiled.abilities[e] ?? 0)).every(e => e) && (values.ability ?? 0) - abilities >= 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1194,7 +1207,8 @@ export class CharacterSheet
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}).catch(() => {
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
this.container.replaceChildren(div('flex flex-col items-center justify-center flex-1 h-full gap-4', [
|
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('text-2xl font-bold tracking-wider', 'Personnage introuvable'),
|
||||||
span(undefined, 'Ce personnage n\'existe pas ou est privé.'),
|
span(undefined, 'Ce personnage n\'existe pas ou est privé.'),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import type { RouteLocationAsRelativeTyped, RouteMapGeneric } from "vue-router";
|
import type { RouteLocationAsRelativeTyped, RouteMapGeneric } from "vue-router";
|
||||||
import { type NodeProperties, type Class, type NodeChildren, dom, mergeClasses, text, div, icon, type Node } from "./dom.util";
|
import { type NodeProperties, type Class, type NodeChildren, dom, mergeClasses, text, div, icon, type Node } from "./dom.util";
|
||||||
import { contextmenu, followermenu } from "./floating.util";
|
import { contextmenu, followermenu, popper, tooltip } from "./floating.util";
|
||||||
import { clamp } from "./general.util";
|
import { clamp } from "./general.util";
|
||||||
import { Tree } from "./tree";
|
import { Tree } from "./tree";
|
||||||
|
import type { Placement } from "@floating-ui/dom";
|
||||||
|
|
||||||
export function link(properties?: NodeProperties & { active?: Class }, link?: RouteLocationAsRelativeTyped<RouteMapGeneric, string>, children?: NodeChildren)
|
export function link(properties?: NodeProperties & { active?: Class }, link?: RouteLocationAsRelativeTyped<RouteMapGeneric, string>, children?: NodeChildren)
|
||||||
{
|
{
|
||||||
|
|
@ -20,20 +21,21 @@ export function loading(size: 'small' | 'normal' | 'large' = 'normal'): HTMLElem
|
||||||
{
|
{
|
||||||
return dom('span', { class: ["after:block after:relative after:rounded-full after:border-transparent after:border-t-accent-purple after:animate-spin", {'w-6 h-6 border-4 border-transparent after:-top-[4px] after:-left-[4px] after:w-6 after:h-6 after:border-4': size === 'normal', 'w-4 h-4 after:-top-[2px] after:-left-[2px] after:w-4 after:h-4 after:border-2': size === 'small', 'w-12 h-12 after:-top-[6px] after:-left-[6px] after:w-12 after:h-12 after:border-[6px]': size === 'large'}] })
|
return dom('span', { class: ["after:block after:relative after:rounded-full after:border-transparent after:border-t-accent-purple after:animate-spin", {'w-6 h-6 border-4 border-transparent after:-top-[4px] after:-left-[4px] after:w-6 after:h-6 after:border-4': size === 'normal', 'w-4 h-4 after:-top-[2px] after:-left-[2px] after:w-4 after:h-4 after:border-2': size === 'small', 'w-12 h-12 after:-top-[6px] after:-left-[6px] after:w-12 after:h-12 after:border-[6px]': size === 'large'}] })
|
||||||
}
|
}
|
||||||
export function async(size: 'small' | 'normal' | 'large' = 'normal', fn: Promise<HTMLElement>): HTMLElement
|
export function async(size: 'small' | 'normal' | 'large' = 'normal', fn: Promise<HTMLElement>)
|
||||||
{
|
{
|
||||||
const load = loading(size);
|
let state = { current: loading(size) };
|
||||||
|
|
||||||
fn.then((element) => {
|
fn.then((element) => {
|
||||||
load.replaceWith(element);
|
state.current.replaceWith(element);
|
||||||
|
state.current = element;
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
load.remove();
|
state.current.remove();
|
||||||
})
|
})
|
||||||
|
|
||||||
return load;
|
return state;
|
||||||
}
|
}
|
||||||
export function button(content: Node, onClick?: () => void, cls?: Class)
|
export function button(content: Node, onClick?: (this: HTMLElement) => void, cls?: Class)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
text-light-100 dark:text-dark-100 font-semibold hover:bg-light-30 dark:hover:bg-dark-30 inline-flex items-center justify-center bg-light-25 dark:bg-dark-25 leading-none outline-none
|
text-light-100 dark:text-dark-100 font-semibold hover:bg-light-30 dark:hover:bg-dark-30 inline-flex items-center justify-center bg-light-25 dark:bg-dark-25 leading-none outline-none
|
||||||
|
|
@ -44,7 +46,7 @@ export function button(content: Node, onClick?: () => void, cls?: Class)
|
||||||
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
|
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
|
||||||
disabled:text-light-50 dark:disabled:text-dark-50 disabled:bg-light-10 dark:disabled:bg-dark-10 disabled:border-dashed disabled:border-light-40 dark:disabled:border-dark-40`, cls], listeners: { click: () => disabled || (onClick && onClick()) } }, [ content ]);
|
disabled:text-light-50 dark:disabled:text-dark-50 disabled:bg-light-10 dark:disabled:bg-dark-10 disabled:border-dashed disabled:border-light-40 dark:disabled:border-dark-40`, cls], listeners: { click: () => disabled || (onClick && onClick.bind(btn)()) } }, [ content ]);
|
||||||
let disabled = false;
|
let disabled = false;
|
||||||
Object.defineProperty(btn, 'disabled', {
|
Object.defineProperty(btn, 'disabled', {
|
||||||
get: () => disabled,
|
get: () => disabled,
|
||||||
|
|
@ -75,6 +77,14 @@ export function buttongroup<T extends any>(options: Array<{ text: string, value:
|
||||||
}}}))
|
}}}))
|
||||||
return div(['flex flex-row', settings?.class?.container], elements);
|
return div(['flex flex-row', settings?.class?.container], elements);
|
||||||
}
|
}
|
||||||
|
export function optionmenu(options: Array<{ title: string, click: () => void }>, settings?: { position?: Placement, class?: { container?: Class, option?: Class } }): (target?: HTMLElement) => void
|
||||||
|
{
|
||||||
|
let close: () => void;
|
||||||
|
const element = div(['flex flex-col divide-y divide-light-30 dark:divide-dark-30 text-light-100 dark:text-dark-100', settings?.class?.container], options.map(e => dom('div', { class: ['flex flex-row px-2 py-1 hover:bg-light-35 dark:hover:bg-dark-35 cursor-pointer', settings?.class?.option], text: e.title, listeners: { click: () => { e.click(); close() } } })));
|
||||||
|
return function(this: HTMLElement, target?: HTMLElement) {
|
||||||
|
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, render?: () => HTMLElement, value: T | Option<T>[] } | undefined;
|
||||||
type StoredOption<T> = { item: Option<T>, dom: HTMLElement, container?: HTMLElement, children?: Array<StoredOption<T>> };
|
type StoredOption<T> = { item: Option<T>, dom: HTMLElement, container?: HTMLElement, children?: Array<StoredOption<T>> };
|
||||||
export function select<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLElement
|
export function select<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLElement
|
||||||
|
|
@ -427,8 +437,7 @@ export function foldable(content: NodeChildren | (() => NodeChildren), title: No
|
||||||
if(state && !_content)
|
if(state && !_content)
|
||||||
{
|
{
|
||||||
_content = typeof content === 'function' ? content() : content;
|
_content = typeof content === 'function' ? content() : content;
|
||||||
//@ts-ignore
|
_content && contentContainer.replaceChildren(..._content.filter(e => !!e));
|
||||||
contentContainer.replaceChildren(..._content);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const contentContainer = div(['hidden group-data-[active]:flex', settings?.class?.content]);
|
const contentContainer = div(['hidden group-data-[active]:flex', settings?.class?.content]);
|
||||||
|
|
@ -472,8 +481,7 @@ export function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content:
|
||||||
this.toggleAttribute('data-focus', true);
|
this.toggleAttribute('data-focus', true);
|
||||||
focus = e.id;
|
focus = e.id;
|
||||||
const _content = typeof e.content === 'function' ? e.content() : e.content;
|
const _content = typeof e.content === 'function' ? e.content() : e.content;
|
||||||
//@ts-expect-error
|
_content && content.replaceChildren(..._content?.filter(e => !!e));
|
||||||
content.replaceChildren(..._content);
|
|
||||||
}}}, e.title));
|
}}}, e.title));
|
||||||
const _content = tabs.find(e => e.id === focus)?.content;
|
const _content = tabs.find(e => e.id === focus)?.content;
|
||||||
const content = div(['', settings?.class?.content], typeof _content === 'function' ? _content() : _content);
|
const content = div(['', settings?.class?.content], typeof _content === 'function' ? _content() : _content);
|
||||||
|
|
@ -487,13 +495,61 @@ export function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content:
|
||||||
configurable: false,
|
configurable: false,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
value: () => {
|
value: () => {
|
||||||
const _content = tabs.find(e => e.id === focus)?.content;
|
let _content = tabs.find(e => e.id === focus)?.content;
|
||||||
//@ts-expect-error
|
_content = (typeof _content === 'function' ? _content() : _content);
|
||||||
_content && content.replaceChildren(...(typeof _content === 'function' ? _content() : _content))
|
_content && content.replaceChildren(..._content.filter(e => !!e));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return container as HTMLDivElement & { refresh: () => void };
|
return container as HTMLDivElement & { refresh: () => void };
|
||||||
}
|
}
|
||||||
|
export function floater(container: HTMLElement, content: NodeChildren | (() => NodeChildren), settings?: { class?: Class, position?: Placement, pinned?: boolean, cover?: 'width' | 'height' | 'all' | 'none' })
|
||||||
|
{
|
||||||
|
let viewport = document.getElementById('mainContainer') ?? undefined;
|
||||||
|
|
||||||
|
const dragstart = (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
window.addEventListener('mousemove', dragmove);
|
||||||
|
window.addEventListener('mouseup', dragend);
|
||||||
|
};
|
||||||
|
const dragmove = (e: MouseEvent) => {
|
||||||
|
const box = floating.content.getBoundingClientRect();
|
||||||
|
const viewbox = viewport?.getBoundingClientRect();
|
||||||
|
box.x = clamp(box.x + e.movementX, viewbox?.left ?? 0, (viewbox?.right ?? Infinity) - box.width);
|
||||||
|
box.y = clamp(box.y + e.movementY, viewbox?.top ?? 0, (viewbox?.bottom ?? Infinity) - box.height);
|
||||||
|
|
||||||
|
Object.assign(floating.content.style, {
|
||||||
|
left: `${box.x}px`,
|
||||||
|
top: `${box.y}px`,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const dragend = (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
window.removeEventListener('mousemove', dragmove);
|
||||||
|
window.removeEventListener('mouseup', dragend);
|
||||||
|
};
|
||||||
|
|
||||||
|
const floating = popper(container, {
|
||||||
|
arrow: true,
|
||||||
|
delay: 150,
|
||||||
|
offset: 12,
|
||||||
|
cover: settings?.cover,
|
||||||
|
placement: settings?.position,
|
||||||
|
class: 'bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 group-data-[pinned]:bg-light-15 dark:group-data-[pinned]:bg-dark-15 group-data-[pinned]:border-light-50 dark:group-data-[pinned]:border-dark-50 text-light-100 dark:text-dark-100 z-[45]',
|
||||||
|
content: () => [ settings?.pinned !== undefined ? dom('div', { class: 'hidden group-data-[pinned]:flex flex-row gap-4 justify-end border-b border-light-35 dark:border-dark-35 cursor-move', listeners: { mousedown: dragstart } }, [ tooltip(icon('radix-icons:cross-1', { width: 12, height: 12, listeners: { click: (e) => { e.preventDefault(); floating.hide(); } }, class: 'p-1 cursor-pointer' }), 'Fermer', 'right') ]) : undefined, div('min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] w-full overflow-auto box-content', typeof content === 'function' ? content() : content) ],
|
||||||
|
viewport
|
||||||
|
});
|
||||||
|
|
||||||
|
if(settings?.pinned === false)
|
||||||
|
floating.content.addEventListener('click', () => {
|
||||||
|
floating.stop();
|
||||||
|
floating.content.addEventListener('mousedown', function() {
|
||||||
|
this.parentElement?.appendChild(this);
|
||||||
|
}, { passive: true, capture: true })
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ToastConfig
|
export interface ToastConfig
|
||||||
{
|
{
|
||||||
|
|
@ -508,13 +564,13 @@ type ToastDom = ToastConfig & { dom: HTMLElement };
|
||||||
export type ToastType = 'info' | 'success' | 'error';
|
export type ToastType = 'info' | 'success' | 'error';
|
||||||
export class Toaster
|
export class Toaster
|
||||||
{
|
{
|
||||||
private static _MAX_DRAG = 130;
|
private static _MAX_DRAG = 150;
|
||||||
private static _list: Array<ToastDom> = [];
|
private static _list: Array<ToastDom> = [];
|
||||||
private static _container: HTMLDivElement;
|
private static _container: HTMLDivElement;
|
||||||
|
|
||||||
static init()
|
static init()
|
||||||
{
|
{
|
||||||
Toaster._container = div('fixed bottom-0 right-0 flex flex-col p-6 gap-2 max-w-[512px] z-50 outline-none min-w-72');
|
Toaster._container = dom('div', { attributes: { id: 'toaster' }, class: 'fixed bottom-0 right-0 flex flex-col p-6 gap-2 max-w-[512px] z-50 outline-none min-w-72' });
|
||||||
document.body.appendChild(Toaster._container);
|
document.body.appendChild(Toaster._container);
|
||||||
}
|
}
|
||||||
static add(_config: ToastConfig)
|
static add(_config: ToastConfig)
|
||||||
|
|
|
||||||
|
|
@ -764,11 +764,11 @@ export class Editor
|
||||||
if (location.current.dropTargets.length === 0)
|
if (location.current.dropTargets.length === 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const target = location.current.dropTargets[0];
|
const target = location.current.dropTargets[0]!;
|
||||||
const instruction = extractInstruction(target.data);
|
const instruction = extractInstruction(target.data);
|
||||||
|
|
||||||
if (instruction !== null)
|
if (instruction !== null)
|
||||||
this.updateTree(instruction, location.initial.dropTargets[0].data.id as string, target.data.id as string);
|
this.updateTree(instruction, location.initial.dropTargets[0]!.data.id as string, target.data.id as string);
|
||||||
},
|
},
|
||||||
}), autoScrollForElements({
|
}), autoScrollForElements({
|
||||||
element: this.tree.container,
|
element: this.tree.container,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { iconExists, loadIcon } from 'iconify-icon';
|
import { iconExists, loadIcon } from 'iconify-icon';
|
||||||
|
|
||||||
export type Node = HTMLElement | SVGElement | Text | undefined;
|
export type Node = HTMLElement | SVGElement | Text | undefined;
|
||||||
export type NodeChildren = Array<Node>;
|
export type NodeChildren = Array<Node> | undefined;
|
||||||
|
|
||||||
export type Class = string | Array<Class> | Record<string, boolean> | undefined;
|
export type Class = string | Array<Class> | Record<string, boolean> | undefined;
|
||||||
type Listener<K extends keyof HTMLElementEventMap> = | ((this: HTMLElement, ev: HTMLElementEventMap[K]) => any) | {
|
type Listener<K extends keyof HTMLElementEventMap> = | ((this: HTMLElement, ev: HTMLElementEventMap[K]) => any) | {
|
||||||
|
|
@ -60,7 +60,7 @@ export function span(cls?: Class, text?: string): HTMLSpanElement
|
||||||
{
|
{
|
||||||
return dom("span", { class: cls, text: text });
|
return dom("span", { class: cls, text: text });
|
||||||
}
|
}
|
||||||
export function svg<K extends keyof SVGElementTagNameMap>(tag: K, properties?: NodeProperties, children?: Omit<NodeChildren, 'HTMLElement' | 'Text'>): SVGElementTagNameMap[K]
|
export function svg<K extends keyof SVGElementTagNameMap>(tag: K, properties?: NodeProperties, children?: SVGElement[]): SVGElementTagNameMap[K]
|
||||||
{
|
{
|
||||||
const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
||||||
|
|
||||||
|
|
@ -116,49 +116,63 @@ export interface IconProperties
|
||||||
rotate?: number|string;
|
rotate?: number|string;
|
||||||
style?: Record<string, string | undefined> | string;
|
style?: Record<string, string | undefined> | string;
|
||||||
class?: Class;
|
class?: Class;
|
||||||
|
listeners?: {
|
||||||
|
[K in keyof HTMLElementEventMap]?: Listener<K>
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const iconCache: Map<string, HTMLElement> = new Map();
|
const iconCache: Map<string, HTMLElement> = new Map();
|
||||||
export function icon(name: string, properties?: IconProperties): HTMLElement
|
export function icon(name: string, properties?: IconProperties): HTMLElement
|
||||||
{
|
{
|
||||||
let el;
|
let element;
|
||||||
|
|
||||||
if(iconCache.has(name))
|
if(iconCache.has(name))
|
||||||
el = iconCache.get(name)!.cloneNode() as HTMLElement;
|
element = iconCache.get(name)!.cloneNode() as HTMLElement;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
el = document.createElement('iconify-icon');
|
element = document.createElement('iconify-icon');
|
||||||
|
|
||||||
if(!iconExists(name))
|
if(!iconExists(name))
|
||||||
loadIcon(name);
|
loadIcon(name);
|
||||||
|
|
||||||
el.setAttribute('icon', name);
|
element.setAttribute('icon', name);
|
||||||
|
|
||||||
iconCache.set(name, el.cloneNode() as HTMLElement);
|
iconCache.set(name, element.cloneNode() as HTMLElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
properties?.mode && el.setAttribute('mode', properties?.mode.toString());
|
properties?.mode && element.setAttribute('mode', properties?.mode.toString());
|
||||||
properties?.inline && el.toggleAttribute('inline', properties?.inline);
|
properties?.inline && element.toggleAttribute('inline', properties?.inline);
|
||||||
el.toggleAttribute('noobserver', properties?.noobserver ?? true);
|
element.toggleAttribute('noobserver', properties?.noobserver ?? true);
|
||||||
properties?.width && el.setAttribute('width', properties?.width.toString());
|
properties?.width && element.setAttribute('width', properties?.width.toString());
|
||||||
properties?.height && el.setAttribute('height', properties?.height.toString());
|
properties?.height && element.setAttribute('height', properties?.height.toString());
|
||||||
properties?.flip && el.setAttribute('flip', properties?.flip.toString());
|
properties?.flip && element.setAttribute('flip', properties?.flip.toString());
|
||||||
properties?.rotate && el.setAttribute('rotate', properties?.rotate.toString());
|
properties?.rotate && element.setAttribute('rotate', properties?.rotate.toString());
|
||||||
|
|
||||||
if(properties?.class)
|
if(properties?.class)
|
||||||
{
|
{
|
||||||
el.setAttribute('class', mergeClasses(properties.class));
|
element.setAttribute('class', mergeClasses(properties.class));
|
||||||
}
|
}
|
||||||
if(properties?.style)
|
if(properties?.style)
|
||||||
{
|
{
|
||||||
if(typeof properties.style === 'string')
|
if(typeof properties.style === 'string')
|
||||||
{
|
{
|
||||||
el.setAttribute('style', properties.style);
|
element.setAttribute('style', properties.style);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
for(const [k, v] of Object.entries(properties.style)) if(v !== undefined) el.attributeStyleMap.set(k, v);
|
for(const [k, v] of Object.entries(properties.style)) if(v !== undefined) element.attributeStyleMap.set(k, v);
|
||||||
|
}
|
||||||
|
if(properties?.listeners)
|
||||||
|
{
|
||||||
|
for(let [k, v] of Object.entries(properties.listeners))
|
||||||
|
{
|
||||||
|
const key = k as keyof HTMLElementEventMap, value = v as Listener<typeof key>;
|
||||||
|
if(typeof value === 'function')
|
||||||
|
element.addEventListener(key, value.bind(element));
|
||||||
|
else if(value)
|
||||||
|
element.addEventListener(key, value.listener.bind(element), value.options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return el;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mergeClasses(classes: Class): string
|
export function mergeClasses(classes: Class): string
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,14 @@ import type { Ability, AspectConfig, CharacterConfig, Feature, FeatureChoice, Fe
|
||||||
import { div, dom, icon, span, text, type NodeChildren } from "#shared/dom.util";
|
import { div, dom, icon, span, text, type NodeChildren } from "#shared/dom.util";
|
||||||
import { MarkdownEditor } from "#shared/editor.util";
|
import { MarkdownEditor } from "#shared/editor.util";
|
||||||
import { preview } from "#shared/proses";
|
import { preview } from "#shared/proses";
|
||||||
import { button, combobox, foldable, input, multiselect, numberpicker, select, tabgroup, table, toggle, type Option } from "#shared/components.util";
|
import { button, combobox, foldable, input, multiselect, numberpicker, optionmenu, select, tabgroup, table, toggle, type Option } from "#shared/components.util";
|
||||||
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating.util";
|
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating.util";
|
||||||
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts } from "#shared/character.util";
|
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts } from "#shared/character.util";
|
||||||
import characterConfig from "#shared/character-config.json";
|
import characterConfig from "#shared/character-config.json";
|
||||||
import { getID } from "#shared/general.util";
|
import { getID } from "#shared/general.util";
|
||||||
import renderMarkdown, { renderText } from "#shared/markdown.util";
|
import markdown, { markdownReference, renderMDAsText } from "#shared/markdown.util";
|
||||||
import { Tree } from "#shared/tree";
|
import { Tree } from "#shared/tree";
|
||||||
import { getText } from "#shared/i18n";
|
import { getText } from "#shared/i18n";
|
||||||
import markdown from "#shared/markdown.util";
|
|
||||||
|
|
||||||
const config = characterConfig as CharacterConfig;
|
const config = characterConfig as CharacterConfig;
|
||||||
export class HomebrewBuilder
|
export class HomebrewBuilder
|
||||||
|
|
@ -33,7 +32,7 @@ export class HomebrewBuilder
|
||||||
{ id: 'spells', title: [ text("Sorts") ], content: () => this.spells() },
|
{ id: 'spells', title: [ text("Sorts") ], content: () => this.spells() },
|
||||||
{ id: 'aspects', title: [ text("Aspects") ], content: () => this.aspects() },
|
{ id: 'aspects', title: [ text("Aspects") ], content: () => this.aspects() },
|
||||||
{ id: 'actions', title: [ text("Actions") ], content: () => this.actions() },
|
{ id: 'actions', title: [ text("Actions") ], content: () => this.actions() },
|
||||||
], { focused: 'actions', class: { container: 'flex-1 outline-none max-w-full w-full overflow-y-auto', tabbar: 'flex w-full flex-row gap-4 items-center justify-center relative' } });
|
], { focused: 'training', class: { container: 'flex-1 outline-none max-w-full w-full overflow-y-auto', tabbar: 'flex w-full flex-row gap-4 items-center justify-center relative' } });
|
||||||
|
|
||||||
this._tabs.children[0]?.appendChild(tooltip(button(icon('radix-icons:clipboard'), () => this.save(), 'p-1'), 'Copier', 'bottom'))
|
this._tabs.children[0]?.appendChild(tooltip(button(icon('radix-icons:clipboard'), () => this.save(), 'p-1'), 'Copier', 'bottom'))
|
||||||
this._container.appendChild(div('flex flex-1 flex-col justify-start items-center px-8 w-full h-full overflow-y-hidden', [
|
this._container.appendChild(div('flex flex-1 flex-col justify-start items-center px-8 w-full h-full overflow-y-hidden', [
|
||||||
|
|
@ -232,6 +231,7 @@ export class HomebrewBuilder
|
||||||
elements: [],
|
elements: [],
|
||||||
effect: '',
|
effect: '',
|
||||||
concentration: false,
|
concentration: false,
|
||||||
|
range: 0,
|
||||||
tags: [],
|
tags: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -243,7 +243,7 @@ export class HomebrewBuilder
|
||||||
confirm('Voulez vous vraiment supprimer ce sort ?').then(e => {
|
confirm('Voulez vous vraiment supprimer ce sort ?').then(e => {
|
||||||
if(e)
|
if(e)
|
||||||
{
|
{
|
||||||
config.spells = config.spells.filter(e => e !== spell);
|
this._config.spells = this._config.spells.filter(e => e !== spell);
|
||||||
|
|
||||||
const element = redraw();
|
const element = redraw();
|
||||||
content.parentElement?.replaceChild(element, content);
|
content.parentElement?.replaceChild(element, content);
|
||||||
|
|
@ -259,41 +259,98 @@ export class HomebrewBuilder
|
||||||
{
|
{
|
||||||
let editing: { type: 'action' | 'reaction' | 'freeaction' | 'passive', id: string } | undefined;
|
let editing: { type: 'action' | 'reaction' | 'freeaction' | 'passive', id: string } | undefined;
|
||||||
const render = (type: 'action' | 'reaction' | 'freeaction' | 'passive', feature: { id: string, name: string, description: string, cost?: number }) => {
|
const render = (type: 'action' | 'reaction' | 'freeaction' | 'passive', feature: { id: string, name: string, description: string, cost?: number }) => {
|
||||||
return div('flex flex-col gap-1', [
|
const md = markdownReference(getText(feature.description), undefined, { tags: { a: preview }, class: 'ms-2 px-2 py-1 border-l-4 border-light-30 dark:border-dark-30' });
|
||||||
div('flex flex-row justify-between', [ input('text', { defaultValue: feature.name, input: value => feature.name = value, placeholder: 'Nom', class: '!mx-0 w-80' }), div('flex flex-row gap-2 items-center', [ type === 'action' || type === 'reaction' ? div('flex flex-row items-center', [ numberpicker({ defaultValue: feature?.cost ?? 0, input: value => feature.cost = value, class: '!mx-1' }), text(`point${(feature?.cost ?? 0) > 1 ? 's' : ''}`)]) : undefined, div('flex flex-row items-center gap-2', [ span('text-sm text-light-70 dark:text-dark-70', type), tooltip(button(icon('radix-icons:pencil-1'), () => {
|
const buttons = div('flex flex-row items-center gap-2', [ span('text-sm text-light-70 dark:text-dark-70', type), tooltip(button(icon('radix-icons:pencil-1'), () => edit(type, feature.id), 'p-1'), 'Modifier', 'left'), tooltip(button(icon('radix-icons:trash'), () => remove(type, feature.id), 'p-1'), 'Supprimer', 'right') ]);
|
||||||
|
return {
|
||||||
}, 'p-1'), 'Modifier', 'left'), tooltip(button(icon('radix-icons:trash'), () => remove(type, feature.id), 'p-1'), 'Supprimer', 'right') ]) ])]),
|
dom: div('flex flex-col gap-2', [
|
||||||
markdown(getText(feature.description), undefined, { tags: { a: preview }, class: 'px-2' }),
|
div('flex flex-row justify-between', [ input('text', { defaultValue: feature.name, input: value => feature.name = value, placeholder: 'Nom', class: '!mx-0 w-80' }), div('flex flex-row gap-2 items-center', [ type === 'action' || type === 'reaction' ? div('flex flex-row items-center', [ numberpicker({ defaultValue: feature?.cost ?? 0, input: value => feature.cost = value, class: '!mx-1', max: type === 'action' ? 3 : 2, min: 0 }), text(`point${(feature?.cost ?? 0) > 1 ? 's' : ''}`)]) : undefined, buttons ])]),
|
||||||
]);
|
md.current,
|
||||||
|
]),
|
||||||
|
buttons,
|
||||||
|
md,
|
||||||
|
type,
|
||||||
|
id: feature.id,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const add = (type: 'action' | 'reaction' | 'freeaction' | 'passive') => {
|
const add = (type: 'action' | 'reaction' | 'freeaction' | 'passive') => {
|
||||||
const feature: { id: string, name: string, description: string, cost?: number } = {
|
const feature: { id: string, name: string, description: string, cost?: number } = {
|
||||||
id: getID(),
|
id: getID(),
|
||||||
name: '',
|
name: '',
|
||||||
description: getID(),
|
description: getID(), // i18nID
|
||||||
cost: type === 'action' || type === 'reaction' ? 1 : undefined,
|
cost: type === 'action' || type === 'reaction' ? 1 : undefined,
|
||||||
}
|
}
|
||||||
|
this._config.texts[feature.description] = { 'fr_FR': '', default: '' };
|
||||||
this._config[type][feature.id] = feature;
|
this._config[type][feature.id] = feature;
|
||||||
|
|
||||||
const element = redraw();
|
const option = render(type, feature);
|
||||||
content.parentElement?.replaceChild(element, content);
|
options.push(option);
|
||||||
content = element;
|
optionHolder.appendChild(option.dom);
|
||||||
};
|
};
|
||||||
const remove = (type: 'action' | 'reaction' | 'freeaction' | 'passive', id: string) => {
|
const remove = (type: 'action' | 'reaction' | 'freeaction' | 'passive', id: string) => {
|
||||||
confirm('Voulez vous vraiment supprimer cet effet ?').then(e => {
|
const feature = this._config[type][id]!;
|
||||||
|
confirm(`Voulez vous vraiment supprimer l'effet "${feature.name}" ?`).then(e => {
|
||||||
if(e)
|
if(e)
|
||||||
{
|
{
|
||||||
|
delete this._config.texts[feature.description];
|
||||||
delete this._config[type][id];
|
delete this._config[type][id];
|
||||||
|
|
||||||
const element = redraw();
|
const idx = options.findIndex(e => e.type === type && e.id === id);
|
||||||
content.replaceWith(element);
|
options.splice(idx, 1)[0]?.dom.remove();
|
||||||
content = element;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const redraw = () => div('flex flex-col gap-4', [...Object.values(this._config.action).map(e => render('action', e)), ...Object.values(this._config.reaction).map(e => render('reaction', e)), ...Object.values(this._config.freeaction).map(e => render('freeaction', e)), ...Object.values(this._config.passive).map(e => render('passive', e))]);
|
const edit = (type: 'action' | 'reaction' | 'freeaction' | 'passive', id: string) => {
|
||||||
let content = redraw();
|
const feature = this._config[type][id]!;
|
||||||
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), () => add('passive'), 'p-1') ]), content ] ) ];
|
const option = options.find(e => e.type === type && e.id === id);
|
||||||
|
|
||||||
|
if(editing)
|
||||||
|
{
|
||||||
|
const idx = options.findIndex(e => e.id === editing!.id && e.type === editing!.type);
|
||||||
|
const rerender = render(editing.type, this._config[editing.type][editing.id]!);
|
||||||
|
|
||||||
|
options[idx]?.dom.replaceWith(rerender.dom);
|
||||||
|
options[idx] = rerender;
|
||||||
|
}
|
||||||
|
editing = { id, type };
|
||||||
|
|
||||||
|
const buttons = div('flex flex-row items-center gap-2', [ span('text-sm text-light-70 dark:text-dark-70', type), tooltip(button(icon('radix-icons:check'), () => {
|
||||||
|
this._config.texts[feature.description].default = editor.content;
|
||||||
|
this._config.texts[feature.description]['fr_FR'] = editor.content;
|
||||||
|
const rerender = render(type, feature);
|
||||||
|
|
||||||
|
option!.buttons.replaceWith(rerender.buttons);
|
||||||
|
option!.buttons = rerender.buttons;
|
||||||
|
|
||||||
|
option!.md.current.replaceWith(rerender.md.current);
|
||||||
|
option!.md = rerender.md;
|
||||||
|
|
||||||
|
editing = undefined;
|
||||||
|
}, 'p-1'), 'Valider', 'left'), tooltip(button(icon('radix-icons:cross-1'), () => {
|
||||||
|
const rerender = render(type, feature);
|
||||||
|
|
||||||
|
option!.buttons.replaceWith(rerender.buttons);
|
||||||
|
option!.buttons = rerender.buttons;
|
||||||
|
|
||||||
|
option!.md.current.replaceWith(rerender.md.current);
|
||||||
|
option!.md = rerender.md;
|
||||||
|
|
||||||
|
editing = undefined;
|
||||||
|
}, 'p-1'), 'Rejeter', 'right') ]);
|
||||||
|
|
||||||
|
option!.buttons.replaceWith(buttons);
|
||||||
|
option!.buttons = buttons;
|
||||||
|
|
||||||
|
const editor = MarkdownEditor.singleton;
|
||||||
|
editor.content = getText(feature.description);
|
||||||
|
editor.onChange = (value) => {};
|
||||||
|
const editorDom = div('p-1 border border-light-35 dark:border-dark-35', [ editor.dom ]);
|
||||||
|
|
||||||
|
option!.md.current.replaceWith(editorDom);
|
||||||
|
option!.md.current = editorDom;
|
||||||
|
}
|
||||||
|
const options = [...Object.values(this._config.action).map(e => render('action', e)), ...Object.values(this._config.reaction).map(e => render('reaction', e)), ...Object.values(this._config.freeaction).map(e => render('freeaction', e)), ...Object.values(this._config.passive).map(e => render('passive', e))];
|
||||||
|
const optionHolder = div('flex flex-col gap-4', options.map(e => e.dom));
|
||||||
|
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), optionmenu([{ title: 'Action', click: () => add('action') }, { title: 'Réaction', click: () => add('reaction') }, { title: 'Action libre', click: () => add('freeaction') }, { title: 'Passif', click: () => add('passive') }], { position: 'left-start' }), 'p-1') ]), optionHolder ] ) ];
|
||||||
}
|
}
|
||||||
edit(feature: Feature): Promise<Feature>
|
edit(feature: Feature): Promise<Feature>
|
||||||
{
|
{
|
||||||
|
|
@ -384,7 +441,7 @@ export class FeatureEditor
|
||||||
private _renderEffect(effect: Partial<FeatureItem>): HTMLDivElement
|
private _renderEffect(effect: Partial<FeatureItem>): HTMLDivElement
|
||||||
{
|
{
|
||||||
const content = div('border border-light-30 dark:border-dark-30 col-span-1', [ div('flex justify-between items-center', [
|
const content = div('border border-light-30 dark:border-dark-30 col-span-1', [ div('flex justify-between items-center', [
|
||||||
div('px-4 flex items-center h-full', [ renderMarkdown(textFromEffect(effect), undefined, { tags: { a: preview } }) ]),
|
div('px-4 flex items-center h-full', [ markdown(textFromEffect(effect), undefined, { tags: { a: preview } }) ]),
|
||||||
div('flex', [ tooltip(button(icon('radix-icons:pencil-1'), () => {
|
div('flex', [ tooltip(button(icon('radix-icons:pencil-1'), () => {
|
||||||
content.replaceWith(this._edit(effect));
|
content.replaceWith(this._edit(effect));
|
||||||
}, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Modifieur", "bottom"), tooltip(button(icon('radix-icons:trash'), () => {
|
}, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Modifieur", "bottom"), tooltip(button(icon('radix-icons:trash'), () => {
|
||||||
|
|
@ -458,20 +515,16 @@ export class FeatureEditor
|
||||||
{
|
{
|
||||||
if(buffer.list === 'spells')
|
if(buffer.list === 'spells')
|
||||||
{
|
{
|
||||||
bottom = [ combobox(config.spells.map(e => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }), div('flex flex-row gap-8', [ dom('span', { class: 'italic', text: `Rang ${e.rank === 4 ? 'spécial' : e.rank}` }), dom('span', { text: spellTypeTexts[e.type] }) ]) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderText(e.effect)) ]) ]), value: e.id })), { defaultValue: buffer.item, change: (value) => (buffer as FeatureList).item = value, class: { container: 'bg-light-25 dark:bg-dark-25 hover:z-10 h-[36px] w-full hover:outline-px outline-light-50 dark:outline-dark-50 !border-none' }, fill: 'contain' }) ];
|
bottom = [ combobox(config.spells.map(e => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }), div('flex flex-row gap-8', [ dom('span', { class: 'italic', text: `Rang ${e.rank === 4 ? 'spécial' : e.rank}` }), dom('span', { text: spellTypeTexts[e.type] }) ]) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderMDAsText(e.effect)) ]) ]), value: e.id })), { defaultValue: buffer.item, change: (value) => (buffer as FeatureList).item = value, class: { container: 'bg-light-25 dark:bg-dark-25 hover:z-10 h-[36px] w-full hover:outline-px outline-light-50 dark:outline-dark-50 !border-none' }, fill: 'contain' }) ];
|
||||||
}
|
}
|
||||||
else
|
else if(buffer.list)
|
||||||
{
|
{
|
||||||
const editor = new MarkdownEditor();
|
bottom = [ combobox(Object.values(config[buffer.list]).map(e => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderMDAsText(getText(e.description))) ]) ]), value: e.id })), { defaultValue: buffer.item, change: (value) => (buffer as FeatureList).item = value, class: { container: 'bg-light-25 dark:bg-dark-25 hover:z-10 h-[36px] w-full hover:outline-px outline-light-50 dark:outline-dark-50 !border-none' }, fill: 'contain' }) ];
|
||||||
editor.content = getText(buffer.item);
|
|
||||||
editor.onChange = (item) => (buffer as FeatureList).item = item;
|
|
||||||
|
|
||||||
bottom = [ div('px-2 py-1 bg-light-25 dark:bg-dark-25 flex-1 flex items-center', [ editor.dom ]) ];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bottom = [ combobox(Object.values(config.features).flatMap(e => e.effect).filter(e => e.category === 'list' && e.list === buffer.list && e.action === 'add').map(e => ({ text: buffer.list !== 'spells' ? renderText(getText((e as Extract<FeatureItem, { category: 'list' }>).item)) : config.spells.find(f => f.id === (e as Extract<FeatureItem, { category: 'list' }>).item)?.name ?? '', value: (e as Extract<FeatureItem, { category: 'list' }>).item })), { defaultValue: buffer.item, change: (item) => buffer.item = item, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full overflow-hidden truncate', option: 'max-h-[90px] text-sm' }, fill: 'contain' }) ];
|
bottom = [ combobox(Object.values(config.features).flatMap(e => e.effect).filter(e => e.category === 'list' && e.list === buffer.list && e.action === 'add').map((e) => (e as FeatureList).list === 'spells' ? config.spells.find(f => f.id === (e as FeatureList).item) : config[(e as FeatureList).list][(e as FeatureList).item]).map((e) => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderMDAsText(getText(e.description))) ]) ]), value: e.id })), { defaultValue: buffer.item, change: (item) => buffer.item = item, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full overflow-hidden truncate', option: 'max-h-[90px] text-sm' }, fill: 'contain' }) ];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'choice':
|
case 'choice':
|
||||||
|
|
@ -735,7 +788,7 @@ function textFromEffect(effect: Partial<FeatureItem | FeatureEquipment>): string
|
||||||
switch(splited[1])
|
switch(splited[1])
|
||||||
{
|
{
|
||||||
case 'defense':
|
case 'defense':
|
||||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ` aux jets de résistance de ${resistanceTexts[splited[2] as Resistance]}.` } }) : textFromValue(effect.value, { prefix: { truely: `Jets de résistance de ${resistanceTexts[splited[2] as Resistance]} = ` }, suffix: { truely: '.' }, falsely: `Opération interdite (Résistance ${resistanceTexts[splited[2] as Resistance]} = interdit).` });
|
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ` aux jets de résistance de ${mainStatTexts[splited[2] as MainStat]}.` } }) : textFromValue(effect.value, { prefix: { truely: `Jets de résistance de ${mainStatTexts[splited[2] as MainStat]} = ` }, suffix: { truely: '.' }, falsely: `Opération interdite (Résistance ${mainStatTexts[splited[2] as MainStat]} = interdit).` });
|
||||||
case 'abilities':
|
case 'abilities':
|
||||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `Max de ${abilityTexts[splited[2] as Ability]} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : effect.operation === 'set' ? textFromValue(effect.value, { prefix: { truely: `Max de ${abilityTexts[splited[2] as Ability]} fixé à ` }, suffix: { truely: '.' }, falsely: `Opération interdite ( ${abilityTexts[splited[2] as Ability]} max = interdit).` }) : textFromValue(effect.value, { prefix: { truely: `Max de ${abilityTexts[splited[2] as Ability]} min à ` }, suffix: { truely: '.' }, falsely: `Opération interdite ( ${abilityTexts[splited[2] as Ability]} max = interdit).` });
|
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `Max de ${abilityTexts[splited[2] as Ability]} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : effect.operation === 'set' ? textFromValue(effect.value, { prefix: { truely: `Max de ${abilityTexts[splited[2] as Ability]} fixé à ` }, suffix: { truely: '.' }, falsely: `Opération interdite ( ${abilityTexts[splited[2] as Ability]} max = interdit).` }) : textFromValue(effect.value, { prefix: { truely: `Max de ${abilityTexts[splited[2] as Ability]} min à ` }, suffix: { truely: '.' }, falsely: `Opération interdite ( ${abilityTexts[splited[2] as Ability]} max = interdit).` });
|
||||||
default: return 'Bonus inconnu';
|
default: return 'Bonus inconnu';
|
||||||
|
|
|
||||||
|
|
@ -40,16 +40,17 @@ export function init()
|
||||||
document.body.appendChild(teleport);
|
document.body.appendChild(teleport);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function popper(container: HTMLElement, properties?: PopperProperties): HTMLElement
|
export function popper(container: HTMLElement, properties?: PopperProperties)
|
||||||
{
|
{
|
||||||
let shown = false, timeout: Timer;
|
let shown = false, manualStop = false, timeout: Timer;
|
||||||
const arrow = svg('svg', { class: 'absolute fill-light-35 dark:fill-dark-35', attributes: { width: "12", height: "8", viewBox: "0 0 20 10" } }, [svg('polygon', { attributes: { points: "0,0 20,0 10,10" } })]);
|
const 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: ['fixed hidden', properties?.class], style: properties?.style, attributes: { 'data-state': 'closed' } });
|
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 rect = properties?.viewport?.getBoundingClientRect() ?? 'viewport';
|
const rect = properties?.viewport?.getBoundingClientRect() ?? 'viewport';
|
||||||
|
|
||||||
function update()
|
function update()
|
||||||
{
|
{
|
||||||
FloatingUI.computePosition(container, content, {
|
FloatingUI.computePosition(container, floater, {
|
||||||
placement: properties?.placement,
|
placement: properties?.placement,
|
||||||
strategy: 'fixed',
|
strategy: 'fixed',
|
||||||
middleware: [
|
middleware: [
|
||||||
|
|
@ -59,14 +60,14 @@ export function popper(container: HTMLElement, properties?: PopperProperties): H
|
||||||
FloatingUI.flip({ rootBoundary: rect }),
|
FloatingUI.flip({ rootBoundary: rect }),
|
||||||
properties?.cover && properties?.cover !== 'none' && FloatingUI.size({ rootBoundary: rect, apply: ({ availableWidth, availableHeight }) => {
|
properties?.cover && properties?.cover !== 'none' && FloatingUI.size({ rootBoundary: rect, apply: ({ availableWidth, availableHeight }) => {
|
||||||
if(properties?.cover === 'width' || properties?.cover === 'all')
|
if(properties?.cover === 'width' || properties?.cover === 'all')
|
||||||
content.style.width = `${availableWidth}px`;
|
floater.style.maxWidth = `${availableWidth}px`;
|
||||||
if(properties?.cover === 'height' || properties?.cover === 'all')
|
if(properties?.cover === 'height' || properties?.cover === 'all')
|
||||||
content.style.height = `${availableHeight}px`;
|
floater.style.maxHeight = `${availableHeight}px`;
|
||||||
} }),
|
} }),
|
||||||
properties?.offset && properties?.arrow ? FloatingUI.arrow({ element: arrow, padding: 8 }) : undefined,
|
properties?.offset && properties?.arrow ? FloatingUI.arrow({ element: arrow, padding: 8 }) : undefined,
|
||||||
]
|
]
|
||||||
}).then(({ x, y, placement, middlewareData }) => {
|
}).then(({ x, y, placement, middlewareData }) => {
|
||||||
Object.assign(content.style, {
|
Object.assign(floater.style, {
|
||||||
left: `${x}px`,
|
left: `${x}px`,
|
||||||
top: `${y}px`,
|
top: `${y}px`,
|
||||||
visibility: middlewareData.hide?.referenceHidden || middlewareData.hide?.escaped ? 'hidden' : 'visible',
|
visibility: middlewareData.hide?.referenceHidden || middlewareData.hide?.escaped ? 'hidden' : 'visible',
|
||||||
|
|
@ -74,7 +75,7 @@ export function popper(container: HTMLElement, properties?: PopperProperties): H
|
||||||
|
|
||||||
const side = placement.split('-')[0] as FloatingUI.Side;
|
const side = placement.split('-')[0] as FloatingUI.Side;
|
||||||
|
|
||||||
content.setAttribute('data-side', side);
|
floater.setAttribute('data-side', side);
|
||||||
|
|
||||||
if(middlewareData.arrow)
|
if(middlewareData.arrow)
|
||||||
{
|
{
|
||||||
|
|
@ -99,25 +100,25 @@ export function popper(container: HTMLElement, properties?: PopperProperties): H
|
||||||
top: arrowY != null ? `${arrowY}px` : '',
|
top: arrowY != null ? `${arrowY}px` : '',
|
||||||
right: '',
|
right: '',
|
||||||
bottom: '',
|
bottom: '',
|
||||||
[staticSide]: `-8px`,
|
[staticSide]: `-7px`,
|
||||||
transform: `rotate(${rotation}deg)`,
|
transform: `rotate(${rotation}deg)`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let stop: () => void | undefined;
|
let _stop: () => void | undefined, empty = true;
|
||||||
function show()
|
function show()
|
||||||
{
|
{
|
||||||
if(shown || !properties?.onShow || properties?.onShow() !== false)
|
if(shown || !properties?.onShow || properties?.onShow() !== false)
|
||||||
{
|
{
|
||||||
if(typeof properties?.content === 'function')
|
if(typeof properties?.content === 'function')
|
||||||
{
|
|
||||||
properties.content = properties.content();
|
properties.content = properties.content();
|
||||||
}
|
|
||||||
if(content.children.length === 0 && (properties?.content && properties.content.length > 0 || properties?.arrow))
|
if(properties?.content && empty)
|
||||||
{
|
{
|
||||||
content.replaceChildren(...(properties!.content as Node[]), arrow);
|
content.replaceChildren(...properties!.content.filter(e => !!e));
|
||||||
|
empty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
@ -125,13 +126,13 @@ export function popper(container: HTMLElement, properties?: PopperProperties): H
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
if(!shown)
|
if(!shown)
|
||||||
{
|
{
|
||||||
teleport!.appendChild(content);
|
teleport!.appendChild(floater);
|
||||||
|
|
||||||
content.setAttribute('data-state', 'open');
|
floater.setAttribute('data-state', 'open');
|
||||||
content.classList.toggle('hidden', false);
|
floater.classList.toggle('hidden', false);
|
||||||
|
|
||||||
update();
|
update();
|
||||||
stop = FloatingUI.autoUpdate(container, content, update, {
|
_stop = FloatingUI.autoUpdate(container, floater, update, {
|
||||||
animationFrame: true,
|
animationFrame: true,
|
||||||
layoutShift: false,
|
layoutShift: false,
|
||||||
elementResize: false,
|
elementResize: false,
|
||||||
|
|
@ -146,18 +147,43 @@ export function popper(container: HTMLElement, properties?: PopperProperties): H
|
||||||
|
|
||||||
function hide()
|
function hide()
|
||||||
{
|
{
|
||||||
if(!properties?.onHide || properties?.onHide() !== false)
|
if(!manualStop && (!properties?.onHide || properties?.onHide() !== false))
|
||||||
{
|
{
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
content.remove();
|
floater.remove();
|
||||||
stop && stop();
|
_stop && _stop();
|
||||||
|
|
||||||
|
floater.setAttribute('data-state', 'closed');
|
||||||
|
floater.classList.toggle('hidden', true);
|
||||||
|
|
||||||
shown = false;
|
shown = false;
|
||||||
}, shown ? properties?.delay ?? 0 : 0);
|
}, shown ? properties?.delay ?? 0 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function start()
|
||||||
|
{
|
||||||
|
manualStop = false;
|
||||||
|
floater.toggleAttribute('data-pinned', false);
|
||||||
|
update();
|
||||||
|
_stop = FloatingUI.autoUpdate(container, floater, update, {
|
||||||
|
animationFrame: true,
|
||||||
|
layoutShift: false,
|
||||||
|
elementResize: false,
|
||||||
|
ancestorScroll: false,
|
||||||
|
ancestorResize: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop()
|
||||||
|
{
|
||||||
|
manualStop = true;
|
||||||
|
floater.toggleAttribute('data-pinned', true);
|
||||||
|
_stop && _stop();
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
function link(element: HTMLElement) {
|
function link(element: HTMLElement) {
|
||||||
Object.entries({
|
Object.entries({
|
||||||
|
|
@ -172,9 +198,31 @@ export function popper(container: HTMLElement, properties?: PopperProperties): H
|
||||||
}
|
}
|
||||||
|
|
||||||
link(container);
|
link(container);
|
||||||
link(content);
|
link(floater);
|
||||||
|
|
||||||
return container;
|
return { container, content: floater, stop, start, show: () => {
|
||||||
|
if(!shown)
|
||||||
|
{
|
||||||
|
teleport!.appendChild(floater);
|
||||||
|
|
||||||
|
floater.setAttribute('data-state', 'open');
|
||||||
|
floater.classList.toggle('hidden', false);
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
shown = true;
|
||||||
|
}, hide: () => {
|
||||||
|
floater.remove();
|
||||||
|
_stop && _stop();
|
||||||
|
|
||||||
|
floater.setAttribute('data-state', 'closed');
|
||||||
|
floater.classList.toggle('hidden', true);
|
||||||
|
|
||||||
|
manualStop = false;
|
||||||
|
floater.toggleAttribute('data-pinned', false);
|
||||||
|
|
||||||
|
shown = false;
|
||||||
|
} };
|
||||||
}
|
}
|
||||||
export function followermenu(target: FloatingUI.ReferenceElement, content: NodeChildren, properties?: FollowerProperties)
|
export function followermenu(target: FloatingUI.ReferenceElement, content: NodeChildren, properties?: FollowerProperties)
|
||||||
{
|
{
|
||||||
|
|
@ -290,14 +338,17 @@ export function tooltip(container: HTMLElement, txt: string | Text, placement: F
|
||||||
arrow: true,
|
arrow: true,
|
||||||
offset: 8,
|
offset: 8,
|
||||||
delay: delay,
|
delay: delay,
|
||||||
content: [ typeof txt === 'string' ? text(txt) : txt ],
|
content: () => [ typeof txt === 'string' ? text(txt) : txt ],
|
||||||
placement: placement,
|
placement: placement,
|
||||||
class: "fixed hidden TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50 max-w-96"
|
class: "border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50 max-w-96"
|
||||||
});
|
}).container;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fullblocker(content: NodeChildren, properties?: ModalProperties)
|
export function fullblocker(content: NodeChildren, properties?: ModalProperties)
|
||||||
{
|
{
|
||||||
|
if(!content)
|
||||||
|
return { close: () => {} };
|
||||||
|
|
||||||
const close = () => (!properties?.onClose || properties.onClose() !== false) && _modal.remove();
|
const close = () => (!properties?.onClose || properties.onClose() !== false) && _modal.remove();
|
||||||
const _modalBlocker = dom('div', { class: [' absolute top-0 left-0 bottom-0 right-0 z-0', { 'bg-light-0 dark:bg-dark-0 opacity-70': properties?.priority ?? false }], listeners: { click: properties?.closeWhenOutside ? (close) : undefined } });
|
const _modalBlocker = dom('div', { class: [' absolute top-0 left-0 bottom-0 right-0 z-0', { 'bg-light-0 dark:bg-dark-0 opacity-70': properties?.priority ?? false }], listeners: { click: properties?.closeWhenOutside ? (close) : undefined } });
|
||||||
const _modal = dom('div', { class: 'fixed flex justify-center items-center top-0 left-0 bottom-0 right-0 inset-0 z-40' }, [ _modalBlocker, ...content]);
|
const _modal = dom('div', { class: 'fixed flex justify-center items-center top-0 left-0 bottom-0 right-0 inset-0 z-40' }, [ _modalBlocker, ...content]);
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import prose, { a, blockquote, tag, h1, h2, h3, h4, h5, hr, li, small, table, td
|
||||||
import { heading } from "hast-util-heading";
|
import { heading } from "hast-util-heading";
|
||||||
import { headingRank } from "hast-util-heading-rank";
|
import { headingRank } from "hast-util-heading-rank";
|
||||||
import { parseId } from "#shared/general.util";
|
import { parseId } from "#shared/general.util";
|
||||||
import { async, loading } from "#shared/components.util";
|
import { async } from "#shared/components.util";
|
||||||
|
|
||||||
export function renderMarkdown(markdown: Root, proses: Record<string, Prose>): HTMLDivElement
|
export function renderMarkdown(markdown: Root, proses: Record<string, Prose>): HTMLDivElement
|
||||||
{
|
{
|
||||||
return dom('div', {}, markdown.children.map(e => renderContent(e, proses)));
|
return dom('div', {}, markdown.children.map(e => renderContent(e, proses)));
|
||||||
}
|
}
|
||||||
export function renderText(markdown: string): string
|
export function renderMDAsText(markdown: string): string
|
||||||
{
|
{
|
||||||
return useMarkdown().text(markdown);
|
return useMarkdown().text(markdown);
|
||||||
}
|
}
|
||||||
|
|
@ -43,9 +43,9 @@ export interface MDProperties
|
||||||
style?: string | Record<string, string>;
|
style?: string | Record<string, string>;
|
||||||
tags?: Record<string, Prose>;
|
tags?: Record<string, Prose>;
|
||||||
}
|
}
|
||||||
export default function(content: string, filter?: string, properties?: MDProperties): HTMLElement
|
export function markdownReference(content: string, filter?: string, properties?: MDProperties)
|
||||||
{
|
{
|
||||||
return async('large', useMarkdown().parse(content).then(data => {
|
const state = async('large', useMarkdown().parse(content).then(data => {
|
||||||
if(filter)
|
if(filter)
|
||||||
{
|
{
|
||||||
const start = data?.children.findIndex(e => heading(e) && parseId(e.properties.id as string | undefined) === filter) ?? -1;
|
const start = data?.children.findIndex(e => heading(e) && parseId(e.properties.id as string | undefined) === filter) ?? -1;
|
||||||
|
|
@ -53,11 +53,11 @@ export default function(content: string, filter?: string, properties?: MDPropert
|
||||||
if(start !== -1)
|
if(start !== -1)
|
||||||
{
|
{
|
||||||
let end = start;
|
let end = start;
|
||||||
const rank = headingRank(data.children[start])!;
|
const rank = headingRank(data.children[start]!)!;
|
||||||
while(end < data.children.length)
|
while(end < data.children.length)
|
||||||
{
|
{
|
||||||
end++;
|
end++;
|
||||||
if(heading(data.children[end]) && headingRank(data.children[end])! <= rank)
|
if(heading(data.children[end]) && headingRank(data.children[end]!)! <= rank)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
data = { ...data, children: data.children.slice(start, end) };
|
data = { ...data, children: data.children.slice(start, end) };
|
||||||
|
|
@ -70,4 +70,9 @@ export default function(content: string, filter?: string, properties?: MDPropert
|
||||||
|
|
||||||
return el;
|
return el;
|
||||||
}));
|
}));
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
export default function(content: string, filter?: string, properties?: MDProperties): HTMLElement
|
||||||
|
{
|
||||||
|
return markdownReference(content, filter, properties).current;
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import { popper } from "#shared/floating.util";
|
||||||
import { Canvas } from "#shared/canvas.util";
|
import { Canvas } from "#shared/canvas.util";
|
||||||
import { Content, iconByType, type LocalContent } from "#shared/content.util";
|
import { Content, iconByType, type LocalContent } from "#shared/content.util";
|
||||||
import { parsePath, unifySlug } from "#shared/general.util";
|
import { parsePath, unifySlug } from "#shared/general.util";
|
||||||
import { async, loading } from "./components.util";
|
import { async, floater, loading } from "./components.util";
|
||||||
|
|
||||||
|
|
||||||
export type CustomProse = (properties: any, children: NodeChildren) => Node;
|
export type CustomProse = (properties: any, children: NodeChildren) => Node;
|
||||||
|
|
@ -20,7 +20,7 @@ export const a: Prose = {
|
||||||
|
|
||||||
const link = overview ? { name: 'explore-path', params: { path: overview.path }, hash: hash } : href, nav = router.resolve(link);
|
const link = overview ? { name: 'explore-path', params: { path: overview.path }, hash: hash } : href, nav = router.resolve(link);
|
||||||
|
|
||||||
const el = dom('a', { class: ['text-accent-blue inline-flex items-center', properties?.class], attributes: { href: nav.href }, listeners: {
|
const element = dom('a', { class: ['text-accent-blue inline-flex items-center', properties?.class], attributes: { href: nav.href }, listeners: {
|
||||||
'click': (e) => {
|
'click': (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
router.push(link);
|
router.push(link);
|
||||||
|
|
@ -32,35 +32,19 @@ export const a: Prose = {
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if(!!overview)
|
return !!overview ? floater(element, () => [async('large', Content.getContent(overview.id).then((_content) => {
|
||||||
{
|
if(_content?.type === 'markdown')
|
||||||
popper(el, {
|
{
|
||||||
arrow: true,
|
return render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'w-full max-h-full overflow-auto py-4 px-6' });
|
||||||
delay: 150,
|
}
|
||||||
offset: 12,
|
if(_content?.type === 'canvas')
|
||||||
cover: "height",
|
{
|
||||||
placement: 'bottom-start',
|
const canvas = new Canvas((_content as LocalContent<'canvas'>).content);
|
||||||
class: 'data-[side=bottom]:animate-slideUpAndFade data-[side=right]:animate-slideLeftAndFade data-[side=left]:animate-slideRightAndFade data-[side=top]:animate-slideDownAndFade w-[300px] bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 data-[state=open]:transition-transform text-light-100 dark:text-dark-100 min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] w-full overflow-auto z-[45]',
|
queueMicrotask(() => canvas.mount());
|
||||||
content: () => {
|
return dom('div', { class: 'w-[600px] h-[600px] relative' }, [canvas.container]);
|
||||||
return [async('large', Content.getContent(overview.id).then((_content) => {
|
}
|
||||||
if(_content?.type === 'markdown')
|
return div('');
|
||||||
{
|
})).current], { position: 'bottom-start', pinned: false }) : element;
|
||||||
return render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'w-full max-h-full overflow-auto py-4 px-6' });
|
|
||||||
}
|
|
||||||
if(_content?.type === 'canvas')
|
|
||||||
{
|
|
||||||
const canvas = new Canvas((_content as LocalContent<'canvas'>).content);
|
|
||||||
queueMicrotask(() => canvas.mount());
|
|
||||||
return dom('div', { class: 'w-[600px] h-[600px] relative' }, [canvas.container]);
|
|
||||||
}
|
|
||||||
return div('');
|
|
||||||
}))];
|
|
||||||
},
|
|
||||||
viewport: document.getElementById('mainContainer') ?? undefined
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const preview: Prose = {
|
export const preview: Prose = {
|
||||||
|
|
@ -99,13 +83,13 @@ export const preview: Prose = {
|
||||||
return dom('div', { class: 'w-[600px] h-[600px] relative' }, [canvas.container]);
|
return dom('div', { class: 'w-[600px] h-[600px] relative' }, [canvas.container]);
|
||||||
}
|
}
|
||||||
return div('');
|
return div('');
|
||||||
}))];
|
})).current];
|
||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
if(!magicKeys.current.has('control') || magicKeys.current.has('meta'))
|
if(!magicKeys.current.has('control') || magicKeys.current.has('meta'))
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
}) : el;
|
}).container : el;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const callout: Prose = {
|
export const callout: Prose = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue