Progress all over the place with viewer, forms, small engine tweaks

This commit is contained in:
Stefan Zermatten
2021-10-21 22:18:01 +02:00
parent 1b5bb981e9
commit e3a1eff751
28 changed files with 554 additions and 258 deletions

View File

@@ -0,0 +1,48 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import computeCreature from '/imports/api/engine/computeCreature.js';
const flipToggle = new ValidatedMethod({
name: 'creatureProperties.flipToggle',
validate({_id}){
if (!_id) throw new Meteor.Error('No _id', '_id is required');
},
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id}) {
// Permission
let property = CreatureProperties.findOne(_id, {
fields: {type: 1, ancestors: 1, enabled: 1, disabled: 1}
});
if (property.type !== 'toggle'){
throw new Meteor.Error('wrong property',
'This method can only be applied to toggles');
}
if (!property.enabled && !property.disabled){
throw new Meteor.Error('Computed toggle',
'Can\'t flip a toggle that is computed')
}
let rootCreature = getRootCreatureAncestor(property);
assertEditPermission(rootCreature, this.userId);
// Invert the current value, disabled is the canonical store of value
const currentValue = !property.disabled;
CreatureProperties.update(_id, {$set: {
enabled: !currentValue,
disabled: currentValue,
}}, {
selector: {type: 'toggle'},
});
// Updating a toggle is likely to change the whole tree, do a full recompute
computeCreature(rootCreature._id);
},
});
export default flipToggle;

View File

@@ -12,3 +12,4 @@ import '/imports/api/creature/creatureProperties/methods/restoreProperty.js';
import '/imports/api/creature/creatureProperties/methods/selectAmmoItem.js'; import '/imports/api/creature/creatureProperties/methods/selectAmmoItem.js';
import '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js'; import '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js';
import '/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js'; import '/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js';
import '/imports/api/creature/creatureProperties/methods/flipToggle.js';

View File

