Iterated on class UI
This commit is contained in:
@@ -1,34 +1,95 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
import { SlotSchema, ComputedOnlySlotSchema } from './Slots.js';
|
||||
|
||||
// Classes are like slots, except they only take class levels and enforce that
|
||||
// lower levels are taken before higher levels
|
||||
let ClassSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
// Only `classLevel`s with the same variable name can fill the class
|
||||
variableName: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
}).extend(SlotSchema);
|
||||
slotTags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'slotTags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
extraTags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.extraTagsCount,
|
||||
},
|
||||
'extraTags.$': {
|
||||
type: Object,
|
||||
},
|
||||
'extraTags.$._id': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
autoValue(){
|
||||
if (!this.isSet) return Random.id();
|
||||
}
|
||||
},
|
||||
'extraTags.$.operation': {
|
||||
type: String,
|
||||
allowedValues: ['OR', 'NOT'],
|
||||
defaultValue: 'OR',
|
||||
},
|
||||
'extraTags.$.tags': {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'extraTags.$.tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
slotCondition: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyClassSchema = createPropertySchema({
|
||||
level: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
missingLevels: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
'missingLevels.$': {
|
||||
type: SimpleSchema.Integer,
|
||||
},
|
||||
}).extend(ComputedOnlySlotSchema);
|
||||
// Computed fields
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
slotCondition: {
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
},
|
||||
|
||||
// Denormalised fields
|
||||
level: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
missingLevels: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
'missingLevels.$': {
|
||||
type: SimpleSchema.Integer,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedClassSchema = new SimpleSchema()
|
||||
.extend(ClassSchema)
|
||||
|
||||
222
app/imports/ui/properties/forms/ClassForm.vue
Normal file
222
app/imports/ui/properties/forms/ClassForm.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<template lang="html">
|
||||
<div class="class-form">
|
||||
<v-row
|
||||
dense
|
||||
>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
ref="focusFirst"
|
||||
label="Name"
|
||||
:value="model.name"
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
label="Variable name"
|
||||
:value="model.variableName"
|
||||
hint="Use this name in calculations to reference this attribute"
|
||||
:error-messages="errors.variableName"
|
||||
@change="change('variableName', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-layout align-center>
|
||||
<v-btn
|
||||
icon
|
||||
style="margin-top: -30px;"
|
||||
class="mr-2"
|
||||
:loading="addExtraTagsLoading"
|
||||
:disabled="extraTagsFull"
|
||||
@click="addExtraTags"
|
||||
>
|
||||
<v-icon>
|
||||
mdi-plus
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<smart-combobox
|
||||
label="Tags Required"
|
||||
hint="The slot must be filled with a property which has all the listed tags"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
:value="model.slotTags"
|
||||
:error-messages="errors.slotTags"
|
||||
@change="change('slotTags', ...arguments)"
|
||||
/>
|
||||
</v-layout>
|
||||
<v-slide-x-transition group>
|
||||
<div
|
||||
v-for="(extras, i) in model.extraTags"
|
||||
:key="extras._id"
|
||||
class="extra-tags layout align-center justify-space-between"
|
||||
>
|
||||
<smart-select
|
||||
label="Operation"
|
||||
style="width: 90px; flex-grow: 0;"
|
||||
:items="extraTagOperations"
|
||||
:value="extras.operation"
|
||||
:error-messages="errors.extraTags && errors.extraTags[i]"
|
||||
@change="change(['extraTags', i, 'operation'], ...arguments)"
|
||||
/>
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
:hint="extras.operation === 'OR' ? 'The slot can be filled with a property that has all of these tags instead' : 'The slot cannot be filled with a property that has any of these tags'"
|
||||
class="mx-2"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
:value="extras.tags"
|
||||
@change="change(['extraTags', i, 'tags'], ...arguments)"
|
||||
/>
|
||||
<v-btn
|
||||
icon
|
||||
style="margin-top: -30px;"
|
||||
@click="$emit('pull', {path: ['extraTags', i]})"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-slide-x-transition>
|
||||
|
||||
<computed-field
|
||||
label="Quantity"
|
||||
hint="How many matching properties must be used to fill this slot, 0 is unlimited"
|
||||
:model="model.quantityExpected"
|
||||
:error-messages="errors.quantityExpected"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['quantityExpected', ...path], value, ack})"
|
||||
/>
|
||||
|
||||
<computed-field
|
||||
label="Condition"
|
||||
hint="A caclulation to determine if this slot should be active"
|
||||
placeholder="Always active"
|
||||
:model="model.slotCondition"
|
||||
:error-messages="errors.slotCondition"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['slotCondition', ...path], value, ack})"
|
||||
/>
|
||||
|
||||
<v-layout justify-center>
|
||||
<v-btn
|
||||
v-if="context.isLibraryForm"
|
||||
color="accent"
|
||||
class="ma-2 mb-4"
|
||||
data-id="test-slot-button"
|
||||
@click="testSlot"
|
||||
>
|
||||
Test Slot
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
|
||||
<inline-computation-field
|
||||
label="Description"
|
||||
:model="model.description"
|
||||
:error-messages="errors.description"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['description', ...path], value, ack})"
|
||||
/>
|
||||
|
||||
<form-sections>
|
||||
<form-section name="Children">
|
||||
<slot name="children" />
|
||||
</form-section>
|
||||
|
||||
<form-section
|
||||
name="Advanced"
|
||||
>
|
||||
<div class="layout wrap justify-space-between">
|
||||
<smart-switch
|
||||
label="Hide when full"
|
||||
style="width: 200px; flex-grow: 0;"
|
||||
class="mx-2"
|
||||
:value="model.hideWhenFull"
|
||||
:error-messages="errors.hideWhenFull"
|
||||
@change="change('hideWhenFull', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Ignored"
|
||||
style="width: 200px; flex-grow: 0;"
|
||||
class="mx-2"
|
||||
:value="model.ignored"
|
||||
:error-messages="errors.ignored"
|
||||
@change="change('ignored', ...arguments)"
|
||||
/>
|
||||
</div>
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
hint="This slot's own tags which will be used to fill other slots"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
:value="model.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
</form-section>
|
||||
</form-sections>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
// TODO: Make this form match the class schema
|
||||
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||
import FormSection from '/imports/ui/properties/forms/shared/FormSection.vue';
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||
import { SlotSchema } from '/imports/api/properties/Slots.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormSection,
|
||||
},
|
||||
mixins: [propertyFormMixin],
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
props: {
|
||||
classForm: Boolean,
|
||||
},
|
||||
data(){
|
||||
let slotTypes = [];
|
||||
for (let key in PROPERTIES){
|
||||
slotTypes.push({text: PROPERTIES[key].name, value: key});
|
||||
}
|
||||
return {
|
||||
slotTypes,
|
||||
addExtraTagsLoading: false,
|
||||
extraTagOperations: ['OR', 'NOT'],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
extraTagsFull(){
|
||||
if (!this.model.extraTags) return false;
|
||||
let maxCount = SlotSchema.get('extraTags', 'maxCount');
|
||||
return this.model.extraTags.length >= maxCount;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
acknowledgeAddResult(){
|
||||
this.addExtraTagsLoading = false;
|
||||
},
|
||||
addExtraTags(){
|
||||
this.addExtraTagsLoading = true;
|
||||
this.$emit('push', {
|
||||
path: ['extraTags'],
|
||||
value: {
|
||||
_id: Random.id(),
|
||||
operation: 'OR',
|
||||
tags: [],
|
||||
},
|
||||
ack: this.acknowledgeAddResult,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -3,6 +3,7 @@ const AdjustmentForm = () => import('/imports/ui/properties/forms/AdjustmentForm
|
||||
const AttributeForm = () => import('/imports/ui/properties/forms/AttributeForm.vue');
|
||||
const BuffForm = () => import('/imports/ui/properties/forms/BuffForm.vue');
|
||||
const BranchForm = () => import('/imports/ui/properties/forms/BranchForm.vue');
|
||||
const ClassForm = () => import('/imports/ui/properties/forms/ClassForm.vue');
|
||||
const ClassLevelForm = () => import('/imports/ui/properties/forms/ClassLevelForm.vue');
|
||||
const ConstantForm = () => import('/imports/ui/properties/forms/ConstantForm.vue');
|
||||
const ContainerForm = () => import('/imports/ui/properties/forms/ContainerForm.vue');
|
||||
@@ -32,8 +33,8 @@ export default {
|
||||
branch: BranchForm,
|
||||
constant: ConstantForm,
|
||||
container: ContainerForm,
|
||||
class: ClassForm,
|
||||
classLevel: ClassLevelForm,
|
||||
class: SlotForm,
|
||||
damage: DamageForm,
|
||||
damageMultiplier: DamageMultiplierForm,
|
||||
effect: EffectForm,
|
||||
|
||||
91
app/imports/ui/properties/viewers/ClassViewer.vue
Normal file
91
app/imports/ui/properties/viewers/ClassViewer.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template lang="html">
|
||||
<div class="class-viewer">
|
||||
<v-row dense>
|
||||
<property-field
|
||||
name="Variable Name"
|
||||
mono
|
||||
:value="model.variableName"
|
||||
/>
|
||||
<property-field
|
||||
name="Condition"
|
||||
:value="model.slotCondition && (model.slotCondition.value || model.slotCondition.calculation)"
|
||||
/>
|
||||
<property-field
|
||||
name="Tags Required"
|
||||
:cols="{cols: 12}"
|
||||
>
|
||||
<div>
|
||||
<property-tags :tags="model.slotTags" />
|
||||
<div
|
||||
v-for="tags in model.extraTags"
|
||||
:key="tags._id"
|
||||
>
|
||||
<div class="text-caption">
|
||||
{{ tags.operation }}
|
||||
</div>
|
||||
<property-tags :tags="tags.tags" />
|
||||
</div>
|
||||
</div>
|
||||
</property-field>
|
||||
<property-description
|
||||
name="Missing Levels"
|
||||
mono
|
||||
:model="model.missingLevels &&
|
||||
(model.missingLevels.length || undefined) &&
|
||||
model.missingLevels.join(', ')
|
||||
"
|
||||
/>
|
||||
<property-field
|
||||
v-if="context.creatureId"
|
||||
name="Level"
|
||||
:value="model.level"
|
||||
/>
|
||||
<property-field
|
||||
v-if="context.creatureId"
|
||||
name="Level Up"
|
||||
:cols="{cols: 12}"
|
||||
>
|
||||
<v-btn
|
||||
v-if="model.missingLevels && model.missingLevels.length"
|
||||
outlined
|
||||
color="accent"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-plus
|
||||
</v-icon>
|
||||
Get Missing Levels
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
outlined
|
||||
color="accent"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-plus
|
||||
</v-icon>
|
||||
Level Up
|
||||
</v-btn>
|
||||
</property-field>
|
||||
<property-description
|
||||
name="Description"
|
||||
:model="model.description"
|
||||
/>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [propertyViewerMixin],
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -16,7 +16,7 @@
|
||||
/>
|
||||
<property-field
|
||||
name="Quantity"
|
||||
:value="model.quantityExpected && model.quantityExpected.value"
|
||||
:calculation="model.quantityExpected"
|
||||
/>
|
||||
<property-field
|
||||
name="Unique"
|
||||
@@ -44,12 +44,14 @@
|
||||
:model="model.description"
|
||||
/>
|
||||
<property-field
|
||||
v-if="!model.quantityExpected || !model.quantityExpected.value || model.spaceLeft"
|
||||
v-if="context.creatureId && (!model.quantityExpected || !model.quantityExpected.value || model.spaceLeft)"
|
||||
name="Fill"
|
||||
:cols="{cols: 12}"
|
||||
>
|
||||
<fill-slot-button :model="model">
|
||||
<v-icon left>mdi-plus</v-icon>
|
||||
<v-icon left>
|
||||
mdi-plus
|
||||
</v-icon>
|
||||
Fill Slot
|
||||
</fill-slot-button>
|
||||
</property-field>
|
||||
@@ -68,9 +70,14 @@
|
||||
}
|
||||
|
||||
export default {
|
||||
mixins: [propertyViewerMixin],
|
||||
components: {
|
||||
FillSlotButton,
|
||||
},
|
||||
mixins: [propertyViewerMixin],
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
slotTypeName(){
|
||||
|
||||
@@ -4,6 +4,7 @@ const AttributeViewer = () => import ('/imports/ui/properties/viewers/AttributeV
|
||||
const BuffViewer = () => import ('/imports/ui/properties/viewers/BuffViewer.vue');
|
||||
const BranchViewer = () => import ('/imports/ui/properties/viewers/BranchViewer.vue');
|
||||
const ContainerViewer = () => import ('/imports/ui/properties/viewers/ContainerViewer.vue');
|
||||
const ClassViewer = () => import ('/imports/ui/properties/viewers/ClassViewer.vue');
|
||||
const ClassLevelViewer = () => import ('/imports/ui/properties/viewers/ClassLevelViewer.vue');
|
||||
const ConstantViewer = () => import ('/imports/ui/properties/viewers/ConstantViewer.vue');
|
||||
const DamageViewer = () => import ('/imports/ui/properties/viewers/DamageViewer.vue');
|
||||
@@ -31,7 +32,7 @@ export default {
|
||||
buff: BuffViewer,
|
||||
branch: BranchViewer,
|
||||
container: ContainerViewer,
|
||||
class: SlotViewer,
|
||||
class: ClassViewer,
|
||||
classLevel: ClassLevelViewer,
|
||||
constant: ConstantViewer,
|
||||
damage: DamageViewer,
|
||||
|
||||
Reference in New Issue
Block a user