You've already forked obsidian-visualiser
New SQL tables structure
This commit is contained in:
@@ -37,7 +37,7 @@ function abilitySpecialFeatures(type: "points" | "max", curiosity: DoubleIndex<T
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import PreviewA from '~/components/prose/PreviewA.vue';
|
||||
import { clamp } from '~/shared/general.util';
|
||||
import { elementTexts, mainStatTexts, spellTypeTexts, type Ability, type Character, type CharacterConfig, type DoubleIndex, type Level, type MainStat, type RaceOption, type SpellConfig, type SpellElement, type SpellType, type TrainingLevel, type TrainingOption } from '~/types/character';
|
||||
import { defaultCharacter, elementTexts, mainStatTexts, spellTypeTexts, type Ability, type Character, type CharacterConfig, type DoubleIndex, type Level, type MainStat, type RaceOption, type SpellConfig, type SpellElement, type SpellType, type TrainingLevel, type TrainingOption } from '~/types/character';
|
||||
|
||||
definePageMeta({
|
||||
guestsGoesTo: '/user/login',
|
||||
@@ -45,36 +45,7 @@ definePageMeta({
|
||||
let id = useRouter().currentRoute.value.params.id;
|
||||
const { add } = useToast();
|
||||
const characterConfig = config as CharacterConfig;
|
||||
const data = ref<Character>({
|
||||
id: -1,
|
||||
name: '',
|
||||
progress: {
|
||||
training: {
|
||||
strength: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
dexterity: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
constitution: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
intelligence: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
curiosity: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
charisma: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
psyche: [[1, 0], [2, 0], [3, 0], [4, 0]],
|
||||
},
|
||||
level: 1,
|
||||
race: {
|
||||
index: undefined,
|
||||
progress: [[1, 0]],
|
||||
},
|
||||
abilities: {},
|
||||
modifiers: {},
|
||||
spells: [],
|
||||
notes: "",
|
||||
},
|
||||
values: {
|
||||
hp: 0,
|
||||
armor: 0,
|
||||
mana: 0,
|
||||
},
|
||||
visibility: "private"
|
||||
});
|
||||
const data = ref<Character>({ ...defaultCharacter });
|
||||
const spellFilter = ref<{
|
||||
ranks: Array<1 | 2 | 3>,
|
||||
types: Array<SpellType>,
|
||||
@@ -90,18 +61,18 @@ const spellFilter = ref<{
|
||||
});
|
||||
|
||||
const peopleOpen = ref(false), trainingOpen = ref(false), abilityOpen = ref(false), spellOpen = ref(false), notesOpen = ref(false), trainingTab = ref(0);
|
||||
const raceOptions = computed(() => data.value.progress.race.index !== undefined ? characterConfig.peoples[data.value.progress.race.index!].options : undefined);
|
||||
const selectedRaceOptions = computed(() => raceOptions !== undefined ? data.value.progress.race.progress!.map(e => raceOptions.value![e[0]][e[1]]) : undefined);
|
||||
const trainingPoints = computed(() => raceOptions.value ? data.value.progress.race.progress?.reduce((p, v) => p + (raceOptions.value![v[0]][v[1]].training ?? 0), 0) : 0);
|
||||
const training = computed(() => Object.entries(characterConfig.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, data.value.progress.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][]);
|
||||
const maxTraining = computed(() => Object.entries(data.value.progress.training).reduce((p, v) => { p[v[0] as MainStat] = v[1].reduce((_p, _v) => Math.max(_p, _v[0]) , 0); return p; }, {} as Record<MainStat, number>));
|
||||
const raceOptions = computed(() => data.value.people !== undefined ? characterConfig.peoples[data.value.people!].options : undefined);
|
||||
const selectedRaceOptions = computed(() => raceOptions !== undefined ? data.value.leveling!.map(e => raceOptions.value![e[0]][e[1]]) : undefined);
|
||||
const trainingPoints = computed(() => raceOptions.value ? data.value.leveling?.reduce((p, v) => p + (raceOptions.value![v[0]][v[1]].training ?? 0), 0) : 0);
|
||||
const training = computed(() => Object.entries(characterConfig.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, data.value.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][]);
|
||||
const maxTraining = computed(() => Object.entries(data.value.training).reduce((p, v) => { p[v[0] as MainStat] = v[1].reduce((_p, _v) => Math.max(_p, _v[0]) , 0); return p; }, {} as Record<MainStat, number>));
|
||||
const trainingSpent = computed(() => Object.values(maxTraining.value).reduce((p, v) => p + v, 0));
|
||||
const modifiers = computed(() => Object.entries(maxTraining.value).reduce((p, v) => { p[v[0] as MainStat] = Math.floor(v[1] / 3) + (data.value.progress.modifiers ? (data.value.progress.modifiers[v[0] as MainStat] ?? 0) : 0); return p; }, {} as Record<MainStat, number>))
|
||||
const modifiers = computed(() => Object.entries(maxTraining.value).reduce((p, v) => { p[v[0] as MainStat] = Math.floor(v[1] / 3) + (data.value.modifiers ? (data.value.modifiers[v[0] as MainStat] ?? 0) : 0); return p; }, {} as Record<MainStat, number>))
|
||||
const modifierPoints = computed(() => (selectedRaceOptions.value ? selectedRaceOptions.value.reduce((p, v) => p + (v?.modifier ?? 0), 0) : 0) + training.value.reduce((p, v) => p + v[1].reduce((_p, _v) => _p + (_v?.modifier ?? 0), 0), 0));
|
||||
const modifierSpent = computed(() => Object.values(data.value.progress.modifiers ?? {}).reduce((p, v) => p + v, 0));
|
||||
const modifierSpent = computed(() => Object.values(data.value.modifiers ?? {}).reduce((p, v) => p + v, 0));
|
||||
const abilityPoints = computed(() => (selectedRaceOptions.value ? selectedRaceOptions.value.reduce((p, v) => p + (v?.abilities ?? 0), 0) : 0) + training.value.flatMap(e => e[1].filter(_e => _e.ability !== undefined)).reduce((p, v) => p + v.ability!, 0));
|
||||
const abilityMax = computed(() => Object.entries(characterConfig.abilities).reduce((p, v) => { p[v[0] as Ability] = abilitySpecialFeatures("max", data.value.progress.training.curiosity, Math.floor(maxTraining.value[v[1].max[0]] / 3) + Math.floor(maxTraining.value[v[1].max[1]] / 3)); return p; }, {} as Record<Ability, number>));
|
||||
const abilitySpent = computed(() => Object.values(data.value.progress.abilities ?? {}).reduce((p, v) => p + v[0], 0));
|
||||
const abilityMax = computed(() => Object.entries(characterConfig.abilities).reduce((p, v) => { p[v[0] as Ability] = abilitySpecialFeatures("max", data.value.training.curiosity, Math.floor(maxTraining.value[v[1].max[0]] / 3) + Math.floor(maxTraining.value[v[1].max[1]] / 3)); return p; }, {} as Record<Ability, number>));
|
||||
const abilitySpent = computed(() => Object.values(data.value.abilities ?? {}).reduce((p, v) => p + v[0], 0));
|
||||
const spellranks = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.spellrank !== undefined)).reduce((p, v) => { p[v.spellrank!]++; return p; }, { instinct: 0, precision: 0, knowledge: 0 } as Record<SpellType, 0 | 1 | 2 | 3>));
|
||||
const spellsPoints = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.spellslot !== undefined)).reduce((p, v) => p + (modifiers.value.hasOwnProperty(v.spellslot as MainStat) ? modifiers.value[v.spellslot as MainStat] : v.spellslot as number), 0));
|
||||
|
||||
@@ -114,36 +85,34 @@ if(id !== 'new')
|
||||
throw new Error('Donnée du personnage introuvables');
|
||||
}
|
||||
|
||||
data.value = { name: character.name, progress: Object.assign(data.value.progress, character.progress) } as Character;
|
||||
data.value = Object.assign(defaultCharacter, data.value, character);
|
||||
}
|
||||
|
||||
console.log(data.value.progress);
|
||||
|
||||
function selectRaceOption(level: Level, choice: number)
|
||||
{
|
||||
const character = data.value;
|
||||
if(level > character.progress.level)
|
||||
if(level > character.level)
|
||||
return;
|
||||
|
||||
if(character.progress.race.progress === undefined)
|
||||
character.progress.race.progress = [[1, 0]];
|
||||
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.progress.race.progress.some(e => e[0] == i))
|
||||
if(!character.leveling.some(e => e[0] == i))
|
||||
return;
|
||||
}
|
||||
|
||||
if(character.progress.race.progress.some(e => e[0] == level))
|
||||
if(character.leveling.some(e => e[0] == level))
|
||||
{
|
||||
character.progress.race.progress.splice(character.progress.race.progress.findIndex(e => e[0] == level), 1, [level, choice]);
|
||||
character.leveling.splice(character.leveling.findIndex(e => e[0] == level), 1, [level, choice]);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.progress.race.progress.push([level, choice]);
|
||||
character.leveling.push([level, choice]);
|
||||
}
|
||||
|
||||
data.value = character;
|
||||
@@ -157,27 +126,27 @@ function switchTrainingOption(stat: MainStat, level: TrainingLevel, choice: numb
|
||||
|
||||
for(let i = 1; i < level; i++) //Check previous levels as a requirement
|
||||
{
|
||||
if(!character.progress.training[stat].some(e => e[0] == i))
|
||||
if(!character.training[stat].some(e => e[0] == i))
|
||||
return;
|
||||
}
|
||||
|
||||
if(character.progress.training[stat].some(e => e[0] == level))
|
||||
if(character.training[stat].some(e => e[0] == level))
|
||||
{
|
||||
if(character.progress.training[stat].some(e => e[0] == level && e[1] === choice))
|
||||
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.progress.training[stat].findIndex(e => e[0] == i);
|
||||
const index = character.training[stat].findIndex(e => e[0] == i);
|
||||
if(index !== -1)
|
||||
character.progress.training[stat].splice(index, 1);
|
||||
character.training[stat].splice(index, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
character.progress.training[stat].splice(character.progress.training[stat].findIndex(e => e[0] == level), 1, [level, choice]);
|
||||
character.training[stat].splice(character.training[stat].findIndex(e => e[0] == level), 1, [level, choice]);
|
||||
}
|
||||
else if(trainingPoints.value && trainingPoints.value > 0)
|
||||
{
|
||||
character.progress.training[stat].push([level, choice]);
|
||||
character.training[stat].push([level, choice]);
|
||||
}
|
||||
|
||||
data.value = character;
|
||||
@@ -186,13 +155,13 @@ function updateLevel()
|
||||
{
|
||||
const character = data.value;
|
||||
|
||||
if(character.progress.race.progress) //Invalidate higher levels
|
||||
if(character.leveling) //Invalidate higher levels
|
||||
{
|
||||
for(let level = 20; level > character.progress.level; level--)
|
||||
for(let level = 20; level > character.level; level--)
|
||||
{
|
||||
const index = character.progress.race.progress.findIndex(e => e[0] == level);
|
||||
const index = character.leveling.findIndex(e => e[0] == level);
|
||||
if(index !== -1)
|
||||
character.progress.race.progress.splice(index, 1);
|
||||
character.leveling.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +182,7 @@ function filterSpells(spells: SpellConfig[])
|
||||
}
|
||||
async function save(leave: boolean)
|
||||
{
|
||||
if(data.value.name === '' || data.value.progress.race.index === undefined || data.value.progress.race.index === -1)
|
||||
if(data.value.name === '' || data.value.people === undefined || data.value.people === -1)
|
||||
{
|
||||
add({ title: 'Données manquantes', content: "Merci de saisir un nom et une race avant de pouvoir enregistrer votre personnage", type: 'error', duration: 25000, timer: true });
|
||||
return;
|
||||
@@ -224,7 +193,7 @@ async function save(leave: boolean)
|
||||
method: 'post',
|
||||
body: data.value,
|
||||
onResponseError: (e) => {
|
||||
add({ title: 'Erreur d\enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
}
|
||||
});
|
||||
add({ content: 'Personnage créé', type: 'success', duration: 25000, timer: true });
|
||||
@@ -237,7 +206,7 @@ async function save(leave: boolean)
|
||||
method: 'post',
|
||||
body: data.value,
|
||||
onResponseError: (e) => {
|
||||
add({ title: 'Erreur d\enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
|
||||
}
|
||||
});
|
||||
add({ content: 'Personnage enregistré', type: 'success', duration: 25000, timer: true });
|
||||
@@ -268,7 +237,7 @@ useShortcuts({
|
||||
</Label>
|
||||
<Label class="flex items-start justify-between flex-col gap-2">
|
||||
<span class="pb-1 mx-2 md:p-0">Niveau</span>
|
||||
<NumberFieldRoot :min="1" :max="20" v-model="data.progress.level" @update:model-value="updateLevel" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
<NumberFieldRoot :min="1" :max="20" v-model="data.level" @update:model-value="updateLevel" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 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>
|
||||
@@ -292,22 +261,22 @@ useShortcuts({
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="m-2 overflow-auto">
|
||||
<Combobox label="Peuple de votre personnage" v-model="data.progress.race.index" :options="config.peoples.map((people, index) => [people.name, index])" @update:model-value="(index) => { data.progress.race.index = index as number | undefined; data.progress.race.progress = [[1, 0]]}" />
|
||||
<template v-if="data.progress.race.index !== undefined">
|
||||
<Combobox label="Peuple de votre personnage" v-model="data.people" :options="config.peoples.map((people, index) => [people.name, index])" @update:model-value="(index) => { data.people = index as number | undefined; data.leveling = [[1, 0]]}" />
|
||||
<template v-if="data.people !== undefined">
|
||||
<div class="w-full border-b border-light-30 dark:border-dark-30 pb-4">
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">{{ characterConfig.peoples[data.progress.race.index].description }}</span>
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">{{ characterConfig.peoples[data.people].description }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 max-h-[50vh] pe-4 relative">
|
||||
<span class="sticky top-0 py-1 bg-light-0 dark:bg-dark-0 z-10 text-xl">Niveaux restants: {{ data.progress.level - (data.progress.race.progress?.length ?? 0) }}</span>
|
||||
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.peoples[data.progress.race.index].options" :class="{ 'opacity-30': index > data.progress.level }">
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-64" v-for="(option, i) of level" @click="selectRaceOption(index as Level, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= data.progress.level, '!border-accent-blue bg-accent-blue bg-opacity-20': data.progress.race.progress?.some(e => e[0] == index && e[1] === i) ?? false }"><MarkdownRenderer :content="raceOptionToText(option)" /></div>
|
||||
<span class="sticky top-0 py-1 bg-light-0 dark:bg-dark-0 z-10 text-xl">Niveaux restants: {{ data.level - (data.leveling?.length ?? 0) }}</span>
|
||||
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.peoples[data.people].options" :class="{ 'opacity-30': index > data.level }">
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-64" v-for="(option, i) of level" @click="selectRaceOption(parseInt(index as unknown as string, 10) as Level, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= data.level, '!border-accent-blue bg-accent-blue bg-opacity-20': data.leveling?.some(e => e[0] == index && e[1] === i) ?? false }"><MarkdownRenderer :content="raceOptionToText(option)" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="trainingOpen" :disabled="data.progress.race.index === undefined" @update:model-value="() => { peopleOpen = false; abilityOpen = false; spellOpen = false; notesOpen = false; }">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="trainingOpen" :disabled="data.people === undefined" @update:model-value="() => { peopleOpen = false; abilityOpen = false; spellOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Entrainement</span>
|
||||
</template>
|
||||
@@ -323,7 +292,7 @@ useShortcuts({
|
||||
<div class="sticky top-1 mx-16 z-10 flex justify-between">
|
||||
<div class="py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold border border-light-30 dark:border-dark-30 flex">{{ text }}
|
||||
<div class="flex gap-2" v-if="maxTraining[stat] >= 0">: Niveau {{ maxTraining[stat] }} (+{{ modifiers[stat] }}
|
||||
<NumberFieldRoot :default-value="data.progress.modifiers[stat] ?? 0" v-model="data.progress.modifiers[stat]" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
<NumberFieldRoot :default-value="data.modifiers[stat] ?? 0" v-model="data.modifiers[stat]" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 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-8 text-base font-normal bg-transparent px-2 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
@@ -331,14 +300,14 @@ useShortcuts({
|
||||
<div class="py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 flex gap-2 justify-center items-center" :class="{ 'text-light-red dark:text-dark-red': (modifierPoints ?? 0) < modifierSpent }">Modifieur bonus: {{ modifierPoints - modifierSpent }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training[stat]" :class="{ 'opacity-30': index > maxTraining[stat] + 1 }">
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption(stat, index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining[stat] + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training[stat]?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
|
||||
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption(stat, parseInt(index as unknown as string, 10) as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining[stat] + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.training[stat]?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="abilityOpen" :disabled="data.progress.race.index === undefined" @update:model-value="() => { trainingOpen = false; peopleOpen = false; spellOpen = false; notesOpen = false; }">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="abilityOpen" :disabled="data.people === undefined" @update:model-value="() => { trainingOpen = false; peopleOpen = false; spellOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Compétences</span>
|
||||
</template>
|
||||
@@ -350,7 +319,7 @@ useShortcuts({
|
||||
<div class="grid gap-4 grid-cols-6">
|
||||
<div v-for="(ability, index) of characterConfig.abilities" class="flex flex-col items-center border border-light-30 dark:border-dark-30 p-2">
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
<NumberFieldRoot :min="0" :default-value="data.progress.abilities[index] ? data.progress.abilities[index][0] : 0" @update:model-value="(value) => { data.progress.abilities[index] = [value, data.progress.abilities[index] ? data.progress.abilities[index][1] : 0]; }" class="border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
<NumberFieldRoot :min="0" :default-value="data.abilities[index] ? data.abilities[index][0] : 0" @update:model-value="(value) => { data.abilities[index] = [value, data.abilities[index] ? data.abilities[index][1] : 0]; }" class="border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 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-8 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>
|
||||
@@ -363,22 +332,22 @@ useShortcuts({
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="spellOpen" :disabled="data.progress.race.index === undefined" @update:model-value="() => { trainingOpen = false; peopleOpen = false; abilityOpen = false; notesOpen = false; }">
|
||||
<Collapsible class="border-b border-light-30 dark:border-dark-30 p-1" v-model="spellOpen" :disabled="data.people === undefined" @update:model-value="() => { trainingOpen = false; peopleOpen = false; abilityOpen = false; notesOpen = false; }">
|
||||
<template #label>
|
||||
<span class="font-bold text-xl">Sorts</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="flex flex-col gap-2 max-h-[50vh] px-4 relative overflow-y-auto">
|
||||
<div class="sticky top-0 py-2 bg-light-0 dark:bg-dark-0 z-10 flex gap-2 items-center">
|
||||
<span class="text-xl pe-4" :class="{ 'text-light-red dark:text-dark-red': spellsPoints < (data.progress.spells?.length ?? 0) }">Sorts: {{ data.progress.spells?.length ?? 0 }}/{{ spellsPoints }}</span>
|
||||
<span class="text-xl pe-4" :class="{ 'text-light-red dark:text-dark-red': spellsPoints < (data.spells?.length ?? 0) }">Sorts: {{ data.spells?.length ?? 0 }}/{{ spellsPoints }}</span>
|
||||
<TextInput label="Nom" v-model="spellFilter.text" />
|
||||
<Combobox label="Rang" v-model="spellFilter.ranks" multiple :options="[['Rang 1', 1], ['Rang 2', 2], ['Rang 3', 3]]" />
|
||||
<Combobox label="Type" v-model="spellFilter.types" multiple :options="[['Précision', 'precision'], ['Savoir', 'knowledge'], ['Instinct', 'instinct']]" />
|
||||
<Combobox label="Element" v-model="spellFilter.elements" multiple :options="[['Feu', 'fire'], ['Glace', 'ice'], ['Foudre', 'thunder'], ['Terre', 'earth'], ['Arcane', 'arcana'], ['Air', 'air'], ['Nature', 'nature'], ['Lumière', 'light'], ['Psy', 'psyche']]" />
|
||||
</div>
|
||||
<div class="grid gap-4 grid-cols-2">
|
||||
<div class="py-1 px-2 border border-light-30 dark:border-dark-30 flex flex-col hover:border-light-50 dark:hover:border-dark-50 cursor-pointer" v-for="spell of filterSpells(config.spells)" :class="{ '!border-accent-blue bg-accent-blue bg-opacity-20': data.progress.spells?.find(e => e === spell.id) }"
|
||||
@click="() => data.progress.spells?.includes(spell.id) ? data.progress.spells.splice(data.progress.spells.findIndex((e: string) => e === spell.id), 1) : data.progress.spells!.push(spell.id)">
|
||||
<div class="py-1 px-2 border border-light-30 dark:border-dark-30 flex flex-col hover:border-light-50 dark:hover:border-dark-50 cursor-pointer" v-for="spell of filterSpells(characterConfig.spells)" :class="{ '!border-accent-blue bg-accent-blue bg-opacity-20': data.spells?.find(e => e === spell.id) }"
|
||||
@click="() => data.spells?.includes(spell.id) ? data.spells.splice(data.spells.findIndex((e: string) => e === spell.id), 1) : data.spells!.push(spell.id)">
|
||||
<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">
|
||||
@@ -404,7 +373,7 @@ useShortcuts({
|
||||
<span class="font-bold text-xl">Notes libres</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<Editor class="min-h-[400px] border border-light-30 dark:border-dark-30" v-model="data.progress.notes" />
|
||||
<Editor class="min-h-[400px] border border-light-30 dark:border-dark-30" v-model="data.notes" />
|
||||
</template>
|
||||
</Collapsible>
|
||||
</div>
|
||||
|
||||
@@ -11,19 +11,7 @@ const id = useRouter().currentRoute.value.params.id;
|
||||
const { user } = useUserSession();
|
||||
const { add } = useToast();
|
||||
|
||||
const { data: character, status, error } = await useAsyncData(() => useRequestFetch()(`/api/character/${id}/compiled`));
|
||||
const { data: values } = await useAsyncData(() => useRequestFetch()(`/api/character/${id}/values`));
|
||||
|
||||
async function updateValues()
|
||||
{
|
||||
await useRequestFetch()(`/api/character/${id}/values`, {
|
||||
method: "post",
|
||||
body: values.value,
|
||||
onResponseError: (e) => {
|
||||
add({ duration: 25000, timer: true, type: "success", title: "Erreur", content: e.response.statusText });
|
||||
}
|
||||
});
|
||||
}
|
||||
const { data: character, status, error } = await useFetch(`/api/character/${id}/compiled`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -38,53 +26,46 @@ async function updateValues()
|
||||
</Head>
|
||||
<div class="flex flex-row gap-4 justify-between">
|
||||
<div></div>
|
||||
<div class="flex flex-row gap-6 items-center justify-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 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>{{ character.race === -1 ? "Race inconnue" : characterConfig.peoples[character.race].name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Niveau {{ character.level }}</span>
|
||||
<span>{{ character.race === -1 ? "Race inconnue" : characterConfig.peoples[character.race].name }}</span>
|
||||
<div class="flex gap-6 lg:border-l border-light-30 dark:border-dark-30 py-4 ps-4">
|
||||
<span class="flex flex-row items-center gap-2">PV: {{ character.health - character.values.hp }}/{{ character.health }}</span>
|
||||
<span class="flex flex-row items-center gap-2">Mana: {{ character.mana - character.values.mana }}/{{ character.mana }}</span>
|
||||
</div>
|
||||
<span class="h-full border-l border-light-30 dark:border-dark-30"></span>
|
||||
<span class="flex flex-row items-center gap-2">PV: <NumberFieldRoot v-model="values.health" @change="updateValues" class="border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 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-14 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>/ {{ character.health }}</span>
|
||||
<span class="flex flex-row items-center gap-2">Mana: <NumberFieldRoot v-model="values.mana" @change="updateValues" class="border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 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-14 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot>/ {{ character.mana }}</span>
|
||||
<span class="flex flex-row items-center gap-2">Armure: <NumberFieldRoot v-model="values.armor" @change="updateValues" class="border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
|
||||
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 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-14 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
|
||||
</NumberFieldRoot></span>
|
||||
</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 justify-center border-b border-light-30 dark:border-dark-30">
|
||||
<div class="flex relative ps-4">
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.strength }}</span><span>Force</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.dexterity }}</span><span>Dextérité</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.constitution }}</span><span>Constitution</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.intelligence }}</span><span>Intelligence</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.curiosity }}</span><span>Curiosité</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.charisma }}</span><span>Charisme</span></div>
|
||||
<div class="flex flex-col px-2 items-center"><span class="text-2xl font-bold">+{{ character.modifier.psyche }}</span><span>Psyché</span></div>
|
||||
<div class="grid 2xl:grid-cols-12 grid-cols-2 gap-4 items-center border-b border-light-30 dark:border-dark-30">
|
||||
<div class="flex relative justify-between ps-4 gap-2 2xl:col-span-6 lg:col-span-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 relative border-l border-light-30 dark:border-dark-30 ps-4">
|
||||
<div class="flex relative 2xl:border-l border-light-30 dark:border-dark-30 ps-4 2xl:col-span-2">
|
||||
<div class="flex flex-1 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="absolute top-0 left-0 bottom-0 right-0 bg-light-0 dark:bg-dark-0 bg-opacity-50 dark:bg-opacity-50 text-xl font-bold flex items-center justify-center">Les données secondaires arrivent bientôt.</div> -->
|
||||
</div>
|
||||
<div class="flex relative border-l border-light-30 dark:border-dark-30 ps-4">
|
||||
<div class="flex relative border-l border-light-30 dark:border-dark-30 ps-4 2xl:col-span-4">
|
||||
<div class="flex flex-col px-2">
|
||||
<span class="text-xl">Défense passive: <span class="text-2xl font-bold">{{ character.defense.static }}</span>/+<span class="text-2xl font-bold">{{ character.defense.passivedodge }}</span>/+<span class="text-2xl font-bold">{{ character.defense.passiveparry }}</span></span>
|
||||
<span class="text-xl">Défense active: <span class="float-right">+<span class="text-2xl font-bold">{{ character.defense.activedodge }}</span>/+<span class="text-2xl font-bold">{{ character.defense.activeparry }}</span></span></span>
|
||||
|
||||
Reference in New Issue
Block a user