Added character creation dialog

This commit is contained in:
Stefan Zermatten
2018-10-11 16:39:55 +02:00
parent 4ac56a31de
commit d330e15dce
7 changed files with 358 additions and 18 deletions

View File

@@ -0,0 +1,294 @@
<template>
<dialog-base>
<template slot="toolbar">New Character</template>
<v-stepper v-model="step" class="no-shadow">
<v-stepper-header class="no-shadow">
<v-stepper-step :complete="step > 1" step="1">
Name
</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step :complete="step > 2" step="2">
Ability Scores
</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step :complete="step > 3" step="3">
Class
</v-stepper-step>
</v-stepper-header>
<v-stepper-items>
<v-stepper-content step="1">
<v-text-field label="Name" v-model="name"></v-text-field>
<v-text-field label="Gender" v-model="gender"></v-text-field>
<v-text-field label="Alignment" v-model="alignment"></v-text-field>
</v-stepper-content>
<v-stepper-content step="2">
<v-text-field label="Race" v-model="race"></v-text-field>
<v-layout row justify-center align-center>
<h3>Point Cost:</h3>
<h1 class="ml-2" :class="cost > 27 ? 'error--text' : ''">{{cost}}</h1>
<span class="ml-1">/27</span>
</v-layout>
<table class="point-buy-table mt-2">
<thead>
<tr class="font-weight-bold">
<td></td>
<td>Base Values</td>
<td>Race Bonus</td>
<td>Score</td>
<td>Modifier</td>
</tr>
</thead>
<tr>
<td>Strength</td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="baseStrength"
min="8"
max="15">
</v-text-field></td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="strengthBonus">
</v-text-field></td>
<td>{{baseStrength + strengthBonus}}</td>
<td>{{mod(baseStrength + strengthBonus)}}</td>
</tr>
<tr>
<td>Dexterity</td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="baseDexterity"
min="8"
max="15">
</v-text-field></td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="dexterityBonus">
</v-text-field></td>
<td>{{baseDexterity + dexterityBonus}}</td>
<td>{{mod(baseDexterity + dexterityBonus)}}</td>
</tr>
<tr>
<td>Constitution</td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="baseConstitution"
min="8"
max="15">
</v-text-field></td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="constitutionBonus">
</v-text-field></td>
<td>{{baseConstitution + constitutionBonus}}</td>
<td>{{mod(baseConstitution + constitutionBonus)}}</td>
</tr>
<tr>
<td>Intelligence</td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="baseIntelligence"
min="8"
max="15">
</v-text-field></td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="intelligenceBonus">
</v-text-field></td>
<td>{{baseIntelligence + intelligenceBonus}}</td>
<td>{{mod(baseIntelligence + intelligenceBonus)}}</td>
</tr>
<tr>
<td>Wisdom</td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="baseWisdom"
min="8"
max="15">
</v-text-field></td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="wisdomBonus">
</v-text-field></td>
<td>{{baseWisdom + wisdomBonus}}</td>
<td>{{mod(baseWisdom + wisdomBonus)}}</td>
</tr>
<tr>
<td>Charisma</td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="baseCharisma"
min="8"
max="15">
</v-text-field></td>
<td><v-text-field
type="number"
height="20"
reverse
v-model.number="charismaBonus">
</v-text-field></td>
<td>{{baseCharisma + charismaBonus}}</td>
<td>{{mod(baseCharisma + charismaBonus)}}</td>
</tr>
</table>
</v-stepper-content>
<v-stepper-content step="3">
<v-text-field
label="Class"
v-model="cls"
>
</v-text-field>
<v-select
:items="hitDiceItems"
label="Hit Dice"
v-model="hitDice">
</v-select>
</v-text-field>
</v-stepper-content>
</v-stepper-items>
</v-stepper>
<template slot="actions">
<v-btn flat @click="$emit('pop')">Cancel</v-btn>
<v-btn flat @click="step--" v-if="step > 1">Back</v-btn>
<v-spacer></v-spacer>
<v-btn color="accent" @click="step++" v-if="step < 3">Next</v-btn>
<v-btn
:flat="step < 3"
:color="step < 3? '' : 'accent'"
@click="submit">
Create
</v-btn>
</template>
</dialog-base>
</template>
<script>
import DialogBase from "/imports/ui/dialogStack/DialogBase.vue";
const getCost = function(score){
const costs = {
8: 0,
9: 1,
10: 2,
11: 3,
12: 4,
13: 5,
14: 7,
15: 9,
};
if (costs[score]){
return costs[score];
} else {
return null;
}
};
export default {
data(){return {
step: 1,
name: "New Character",
gender: "",
alignment: "",
race: "",
baseStrength: 10,
baseDexterity: 10,
baseConstitution: 10,
baseIntelligence: 10,
baseWisdom: 10,
baseCharisma: 10,
strengthBonus: 0,
dexterityBonus: 0,
constitutionBonus: 0,
intelligenceBonus: 0,
wisdomBonus: 0,
charismaBonus: 0,
hitDiceItems: ["d6", "d8", "d10", "d12"],
hitDice: "",
cls: "",
}},
methods: {
mod(score){
let mod = Math.floor((score - 10) / 2);
if (mod >= 0) {
return `+${mod}`;
} else {
return `${mod}`;
}
},
submit(){
let char = {
name: this.name,
gender: this.gender,
alignment: this.alignment,
race: this.race,
baseStrength: this.baseStrength,
baseDexterity: this.baseDexterity,
baseConstitution: this.baseConstitution,
baseIntelligence: this.baseIntelligence,
baseWisdom: this.baseWisdom,
baseCharisma: this.baseCharisma,
strengthBonus: this.strengthBonus,
dexterityBonus: this.dexterityBonus,
constitutionBonus: this.constitutionBonus,
intelligenceBonus: this.intelligenceBonus,
wisdomBonus: this.wisdomBonus,
charismaBonus: this.charismaBonus,
hitDice: this.hitDice,
cls: this.cls,
};
this.$emit("pop", char);
},
},
computed: {
cost(){
return [
this.baseStrength,
this.baseDexterity,
this.baseConstitution,
this.baseIntelligence,
this.baseWisdom,
this.baseCharisma,
].map(getCost)
.reduce((memo, score) => memo + score, 0);
},
},
components: {
DialogBase,
},
};
</script>
<style scoped>
.no-shadow {
box-shadow: none;
}
.point-buy-table {
width: 100%;
}
.point-buy-table td {
text-align: center;
padding: 0 8px 0 8px;
max-width: 50px;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<v-btn fab small>
<v-btn fab small @click="$emit('click')">
<v-icon>
{{icon}}
</v-icon>

View File

@@ -1,13 +1,39 @@
<template>
<v-card>
<v-toolbar color="primary" dark>
<slot name="toolbar"></slot>
</v-toolbar>
<v-layout>
<slot></slot>
</v-layout>
<v-card-actions>
<slot name="actions"></slot>
</v-card-actions>
<v-layout column style="height: 100%;">
<v-toolbar color="primary" dark class="base-dialog-toolbar" :flat="!offsetTop">
<slot name="toolbar"></slot>
</v-toolbar>
<div id="base-dialog-body" v-scroll:#base-dialog-body="onScroll">
<slot></slot>
</div>
<v-card-actions>
<slot name="actions"></slot>
</v-card-actions>
</v-layout>
</v-card>
</template>
<script>
export default {
data(){ return {
offsetTop: 0,
}},
methods: {
onScroll(e){
this.offsetTop = e.target.scrollTop
},
},
}
</script>
<style scoped>
.base-dialog-toolbar {
z-index: 1;
border-radius: 2px 2px 0 0;
}
#base-dialog-body {
flex-grow: 1;
overflow: auto;
}
</style>