@@ -3,9 +3,14 @@ import walkDown from '/imports/api/engine/computation/utility/walkdown.js';
export default function computeInactiveStatus(node){ export default function computeInactiveStatus(node){
const prop = node.node; const prop = node.node;
if (isActive(prop)) return; if (isActive(prop)) return;
// Unequipped items, notes, and actions disable their children, // Unequipped items, notes, spells, and actions disable their children,
// but are not disabled themselves // but are not disabled themselves
if (prop.type !== 'item' && prop.type !== 'note' && prop.type !== 'action' ){ if (
prop.type !== 'item' &&
prop.type !== 'note' &&
prop.type !== 'action' &&
prop.type !== 'spell'
){
prop.inactive = true; prop.inactive = true;
prop.deactivatedBySelf = true; prop.deactivatedBySelf = true;
} }
@@ -20,7 +25,7 @@ function isActive(prop){
if (prop.disabled) return false; if (prop.disabled) return false;
switch (prop.type){ switch (prop.type){
case 'item': return !!prop.equipped; case 'item': return !!prop.equipped;
case 'spell': return !!prop.prepared || !!prop.alwaysPrepared; case 'spell': return false;
case 'note': return false; case 'note': return false;
case 'action': return false; case 'action': return false;
default: return true; default: return true;

View File

@@ -9,6 +9,7 @@ const linkDependenciesByType = {
effect: linkStats, effect: linkStats,
skill: linkSkill, skill: linkSkill,
spell: linkResources, spell: linkResources,
toggle: linkVariableName,
} }
export default function linkTypeDependencies(dependencyGraph, prop, computation){ export default function linkTypeDependencies(dependencyGraph, prop, computation){

View File

@@ -3,6 +3,7 @@ import computeVariableAsAttribute from './computeVariable/computeVariableAsAttri
import computeVariableAsSkill from './computeVariable/computeVariableAsSkill.js'; import computeVariableAsSkill from './computeVariable/computeVariableAsSkill.js';
import computeVariableAsConstant from './computeVariable/computeVariableAsConstant.js'; import computeVariableAsConstant from './computeVariable/computeVariableAsConstant.js';
import computeVariableAsClass from './computeVariable/computeVariableAsClass.js'; import computeVariableAsClass from './computeVariable/computeVariableAsClass.js';
import computeVariableAsToggle from './computeVariable/computeVariableAsToggle.js';
import computeImplicitVariable from './computeVariable/computeImplicitVariable.js'; import computeImplicitVariable from './computeVariable/computeImplicitVariable.js';
export default function computeVariable(computation, node){ export default function computeVariable(computation, node){
@@ -50,13 +51,15 @@ function combineAggregations(computation, node){
function computeVariableProp(computation, node, prop){ function computeVariableProp(computation, node, prop){
if (!prop) return; if (!prop) return;
if (prop.type === 'attribute'){ if (prop.type === 'attribute'){
computeVariableAsAttribute(computation, node, prop) computeVariableAsAttribute(computation, node, prop);
} else if (prop.type === 'skill'){ } else if (prop.type === 'skill'){
computeVariableAsSkill(computation, node, prop) computeVariableAsSkill(computation, node, prop);
} else if (prop.type === 'constant'){ } else if (prop.type === 'constant'){
computeVariableAsConstant(computation, node, prop) computeVariableAsConstant(computation, node, prop);
} else if (prop.type === 'class'){ } else if (prop.type === 'class'){
computeVariableAsClass(computation, node, prop) computeVariableAsClass(computation, node, prop);
} else if (prop.type === 'toggle'){
computeVariableAsToggle(computation, node, prop);
} }
} }

View File

@@ -0,0 +1,7 @@
import getAggregatorResult from './getAggregatorResult.js';
export default function computeVariableAsToggle(computation, node, prop){
let result = getAggregatorResult(node, prop) || 0;
prop.value = !!result || !!prop.enabled || !!prop.condition?.value;
}

View File

@@ -36,6 +36,7 @@ function compute(computation, node){
// Determine the prop's active status by its toggles // Determine the prop's active status by its toggles
computeToggles(computation, node); computeToggles(computation, node);
computeCalculations(computation, node); computeCalculations(computation, node);
if (node.data) delete node.data._computationDetails;
// Compute the property by type // Compute the property by type
computeByType[node.data?.type || '_variable']?.(computation, node); computeByType[node.data?.type || '_variable']?.(computation, node);
} }

View File

@@ -18,7 +18,7 @@ let SavingThrowSchema = createPropertySchema({
// Who this saving throw applies to // Who this saving throw applies to
target: { target: {
type: String, type: String,
defaultValue: 'every', defaultValue: 'target',
allowedValues: [ allowedValues: [
'self', 'self',
'target', 'target',

View File

@@ -75,6 +75,18 @@
</v-card> </v-card>
</div> </div>
<div
v-for="toggle in toggles"
:key="toggle._id"
class="toggle"
>
<toggle-card
:model="toggle"
:data-id="toggle._id"
@click="clickProperty({_id: toggle._id})"
/>
</div>
<div <div
v-for="stat in stats" v-for="stat in stats"
:key="stat._id" :key="stat._id"
@@ -335,9 +347,10 @@
import ActionCard from '/imports/ui/properties/components/actions/ActionCard.vue'; import ActionCard from '/imports/ui/properties/components/actions/ActionCard.vue';
import RestButton from '/imports/ui/creature/RestButton.vue'; import RestButton from '/imports/ui/creature/RestButton.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import ToggleCard from '/imports/ui/properties/components/toggles/ToggleCard.vue';
//import castSpellWithSlot from '/imports/api/creature/actions/castSpellWithSlot.js'; //import castSpellWithSlot from '/imports/api/creature/actions/castSpellWithSlot.js';
const getProperties = function(creature, filter,){ const getProperties = function(creature, filter){
if (!creature) return; if (!creature) return;
if (creature.settings.hideUnusedStats){ if (creature.settings.hideUnusedStats){
filter.hide = {$ne: true}; filter.hide = {$ne: true};
@@ -378,6 +391,7 @@
ResourceCard, ResourceCard,
SpellSlotListTile, SpellSlotListTile,
ActionCard, ActionCard,
ToggleCard,
}, },
props: { props: {
creatureId: { creatureId: {
@@ -395,6 +409,17 @@
stats(){ stats(){
return getAttributeOfType(this.creature, 'stat'); return getAttributeOfType(this.creature, 'stat');
}, },
toggles(){
return CreatureProperties.find({
'ancestors.id': this.creatureId,
type: 'toggle',
removed: {$ne: true},
deactivatedByAncestor: {$ne: true},
showUI: true,
}, {
sort: {order: 1}
});
},
modifiers(){ modifiers(){
return getAttributeOfType(this.creature, 'modifier'); return getAttributeOfType(this.creature, 'modifier');
}, },

View File

@@ -0,0 +1,69 @@
<template lang="html">
<v-card
:hover="hasClickListener"
@click="click"
>
<div class="layout align-center">
<div
class="value layout justify-center flex-grow-0"
>
<smart-checkbox
:value="toggleValue"
:disabled="toggleDisabled"
@change="(val, ack) => toggleToggle(val, ack)"
@click.native.stop=""
/>
</div>
<v-card-title class="name text-subtitle-1 text-truncate d-block pl-0">
{{ model.name }}
</v-card-title>
</div>
</v-card>
</template>
<script lang="js">
import flipToggle from '/imports/api/creature/creatureProperties/methods/flipToggle.js';
export default {
props: {
model: {
type: Object,
required: true,
},
},
computed: {
hasClickListener(){
return this.$listeners && !!this.$listeners.click
},
toggleValue(){
if (this.model.enabled) return true;
if (this.model.disabled) return false;
if (!this.model.condition) return undefined;
return !!this.model.condition.value
},
toggleDisabled(){
return !this.model.enabled && !this.model.disabled;
},
},
methods: {
click(e){
this.$emit('click', e);
},
toggleToggle(value, ack){
flipToggle.call({
_id: this.model._id
}, (error) =>{
if (error) console.warn(error);
ack && ack(error && error.reason || error);
});
},
},
}
</script>
<style lang="css" scoped>
.value {
min-width: 64px;
justify-content: center;
}
</style>

View File

@@ -19,7 +19,7 @@
cols="12" cols="12"
md="6" md="6"
> >
<smart-select <smart-combobox
label="Damage Type" label="Damage Type"
style="flex-basis: 200px;" style="flex-basis: 200px;"
hint="Use the Healing type to restore hit points" hint="Use the Healing type to restore hit points"
@@ -57,12 +57,8 @@
<script lang="js"> <script lang="js">
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js'; import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js'; import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
import CalculationErrorList from '/imports/ui/properties/forms/shared/CalculationErrorList.vue';
export default { export default {
components: {
CalculationErrorList,
},
mixins: [propertyFormMixin], mixins: [propertyFormMixin],
props: { props: {
parentTarget: { parentTarget: {
@@ -90,10 +86,6 @@ export default {
self: 'The damage will be applied to the character taking the action', self: 'The damage will be applied to the character taking the action',
target: 'The damage will be applied to the target of the action', target: 'The damage will be applied to the target of the action',
}; };
if (this.parentTarget === 'singleTarget'){
hints.each = hints.target;
hints.every = hints.target;
}
return hints[this.model.target]; return hints[this.model.target];
} }
}, },

View File

@@ -1,9 +1,14 @@
<template lang="html"> <template lang="html">
<div class="folder-form layout justify-start wrap"> <div class="folder-form layout justify-start wrap">
<div data-id="change-ref"> <property-field
name="Linked Property"
data-id="change-ref"
style="cursor: pointer;"
@click="changeReference"
>
<v-input <v-input
:label="model.cache && model.cache.node ? '' : 'Linked Property'" :label="model.cache && model.cache.node ? '' : 'Linked Property'"
style="flex-basis: 300px; cursor: pointer;" style="overflow: hidden;"
readonly readonly
outlined outlined
persistent-hint persistent-hint
@@ -16,16 +21,14 @@
:error-messages="model.cache.error || errors.ref" :error-messages="model.cache.error || errors.ref"
prepend-icon="mdi-vector-link" prepend-icon="mdi-vector-link"
append-icon="mdi-refresh" append-icon="mdi-refresh"
@click="changeReference" @click:append.stop="updateReferenceNode"
@click:prepend="changeReference"
@click:append="updateReferenceNode"
> >
<tree-node-view <tree-node-view
v-if="model && model.cache && model.cache.node" v-if="model && model.cache && model.cache.node"
:model="model.cache.node" :model="model.cache.node"
/> />
</v-input> </v-input>
</div> </property-field>
</div> </div>
</template> </template>
@@ -33,10 +36,12 @@
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue'; import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js'; import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
import updateReferenceNode from '/imports/api/library/methods/updateReferenceNode.js'; import updateReferenceNode from '/imports/api/library/methods/updateReferenceNode.js';
import PropertyField from '/imports/ui/properties/viewers/shared/PropertyField.vue';
export default { export default {
components: { components: {
TreeNodeView, TreeNodeView,
PropertyField,
}, },
mixins: [propertyFormMixin], mixins: [propertyFormMixin],
data(){return { data(){return {

View File

@@ -1,23 +1,33 @@
<template lang="html"> <template lang="html">
<div class="roll-form"> <div class="roll-form">
<div class="layout wrap"> <v-row dense>
<text-field <v-col
label="Name" cols="12"
:value="model.name" md="6"
:error-messages="errors.name" >
@change="change('name', ...arguments)" <text-field
/> label="Name"
<text-field :value="model.name"
label="Variable name" :error-messages="errors.name"
:value="model.variableName" @change="change('name', ...arguments)"
style="flex-basis: 300px;" />
hint="Use this name in action formulae to refer to the result of this roll" </v-col>
:error-messages="errors.variableName" <v-col
@change="change('variableName', ...arguments)" cols="12"
/> md="6"
</div> >
<text-field
label="Variable name"
:value="model.variableName"
style="flex-basis: 300px;"
hint="Use this name in action formulae to refer to the result of this roll"
:error-messages="errors.variableName"
@change="change('variableName', ...arguments)"
/>
</v-col>
</v-row>
<computed-field <computed-field
label="Roll bonus" label="Roll"
hint="The calculation that will be evaluated when the roll is triggered by an action. The result will be saved as the variable name in the context of the roll." hint="The calculation that will be evaluated when the roll is triggered by an action. The result will be saved as the variable name in the context of the roll."
:model="model.roll" :model="model.roll"
:error-messages="errors.roll" :error-messages="errors.roll"

View File

@@ -1,39 +1,59 @@
<template lang="html"> <template lang="html">
<div class="saving-throw-form"> <div class="saving-throw-form">
<text-field <v-row dense>
ref="focusFirst" <v-col
label="Name" cols="12"
:value="model.name" md="6"
:error-messages="errors.name" >
@change="change('name', ...arguments)" <text-field
/> ref="focusFirst"
label="Name"
<computed-field :value="model.name"
label="DC" :error-messages="errors.name"
hint="A calculation of the DC that the target of an action needs to save against in order to succeed. If the saving throw is lower than the DC, the children of this property will be activated." @change="change('name', ...arguments)"
:model="model.dc" />
:error-messages="errors.dc" </v-col>
@change="({path, value, ack}) => <v-col
$emit('change', {path: ['dc', ...path], value, ack})" cols="12"
/> md="6"
>
<smart-combobox <computed-field
label="Save" label="DC"
hint="Which stat the saving throw targets" hint="A calculation of the DC that the target of an action needs to save against in order to succeed. If the saving throw is lower than the DC, the children of this property will be activated."
:value="model.stat" :model="model.dc"
:items="saveList" :error-messages="errors.dc"
:error-messages="errors.stat" @change="({path, value, ack}) =>
@change="change('stat', ...arguments)" $emit('change', {path: ['dc', ...path], value, ack})"
/> />
<smart-select </v-col>
label="Target" <v-col
:hint="targetOptionHint" cols="12"
:items="targetOptions" md="6"
:value="model.target" >
:error-messages="errors.target" <smart-combobox
:menu-props="{auto: true, lazy: true}" label="Save"
@change="change('target', ...arguments)" hint="Which stat the saving throw targets"
/> :value="model.stat"
:items="saveList"
:error-messages="errors.stat"
@change="change('stat', ...arguments)"
/>
</v-col>
<v-col
cols="12"
md="6"
>
<smart-select
label="Target"
:hint="targetOptionHint"
:items="targetOptions"
:value="model.target"
:error-messages="errors.target"
:menu-props="{auto: true, lazy: true}"
@change="change('target', ...arguments)"
/>
</v-col>
</v-row>
<smart-combobox <smart-combobox
label="Tags" label="Tags"
class="mr-2" class="mr-2"
@@ -61,25 +81,16 @@ export default {
text: 'Self', text: 'Self',
value: 'self', value: 'self',
}, { }, {
text: 'Roll once for each target', text: 'Target',
value: 'each', value: 'target',
}, {
text: 'Roll once and apply to every target',
value: 'every',
}, },
]; ];
}, },
targetOptionHint(){ targetOptionHint(){
let hints = { let hints = {
self: 'The damage will be applied to the character\'s own attribute when taking the action', self: 'The save will be applied to the character taking the action',
target: 'The damage will be applied to the target of the action', target: 'The save will be applied to the targets 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]; return hints[this.model.target];
} }
}, },

View File

@@ -22,7 +22,6 @@
:error-messages="errors.description" :error-messages="errors.description"
@change="change('description', ...arguments)" @change="change('description', ...arguments)"
/> />
<calculation-error-list :calculations="model.descriptionCalculations" />
<text-field <text-field
label="Picture URL" label="Picture URL"
@@ -51,6 +50,7 @@
@change="change('slotQuantityFilled', ...arguments)" @change="change('slotQuantityFilled', ...arguments)"
/> />
<text-field <text-field
v-if="context.isLibraryForm"
label="Condition" label="Condition"
hint="A caclulation to determine if this can be added to the character" hint="A caclulation to determine if this can be added to the character"
placeholder="Always active" placeholder="Always active"
@@ -81,6 +81,9 @@
CalculationErrorList, CalculationErrorList,
}, },
mixins: [propertyFormMixin], mixins: [propertyFormMixin],
inject: {
context: { default: {} }
},
data(){ data(){
let slotTypes = []; let slotTypes = [];
for (let key in PROPERTIES){ for (let key in PROPERTIES){

View File

@@ -1,34 +1,69 @@
<template lang="html"> <template lang="html">
<div class="feature-form"> <div class="feature-form">
<text-field <v-row dense>
ref="focusFirst" <v-col
label="Name" cols="12"
:value="model.name" md="6"
:error-messages="errors.name"
@change="change('name', ...arguments)"
/>
<v-layout
column
align-center
>
<v-radio-group
:value="radioSelection"
@change="radioChange"
> >
<v-radio <text-field
value="enabled" ref="focusFirst"
label="Enabled" label="Name"
:value="model.name"
:error-messages="errors.name"
@change="change('name', ...arguments)"
/> />
<v-radio </v-col>
value="disabled" <v-col
label="Disabled" 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-radio </v-col>
value="calculated" <v-col
label="Calculated" cols="12"
md="6"
>
<smart-checkbox
label="Show on character sheet"
:value="model.showUI"
:error-messages="errors.showUI"
@change="change('showUI', ...arguments)"
/> />
</v-radio-group> </v-col>
</v-layout>
<v-col
cols="12"
md="6"
>
<v-layout
column
>
<v-radio-group
:value="radioSelection"
@change="radioChange"
>
<v-radio
value="enabled"
label="Enabled"
/>
<v-radio
value="disabled"
label="Disabled"
/>
<v-radio
value="calculated"
label="Calculated"
/>
</v-radio-group>
</v-layout>
</v-col>
</v-row>
<v-fade-transition> <v-fade-transition>
<computed-field <computed-field
v-show="radioSelection === 'calculated'" v-show="radioSelection === 'calculated'"

View File

@@ -28,6 +28,7 @@
name="To hit" name="To hit"
large large
center center
signed
:value="rollBonus" :value="rollBonus"
/> />
<property-field <property-field
@@ -93,6 +94,7 @@
/> />
</div> </div>
</property-field> </property-field>
<slot />
<property-description <property-description
name="Summary" name="Summary"
:model="model.summary" :model="model.summary"

View File

@@ -1,17 +1,15 @@
<template lang="html"> <template lang="html">
<div class="note-viewer"> <div class="note-viewer">
<property-name :value="model.name" /> <v-row dense>
<property-description <property-description
:string="model.summary" name="Summary"
:calculations="model.summaryCalculations" :model="model.summary"
:inactive="model.inactive" />
/> <property-description
<v-divider class="mt-3 mb-3" /> name="Description"
<property-description :model="model.description"
:string="model.description" />
:calculations="model.descriptionCalculations" </v-row>
:inactive="model.inactive"
/>
</div> </div>
</template> </template>

View File

@@ -1,17 +1,25 @@
<template lang="html"> <template lang="html">
<div class="proficiency-viewer"> <div class="proficiency-viewer">
<div class="text-h5"> <v-row dense>
{{ model.stats && model.stats.join(' ') }} <property-field
</div> v-if="model.value !== undefined"
<div class="text-h5 layout"> name="Proficiency"
<proficiency-icon >
:value="model.value" <proficiency-icon
class="mr-1" :value="model.value"
style="height: 12px"
class="ml-1 mr-2"
/>
<div>
{{ proficiencyText }}
</div>
</property-field>
<property-field
name="Stats"
:value="model.stats && model.stats.join(', ')"
mono
/> />
<div> </v-row>
{{ proficiencyText }}
</div>
</div>
</div> </div>
</template> </template>
@@ -27,6 +35,7 @@ export default {
computed: { computed: {
proficiencyText(){ proficiencyText(){
switch (this.model.value){ switch (this.model.value){
case 0.49: return 'Half proficiency bonus rounded down';
case 0.5: return 'Half proficiency bonus'; case 0.5: return 'Half proficiency bonus';
case 1: return 'Proficient'; case 1: return 'Proficient';
case 2: return 'Double proficiency bonus'; case 2: return 'Double proficiency bonus';

View File

@@ -1,21 +1,25 @@
<template lang="html"> <template lang="html">
<div class="reference-viewer"> <div class="reference-viewer">
<property-field <v-row>
v-if="model.cache.error" <property-field
name="Error" v-if="model.cache.error"
:value="model.cache.error" name="Error"
/> :value="model.cache.error"
<template v-else-if="model.ref && model.ref.id"> />
<div class="text-caption"> <property-field
Linked Property v-else-if="model.ref && model.ref.id"
</div> name="Linked Property"
<tree-node-view :model="model.cache.node" /> >
</template> <div style="overflow: hidden;">
<property-field <tree-node-view :model="model.cache.node" />
v-if="model.cache.library && model.cache.library.name" </div>
name="Library" </property-field>
:value="model.cache.library.name" <property-field
/> v-if="model.cache.library && model.cache.library.name"
name="Library"
:value="model.cache.library.name"
/>
</v-row>
</div> </div>
</template> </template>

View File

@@ -1,23 +1,25 @@
<template lang="html"> <template lang="html">
<div class="buff-viewer"> <div class="roll-viewer">
<property-name :value="model.name" /> <v-row dense>
<property-variable-name :value="model.variableName" /> <property-field
<property-field name="Variable Name"
name="Roll" mono
:value="'rollResult' in model ? model.rollResult : model.roll" :value="model.variableName"
/> />
<calculation-error-list :errors="model.rollErrors" /> <property-field
name="Roll"
large
center
:calculation="model.roll"
/>
</v-row>
</div> </div>
</template> </template>
<script lang="js"> <script lang="js">
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js' import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
import CalculationErrorList from '/imports/ui/properties/forms/shared/CalculationErrorList.vue';
export default { export default {
components: {
CalculationErrorList,
},
mixins: [propertyViewerMixin], mixins: [propertyViewerMixin],
} }
</script> </script>

View File

@@ -1,14 +1,23 @@
<template lang="html"> <template lang="html">
<div class="buff-viewer"> <div class="saving-throw-viewer">
<property-name :value="model.name" /> <v-row dense>
<property-field <property-field
name="Save" name="DC"
:value="model.stat" large
/> center
<property-field :calculation="model.dc"
name="DC" />
:value="'dcResult' in model ? model.dcResult : model.dc" <property-field
/> name="Save"
mono
:value="model.stat"
/>
<property-field
v-if="model.target === 'self'"
name="Target"
value="Self"
/>
</v-row>
</div> </div>
</template> </template>

View File

@@ -1,37 +1,55 @@
<template lang="html"> <template lang="html">
<div class="slot-filler-viewer"> <div class="slot-filler-viewer">
<property-name :value="model.name" /> <v-row dense>
<v-img <property-field
v-if="model.picture" name="Type"
:src="model.picture" :value="slotTypeName"
:height="200" />
contain <property-field
class="slot-card-image" name="Quantity filled"
/> :value="model.slotQuantityFilled"
<property-field />
name="Type" <property-field
:value="model.slotFillerType" v-if="context.creatureId"
/> name="Condition"
<property-field mono
name="Quantity" :value="model.slotFillerCondition"
:value="model.slotQuantityFilled" />
/> <property-field
<property-field v-if="model.picture"
name="Condition" name="Image"
:value="model.slotFillerCondition" :cols="{cols: 12}"
/> >
<property-description <v-img
:string="model.description" :src="model.picture"
:calculations="model.descriptionCalculations" :height="200"
:inactive="model.inactive" contain
/> class="slot-card-image"
/>
</property-field>
<property-field
name="Description"
:cols="{cols: 12}"
:value="model.description"
/>
</v-row>
</div> </div>
</template> </template>
<script lang="js"> <script lang="js">
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'; import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js';
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
export default { export default {
mixins: [propertyViewerMixin], mixins: [propertyViewerMixin],
inject: {
context: { default: {} }
},
computed: {
slotTypeName(){
if (!this.model.slotFillerType) return;
return getPropertyName(this.model.slotFillerType);
},
}
} }
</script> </script>

View File

@@ -50,7 +50,7 @@
<script lang="js"> <script lang="js">
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js' import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
import {getPropertyName} from '/imports/constants/PROPERTIES.js'; import { getPropertyName } from '/imports/constants/PROPERTIES.js';
const uniqueText = { const uniqueText = {
uniqueInSlot: 'Each property inside this slot should be unique', uniqueInSlot: 'Each property inside this slot should be unique',

View File

@@ -1,24 +1,36 @@
<template lang="html"> <template lang="html">
<div class="spell-list-viewer"> <div class="spell-list-viewer">
<property-name :value="model.name" /> <v-row dense>
<property-variable-name :value="model.variableName" /> <property-field
<property-field name="Variable Name"
name="Maximum prepared spells" mono
:value="'maxPreparedResult' in model ? model.maxPreparedResult : model.maxPrepared" :value="model.variableName"
/> />
<property-field
name="Spell Save DC" <property-field
:value="'dcResult' in model ? model.dcResult : model.dc" name="Maximum prepared spells"
/> large
<property-field center
name="Attack roll bonus" :calculation="model.maxPrepared"
:value="'attackRollBonusResult' in model ? model.attackRollBonusResult : model.attackRollBonus" />
/> <property-field
<property-description name="Spell Save DC"
:string="model.description" large
:calculations="model.descriptionCalculations" center
:inactive="model.inactive" :calculation="model.dc"
/> />
<property-field
name="Attack roll bonus"
large
center
signed
:calculation="model.attackRollBonus"
/>
<property-description
name="Description"
:model="model.description"
/>
</v-row>
</div> </div>
</template> </template>

View File

@@ -1,9 +1,16 @@
<template lang="html"> <template lang="html">
<div class="spell-viewer"> <action-viewer
<property-name :value="model.name" /> :model="model"
<p class="text-caption"> class="spell-viewer"
{{ levelAndSchool }} >
</p> <property-field
name="School"
:value="model.school"
/>
<property-field
name="Level"
:value="levelText"
/>
<property-field <property-field
name="Casting time" name="Casting time"
:value="model.castingTime" :value="model.castingTime"
@@ -20,16 +27,12 @@
name="Duration" name="Duration"
:value="model.duration" :value="model.duration"
/> />
<property-description </action-viewer>
:string="model.description"
:calculations="model.descriptionCalculations"
:inactive="model.inactive"
/>
</div>
</template> </template>
<script lang="js"> <script lang="js">
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'; import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js';
import ActionViewer from './ActionViewer.vue';
const levelText = [ const levelText = [
'cantrip', '1st-level', '2nd-level', '3rd-level', '4th-level', '5th-level', 'cantrip', '1st-level', '2nd-level', '3rd-level', '4th-level', '5th-level',
@@ -37,14 +40,13 @@ const levelText = [
]; ];
export default { export default {
components: {
ActionViewer,
},
mixins: [propertyViewerMixin], mixins: [propertyViewerMixin],
computed:{ computed:{
levelAndSchool(){ levelText(){
if (this.model.level == 0){ return levelText[this.model.level]
return `${this.model.school} ${levelText[0]}`
} else {
return `${levelText[this.model.level]} ${this.model.school}`
}
}, },
spellComponents(){ spellComponents(){
let components = []; let components = [];

View File

@@ -1,24 +1,25 @@
<template lang="html"> <template lang="html">
<div class="toggle-viewer"> <div class="toggle-viewer">
<property-name :value="model.name" /> <v-row dense>
<property-field
v-if="model.disabled || model.enabled"
name="Status"
:value="model.enabled ? 'Enabled' : 'Disabled'"
/>
<template
v-else-if="model.condition"
>
<property-field <property-field
name="Condition" name="Variable Name"
:value="model.condition" mono
:value="model.variableName"
/> />
<property-field <property-field
v-if="'toggleResult' in model" v-if="model.disabled || model.enabled"
name="Result" name="Status"
:value="model.toggleResult" :value="model.enabled ? 'Enabled' : 'Disabled'"
/> />
</template> <template
v-else-if="model.condition"
>
<property-field
name="Condition"
:calculation="model.condition"
/>
</template>
</v-row>
</div> </div>
</template> </template>

View File

@@ -9,6 +9,7 @@
<v-sheet <v-sheet
outlined outlined
class="pa-2 layout column align-start fill-height" class="pa-2 layout column align-start fill-height"
@click="$emit('click', $event)"
> >
<v-sheet <v-sheet
v-if="name" v-if="name"
@@ -38,11 +39,7 @@
{{ value }} {{ value }}
</template> </template>
<template v-else-if="calculation !== undefined"> <template v-else-if="calculation !== undefined">
{{ {{ calculationText }}
calculation.value !== undefined ?
calculation.value :
calculation.calculation
}}
</template> </template>
</slot> </slot>
</div> </div>
@@ -52,6 +49,8 @@
</template> </template>
<script lang="js"> <script lang="js">
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
export default { export default {
props: { props: {
name: { name: {
@@ -70,6 +69,7 @@ export default {
end: Boolean, end: Boolean,
large: Boolean, large: Boolean,
mono: Boolean, mono: Boolean,
signed: Boolean,
cols: { cols: {
type: Object, type: Object,
default: () => ({cols: 12, sm: 6, md: 4}), default: () => ({cols: 12, sm: 6, md: 4}),
@@ -80,22 +80,45 @@ export default {
if (!this.calculation) return; if (!this.calculation) return;
return this.calculation && this.calculation.value === undefined; return this.calculation && this.calculation.value === undefined;
}, },
valueNotReduced(){
if (!this.calculation) return;
return typeof this.calculation.value === 'string'
},
calculationText(){
const calculation = this.calculation;
if (!calculation) {
return undefined;
}
if (calculation.value === undefined){
return calculation.calculation;
}
if (this.signed) {
return numberToSignedString(calculation.value);
}
return calculation.value;
},
// large and center are only applied to calculations if we are showing their // large and center are only applied to calculations if we are showing their
// value, if we are showing the calculation itself, large and center are // value, if we are showing the calculation itself, large and center are
// turned off // turned off
isLarge(){ isLarge(){
if (this.showCalculationInsteadOfValue) return false; if (this.showCalculationInsteadOfValue) return false;
if (this.valueNotReduced) return false;
return this.large; return this.large;
}, },
isCenter(){ isCenter(){
if (this.showCalculationInsteadOfValue) return false; if (this.showCalculationInsteadOfValue) return false;
if (this.valueNotReduced) return false;
return this.center; return this.center;
}, },
isMono(){ isMono(){
if (this.showCalculationInsteadOfValue) return true; if (this.showCalculationInsteadOfValue) return true;
if (this.valueNotReduced) return true;
return this.mono; return this.mono;
} },
}, },
methods: {
numberToSignedString,
}
} }
</script> </script>