Training viewer and properties manager preparation
This commit is contained in:
parent
42915d699f
commit
218b68db60
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { MAIN_STATS, mainStatTexts, type CharacterConfig } from '~/types/character';
|
||||||
|
import PreviewA from './prose/PreviewA.vue';
|
||||||
|
|
||||||
|
const { config } = defineProps<{
|
||||||
|
config: CharacterConfig
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const position = ref(0);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TabsRoot class="flex flex-1 flex-row justify-start items-stretch w-full" :default-value="MAIN_STATS[0]">
|
||||||
|
<TabsList class="flex flex-col gap-3 relative">
|
||||||
|
<TabsTrigger v-for="(stat, i) of MAIN_STATS" :value="stat" class="block w-2.5 h-2.5 m-px outline outline-1 outline-transparent hover:outline-light-70 dark:hover:outline-dark-70 rounded-full bg-light-40 dark:bg-dark-40 cursor-pointer" @click="position = i"></TabsTrigger>
|
||||||
|
<!-- <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> -->
|
||||||
|
<span :style="{ 'top': position * 1.5 + 'em' }" :data-text="mainStatTexts[MAIN_STATS[position]]" class="rounded-full w-3 h-3 bg-accent-blue absolute transition-[top]
|
||||||
|
after:content-[attr(data-text)] after:absolute after:-top-2 after:left-4 after:p-px after:bg-light-0 dark:after:bg-dark-0"></span>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent v-for="(stat) of MAIN_STATS" :value="stat" class="flex-1">
|
||||||
|
<div class="flex flex-row overflow-x-auto w-full flex-nowrap gap-4">
|
||||||
|
<div class="w-96 flex flex-col gap-4 justify-between" v-for="(level) of config.training[stat]">
|
||||||
|
<div class="border border-light-35 dark:border-dark-35 px-3 py-1" v-for="(option) of level"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</TabsRoot>
|
||||||
|
</template>
|
||||||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -39,7 +39,7 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 flex-row relative h-screen overflow-hidden">
|
<div class="flex flex-1 flex-row relative max-w-[100vw] h-screen overflow-hidden">
|
||||||
<CollapsibleContent asChild forceMount>
|
<CollapsibleContent asChild forceMount>
|
||||||
<div class="bg-light-0 dark:bg-dark-0 z-40 w-screen md:w-[18rem] border-r border-light-30 dark:border-dark-30 flex flex-col justify-between my-2 max-md:data-[state=closed]:hidden">
|
<div class="bg-light-0 dark:bg-dark-0 z-40 w-screen md:w-[18rem] border-r border-light-30 dark:border-dark-30 flex flex-col justify-between my-2 max-md:data-[state=closed]:hidden">
|
||||||
<div class="flex-1 px-2 max-w-full max-h-full overflow-y-auto overflow-x-hidden">
|
<div class="flex-1 px-2 max-w-full max-h-full overflow-y-auto overflow-x-hidden">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import characterConfig from '#shared/character-config.json';
|
||||||
|
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||||
|
import PreviewA from '~/components/prose/PreviewA.vue';
|
||||||
|
import { mainStatTexts, MAIN_STATS, type CharacterConfig } from '~/types/character';
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
const config = ref<CharacterConfig>(characterConfig);
|
||||||
|
const trainingTab = ref(0);
|
||||||
|
|
||||||
|
function copy()
|
||||||
|
{
|
||||||
|
navigator.clipboard.writeText(JSON.stringify(config.value));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Head>
|
||||||
|
<Title>d[any] - Edition de données</Title>
|
||||||
|
</Head>
|
||||||
|
<TabsRoot class="flex flex-1 flex-col justify-start items-center gap-4 px-8 w-full" default-value="training">
|
||||||
|
<TabsList class="flex flex-row gap-4 self-center relative px-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="peoples" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Peuples</TabsTrigger>
|
||||||
|
<TabsTrigger value="training" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Entrainement</TabsTrigger>
|
||||||
|
<TabsTrigger value="abilities" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Compétences</TabsTrigger>
|
||||||
|
<TabsTrigger value="spells" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Sorts</TabsTrigger>
|
||||||
|
<Tooltip message="Copier le JSON" side="right"><Button icon @click="copy" class="p-2"><Icon icon="radix-icons:clipboard-copy" /></Button></Tooltip>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="peoples" class="flex-1 overflow-auto">
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="training">
|
||||||
|
<TrainingViewer :config="config" />
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="abilities" class="flex-1 overflow-auto">
|
||||||
|
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="spells" class="flex-1 overflow-auto">
|
||||||
|
|
||||||
|
</TabsContent>
|
||||||
|
</TabsRoot>
|
||||||
|
<!-- <Collapsible class="border-b border-light-30 dark:border-dark-30 p-1">
|
||||||
|
<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.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(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">
|
||||||
|
<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="">Rang {{ spell.rank }}</span><span>/</span>
|
||||||
|
<span class="">{{ spellTypeTexts[spell.type] }}</span><span>/</span>
|
||||||
|
<span class="">{{ spell.cost }} mana</span><span>/</span>
|
||||||
|
<span class="">{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MarkdownRenderer :content="spell.effect" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Collapsible> -->
|
||||||
|
</template>
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { unifySlug } from '~/shared/general.util';
|
|
||||||
import type { Feature } from '~/types/character';
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
guestsGoesTo: '/user/login',
|
|
||||||
rights: ['admin', 'editor'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const id = unifySlug(useRouter().currentRoute.value.params.id);
|
|
||||||
const { add } = useToast();
|
|
||||||
const homebrew = ref<Feature>({
|
|
||||||
id: id === 'new' ? -1 : parseInt(id, 10),
|
|
||||||
list: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
if(id !== 'new')
|
|
||||||
{
|
|
||||||
const _homebrew = await useRequestFetch()(`/api/homebrew/${id}`);
|
|
||||||
|
|
||||||
if(!_homebrew) throw new Error('Donnée de personnalisation introuvable');
|
|
||||||
|
|
||||||
homebrew.value = Object.assign(homebrew.value, _homebrew);
|
|
||||||
}
|
|
||||||
|
|
||||||
function save(force: boolean)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Head>
|
|
||||||
<Title>d[any] - Edition de {{ homebrew.name || 'nouvelle personnalisation' }}</Title>
|
|
||||||
</Head>
|
|
||||||
<div class="flex flex-col gap-8 align-center">
|
|
||||||
<div class="flex flex-row gap-4 align-center justify-center">
|
|
||||||
<div class="flex flex-row gap-4 align-center justify-center">
|
|
||||||
<Label class="flex items-center justify-between flex-row gap-2">
|
|
||||||
<span class="pb-1 mx-2 md:p-0">Titre</span>
|
|
||||||
<input class="caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50
|
|
||||||
bg-light-20 dark:bg-dark-20 outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40
|
|
||||||
border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20"
|
|
||||||
type="text" v-model="homebrew.name">
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div class="self-center">
|
|
||||||
<Tooltip side="right" message="Ctrl+S"><Button @click="() => save(true)">Enregistrer</Button></Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-1 flex-col min-w-[800px] w-[75vw] max-w-[1200px]">
|
|
||||||
<div class="m-2 overflow-auto">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<template>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -8,7 +8,6 @@ export const TRAINING_LEVELS = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] as const;
|
||||||
export const SPELL_TYPES = ["precision","knowledge","instinct","arts"] as const; export type SpellType = typeof SPELL_TYPES[number];
|
export const SPELL_TYPES = ["precision","knowledge","instinct","arts"] as const; export type SpellType = typeof SPELL_TYPES[number];
|
||||||
export const CATEGORIES = ["action","reaction","freeaction","misc"] as const; export type Category = typeof CATEGORIES[number];
|
export const CATEGORIES = ["action","reaction","freeaction","misc"] as const; export type Category = typeof CATEGORIES[number];
|
||||||
export const SPELL_ELEMENTS = ["fire","ice","thunder","earth","arcana","air","nature","light","psyche"] as const; export type SpellElement = typeof SPELL_ELEMENTS[number];
|
export const SPELL_ELEMENTS = ["fire","ice","thunder","earth","arcana","air","nature","light","psyche"] as const; export type SpellElement = typeof SPELL_ELEMENTS[number];
|
||||||
export const RESISTANCES = ["stun","bleed","poison","fear","influence","charm","possesion","precision","knowledge","instinct"] as const; export type Resistance = typeof RESISTANCES[number];
|
|
||||||
|
|
||||||
export type DoubleIndex<T extends number | string> = [T, number];
|
export type DoubleIndex<T extends number | string> = [T, number];
|
||||||
|
|
||||||
|
|
@ -109,7 +108,6 @@ export type CharacterConfig = {
|
||||||
peoples: Race[],
|
peoples: Race[],
|
||||||
training: Record<MainStat, Record<TrainingLevel, TrainingOption[]>>;
|
training: Record<MainStat, Record<TrainingLevel, TrainingOption[]>>;
|
||||||
abilities: Record<Ability, AbilityConfig>;
|
abilities: Record<Ability, AbilityConfig>;
|
||||||
resistances: Record<Resistance, ResistanceConfig>;
|
|
||||||
spells: SpellConfig[];
|
spells: SpellConfig[];
|
||||||
};
|
};
|
||||||
export type SpellConfig = {
|
export type SpellConfig = {
|
||||||
|
|
@ -128,10 +126,6 @@ export type AbilityConfig = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
};
|
};
|
||||||
export type ResistanceConfig = {
|
|
||||||
name: string;
|
|
||||||
statistic: MainStat;
|
|
||||||
};
|
|
||||||
export type Race = {
|
export type Race = {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|
@ -150,12 +144,19 @@ export type RaceOption = {
|
||||||
spellslots?: number;
|
spellslots?: number;
|
||||||
};
|
};
|
||||||
export type FeatureItem = {
|
export type FeatureItem = {
|
||||||
category: "freeaction" | "misc";
|
category: "misc";
|
||||||
text: string;
|
text: string;
|
||||||
} | {
|
} | {
|
||||||
category: "action" | "reaction";
|
category: "action";
|
||||||
cost: 1 | 2 | 3;
|
cost: 1 | 2 | 3;
|
||||||
text: string;
|
text: string;
|
||||||
|
} | {
|
||||||
|
category: "reaction";
|
||||||
|
cost: 1 | 2;
|
||||||
|
text: string;
|
||||||
|
} | {
|
||||||
|
category: "freeaction";
|
||||||
|
text: string;
|
||||||
} | {
|
} | {
|
||||||
category: "value";
|
category: "value";
|
||||||
type: "add" | "remove" | "set";
|
type: "add" | "remove" | "set";
|
||||||
|
|
@ -167,12 +168,7 @@ export type FeatureItem = {
|
||||||
kind: "spells";
|
kind: "spells";
|
||||||
asset: string;
|
asset: string;
|
||||||
}
|
}
|
||||||
export type Feature = {
|
type FeatureCategory = FeatureItem["category"];
|
||||||
id: number;
|
|
||||||
name?: string
|
|
||||||
text?: string;
|
|
||||||
list?: FeatureItem[];
|
|
||||||
};
|
|
||||||
export type TrainingOption = {
|
export type TrainingOption = {
|
||||||
description: Array<{
|
description: Array<{
|
||||||
text: string;
|
text: string;
|
||||||
|
|
@ -189,7 +185,8 @@ export type TrainingOption = {
|
||||||
mastery?: keyof CompiledCharacter["mastery"];
|
mastery?: keyof CompiledCharacter["mastery"];
|
||||||
spellrank?: SpellType;
|
spellrank?: SpellType;
|
||||||
defense?: Array<keyof CompiledCharacter["defense"]>;
|
defense?: Array<keyof CompiledCharacter["defense"]>;
|
||||||
resistance?: [Resistance, "attack" | "defense"][];
|
resistance?: Record<MainStat, number>;
|
||||||
|
bonus?: Record<string, number>;
|
||||||
spell?: string;
|
spell?: string;
|
||||||
|
|
||||||
//Used during character creation, not used by compiler
|
//Used during character creation, not used by compiler
|
||||||
|
|
@ -240,13 +237,13 @@ export type CompiledCharacter = {
|
||||||
magicinstinct: number;
|
magicinstinct: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
//First is attack, second is defense
|
bonus: Record<string, number>; //Any special bonus goes here
|
||||||
resistance: Record<Resistance, [number, number]>;
|
resistance: Record<MainStat, number>;
|
||||||
|
|
||||||
modifier: Record<MainStat, number>;
|
modifier: Record<MainStat, number>;
|
||||||
abilities: Partial<Record<Ability, number>>;
|
abilities: Partial<Record<Ability, number>>;
|
||||||
level: number;
|
level: number;
|
||||||
features: Record<Category, string[]>; //Currently: List of training option as text. TODO: Update to a more complex structure later
|
features: { [K in FeatureCategory]: Array<Extract<FeatureItem, { category: K }>> };
|
||||||
|
|
||||||
notes: string;
|
notes: string;
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue