Library attribute insert form complete

This commit is contained in:
Stefan Zermatten
2019-07-02 17:28:07 +02:00
parent 4abb5edbf3
commit 93d8a8d33e
9 changed files with 152 additions and 79 deletions

View File

@@ -1,4 +1,4 @@
{
"undef": false,
"esversion": 6
"esversion": 9,
}

View File

@@ -26,11 +26,14 @@ let AttributeSchema = schema({
name: {
type: String,
optional: true,
defaultValue: 'New Attribute',
},
// The technical, lowercase, single-word name used in formulae
variableName: {
type: String,
regEx: VARIABLE_NAME_REGEX,
min: 3,
defaultValue: 'newAttribute',
},
// How it is displayed and computed is determined by type
type: {
@@ -46,6 +49,7 @@ let AttributeSchema = schema({
'spellSlot', // Level 1, 2, 3... spell slots
'utility', // Aren't displayed, Jump height, Carry capacity
],
defaultValue: 'stat',
index: 1,
},
// The starting value, before effects

View File

@@ -65,10 +65,7 @@ function assertNodeEditPermission(node, userId){
const insertNode = new ValidatedMethod({
name: 'LibraryNodes.methods.insert',
mixins: [
simpleSchemaMixin,
],
schema: LibraryNodeSchema,
validate: null,
run(libraryNode) {
assertNodeEditPermission(libraryNode, this.userId);
return LibraryNodes.insert(libraryNode);

View File

@@ -2,29 +2,37 @@
* Forms that take in a schema and a model of the current data, manages smart
* inputs, and sends update events when valid data model changes must occur
*/
export default function schemaFormMixin(schema){
return {
data(){ return {
valid: true,
};},
created(){
this.validationContext = schema.newContext();
const schemaFormMixin = {
data(){ return {
valid: true,
};},
computed: {
errors(){
this.valid = true;
if (!this.model){
throw new Error("this.model must be set");
}
if (!this.validationContext) return {};
let cleanModel = this.validationContext.clean(this.model, {
getAutoValues: false,
});
this.validationContext.validate(cleanModel);
let errors = {};
this.validationContext.validationErrors().forEach(error => {
if (this.valid) this.valid = false;
errors[error.name] = this.schema.messageForError(error);
});
return errors;
},
computed: {
errors(){
this.valid = true;
if (!this.model){
throw new Error("this.model must be set");
}
let cleanModel = this.validationContext.clean(this.model);
this.validationContext.validate(cleanModel);
let errors = {};
this.validationContext.validationErrors().forEach(error => {
if (this.valid) this.valid = false;
errors[error.name] = Attributes.simpleSchema().messageForError(error);
});
return errors;
},
},
methods: {
change(modifier, ack){
for (let key in modifier){
this.$set(this.model, key, modifier[key])
}
if (ack) ack();
},
};
}
},
};
export default schemaFormMixin;

View File

@@ -2,14 +2,14 @@
<div>
<text-field
label="Name"
:value="attribute.name"
:value="model.name"
@change="(name, ack) => $emit('change', {name}, ack)"
:error-messages="errors.name"
:debounce-time="debounceTime"
/>
<text-field
label="Variable name"
:value="attribute.variableName"
:value="model.variableName"
@change="(variableName, ack) => $emit('change', {variableName}, ack)"
hint="Use this name in formulae to reference this attribute"
:error-messages="errors.variableName"
@@ -18,8 +18,8 @@
<text-field
label="Base Value"
type="number"
:value="attribute.baseValue"
@change="(baseValue, ack) => $emit('change', {baseValue: +baseValue}, ack)"
:value="model.baseValue"
@change="(baseValue, ack) => $emit('change', {baseValue}, ack)"
hint="This is the value of the attribute before effects are applied"
:error-messages="errors.baseValue"
:debounce-time="debounceTime"
@@ -27,15 +27,15 @@
<text-field
label="Damage"
type="number"
:value="-attribute.adjustment"
@change="(damage, ack) => $emit('change', {adjustment: -damage || null}, ack)"
:value="damage"
@change="(damage, ack) => $emit('change', {adjustment: -damage || damage}, ack)"
:error-messages="errors.adjustment"
:debounce-time="debounceTime"
/>
<smart-select
label="Type"
:items="attributeTypes"
:value="attribute.type"
:value="model.type"
:error-messages="errors.type"
:menu-props="{auto: true, lazy: true}"
@change="(type, ack) => $emit('change', {type}, ack)"
@@ -43,7 +43,7 @@
/>
<v-switch
label="Allow decimal values"
:value="attribute.decimal"
:value="model.decimal"
:error-messages="errors.decimal"
@change="e => $emit('change', {decimal: !!e})"
/>
@@ -51,7 +51,7 @@
label="Reset"
clearable
:items="resetOptions"
:value="attribute.reset"
:value="model.reset"
:error-messages="errors.reset"
:menu-props="{auto: true, lazy: true}"
@change="(reset, ack) => $emit('change', {reset}, ack)"
@@ -60,9 +60,9 @@
<text-field
label="Reset Multiplier"
type="number"
:value="attribute.resetMultiplier"
:value="model.resetMultiplier"
:error-messages="errors.resetMultiplier"
@change="(resetMultiplier, ack) => $emit('change', {resetMultiplier: +resetMultiplier}, ack)"
@change="(resetMultiplier, ack) => $emit('change', {resetMultiplier}, ack)"
hint="Some attributes, like hit dice, only reset by half their total on a long rest"
:debounce-time="debounceTime"
/>
@@ -72,7 +72,7 @@
<script>
export default {
props: {
attribute: {
model: {
type: Object,
default: () => ({}),
},
@@ -82,6 +82,11 @@
},
debounceTime: Number,
},
computed: {
damage(){
return this.model.adjustment && -this.model.adjustment
},
},
data(){ return{
attributeTypes: [
{

View File

@@ -1,27 +1,24 @@
<template lang="html">
<dialog-base :override-back-button="step === 2 ? back : undefined">
<div slot="toolbar">{{ step === 2 ? `Add ${property.name}` : 'Add Library Content'}}</div>
<v-window v-model="step" slot="unwrapped-content" style="height: 100%;">
<v-window-item :value="1">
<property-selector @select="setProperty"/>
</v-window-item>
<v-window-item :value="2">
<v-card-text>
<library-node-form :type="property && property.type"/>
</v-card-text>
</v-window-item>
</v-window>
<div slot="actions" v-if="step === 2" class="layout row justify-end">
<v-btn flat>Insert</v-btn>
</div>
</dialog-base>
<transition-group name="slide">
<dialog-base v-show="step == 1" class="step-1" key="left">
<div slot="toolbar">Add Library Content</div>
<property-selector @select="setProperty"/>
</dialog-base>
<library-node-insert-form
v-show="step == 2"
class="step-2"
key="right"
:type="property && property.type"
:property-name="property && property.name"
@back="step = 1"
/>
</transition-group>
</template>
<script>
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import PropertySelector from '/imports/ui/creature/properties/PropertySelector.vue';
import LibraryNodeForm from '/imports/ui/library/LibraryNodeForm.vue';
import LibraryNodeInsertForm from '/imports/ui/library/LibraryNodeInsertForm.vue';
export default {
data() { return {
@@ -31,12 +28,9 @@ export default {
components: {
DialogBase,
PropertySelector,
LibraryNodeForm,
LibraryNodeInsertForm,
},
methods: {
back(){
this.step = 1;
},
setProperty(property){
this.property = property;
this.step = 2;
@@ -46,4 +40,16 @@ export default {
</script>
<style lang="css" scoped>
.slide-enter-active, .slide-leave-active {
transition: transform .3s ease;
}
.slide-enter-active.step-1, .slide-leave-active.step-1{
position: absolute;
}
.slide-enter.step-1, .slide-leave-to.step-1 {
transform: translateX(-100%);
}
.slide-enter.step-2, .slide-leave-to.step-2 {
transform: translateX(100%);
}
</style>

View File

@@ -1,16 +0,0 @@
<template lang="html">
<component v-if="type" :is="type" class="library-node-form"/>
</template>
<script>
import propertyFormIndex from '/imports/ui/creature/properties/propertyFormIndex.js';
export default {
props: {
type: String,
},
components: propertyFormIndex,
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,67 @@
<template lang="html">
<dialog-base :override-back-button="() => $emit('back')">
<div slot="toolbar">Add {{propertyName}}</div>
<v-card-text>
<component
v-if="type"
:is="type"
class="library-node-form"
:model="model"
:errors="errors"
@change="change"
/>
</v-card-text>
<div
slot="actions"
class="layout row justify-end"
>
<v-btn
flat
:disabled="!valid"
@click="$store.dispatch('popDialogStack', model)"
>Insert</v-btn>
</div>
</dialog-base>
</template>
<script>
import librarySchemas from '/imports/api/library/librarySchemas.js';
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import propertyFormIndex from '/imports/ui/creature/properties/propertyFormIndex.js';
import schemaFormMixin from '/imports/ui/components/forms/schemaFormMixin.js';
export default {
components: {
...propertyFormIndex,
DialogBase,
},
mixins: [schemaFormMixin],
data(){return {
model: {
libraryNodeType: this.type,
},
schema: undefined,
validationContext: undefined,
};},
props: {
propertyName: String,
type: String,
},
watch: {
type(newType){
this.schema = librarySchemas[newType];
this.validationContext = this.schema.newContext();
let model = this.schema.clean({});
model.libraryNodeType = newType;
this.model = model;
},
},
methods: {
insert(){
console.log(this.model);
}
}
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -33,6 +33,8 @@
elementId: 'insert-library-node-fab',
callback(libraryNode){
if (!libraryNode) return;
console.log({libraryNode});
throw "TODO: give this library node ancestry before inserting it"
let libraryNodeId = insertNode.call(libraryNode);
return libraryNodeId;
}