You've already forked obsidian-visualiser
Progress on visual rework of the character editor
This commit is contained in:
38
components/character/editor/AbilityEditor.vue
Normal file
38
components/character/editor/AbilityEditor.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<template v-if="model && model.people !== undefined">
|
||||
<div class="flex flex-1 gap-12 px-2 py-4 justify-center items-center sticky top-0 bg-light-0 dark:bg-dark-0 w-full z-10">
|
||||
<Label class="flex items-center justify-between gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Points restants</span>
|
||||
<NumberFieldRoot disabled :v-model="0" class="flex justify-center border border-light-25 dark:border-dark-25 bg-light-10 dark:bg-dark-10 text-light-60 dark:text-dark-60">
|
||||
<NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Button @click="emit('next')">Suivant</Button>
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap justify-center items-center flex-1 gap-12 mx-8 my-4 px-48">
|
||||
<template v-for="ability of config.abilities">
|
||||
<div class="flex flex-col border border-light-50 dark:border-dark-50 p-4 gap-2 w-[200px] relative">
|
||||
<div class="flex justify-between">
|
||||
<NumberFieldRoot :min="0" class="flex w-20 justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
|
||||
<NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
<Tooltip side="bottom" :message="`${mainStatTexts[ability.max[0]]} (0) + ${mainStatTexts[ability.max[1]]} (0) + 0`"><span class="text-lg text-end cursor-pointer">/ {{ 0 }}</span></Tooltip>
|
||||
</div>
|
||||
<span class="text-xl text-center font-bold">{{ ability.name }}</span>
|
||||
<span class="absolute -bottom-px -left-px h-[3px] bg-accent-blue" :style="{ width: `200px` }"></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mainStatTexts, type Character, type CharacterConfig } from '~/types/character';
|
||||
|
||||
const { config } = defineProps<{
|
||||
config: CharacterConfig,
|
||||
}>();
|
||||
const model = defineModel<Character>({ required: true });
|
||||
|
||||
const emit = defineEmits(['next']);
|
||||
</script>
|
||||
38
components/character/editor/AspectSelector.vue
Normal file
38
components/character/editor/AspectSelector.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<template v-if="model && model.people !== undefined">
|
||||
<div class="flex flex-1 gap-12 px-2 py-4 justify-center items-center sticky top-0 bg-light-0 dark:bg-dark-0 w-full z-10">
|
||||
<Label class="flex items-center justify-between gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Physique</span>
|
||||
<NumberFieldRoot disabled :v-model="0" class="flex justify-center border border-light-25 dark:border-dark-25 bg-light-10 dark:bg-dark-10 text-light-60 dark:text-dark-60">
|
||||
<NumberFieldInput class="tabular-nums w-14 bg-transparent px-3 py-1 outline-none" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Label class="flex items-center justify-between gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Mental</span>
|
||||
<NumberFieldRoot disabled :v-model="0" class="flex justify-center border border-light-25 dark:border-dark-25 bg-light-10 dark:bg-dark-10 text-light-60 dark:text-dark-60">
|
||||
<NumberFieldInput class="tabular-nums w-14 bg-transparent px-3 py-1 outline-none" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Label class="flex items-center justify-between gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Caractère</span>
|
||||
<NumberFieldRoot disabled :v-model="0" class="flex justify-center border border-light-25 dark:border-dark-25 bg-light-10 dark:bg-dark-10 text-light-60 dark:text-dark-60">
|
||||
<NumberFieldInput class="tabular-nums w-14 bg-transparent px-3 py-1 outline-none" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Button @click="emit('next')" :disabled="model.aspect === undefined">Enregistrer</Button>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 gap-4 mx-8 my-4">
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Character, CharacterConfig } from '~/types/character';
|
||||
|
||||
const { config } = defineProps<{
|
||||
config: CharacterConfig,
|
||||
}>();
|
||||
const model = defineModel<Character>({ required: true });
|
||||
|
||||
const emit = defineEmits(['next']);
|
||||
</script>
|
||||
113
components/character/editor/LevelEditor.vue
Normal file
113
components/character/editor/LevelEditor.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<template v-if="model && model.people !== undefined">
|
||||
<div class="flex flex-1 gap-12 px-2 py-4 justify-center items-center sticky top-0 bg-light-0 dark:bg-dark-0 w-full z-10">
|
||||
<Label class="flex items-center justify-between gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Niveau</span>
|
||||
<NumberFieldRoot :min="1" :max="20" v-model="model.level" @update:model-value="updateLevel" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
|
||||
<NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Label class="flex items-center justify-between gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Attributions restantes</span>
|
||||
<NumberFieldRoot disabled :v-model="0" class="flex justify-center border border-light-25 dark:border-dark-25 bg-light-10 dark:bg-dark-10 text-light-60 dark:text-dark-60">
|
||||
<NumberFieldInput class="tabular-nums w-14 bg-transparent px-3 py-1 outline-none" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Label class="flex items-center justify-between gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Vie</span>
|
||||
<NumberFieldRoot disabled :v-model="0" class="flex justify-center border border-light-25 dark:border-dark-25 bg-light-10 dark:bg-dark-10 text-light-60 dark:text-dark-60">
|
||||
<NumberFieldInput class="tabular-nums w-14 bg-transparent px-3 py-1 outline-none" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Label class="flex items-center justify-between gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Mana</span>
|
||||
<NumberFieldRoot disabled :v-model="0" class="flex justify-center border border-light-25 dark:border-dark-25 bg-light-10 dark:bg-dark-10 text-light-60 dark:text-dark-60">
|
||||
<NumberFieldInput class="tabular-nums w-14 bg-transparent px-3 py-1 outline-none" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Button @click="emit('next')">Suivant</Button>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 gap-4 mx-8 my-4">
|
||||
<template v-for="(level, index) of config.peoples[model.people].options">
|
||||
<div class="w-full flex h-px"><div class="border-t border-dashed border-light-50 dark:border-dark-50 w-full" :class="{ 'opacity-30': index > model.level }"></div><span class="sticky top-0">{{ index }}</span></div>
|
||||
<div class="flex flex-row gap-4 justify-center" :class="{ 'opacity-30': index > model.level }">
|
||||
<template v-for="(option, i) of level">
|
||||
<div class="flex border border-light-50 dark:border-dark-50 px-4 py-2 w-[400px]" @click="chooseOption(parseInt(index as unknown as string, 10) as Level, i)"
|
||||
:class="{ 'hover:border-light-70 dark:hover:border-dark-70 cursor-pointer': index <= model.level, '!border-accent-blue bg-accent-blue bg-opacity-20': model.leveling?.some(e => e[0] == index && e[1] === i) ?? false }">
|
||||
<span class="text-wrap whitespace-pre">{{ raceOptionToText(option) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Character, CharacterConfig, Level, RaceOption } from '~/types/character';
|
||||
|
||||
const { config } = defineProps<{
|
||||
config: CharacterConfig,
|
||||
}>();
|
||||
const model = defineModel<Character>({ required: true });
|
||||
|
||||
const emit = defineEmits(['next']);
|
||||
|
||||
function raceOptionToText(option: RaceOption): string
|
||||
{
|
||||
const text = [];
|
||||
if(option.training) text.push(`+${option.training} point${option.training > 1 ? 's' : ''} de statistique${option.training > 1 ? 's' : ''}.`);
|
||||
if(option.shaping) text.push(`+${option.shaping} transformation${option.shaping > 1 ? 's' : ''} par jour.`);
|
||||
if(option.modifier) text.push(`+${option.modifier} au modifieur de votre choix.`);
|
||||
if(option.abilities) text.push(`+${option.abilities} point${option.abilities > 1 ? 's' : ''} de compétence${option.abilities > 1 ? 's' : ''}.`);
|
||||
if(option.health) text.push(`+${option.health} PV max.`);
|
||||
if(option.mana) text.push(`+${option.mana} mana max.`);
|
||||
if(option.spellslots) text.push(`+${option.spellslots} sort${option.spellslots > 1 ? 's' : ''} maitrisé${option.spellslots > 1 ? 's' : ''}.`);
|
||||
return text.join('\n');
|
||||
}
|
||||
function chooseOption(level: Level, choice: number)
|
||||
{
|
||||
const character = model.value;
|
||||
if(level > character.level)
|
||||
return;
|
||||
|
||||
if(character.leveling === undefined)
|
||||
character.leveling = [[1, 0]];
|
||||
|
||||
if(level == 1)
|
||||
return;
|
||||
|
||||
for(let i = 1; i < level; i++) //Check previous levels as a requirement
|
||||
{
|
||||
if(!character.leveling.some(e => e[0] == i))
|
||||
return;
|
||||
}
|
||||
|
||||
if(character.leveling.some(e => e[0] == level))
|
||||
{
|
||||
character.leveling.splice(character.leveling.findIndex(e => e[0] == level), 1, [level, choice]);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.leveling.push([level, choice]);
|
||||
}
|
||||
|
||||
model.value = character;
|
||||
}
|
||||
function updateLevel()
|
||||
{
|
||||
const character = model.value;
|
||||
|
||||
if(character.leveling) //Invalidate higher levels
|
||||
{
|
||||
for(let level = 20; level > character.level; level--)
|
||||
{
|
||||
const index = character.leveling.findIndex(e => e[0] == level);
|
||||
if(index !== -1)
|
||||
character.leveling.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
model.value = character;
|
||||
}
|
||||
</script>
|
||||
@@ -8,8 +8,9 @@
|
||||
<div class="flex flex-1 gap-4 p-2 overflow-x-auto justify-center">
|
||||
<div v-for="(people, i) of config.peoples" @click="model.people = i" class="flex flex-col flex-nowrap gap-2 p-2 border border-light-35 dark:border-dark-35
|
||||
cursor-pointer hover:border-light-70 dark:hover:border-dark-70 w-[320px]" :class="{ '!border-accent-blue outline-2 outline outline-accent-blue': model.people === i }">
|
||||
<span class="text-xl font-bold text-center">{{ people.name }}</span>
|
||||
<Avatar :src="people.name" :text="`Image placeholder`" class="h-[320px]" />
|
||||
<span class="text-xl font-bold text-center">{{ people.name }}</span>
|
||||
<span class="w-full border-b border-light-50 dark:border-dark-50"></span>
|
||||
<span class="text-wrap word-break">{{ people.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
69
components/character/editor/TrainingEditor.vue
Normal file
69
components/character/editor/TrainingEditor.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<TrainingViewer :config="config">
|
||||
<template #addin="{ stat }">
|
||||
<div class="flex flex-1 gap-12 px-2 py-4 justify-center items-center sticky top-0 bg-light-0 dark:bg-dark-0 w-full z-10">
|
||||
<Label class="flex items-center justify-between gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Points restants</span>
|
||||
<NumberFieldRoot disabled :v-model="0" class="flex justify-center border border-light-25 dark:border-dark-25 bg-light-10 dark:bg-dark-10 text-light-60 dark:text-dark-60">
|
||||
<NumberFieldInput class="tabular-nums w-14 bg-transparent px-3 py-1 outline-none" />
|
||||
</NumberFieldRoot>
|
||||
</Label>
|
||||
<Button @click="emit('next')">Suivant</Button>
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ stat, level, option }">
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-[400px] hover:border-light-50 dark:hover:border-dark-50" @click="toggleOption(stat, parseInt(level as unknown as string, 10) as TrainingLevel, option)" :class="{ /*'opacity-30': level > maxTraining[stat] + 1, 'hover:border-light-60 dark:hover:border-dark-60': level <= maxTraining[stat] + 1, */'!border-accent-blue bg-accent-blue bg-opacity-20': level == 0 || (model.training[stat]?.some(e => e[0] == level && e[1] === option) ?? false) }">
|
||||
<MarkdownRenderer :proses="{ 'a': PreviewA }" :content="config.training[stat][level][option].description.map(e => e.text).join('\n')" />
|
||||
</div>
|
||||
</template>
|
||||
</TrainingViewer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PreviewA from '~/components/prose/PreviewA.vue';
|
||||
import { MAIN_STATS, type Character, type CharacterConfig, type MainStat, type TrainingLevel } from '~/types/character';
|
||||
|
||||
const { config } = defineProps<{
|
||||
config: CharacterConfig,
|
||||
}>();
|
||||
const model = defineModel<Character>({ required: true, });
|
||||
|
||||
const maxTraining = Object.fromEntries(MAIN_STATS.map(e => [e, 0]));
|
||||
|
||||
const emit = defineEmits(['next']);
|
||||
|
||||
function toggleOption(stat: MainStat, level: TrainingLevel, choice: number)
|
||||
{
|
||||
const character = model.value;
|
||||
|
||||
if(level == 0)
|
||||
return;
|
||||
|
||||
for(let i = 1; i < level; i++) //Check previous levels as a requirement
|
||||
{
|
||||
if(!character.training[stat].some(e => e[0] == i))
|
||||
return;
|
||||
}
|
||||
|
||||
if(character.training[stat].some(e => e[0] == level))
|
||||
{
|
||||
if(character.training[stat].some(e => e[0] == level && e[1] === choice))
|
||||
{
|
||||
for(let i = 15; i >= level; i --) //Invalidate higher levels
|
||||
{
|
||||
const index = character.training[stat].findIndex(e => e[0] == i);
|
||||
if(index !== -1)
|
||||
character.training[stat].splice(index, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
character.training[stat].splice(character.training[stat].findIndex(e => e[0] == level), 1, [level, choice]);
|
||||
}
|
||||
else //if(trainingPoints.value && trainingPoints.value > 0)
|
||||
{
|
||||
character.training[stat].push([level, choice]);
|
||||
}
|
||||
|
||||
model.value = character;
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user