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, "undef": false,
"esversion": 6 "esversion": 9,
} }

View File

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

View File

@@ -65,10 +65,7 @@ function assertNodeEditPermission(node, userId){
const insertNode = new ValidatedMethod({ const insertNode = new ValidatedMethod({
name: 'LibraryNodes.methods.insert', name: 'LibraryNodes.methods.insert',
mixins: [ validate: null,
simpleSchemaMixin,
],
schema: LibraryNodeSchema,
run(libraryNode) { run(libraryNode) {
assertNodeEditPermission(libraryNode, this.userId); assertNodeEditPermission(libraryNode, this.userId);
return LibraryNodes.insert(libraryNode); 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 * 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 * inputs, and sends update events when valid data model changes must occur
*/ */
export default function schemaFormMixin(schema){ const schemaFormMixin = {
return { data(){ return {
data(){ return { valid: true,
valid: true, };},
};}, computed: {
created(){ errors(){
this.validationContext = schema.newContext(); 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(){ methods: {
this.valid = true; change(modifier, ack){
if (!this.model){ for (let key in modifier){
throw new Error("this.model must be set"); this.$set(this.model, key, modifier[key])
} }
let cleanModel = this.validationContext.clean(this.model); if (ack) ack();
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;
},
}, },
}; },
} };
export default schemaFormMixin;

View File

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

View File

@@ -1,27 +1,24 @@
<template lang="html"> <template lang="html">
<dialog-base :override-back-button="step === 2 ? back : undefined"> <transition-group name="slide">
<div slot="toolbar">{{ step === 2 ? `Add ${property.name}` : 'Add Library Content'}}</div> <dialog-base v-show="step == 1" class="step-1" key="left">
<v-window v-model="step" slot="unwrapped-content" style="height: 100%;"> <div slot="toolbar">Add Library Content</div>
<v-window-item :value="1"> <property-selector @select="setProperty"/>
<property-selector @select="setProperty"/> </dialog-base>
</v-window-item> <library-node-insert-form
<v-window-item :value="2"> v-show="step == 2"
<v-card-text> class="step-2"
<library-node-form :type="property && property.type"/> key="right"
</v-card-text> :type="property && property.type"
</v-window-item> :property-name="property && property.name"
</v-window> @back="step = 1"
/>
<div slot="actions" v-if="step === 2" class="layout row justify-end"> </transition-group>
<v-btn flat>Insert</v-btn>
</div>
</dialog-base>
</template> </template>
<script> <script>
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue'; import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import PropertySelector from '/imports/ui/creature/properties/PropertySelector.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 { export default {
data() { return { data() { return {
@@ -31,12 +28,9 @@ export default {
components: { components: {
DialogBase, DialogBase,
PropertySelector, PropertySelector,
LibraryNodeForm, LibraryNodeInsertForm,
}, },
methods: { methods: {
back(){
this.step = 1;
},
setProperty(property){ setProperty(property){
this.property = property; this.property = property;
this.step = 2; this.step = 2;
@@ -46,4 +40,16 @@ export default {
</script> </script>
<style lang="css" scoped> <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> </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', elementId: 'insert-library-node-fab',
callback(libraryNode){ callback(libraryNode){
if (!libraryNode) return; if (!libraryNode) return;
console.log({libraryNode});
throw "TODO: give this library node ancestry before inserting it"
let libraryNodeId = insertNode.call(libraryNode); let libraryNodeId = insertNode.call(libraryNode);
return libraryNodeId; return libraryNodeId;
} }