View File

@@ -12,7 +12,7 @@
class="dialog"
:style="getDialogStyle(index)"
>
<component :is="dialog.component" :data="dialog.data"></component>
<component :is="dialog.component" :data="dialog.data" @pop="popDialogStack($event)"></component>
</div>
</transition-group>
</v-layout>
@@ -32,8 +32,8 @@
},
},
methods: {
popDialogStack(){
store.dispatch("popDialogStack");
popDialogStack(result){
store.dispatch("popDialogStack", result);
},
backdropClicked(event){
if (event.target === event.currentTarget) this.popDialogStack();
@@ -61,8 +61,8 @@
}
.dialog-sizer {
position: relative;
height: 500px;
width: 500px;
height: 600px;
width: 600px;
z-index: 5;
flex: initial;
}

View File

@@ -8,6 +8,7 @@ dialogStack.dialogs = [];
const dialogStackStore = {
state: {
dialogs: [],
currentResult: null,
},
mutations: {
pushDialogStack(state, {component, data, element, returnElement, callback}){
@@ -24,15 +25,18 @@ const dialogStackStore = {
updateHistory();
},
popDialogStackMutation (state, result){
console.log({popped: result});
const dialog = state.dialogs.pop();
state.currentResult = null;
updateHistory();
if (!dialog) return;
dialog.callback && dialog.callback(result);
if (dialog.callback) dialog.callback(result);
},
},
actions: {
popDialogStack(context, result){
if (history && history.state && history.state.openDialogs){
context.state.currentResult = result;
history.back();
} else {
context.commit("popDialogStackMutation", result)

View File

@@ -5,7 +5,7 @@ if (window){
let state = event.state;
let numDialogs = store.state.dialogStack.dialogs.length;
if (_.isFinite(state.openDialogs) && numDialogs > state.openDialogs){
store.commit("popDialogStackMutation");
store.commit("popDialogStackMutation", store.state.dialogStack.currentResult);
}
};
}

View File

@@ -65,15 +65,17 @@
<v-icon>add</v-icon>
<v-icon>close</v-icon>
</v-btn>
<labeled-fab icon="face" label="New Character"></labeled-fab>
<labeled-fab icon="face" label="New Character" @click="insertCharacter"></labeled-fab>
<labeled-fab icon="group" label="New Party"></labeled-fab>
</v-speed-dial>
</toolbar-layout>
</template>
<script>
import store from "/imports/ui/vuexStore.js";
import ToolbarLayout from "/imports/ui/layouts/ToolbarLayout.vue";
import LabeledFab from "/imports/ui/components/LabeledFab.vue";
import CharacterCreationDialog from "/imports/ui/character/CharacterCreationDialog.vue";
const characterTransform = function(char){
char.url = `\/character\/${char._id}\/${char.urlName || "-"}`;
@@ -118,6 +120,20 @@
).map(characterTransform);
},
},
methods: {
insertCharacter(e){
console.log(e);
store.commit("pushDialogStack", {
component: CharacterCreationDialog,
data: {},
element: undefined,
returnElement: undefined,
callback(result){
console.log({result});
},
});
},
},
components: {
ToolbarLayout,
LabeledFab,