Began generalizing insert forms for character properties to reduce duplicated code

This commit is contained in:
Stefan Zermatten
2019-04-01 16:58:15 +02:00
parent 6d68796a11
commit 053a7a36a6
9 changed files with 149 additions and 172 deletions

View File

@@ -60,7 +60,7 @@
callback(feature){
if (!feature) return;
feature.charId = charId;
let featureId = insertFeature.call({feature});
let featureId = insertFeature.call(feature);
return featureId
}
});

View File

@@ -15,23 +15,6 @@
<v-card-text>
{{description}}
</v-card-text>
<v-card-actions v-if="uses">
<v-spacer/>
<v-btn
flat
:disabled="uses - used <= 0"
@click="$emit('used')"
>
Use
</v-btn>
<v-btn
flat
:disabled="!used"
@click="$emit('reset')"
>
Reset
</v-btn>
</v-card-actions>
</toolbar-card>
</template>
@@ -43,9 +26,6 @@
charId: String,
name: String,
description: String,
uses: Number,
used: Number,
reset: String,
color: String,
enabled: Boolean,
alwaysEnabled: Boolean,

View File

@@ -1,74 +1,51 @@
<template lang="html">
<dialog-base>
<div slot="toolbar">
New Feature
</div>
<feature-edit
<property-insert-dialog
documentType="Feature"
:doc="feature"
:schema="schema"
@updateErrors="newErrors => errors = newErrors"
>
<feature-form
:feature="feature"
:errors="errors"
@change="change"
@update="update"
:debounce-time="0"
:errors="errors"
/>
<v-spacer slot="actions"/>
<v-btn
flat
slot="actions"
:disabled="!valid"
@click="$store.dispatch('popDialogStack', feature)"
>
Insert Feature
</v-btn>
</dialog-base>
</property-insert-dialog>
</template>
<script>
import FeatureEdit from '/imports/ui/components/features/FeatureEdit.vue';
import Features from '/imports/api/creature/properties/Features.js';
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import FeatureForm from '/imports/ui/components/features/FeatureForm.vue';
import Features, { FeatureSchema } from '/imports/api/creature/properties/Features.js';
import PropertyInsertDialog from '/imports/ui/components/properties/PropertyInsertDialog.vue';
export default {
components: {
FeatureEdit,
DialogBase,
FeatureForm,
PropertyInsertDialog,
},
data(){ return {
feature: {
name: 'New Feature',
description: null,
uses: null,
used: 0,
reset: null,
enabled: true,
alwaysEnabled: true,
color: '#9E9E9E',
},
valid: true,
schema: FeatureSchema,
errors: {},
}},
methods: {
change(update, ack){
update(update, ack){
for (key in update){
this.feature[key] = update[key];
}
if (ack) ack();
},
},
created(){
this.validationContext = Features.simpleSchema().newContext();
},
computed: {
errors(){
this.valid = true;
let cleanAtt = this.validationContext.clean(this.feature)
this.validationContext.validate(cleanAtt, {keys: [
'name', 'description', 'uses', 'used', 'reset', 'enabled',
'alwaysEnabled', 'color',
]});
let errors = {};
this.validationContext.validationErrors().forEach(error => {
if (this.valid) this.valid = false;
errors[error.name] = Features.simpleSchema().messageForError(error);
});
return errors;
try {
FeatureSchema.validate({$set: update}, {clean: true, modifier: true});
ack()
} catch (e){
ack(e.message);
}
},
},
};

View File

@@ -3,35 +3,10 @@
<text-field
label="Name"
:value="feature.name"
@change="(name, ack) => $emit('change', {name}, ack)"
@change="(name, ack) => $emit('update', {name}, ack)"
:error-messages="errors.name"
:debounce-time="debounceTime"
/>
<text-field
label="Used"
type="number"
:value="feature.used"
@change="(used, ack) => $emit('change', {used}, ack)"
:error-messages="errors.used"
:debounce-time="debounceTime"
/>
<text-field
label="Uses"
:value="feature.uses"
@change="(uses, ack) => $emit('change', {uses}, ack)"
:error-messages="errors.uses"
:debounce-time="debounceTime"
/>
<smart-select
label="Reset"
clearable
:items="resetOptions"
:value="feature.reset"
:error-messages="errors.reset"
:menu-props="{auto: true, lazy: true}"
@change="(reset, ack) => $emit('change', {reset}, ack)"
:debounce-time="debounceTime"
/>
<smart-select
label="Enabled"
:items="enabledOptions"
@@ -45,7 +20,7 @@
label="Description"
:value="feature.description"
:error-messages="errors.description"
@change="(description, ack) => $emit('change', {description}, ack)"
@change="(description, ack) => $emit('update', {description}, ack)"
:debounce-time="debounceTime"
/>
</div>
@@ -65,15 +40,6 @@
debounceTime: Number,
},
data(){ return{
resetOptions: [
{
text: 'Short rest',
value: 'shortRest',
}, {
text: 'Long rest',
value: 'longRest',
}
],
enabledOptions: [
{
text: 'Always enabled',
@@ -98,11 +64,11 @@
methods: {
changeEnabled(value, ack){
if (value === 'always'){
this.$emit('change', {enabled: true, alwaysEnabled: true}, ack);
this.$emit('update', {enabled: true, alwaysEnabled: true}, ack);
} else if (value === 'enabled'){
this.$emit('change', {enabled: true, alwaysEnabled: false}, ack);
this.$emit('update', {enabled: true, alwaysEnabled: false}, ack);
} else if (value === 'disabled'){
this.$emit('change', {enabled: false, alwaysEnabled: false}, ack);
this.$emit('update', {enabled: false, alwaysEnabled: false}, ack);
}
}
}

View File

@@ -0,0 +1,42 @@
<template lang="html">
<dialog-base>
<slot name="toolbar" slot="toolbar" :color="color">
<div>
{{doc.name}}
</div>
</slot>
<slot/>
<slot name="form" slot="edit" @update="update"/>
</dialog-base>
</template>
<script>
import getCollectionByName from '/imports/api/parenting/getCollectionByName.js';
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
import propertyUpdateMethods from '/imports/api/creature/properties/propertyUpdateMethods.js';
export default {
props: {
collection: String,
id: String,
},
meteor: {
doc(){
return fetchDocByRef({
id: this.id,
collection: this.collection,
});
},
},
methods: {
update(update, ack){
propertyUpdateMethods[this.collection].call({
_id: this.id,
update,
}, error => ack(error));
}
}
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,67 @@
<template lang="html">
<dialog-base>
<template slot="toolbar">
<div>
New {{documentType}}
</div>
</template>
<template #default>
<slot @update="update"/>
</template>
<template slot="actions">
<v-spacer/>
<v-btn
flat
:disabled="!valid"
@click="$store.dispatch('popDialogStack', doc)"
>
Insert {{documentType}}
</v-btn>
</template>
</dialog-base>
</template>
<script>
import Vue from 'vue';
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
export default {
components: {
DialogBase,
},
props: {
documentType: String,
schema: Object,
doc: Object,
},
data(){ return {
valid: true,
}},
watch: {
doc: {
handler(newDoc){
let validationContext = this.schema.newContext();
this.valid = true;
let cleanAtt = validationContext.clean(newDoc)
validationContext.validate(cleanAtt, {keys: [
'name', 'description', 'uses', 'used', 'reset', 'enabled',
'alwaysEnabled', 'color',
]});
let errors = {};
validationContext.validationErrors().forEach(error => {
if (this.valid) this.valid = false;
errors[error.name] = this.schema.messageForError(error);
});
this.$emit('updateErrors', errors);
},
deep: true,
},
},
methods: {
log: console.log
}
};
</script>
<style lang="css" scoped>
</style>

View File

@@ -15,6 +15,11 @@
</v-btn>
</template>
</v-toolbar>
<template v-if="breadcrumbs">
<v-card-text>
example > bread > crumb
</v-card-text>
</template>
<v-card-text id="base-dialog-body" v-scroll:#base-dialog-body="onScroll">
<v-tabs-items :value="isEditing ? 1 : 0" touchless>
<v-tab-item>
@@ -37,6 +42,7 @@
export default {
props: {
color: String,
breadcrumbs: Object,
},
data(){ return {
offsetTop: 0,

View File

@@ -14,7 +14,6 @@
@enter="enter"
@leave="leave"
>
<div class="sibling" key="sibling"/>
<v-card
v-for="(dialog, index) in dialogs"
:key="dialog._id"
@@ -25,7 +24,9 @@
:style="getDialogStyle(index)"
:elevation="6"
>
<component :is="dialog.component" v-bind="dialog.data" @pop="popDialogStack($event)" class="dialog-component"></component>
<transition name="slide">
<component :is="dialog.component" v-bind="dialog.data" @pop="popDialogStack($event)" class="dialog-component"></component>
</transition>
</v-card>
</transition-group>
</v-layout>
@@ -168,7 +169,7 @@
e.preventDefault();
}
},
watch:{
watch: {
dialogs(newDialogs) {
let el = document.documentElement;
if (newDialogs.length) {

62
app/package-lock.json generated
View File

@@ -1456,37 +1456,6 @@
"requires": {
"inherits": "~2.0.1",
"readable-stream": "^2.0.2"
},
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
},
"dependencies": {
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
}
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"stream-http": {
@@ -1499,37 +1468,6 @@
"readable-stream": "^2.3.3",
"to-arraybuffer": "^1.0.0",
"xtend": "^4.0.0"
},
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
},
"dependencies": {
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
}
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"string_decoder": {