Started work on library node insert forms

This commit is contained in:
Stefan Zermatten
2019-06-27 16:52:28 +02:00
parent bd4fb58935
commit 9757da2cae
10 changed files with 173 additions and 8 deletions

View File

@@ -5,6 +5,7 @@ import librarySchemas from '/imports/api/library/librarySchemas.js';
import Libraries from '/imports/api/library/Libraries.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import getModifierFields from '/imports/api/getModifierFields.js';
import simpleSchemaMixin from '/imports/api/creature/mixins/simpleSchemaMixin.js';
let LibraryNodes = new Mongo.Collection('libraryNodes');
@@ -62,6 +63,18 @@ function assertNodeEditPermission(node, userId){
return assertEditPermission(lib, userId);
}
const insertNode = new ValidatedMethod({
name: 'LibraryNodes.methods.insert',
mixins: [
simpleSchemaMixin,
],
schema: LibraryNodeSchema,
run(libraryNode) {
assertNodeEditPermission(libraryNode, this.userId);
return LibraryNodes.insert(libraryNode);
},
});
const updateNode = new ValidatedMethod({
name: 'LibraryNodes.methods.update',
validate({_id, update}){
@@ -101,4 +114,4 @@ function libraryNodesToTree(ancestorId){
}
export default LibraryNodes;
export { LibraryNodeSchema, updateNode };
export { LibraryNodeSchema, insertNode, updateNode };

View File

@@ -46,6 +46,7 @@ Meteor.publish('library', function(libraryId){
const user = Meteor.user();
const userId = user && user._id;
if (!userId) return [];
const subs = user && user.subscribedLibraries || [];
let libraryCursor = Libraries.find({
_id: libraryId,
$or: [

View File

@@ -0,0 +1,30 @@
/**
* 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();
},
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;
},
},
};
}

View File

@@ -79,11 +79,11 @@ export default {
this.$emit('change', val, this.acknowledgeChange);
},
hasChangeListener(){
return this.$listeners && this.$listeners.change
return this.$listeners && this.$listeners.change;
},
forceSafeValueUpdate(){
// hack to force the value to update on the child component
this.safeValue = null
this.safeValue = null;
this.$nextTick(() => this.safeValue = this.value);
},
},

View File

@@ -0,0 +1,47 @@
<template lang="html">
<div>
<v-container fluid grid-list-lg fill-height>
<v-layout row wrap>
<v-flex v-for="property in properties" :key="property.name" xs4>
<v-card hover @click="$emit('select', property.type)">
<div class="layout row align-center justify-center" style="min-height: 70px;">
<v-icon x-large>{{ property.icon }}</v-icon>
</div>
<h3 class="subtitle pb-3" style="text-align: center;">
{{ property.name }}
</h3>
</v-card>
</v-flex>
</v-layout>
</v-container>
</div>
</template>
<script>
export default {
data(){return {
properties: [
{name: 'Creature', icon: 'accessibility', type: 'creature'},
{name: 'Action', icon: 'offline-bolt', type: 'action'},
{name: 'Attribute', icon: 'star-rate', type: 'attribute'},
{name: 'Class', icon: 'school', type: 'class'},
{name: 'Class Level', icon: 'plus-one', type: 'classLevel'},
{name: 'Damage Multiplier', icon: 'layers', type: 'damageMultiplier'},
{name: 'Effect', icon: 'show-chart', type: 'effect'},
{name: 'Experience', icon: 'add', type: 'experience'},
{name: 'Feature', icon: 'subject', type: 'feature'},
{name: 'Folder', icon: 'folder', type: 'folder'},
{name: 'Note', icon: 'note', type: 'note'},
{name: 'Proficiency', icon: 'radio-button-checked', type: 'proficiency'},
{name: 'Skill', icon: 'check-box', type: 'skill'},
{name: 'Spell List', icon: 'list', type: 'spellList'},
{name: 'Spell', icon: 'whatshot', type: 'spell'},
{name: 'Container', icon: 'work', type: 'container'},
{name: 'Item', icon: 'category', type: 'item'},
],
}}
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -44,6 +44,9 @@
},
valid: true,
}},
created(){
this.validationContext = Attributes.simpleSchema().newContext();
},
methods: {
change(update, ack){
for (key in update){
@@ -58,9 +61,6 @@
if (ack) ack();
},
},
created(){
this.validationContext = Attributes.simpleSchema().newContext();
},
computed: {
errors(){
this.valid = true;

View File

@@ -1,10 +1,10 @@
<template>
<v-layout column style="height: 100%;">
<v-toolbar :color="color || 'secondary'" dark class="base-dialog-toolbar" :flat="!offsetTop">
<v-btn icon flat @click="close">
<v-btn icon flat @click="back">
<v-icon>arrow_back</v-icon>
</v-btn>
<slot name="toolbar"></slot>
<slot name="toolbar"/>
<template v-if="$slots.edit">
<v-spacer/>
<v-btn icon flat @click="$emit('remove')" v-if="isEditing">
@@ -20,6 +20,7 @@
example > bread > crumb
</v-card-text>
</template>
<slot name="unwrapped-content"/>
<v-card-text id="base-dialog-body" v-scroll:#base-dialog-body="onScroll">
<v-tabs-items :value="isEditing ? 1 : 0" touchless>
<v-tab-item>
@@ -43,6 +44,7 @@
props: {
color: String,
breadcrumbs: Object,
overrideBackButton: Function,
},
data(){ return {
offsetTop: 0,
@@ -52,6 +54,13 @@
onScroll(e){
this.offsetTop = e.target.scrollTop
},
back(){
if (this.overrideBackButton){
this.overrideBackButton();
} else {
this.close();
}
},
close(){
store.dispatch("popDialogStack");
},

View File

@@ -4,6 +4,7 @@ import AttributeCreationDialog from '/imports/ui/creature/properties/attributes/
import FeatureCreationDialog from '/imports/ui/creature/properties/features/FeatureCreationDialog.vue';
import FeatureDialogContainer from '/imports/ui/creature/properties/features/FeatureDialogContainer.vue';
import LibraryCreationDialog from '/imports/ui/library/LibraryCreationDialog.vue';
import LibraryNodeCreationDialog from '/imports/ui/library/LibraryNodeCreationDialog.vue';
import SkillDialogContainer from '/imports/ui/creature/properties/skills/SkillDialogContainer.vue';
export default {
@@ -13,5 +14,6 @@ export default {
FeatureCreationDialog,
FeatureDialogContainer,
LibraryCreationDialog,
LibraryNodeCreationDialog,
SkillDialogContainer,
};

View File

@@ -0,0 +1,42 @@
<template lang="html">
<dialog-base :override-back-button="step === 2 && back">
<v-window v-model="step" slot="unwrapped-content" style="height: 100%;">
<v-window-item :value="1">
<property-selector @select="setType"/>
</v-window-item>
<v-window-item :value="2">
<v-card-text>
{{ type }}
</v-card-text>
</v-window-item>
</v-window>
</dialog-base>
</template>
<script>
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import PropertySelector from '/imports/ui/components/properties/PropertySelector.vue';
export default {
data() { return {
type: undefined,
step: 1,
};},
components: {
DialogBase,
PropertySelector,
},
methods: {
back(){
this.step = 1;
},
setType(type){
this.type = type;
this.step = 2;
}
},
};
</script>
<style lang="css" scoped>
</style>

View File

@@ -6,17 +6,38 @@
:library-id="$route.params.id"
/>
</v-card>
<v-btn fixed fab bottom right
color="primary"
@click="insertLibraryNode"
data-id="insert-library-node-fab"
>
<v-icon>add</v-icon>
</v-btn>
</toolbar-layout>
</template>
<script>
import ToolbarLayout from '/imports/ui/layouts/ToolbarLayout.vue';
import LibraryContentsContainer from '/imports/ui/library/LibraryContentsContainer.vue';
import {insertNode} from '/imports/api/library/LibraryNodes.js';
export default {
components: {
ToolbarLayout,
LibraryContentsContainer,
},
methods: {
insertLibraryNode(){
this.$store.commit('pushDialogStack', {
component: 'library-node-creation-dialog',
elementId: 'insert-library-node-fab',
callback(libraryNode){
if (!libraryNode) return;
let libraryNodeId = insertNode.call(libraryNode);
return libraryNodeId;
}
});
},
},
};
</script>