Started work on library node insert forms
This commit is contained in:
@@ -5,6 +5,7 @@ import librarySchemas from '/imports/api/library/librarySchemas.js';
|
|||||||
import Libraries from '/imports/api/library/Libraries.js';
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||||
import getModifierFields from '/imports/api/getModifierFields.js';
|
import getModifierFields from '/imports/api/getModifierFields.js';
|
||||||
|
import simpleSchemaMixin from '/imports/api/creature/mixins/simpleSchemaMixin.js';
|
||||||
|
|
||||||
let LibraryNodes = new Mongo.Collection('libraryNodes');
|
let LibraryNodes = new Mongo.Collection('libraryNodes');
|
||||||
|
|
||||||
@@ -62,6 +63,18 @@ function assertNodeEditPermission(node, userId){
|
|||||||
return assertEditPermission(lib, 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({
|
const updateNode = new ValidatedMethod({
|
||||||
name: 'LibraryNodes.methods.update',
|
name: 'LibraryNodes.methods.update',
|
||||||
validate({_id, update}){
|
validate({_id, update}){
|
||||||
@@ -101,4 +114,4 @@ function libraryNodesToTree(ancestorId){
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default LibraryNodes;
|
export default LibraryNodes;
|
||||||
export { LibraryNodeSchema, updateNode };
|
export { LibraryNodeSchema, insertNode, updateNode };
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ Meteor.publish('library', function(libraryId){
|
|||||||
const user = Meteor.user();
|
const user = Meteor.user();
|
||||||
const userId = user && user._id;
|
const userId = user && user._id;
|
||||||
if (!userId) return [];
|
if (!userId) return [];
|
||||||
|
const subs = user && user.subscribedLibraries || [];
|
||||||
let libraryCursor = Libraries.find({
|
let libraryCursor = Libraries.find({
|
||||||
_id: libraryId,
|
_id: libraryId,
|
||||||
$or: [
|
$or: [
|
||||||
|
|||||||
30
app/imports/ui/components/forms/schemaFormMixin.js
Normal file
30
app/imports/ui/components/forms/schemaFormMixin.js
Normal 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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -79,11 +79,11 @@ export default {
|
|||||||
this.$emit('change', val, this.acknowledgeChange);
|
this.$emit('change', val, this.acknowledgeChange);
|
||||||
},
|
},
|
||||||
hasChangeListener(){
|
hasChangeListener(){
|
||||||
return this.$listeners && this.$listeners.change
|
return this.$listeners && this.$listeners.change;
|
||||||
},
|
},
|
||||||
forceSafeValueUpdate(){
|
forceSafeValueUpdate(){
|
||||||
// hack to force the value to update on the child component
|
// hack to force the value to update on the child component
|
||||||
this.safeValue = null
|
this.safeValue = null;
|
||||||
this.$nextTick(() => this.safeValue = this.value);
|
this.$nextTick(() => this.safeValue = this.value);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
47
app/imports/ui/components/properties/PropertySelector.vue
Normal file
47
app/imports/ui/components/properties/PropertySelector.vue
Normal 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>
|
||||||
@@ -44,6 +44,9 @@
|
|||||||
},
|
},
|
||||||
valid: true,
|
valid: true,
|
||||||
}},
|
}},
|
||||||
|
created(){
|
||||||
|
this.validationContext = Attributes.simpleSchema().newContext();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
change(update, ack){
|
change(update, ack){
|
||||||
for (key in update){
|
for (key in update){
|
||||||
@@ -58,9 +61,6 @@
|
|||||||
if (ack) ack();
|
if (ack) ack();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created(){
|
|
||||||
this.validationContext = Attributes.simpleSchema().newContext();
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
errors(){
|
errors(){
|
||||||
this.valid = true;
|
this.valid = true;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-layout column style="height: 100%;">
|
<v-layout column style="height: 100%;">
|
||||||
<v-toolbar :color="color || 'secondary'" dark class="base-dialog-toolbar" :flat="!offsetTop">
|
<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-icon>arrow_back</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<slot name="toolbar"></slot>
|
<slot name="toolbar"/>
|
||||||
<template v-if="$slots.edit">
|
<template v-if="$slots.edit">
|
||||||
<v-spacer/>
|
<v-spacer/>
|
||||||
<v-btn icon flat @click="$emit('remove')" v-if="isEditing">
|
<v-btn icon flat @click="$emit('remove')" v-if="isEditing">
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
example > bread > crumb
|
example > bread > crumb
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</template>
|
</template>
|
||||||
|
<slot name="unwrapped-content"/>
|
||||||
<v-card-text id="base-dialog-body" v-scroll:#base-dialog-body="onScroll">
|
<v-card-text id="base-dialog-body" v-scroll:#base-dialog-body="onScroll">
|
||||||
<v-tabs-items :value="isEditing ? 1 : 0" touchless>
|
<v-tabs-items :value="isEditing ? 1 : 0" touchless>
|
||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
props: {
|
props: {
|
||||||
color: String,
|
color: String,
|
||||||
breadcrumbs: Object,
|
breadcrumbs: Object,
|
||||||
|
overrideBackButton: Function,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data(){ return {
|
||||||
offsetTop: 0,
|
offsetTop: 0,
|
||||||
@@ -52,6 +54,13 @@
|
|||||||
onScroll(e){
|
onScroll(e){
|
||||||
this.offsetTop = e.target.scrollTop
|
this.offsetTop = e.target.scrollTop
|
||||||
},
|
},
|
||||||
|
back(){
|
||||||
|
if (this.overrideBackButton){
|
||||||
|
this.overrideBackButton();
|
||||||
|
} else {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
close(){
|
close(){
|
||||||
store.dispatch("popDialogStack");
|
store.dispatch("popDialogStack");
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import AttributeCreationDialog from '/imports/ui/creature/properties/attributes/
|
|||||||
import FeatureCreationDialog from '/imports/ui/creature/properties/features/FeatureCreationDialog.vue';
|
import FeatureCreationDialog from '/imports/ui/creature/properties/features/FeatureCreationDialog.vue';
|
||||||
import FeatureDialogContainer from '/imports/ui/creature/properties/features/FeatureDialogContainer.vue';
|
import FeatureDialogContainer from '/imports/ui/creature/properties/features/FeatureDialogContainer.vue';
|
||||||
import LibraryCreationDialog from '/imports/ui/library/LibraryCreationDialog.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';
|
import SkillDialogContainer from '/imports/ui/creature/properties/skills/SkillDialogContainer.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -13,5 +14,6 @@ export default {
|
|||||||
FeatureCreationDialog,
|
FeatureCreationDialog,
|
||||||
FeatureDialogContainer,
|
FeatureDialogContainer,
|
||||||
LibraryCreationDialog,
|
LibraryCreationDialog,
|
||||||
|
LibraryNodeCreationDialog,
|
||||||
SkillDialogContainer,
|
SkillDialogContainer,
|
||||||
};
|
};
|
||||||
|
|||||||
42
app/imports/ui/library/LibraryNodeCreationDialog.vue
Normal file
42
app/imports/ui/library/LibraryNodeCreationDialog.vue
Normal 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>
|
||||||
@@ -6,17 +6,38 @@
|
|||||||
:library-id="$route.params.id"
|
:library-id="$route.params.id"
|
||||||
/>
|
/>
|
||||||
</v-card>
|
</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>
|
</toolbar-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ToolbarLayout from '/imports/ui/layouts/ToolbarLayout.vue';
|
import ToolbarLayout from '/imports/ui/layouts/ToolbarLayout.vue';
|
||||||
import LibraryContentsContainer from '/imports/ui/library/LibraryContentsContainer.vue';
|
import LibraryContentsContainer from '/imports/ui/library/LibraryContentsContainer.vue';
|
||||||
|
import {insertNode} from '/imports/api/library/LibraryNodes.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ToolbarLayout,
|
ToolbarLayout,
|
||||||
LibraryContentsContainer,
|
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>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user