Library attribute insert form complete
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
67
app/imports/ui/library/LibraryNodeInsertForm.vue
Normal file
67
app/imports/ui/library/LibraryNodeInsertForm.vue
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user