Reorganized forms into their own folder

This commit is contained in:
Stefan Zermatten
2019-07-22 13:05:11 +02:00
parent dfa302a4a9
commit 4062d79e90
22 changed files with 51 additions and 150 deletions

View File

@@ -0,0 +1,189 @@
<template lang="html">
<div class="action-form">
<text-field
label="Name"
:value="model.name"
@change="(value, ack) => $emit('change', {path: ['name'], value, ack})"
:error-messages="errors.name"
:debounce-time="debounceTime"
/>
<smart-select
label="Type"
:items="actionTypes"
:value="model.type"
:error-messages="errors.type"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', {path: ['type'], value, ack})"
:hint="actionTypeHints[model.type]"
:debounce-time="debounceTime"
/>
<form-sections>
<form-section name="Advanced">
<smart-select
label="Target"
style="flex-basis: 300px;"
:items="targetOptions"
:value="model.target"
:error-messages="errors.target"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', {path: ['target'], value, ack})"
:debounce-time="debounceTime"
/>
<div class="layout row wrap">
<text-field
label="Uses"
hint="How many times this action can be used before needing to be reset"
style="flex-basis: 300px;"
:value="model.uses"
@change="(value, ack) => $emit('change', {path: ['uses'], value, ack})"
:error-messages="errors.uses"
:debounce-time="debounceTime"
/>
<text-field
label="Uses used"
type="number"
hint="How many times this action has already been used"
style="flex-basis: 300px;"
:value="model.usesUsed"
@change="(value, ack) => $emit('change', {path: ['usesUsed'], value, ack})"
:error-messages="errors.uses"
:debounce-time="debounceTime"
/>
</div>
<smart-select
label="Reset"
clearable
style="flex-basis: 300px;"
:items="resetOptions"
:value="model.reset"
:error-messages="errors.reset"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', {path: ['reset'], value, ack})"
:debounce-time="debounceTime"
/>
</form-section>
<form-section name="Damage and Adjustments">
<div class="caption">
Adjustments can be used to automatically spend resources or deal
damage when taking an action.
They apply damage to an attribute each time the action is taken.
</div>
<adjustment-list-form
:model="model.adjustments"
:parent-target="model.target"
@change="({path, value, ack}) => $emit('change', {path: ['adjustments', ...path], value, ack})"
@push="({path, value, ack}) => $emit('push', {path: ['adjustments', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['adjustments', ...path], ack})"
/>
</form-section>
<form-section name="Buffs">
<div class="caption">
Buffs apply temporary effects to characters when taking an action.
</div>
<buff-list-form
:model="model.buffs"
:parent-target="model.target"
:stored="stored"
@change="({path, value, ack}) => $emit('change', {path: ['buffs', ...path], value, ack})"
@push="({path, value, ack}) => $emit('push', {path: ['buffs', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['buffs', ...path], ack})"
/>
</form-section>
</form-sections>
</div>
</template>
<script>
import FormSection, {FormSections} from '/imports/ui/forms/components/FormSection.vue';
import AdjustmentListForm from 'imports/ui/forms/AdjustmentListForm.vue';
import BuffListForm from 'imports/ui/forms/BuffListForm.vue';
export default {
components: {
FormSection,
FormSections,
AdjustmentListForm,
BuffListForm,
},
props: {
stored: {
type: Boolean,
},
model: {
type: Object,
default: () => ({}),
},
errors: {
type: Object,
default: () => ({}),
},
debounceTime: Number,
},
data(){
let data = {
actionTypes: [
{
text: 'Action',
value: 'action',
}, {
text: 'Bonus action',
value: 'bonus',
}, {
text: 'Attack action',
value: 'attack',
help: 'Attack actions replace a single attack when you choose to use your Action to attack',
}, {
text: 'Reaction',
value: 'reaction',
}, {
text: 'Free action',
value: 'free',
help: 'You can take one free action on your turn without using an action or bonus action'
}, {
text: 'Long action',
value: 'long',
help: 'Long actions take longer than one turn to complete'
},
],
targetOptions: [
{
text: 'Self',
value: 'self',
}, {
text: 'Single target',
value: 'singleTarget',
}, {
text: 'Multiple targets',
value: 'multipleTargets',
},
],
resetOptions: [
{
text: 'Short rest',
value: 'shortRest',
}, {
text: 'Long rest',
value: 'longRest',
}
],
};
data.actionTypeHints = {};
data.actionTypes.forEach(type => {
data.actionTypeHints[type.value] = type.help;
});
return data;
},
};
</script>
<style lang="css" scoped>
.no-flex {
flex: initial;
}
.layout.row.wrap {
margin-right: -8px;
}
.layout.row.wrap > *{
margin-right: 8px;
}
</style>

View File

@@ -0,0 +1,98 @@
<template lang="html">
<div>
<div class="layout row">
<text-field
label="Attribute"
hint="The attribute this adjustment will apply to"
style="flex-basis: 300px;"
:value="model.stat"
@change="(value, ack) => $emit('change', {path: ['stat'], value, ack})"
:error-messages="errors.stat"
:debounce-time="debounceTime"
/>
<text-field
label="Damage"
hint="The amount of damage to apply to the selected stat, can be a calculation or roll"
style="flex-basis: 300px;"
:value="model.damage"
@change="(value, ack) => $emit('change', {path: ['damage'], value, ack})"
:error-messages="errors.damage"
:debounce-time="debounceTime"
/>
</div>
<smart-select
v-if="parentTarget !== 'self'"
label="Target"
:hint="targetOptionHint"
:items="targetOptions"
:value="model.target"
:error-messages="errors.target"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', {path: ['target'], value, ack})"
:debounce-time="debounceTime"
/>
</div>
</template>
<script>
export default {
props: {
model: {
type: Object,
default: () => ({}),
},
errors: {
type: Object,
default: () => ({}),
},
parentTarget: {
type: String,
},
debounceTime: Number,
},
computed: {
targetOptions(){
if (this.parentTarget === 'singleTarget') {
return [
{
text: 'Self',
value: 'self',
}, {
text: 'Target',
value: 'every',
},
];
} else {
return [
{
text: 'Self',
value: 'self',
}, {
text: 'Roll once for each target',
value: 'each',
}, {
text: 'Roll once and apply to every target',
value: 'every',
},
];
}
},
targetOptionHint(){
let hints = {
self: 'The damage will be applied to the character\'s own attribute when taking the action',
target: 'The damage will be applied to the target of the action',
each: 'The damage will be rolled separately for each of the targets of the action',
every: 'The damage will be rolled once and applied to each of the targets of the action',
};
if (this.parentTarget === 'singleTarget'){
hints.each = hints.target;
hints.every = hints.target;
}
return hints[this.model.target];
}
},
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,74 @@
<template lang="html">
<div>
<v-slide-x-transition group>
<div
v-for="(adjustment, i) in model"
:key="adjustment._id || i"
>
<v-divider v-if="i !== 0"/>
<adjustment-form
class="mt-4"
:model="adjustment"
:parent-target="parentTarget"
@change="({path, value, ack}) => $emit('change', {path: [i, ...path], value, ack})"
/>
<div>
<v-btn outline icon large class="ma-3" @click="$emit('pull', {path: [i]})">
<v-icon>delete</v-icon>
</v-btn>
</div>
</div>
</v-slide-x-transition>
<div class="layout row justify-end">
<v-btn
:loading="addAdjustmentLoading"
:disabled="addAdjustmentLoading"
outline
@click="addAdjustment"
>
<v-icon>add</v-icon>
Add Adjustment
</v-btn>
</div>
</div>
</template>
<script>
import AdjustmentForm from 'imports/ui/forms/AdjustmentForm.vue';
import AdjustmentSchema from '/imports/api/creature/subSchemas/AdjustmentSchema.js';
export default {
components: {
AdjustmentForm,
},
data(){return {
addAdjustmentLoading: false,
}},
methods: {
acknowledgeAddAdjustment(){
this.addAdjustmentLoading = false;
},
addAdjustment(){
this.addAdjustmentLoading = true;
this.$emit('push', {
path: [],
value: AdjustmentSchema.clean({}),
ack: this.acknowledgeAddAdjustment,
});
},
},
props: {
model: {
type: Array,
default: () => ([]),
},
parentTarget: {
type: String,
},
debounceTime: Number,
},
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,174 @@
<template lang="html">
<div class="attribute-form">
<div class="layout column align-center">
<text-field
label="Base Value"
type="number"
class="base-value-field text-xs-center large-format no-flex"
:value="model.baseValue"
@change="(value, ack) => $emit('change', {path: ['baseValue'], value, ack})"
hint="This is the value of the attribute before effects are applied"
:error-messages="errors.baseValue"
:debounce-time="debounceTime"
/>
</div>
<div class="layout row wrap">
<text-field
label="Name"
:value="model.name"
@change="(value, ack) => $emit('change', {path: ['name'], value, ack})"
:error-messages="errors.name"
:debounce-time="debounceTime"
/>
<text-field
label="Variable name"
:value="model.variableName"
style="flex-basis: 300px;"
@change="(value, ack) => $emit('change', {path: ['variableName'], value, ack})"
hint="Use this name in formulae to reference this attribute"
:error-messages="errors.variableName"
:debounce-time="debounceTime"
/>
</div>
<smart-select
label="Type"
:items="attributeTypes"
:value="model.type"
:error-messages="errors.type"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', {path: ['type'], value, ack})"
:hint="attributeTypeHints[model.type]"
:debounce-time="debounceTime"
/>
<form-section name="Advanced" standalone>
<div class="layout column align-center">
<v-switch
label="Allow decimal values"
class="no-flex"
:value="model.decimal"
:error-messages="errors.decimal"
@change="e => $emit('change', $emit('change', {path: ['decimal'], value: !!e, ack}))"
/>
<div class="layout row justify-center" style="align-self: stretch;">
<text-field
label="Damage"
type="number"
class="damage-field text-xs-center"
style="max-width: 300px;"
hint="The attribute's final value is reduced by this amount"
:value="model.damage"
@change="(value, ack) => $emit('change', {path: ['damage'], value, ack})"
:error-messages="errors.adjustment"
:debounce-time="debounceTime"
/>
</div>
</div>
<div class="layout row wrap">
<smart-select
label="Reset"
clearable
style="flex-basis: 300px;"
:items="resetOptions"
:value="model.reset"
:error-messages="errors.reset"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', {path: ['reset'], value, ack})"
:debounce-time="debounceTime"
/>
<text-field
label="Reset Multiplier"
type="number"
style="flex-basis: 400px;"
:value="model.resetMultiplier"
:error-messages="errors.resetMultiplier"
@change="(value, ack) => $emit('change', {path: ['resetMultiplier'], value, ack})"
hint="Some attributes, like hit dice, only reset by half their total on a long rest"
:debounce-time="debounceTime"
/>
</div>
</form-section>
</div>
</template>
<script>
import FormSection from '/imports/ui/forms/components/FormSection.vue';
export default {
components: {
FormSection,
},
props: {
model: {
type: Object,
default: () => ({}),
},
errors: {
type: Object,
default: () => ({}),
},
debounceTime: Number,
},
data(){
let data = {
attributeTypes: [
{
text: 'Ability score',
value: 'ability',
help: 'Ability scores are your primary attributes, like Strength and Intelligence',
}, {
text: 'Stat',
value: 'stat',
help: 'Stats are attributes with a numerical value like speed or carrying capacity',
}, {
text: 'Modifier',
value: 'modifier',
help: 'Modifiers are attributes that are added to rolls, like proficiency bonus',
}, {
text: 'Hit dice',
value: 'hitDice',
}, {
text: 'Health bar',
value: 'healthBar',
}, {
text: 'Resource',
value: 'resource',
help: 'Resources are attributes that are spent to fuel actions, like sorcery points or ki'
}, {
text: 'Spell slot',
value: 'spellSlot',
}, {
text: 'Utility',
value: 'utility',
help: 'Utility attributes aren\'t displayed on your character sheet, but can be referenced or used in calculations',
},
],
resetOptions: [
{
text: 'Short rest',
value: 'shortRest',
}, {
text: 'Long rest',
value: 'longRest',
}
],
};
data.attributeTypeHints = {};
data.attributeTypes.forEach(type => {
data.attributeTypeHints[type.value] = type.help;
});
return data;
},
};
</script>
<style lang="css" scoped>
.no-flex {
flex: initial;
}
.layout.row.wrap {
margin-right: -8px;
}
.layout.row.wrap > *{
margin-right: 8px;
}
</style>

View File

@@ -0,0 +1,113 @@
<template lang="html">
<div class="buff-form">
<text-field
label="Name"
:value="model.name"
@change="(value, ack) => $emit('change', {path: ['name'], value, ack})"
:error-messages="errors.name"
:debounce-time="debounceTime"
/>
<text-area
label="Description"
:value="model.description"
@change="(value, ack) => $emit('change', {path: ['description'], value, ack})"
:error-messages="errors.description"
:debounce-time="debounceTime"
/>
<text-field
label="Duration"
hint="How long the buff lasts"
:value="model.duration"
@change="(value, ack) => $emit('change', {path: ['duration'], value, ack})"
:error-messages="errors.duration"
:debounce-time="debounceTime"
/>
<div v-if="stored">
<smart-select
v-if="parentTarget !== 'self'"
label="Target"
:hint="targetOptionHint"
:items="targetOptions"
:value="model.target"
:error-messages="errors.target"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', {path: ['target'], value, ack})"
:debounce-time="debounceTime"
/>
<effect-list-form
:model="model.effects"
@change="({path, value, ack}) => $emit('change', {path: ['effects', ...path], value, ack})"
@push="({path, value, ack}) => $emit('push', {path: ['effects', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['effects', ...path], ack})"
/>
</div>
</div>
</template>
<script>
import EffectListForm from '/imports/ui/forms/EffectListForm.vue';
export default {
props: {
stored: Boolean,
model: {
type: Object,
default: () => ({}),
},
errors: {
type: Object,
default: () => ({}),
},
parentTarget: {
type: String,
},
debounceTime: Number,
},
components: {
EffectListForm,
},
computed: {
targetOptions(){
if (this.parentTarget === 'singleTarget') {
return [
{
text: 'Self',
value: 'self',
}, {
text: 'Target',
value: 'every',
},
];
} else {
return [
{
text: 'Self',
value: 'self',
}, {
text: 'Roll once for each target',
value: 'each',
}, {
text: 'Roll once and apply to every target',
value: 'every',
},
];
}
},
targetOptionHint(){
let hints = {
self: 'The buff will be applied to the character taking the action',
target: 'The buff will be applied to the target of the action',
each: 'The buff will be rolled separately for each of the targets of the action',
every: 'The buff will be rolled once and applied to each of the targets of the action',
};
if (this.parentTarget === 'singleTarget'){
hints.each = hints.target;
hints.every = hints.target;
}
return hints[this.model.target];
}
}
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,77 @@
<template lang="html">
<div>
<v-slide-x-transition group>
<div
v-for="(buff, i) in model"
:key="buff._id || i"
>
<v-divider v-if="i !== 0"/>
<div class="layout row align-center">
<div style="flex-grow: 1;">
<buff-form
class="mt-4"
:model="buff"
:parent-target="parentTarget"
:stored="stored"
@change="({path, value, ack}) => $emit('change', {path: [i, ...path], value, ack})"
@push="({path, value, ack}) => $emit('push', {path: [i, ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: [i, ...path], ack})"
/>
</div>
<v-btn outline icon large class="ma-3" @click="$emit('pull', {path: [i]})">
<v-icon>delete</v-icon>
</v-btn>
</div>
</div>
</v-slide-x-transition>
<div class="layout row justify-end">
<v-btn
:loading="addBuffLoading"
:disabled="addBuffLoading"
outline
@click="addBuff"
>
<v-icon>add</v-icon>
Add Buff
</v-btn>
</div>
</div>
</template>
<script>
import BuffForm from 'imports/ui/forms/BuffForm.vue';
import {StoredBuffSchema, AppliedBuffSchema} from '/imports/api/creature/properties/Buffs.js';
export default {
components: {
BuffForm,
},
props: {
stored: Boolean,
model: {
type: Array,
default: () => ([]),
},
parentTarget: {
type: String,
},
debounceTime: Number,
},
data(){return {
addBuffLoading: false,
}},
methods: {
acknowledgeAddBuff(){
this.addBuffLoading = false;
},
addBuff(){
this.addBuffLoading = true;
let schema = this.stored ? StoredBuffSchema : AppliedBuffSchema;
this.$emit('push', {path: [], value: schema.clean({}), ack: this.acknowledgeAddBuff});
},
},
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,147 @@
<template lang="html">
<div class="layout row wrap justify-start effect-form">
<smart-select
label="Operation"
append-icon="arrow_drop_down"
class="mx-2"
:menu-props="{transition: 'slide-y-transition', lazy: true}"
:items="operations"
:value="model.operation"
@change="(value, ack) => $emit('change', {path: ['operation'], value, ack})"
>
<v-icon
class="icon"
slot="prepend"
:class="iconClass"
>{{displayedIcon}}</v-icon>
<template slot="item" slot-scope="item">
<v-icon
class="icon mr-2"
>{{getEffectIcon(item.item.value, 1)}}</v-icon>
{{item.item.text}}
</template>
</smart-select>
<text-field
label="Value"
class="mr-2"
:persistent-hint="needsValue"
:value="needsValue ? (model.calculation) : ' '"
:disabled="!needsValue"
:hint="!isFinite(model.calculation) && model.result ? model.result + '' : '' "
@change="(value, ack) => $emit('change', {path: ['calculation'], value, ack})"
/>
<text-field
label="Stat"
class="mr-2"
append-icon="arrow_drop_down"
item-text="name"
item-value="variableName"
:menu-props="{transition: 'slide-y-transition', lazy: true}"
:value="model.stat"
:items="stats"
@change="(value, ack) => $emit('change', {path: ['stat'], value, ack})"
/>
</div>
</template>
<script>
import getEffectIcon from '/imports/ui/utility/getEffectIcon.js';
const ICON_SPIN_DURATION = 300;
export default {
props: {
model: {
type: Object,
default: () => ({}),
},
stats: {
type: Array,
},
},
data(){ return {
displayedIcon: 'add',
iconClass: '',
operations: [
{value: 'base', text: 'Base Value'},
{value: 'add', text: 'Add'},
{value: 'mul', text: 'Multiply'},
{value: 'min', text: 'Minimum'},
{value: 'max', text: 'Maximum'},
{value: 'advantage', text: 'Advantage'},
{value: 'disadvantage', text: 'Disadvantage'},
{value: 'passiveAdd', text: 'Passive Bonus'},
{value: 'fail', text: 'Fail'},
{value: 'conditional', text: 'Conditional Benefit'},
],
}},
computed: {
needsValue(){
switch(this.model.operation) {
case 'base': return true;
case 'add': return true;
case 'mul': return true;
case 'min': return true;
case 'max': return true;
case 'advantage': return false;
case 'disadvantage': return false;
case 'passiveAdd': return true;
case 'fail': return false;
case 'conditional': return true;
}
},
},
methods: {
getEffectIcon,
},
watch: {
'model.operation': {
immediate: true,
handler(newValue, oldValue, e){
let newIcon = getEffectIcon(newValue, 1);
if (!oldValue){
// Skip animation
this.displayedIcon = newIcon;
} else {
this.iconClass="leaving";
setTimeout(() => {
this.displayedIcon = newIcon;
this.iconClass="arriving";
requestAnimationFrame(() => {
this.iconClass="";
});
}, ICON_SPIN_DURATION / 2);
}
},
},
}
};
</script>
<style lang="css" scoped>
.theme--light .icon {
color: black;
}
.icon {
min-width: 30px;
transition: transform 0.15s linear, opacity 0.15s ease;
transform-origin: 18px center;
margin-left: -12px;
}
.icon.leaving {
transform: translateY(-24px);
opacity: 0;
}
.icon.arriving {
transform: translateY(24px);
opacity: 0;
transition: none;
}
.hidden {
visibility: hidden;
}
.effect-form > div {
flex-basis: 220px;
}
</style>

View File

@@ -0,0 +1,151 @@
<template lang="html">
<div>
<v-slide-x-transition group>
<div
v-for="(effect, i) in model"
:key="effect._id || i"
>
<v-divider v-if="i !== 0"/>
<div class="layout row align-center">
<div style="flex-grow: 1;">
<effect-form
class="mt-4"
:model="effect"
:parent-target="parentTarget"
:stored="stored"
@change="({path, value, ack}) => $emit('change', {path: [i, ...path], value, ack})"
@pull="(ack) => $emit('pull', {path: [i], ack})"
/>
</div>
<v-btn outline icon large class="ma-3" @click="$emit('pull', {path: [i]})">
<v-icon>delete</v-icon>
</v-btn>
</div>
</div>
</v-slide-x-transition>
<div class="layout row justify-end">
<v-btn
:loading="addEffectLoading"
:disabled="addEffectLoading"
outline
@click="addEffect"
>
<v-icon>add</v-icon>
Add Effect
</v-btn>
</div>
</div>
</template>
<script>
import EffectForm from '/imports/ui/forms/EffectForm.vue';
import { EffectSchema } from '/imports/api/creature/properties/Effects.js';
export default {
components: {
EffectForm,
},
props: {
stored: Boolean,
model: {
type: Array,
default: () => ([]),
},
parentTarget: {
type: String,
},
debounceTime: Number,
},
data(){return {
addEffectLoading: false,
}},
methods: {
acknowledgeAddEffect(){
this.addEffectLoading = false;
},
addEffect(){
this.addEffectLoading = true;
this.$emit('push', {
path: [],
value: EffectSchema.clean({}),
ack: this.acknowledgeAddEffect,
});
},
},
}
</script>
<style lang="css" scoped>
</style>
<template lang="html">
<div>
<v-slide-x-transition group>
<div
v-for="(effect, i) in model"
:key="effect._id || i"
>
<v-divider v-if="i !== 0"/>
<div class="layout row align-center">
<div style="flex-grow: 1;">
<effect-form
class="mt-4"
:model="effect"
@change="({path, value, ack}) => $emit('change', {path: [i, ...path], value, ack})"
/>
</div>
<v-btn outline icon large class="ma-3" @click="$emit('pull', {path: [i]})">
<v-icon>delete</v-icon>
</v-btn>
</div>
</div>
</v-slide-x-transition>
<div class="layout row justify-end">
<v-btn
:loading="addEffectLoading"
:disabled="addEffectLoading"
outline
@click="addEffect"
>
<v-icon>add</v-icon>
Add Effect
</v-btn>
</div>
</div>
</template>
<script>
import EffectForm from '/imports/ui/forms/EffectForm.vue';
import { EffectSchema } from '/imports/api/creature/properties/Effects.js';
export default {
components: {
EffectForm,
},
props: {
model: {
type: Array,
default: () => ([]),
},
debounceTime: Number,
},
data(){return {
addEffectLoading: false,
}},
methods: {
acknowledgeAddEffect(){
this.addEffectLoading = false;
},
addEffect(){
this.addEffectLoading = true;
this.$emit('push', {
path: [],
value: EffectSchema.clean({}),
ack: this.acknowledgeAddEffect,
});
},
},
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,83 @@
<template lang="html">
<div class="feature-form">
<text-field
label="Name"
:value="model.name"
@change="(value, ack) => $emit('change', {path: ['name'], value, ack})"
:error-messages="errors.name"
:debounce-time="debounceTime"
/>
<smart-select
label="Enabled"
:items="enabledOptions"
:value="enabledStatus"
:error-messages="errors.enabled || errors.alwaysEnabled"
:menu-props="{auto: true, lazy: true}"
@change="changeEnabled"
:debounce-time="debounceTime"
/>
<text-area
label="Description"
:value="model.description"
:error-messages="errors.description"
@change="(value, ack) => $emit('change', {path: ['description'], value, ack})"
:debounce-time="debounceTime"
/>
</div>
</template>
<script>
export default {
props: {
model: {
type: Object,
default: () => ({}),
},
errors: {
type: Object,
default: () => ({}),
},
debounceTime: Number,
},
data(){ return{
enabledOptions: [
{
text: 'Always enabled',
value: 'always',
}, {
text: 'Enabled',
value: 'enabled',
}, {
text: 'Disabled',
value: 'disabled',
}
],
}},
computed: {
enabledStatus(){
if (!this.model) return;
if (this.model.alwaysEnabled) return 'always';
if (this.model.enabled) return 'enabled';
return 'disabled';
},
},
methods: {
changeEnabled(value, ack){
let change = ({enabled, alwaysEnabled}) => {
this.$emit('change', {path: ['enabled'], value: enabled, ack});
this.$emit('change', {path: ['alwaysEnabled'], value: alwaysEnabled, ack});
}
if (value === 'always'){
change({enabled: true, alwaysEnabled: true});
} else if (value === 'enabled'){
change({enabled: true, alwaysEnabled: false});
} else if (value === 'disabled'){
change({enabled: false, alwaysEnabled: false});
}
},
},
};
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,34 @@
<template lang="html">
<form-sections v-if="standalone">
<v-expansion-panel-content>
<div slot="header" class="subheading">
{{name}}
</div>
<v-card-text>
<slot/>
</v-card-text>
</v-expansion-panel-content>
</form-sections>
<v-expansion-panel-content v-else>
<div slot="header" class="subheading">
{{name}}
</div>
<v-card-text>
<slot/>
</v-card-text>
</v-expansion-panel-content>
</template>
<script>
import FormSections from '/imports/ui/forms/components/FormSections.vue';
export default {
components: {
FormSections,
},
props: {
name: String,
standalone: Boolean,
},
}
export { FormSections };
</script>

View File

@@ -0,0 +1,9 @@
<template lang="html">
<v-expansion-panel popout expand>
<slot/>
</v-expansion-panel>
</template>
<script>
export default {}
</script>

View File

@@ -0,0 +1,13 @@
import ActionForm from 'imports/ui/forms/ActionForm.vue';
import AttributeForm from 'imports/ui/forms/AttributeForm.vue';
import BuffForm from '/imports/ui/forms/BuffForm.vue';
import EffectForm from '/imports/ui/forms/EffectForm.vue';
import FeatureForm from '/imports/ui/forms/FeatureForm.vue';
export default {
action: ActionForm,
attribute: AttributeForm,
buff: BuffForm,
effect: EffectForm,
feature: FeatureForm,
};

View File

@@ -0,0 +1,59 @@
/**
* 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
*/
import { get, toPath } from 'lodash';
function resolvePath(model, path){
let arrayPath = toPath(path);
if (arrayPath.length === 1){
return { object: model, key: arrayPath[0] };
}
let objectPath = arrayPath.slice(0, -1);
let key = arrayPath.slice(-1);
let object = get(model, objectPath);
return {object, key};
};
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;
},
},
methods: {
// Sets the value at the given path
change({path, value, ack}){
let {object, key} = resolvePath(this.model, path);
this.$set(object, key, value);
if (ack) ack();
},
push({path, value, ack}){
get(this.model, path).push(value);
if (ack) ack();
},
pull({path, ack}){
let {object, key} = resolvePath(this.model, path);
object.splice(key, 1);
if (ack) ack();
},
},
};
export default schemaFormMixin;