Overhaul of character action components, actions now consume resources
This commit is contained in:
@@ -343,7 +343,45 @@ const adjustQuantity = new ValidatedMethod({
|
||||
let currentProperty = CreatureProperties.findOne(_id);
|
||||
// Check permissions
|
||||
assertPropertyEditPermission(currentProperty, this.userId);
|
||||
adjustQuantityWork({property: currentProperty, operation, value})
|
||||
adjustQuantityWork({property: currentProperty, operation, value});
|
||||
recomputeCreatures(currentProperty);
|
||||
},
|
||||
});
|
||||
|
||||
const selectAmmoItem = new ValidatedMethod({
|
||||
name: 'creatureProperties.selectAmmoItem',
|
||||
validate: new SimpleSchema({
|
||||
actionId: SimpleSchema.RegEx.Id,
|
||||
itemId: SimpleSchema.RegEx.Id,
|
||||
itemConsumedIndex: Number,
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({actionId, itemId, itemConsumedIndex}) {
|
||||
let action = CreatureProperties.findOne(actionId);
|
||||
// Check permissions
|
||||
assertPropertyEditPermission(action, this.userId);
|
||||
// Check that this index has a document to edit
|
||||
let itemConsumed = action.resources.itemsConsumed[itemConsumedIndex];
|
||||
if (!itemConsumed){
|
||||
throw new Meteor.Error('Resouce not found',
|
||||
'Could not set ammo, because the ammo document was not found');
|
||||
}
|
||||
let itemToLink = CreatureProperties.findOne(itemId);
|
||||
if (!itemToLink){
|
||||
throw new Meteor.Error('Item not found',
|
||||
'Could not set ammo: the item was not found');
|
||||
}
|
||||
let path = `resources.itemsConsumed.${itemConsumedIndex}.itemId`;
|
||||
CreatureProperties.update(actionId, {
|
||||
$set: {[path]: itemId}
|
||||
}, {
|
||||
selector: action,
|
||||
});
|
||||
recomputeCreatures(action);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -416,6 +454,7 @@ export {
|
||||
updateProperty,
|
||||
damageProperty,
|
||||
adjustQuantity,
|
||||
selectAmmoItem,
|
||||
pushToProperty,
|
||||
pullFromProperty,
|
||||
softRemoveProperty,
|
||||
|
||||
@@ -15,6 +15,8 @@ export default class ComputationMemo {
|
||||
this.classes = {};
|
||||
this.togglesById = {};
|
||||
this.toggleIds = new Set();
|
||||
// Equipped items that might be used as ammo
|
||||
this.equipmentById = {};
|
||||
// Properties that have calculations, but don't impact other properties
|
||||
this.endStepPropsById = {};
|
||||
// First note all the ids of all the toggles
|
||||
@@ -40,6 +42,10 @@ export default class ComputationMemo {
|
||||
) {
|
||||
// Add all the stats
|
||||
this.addStat(prop);
|
||||
} else if (
|
||||
prop.type === 'item'
|
||||
) {
|
||||
this.addEquipment(prop);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
@@ -185,6 +191,10 @@ export default class ComputationMemo {
|
||||
});
|
||||
return targets;
|
||||
}
|
||||
addEquipment(prop){
|
||||
prop = this.registerProperty(prop);
|
||||
this.equipmentById[prop._id] = prop;
|
||||
}
|
||||
addEndStepProp(prop){
|
||||
prop = this.registerProperty(prop);
|
||||
this.endStepPropsById[prop._id] = prop;
|
||||
|
||||
@@ -37,6 +37,7 @@ function computeAction(prop, memo){
|
||||
if (attConsumed.variableName){
|
||||
let stat = memo.statsByVariableName[attConsumed.variableName];
|
||||
prop.resources.attributesConsumed[i].statId = stat && stat._id;
|
||||
prop.resources.attributesConsumed[i].statName = stat && stat.name;
|
||||
let available = stat && stat.currentValue || 0;
|
||||
prop.resources.attributesConsumed[i].available = available;
|
||||
if (available < attConsumed.quantity){
|
||||
@@ -45,7 +46,22 @@ function computeAction(prop, memo){
|
||||
}
|
||||
});
|
||||
// Items consumed
|
||||
// TODO
|
||||
prop.resources.itemsConsumed.forEach((itemConsumed, i) => {
|
||||
let item = itemConsumed.itemId && memo.equipmentById[itemConsumed.itemId];
|
||||
prop.resources.itemsConsumed[i].itemId = item && item._id;
|
||||
let available = item && item.quantity || 0;
|
||||
prop.resources.itemsConsumed[i].available = available;
|
||||
let name = item && item.name;
|
||||
if (item && item.quantity !== 1 && item.plural){
|
||||
name = item.plural;
|
||||
}
|
||||
prop.resources.itemsConsumed[i].itemName = name;
|
||||
prop.resources.itemsConsumed[i].itemIcon = item && item.icon;
|
||||
prop.resources.itemsConsumed[i].itemColor = item && item.color;
|
||||
if (!item || available < itemConsumed.quantity){
|
||||
prop.insufficientResources = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function computeAttack(prop, memo){
|
||||
|
||||
@@ -42,6 +42,7 @@ const calculationPropertyTypes = [
|
||||
'proficiency',
|
||||
'classLevel',
|
||||
'toggle',
|
||||
'item',
|
||||
// End step types
|
||||
'action',
|
||||
'attack',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import { storedIconsSchema } from '/imports/api/icons/Icons.js'
|
||||
|
||||
/*
|
||||
* Actions are things a character can do
|
||||
@@ -133,6 +134,19 @@ const ComputedOnlyActionSchema = new SimpleSchema({
|
||||
// This appears both in the computed and uncomputed schema because it can be
|
||||
// set by both a computation or a form
|
||||
'resources.itemsConsumed.$.itemId': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
optional: true,
|
||||
},
|
||||
'resources.itemsConsumed.$.itemName': {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
'resources.itemsConsumed.$.itemIcon': {
|
||||
type: storedIconsSchema,
|
||||
optional: true,
|
||||
},
|
||||
'resources.itemsConsumed.$.itemColor': {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
@@ -147,6 +161,12 @@ const ComputedOnlyActionSchema = new SimpleSchema({
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
optional: true,
|
||||
},
|
||||
'resources.attributesConsumed.$.statName': {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
// True if the uses left is zero, or any item or attribute consumed is
|
||||
// insufficient
|
||||
insufficientResources: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
drag_handle
|
||||
</v-icon>
|
||||
<!--{{node && node.order}}-->
|
||||
<component
|
||||
:is="treeNodeView"
|
||||
<tree-node-view
|
||||
:model="node"
|
||||
:selected="selected"
|
||||
/>
|
||||
@@ -80,13 +79,13 @@
|
||||
**/
|
||||
import { canBeParent } from '/imports/api/parenting/parenting.js';
|
||||
import { getPropertyIcon } from '/imports/constants/PROPERTIES.js';
|
||||
import treeNodeViewIndex from '/imports/ui/properties/treeNodeViews/treeNodeViewIndex.js';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
|
||||
export default {
|
||||
name: 'TreeNode',
|
||||
components: {
|
||||
...treeNodeViewIndex
|
||||
},
|
||||
components: {
|
||||
TreeNodeView,
|
||||
},
|
||||
props: {
|
||||
node: Object,
|
||||
group: String,
|
||||
@@ -100,10 +99,6 @@
|
||||
expanded: false,
|
||||
}},
|
||||
computed: {
|
||||
treeNodeView(){
|
||||
let type = this.node.type;
|
||||
return treeNodeViewIndex[type] || treeNodeViewIndex.default;
|
||||
},
|
||||
hasChildren(){
|
||||
return this.children && this.children.length || this.lazy && !this.expanded;
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template lang="html">
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
outline
|
||||
style="width: 160px;"
|
||||
@click="rest"
|
||||
|
||||
@@ -258,25 +258,16 @@
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="attacks.length"
|
||||
class="actions"
|
||||
v-for="attack in attacks"
|
||||
:key="attack._id"
|
||||
class="attacks"
|
||||
>
|
||||
<v-card>
|
||||
<v-list
|
||||
two-line
|
||||
subheader
|
||||
>
|
||||
<v-subheader>Attacks</v-subheader>
|
||||
<action-list-tile
|
||||
v-for="attack in attacks"
|
||||
:key="attack._id"
|
||||
attack
|
||||
:model="attack"
|
||||
:data-id="attack._id"
|
||||
@click="clickProperty({_id: attack._id})"
|
||||
/>
|
||||
</v-list>
|
||||
</v-card>
|
||||
<action-card
|
||||
attack
|
||||
:model="attack"
|
||||
:data-id="attack._id"
|
||||
@click="clickProperty({_id: attack._id})"
|
||||
/>
|
||||
</div>
|
||||
</column-layout>
|
||||
</div>
|
||||
@@ -294,7 +285,6 @@
|
||||
import SkillListTile from '/imports/ui/properties/components/skills/SkillListTile.vue';
|
||||
import ResourceCard from '/imports/ui/properties/components/attributes/ResourceCard.vue';
|
||||
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||
import ActionListTile from '/imports/ui/properties/components/actions/ActionListTile.vue';
|
||||
import ActionCard from '/imports/ui/properties/components/actions/ActionCard.vue';
|
||||
import RestButton from '/imports/ui/creature/RestButton.vue';
|
||||
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
||||
@@ -337,7 +327,6 @@
|
||||
SkillListTile,
|
||||
ResourceCard,
|
||||
SpellSlotListTile,
|
||||
ActionListTile,
|
||||
ActionCard,
|
||||
},
|
||||
props: {
|
||||
@@ -390,14 +379,7 @@
|
||||
return getSkillOfType(this.creature, 'language');
|
||||
},
|
||||
actions(){
|
||||
let props = getProperties(this.creature, {type: 'action'}).map(action => {
|
||||
action.children = getActiveProperties({
|
||||
ancestorId: action._id,
|
||||
options: {sort: {order: 1}},
|
||||
});
|
||||
return action;
|
||||
});
|
||||
return props;
|
||||
return getProperties(this.creature, {type: 'action'});
|
||||
},
|
||||
attacks(){
|
||||
let props = getProperties(this.creature, {type: 'attack'}).map(attack => {
|
||||
|
||||
@@ -160,7 +160,6 @@ export default {
|
||||
});
|
||||
},
|
||||
push({path, value, ack}){
|
||||
console.log({path, value, ack});
|
||||
pushToProperty.call({_id: this._id, path, value}, (error) =>{
|
||||
if (error) console.warn(error);
|
||||
ack && ack(error && error.reason || error);
|
||||
|
||||
@@ -1,58 +1,106 @@
|
||||
<template lang="html">
|
||||
<v-card
|
||||
class="action"
|
||||
@click="$emit('click')"
|
||||
ref="card"
|
||||
class="action-card"
|
||||
:class="cardClasses"
|
||||
:elevation="hovering ? 8 : undefined"
|
||||
>
|
||||
<v-card-title
|
||||
primary-title
|
||||
class="layout row pa-2"
|
||||
>
|
||||
<div class="layout row align-center px-3">
|
||||
<div class="avatar">
|
||||
<v-btn
|
||||
flat
|
||||
icon
|
||||
class="headline"
|
||||
outline
|
||||
style="margin-left: -4px; font-size: 18px;"
|
||||
color="primary"
|
||||
:loading="doActionLoading"
|
||||
:disabled="model.insufficientResources"
|
||||
@click.stop="doAction"
|
||||
>
|
||||
<template v-if="rollBonus">
|
||||
<template v-if="attack && !rollBonusTooLong">
|
||||
{{ rollBonus }}
|
||||
</template>
|
||||
<v-icon v-else>
|
||||
$vuetify.icons.action
|
||||
{{ actionTypeIcon }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<div class="action-header flex">
|
||||
<div class="action-title">
|
||||
<div
|
||||
class="action-header flex layout column justify-center pl-1"
|
||||
style="height: 72px; cursor: pointer;"
|
||||
@mouseover="hovering = true"
|
||||
@mouseleave="hovering = false"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<div
|
||||
class="action-title my-1"
|
||||
>
|
||||
{{ model.name }}
|
||||
</div>
|
||||
<div class="action-type">
|
||||
action type text
|
||||
<div class="action-sub-title layout row align-center">
|
||||
<div class="flex">
|
||||
{{ model.actionType }}
|
||||
</div>
|
||||
<div v-if="model.uses">
|
||||
{{ usesLeft }} uses
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-uses">
|
||||
{{ usesLeft }}/{{ totalUses }}
|
||||
</div>
|
||||
</v-card-title>
|
||||
<v-card-text
|
||||
v-if="childText"
|
||||
class="action-details"
|
||||
v-html="childText"
|
||||
/>
|
||||
</div>
|
||||
<div class="px-3 pb-3">
|
||||
<attribute-consumed-view
|
||||
v-for="attributeConsumed in model.resources.attributesConsumed"
|
||||
:key="attributeConsumed._id"
|
||||
class="action-child"
|
||||
:model="attributeConsumed"
|
||||
/>
|
||||
<item-consumed-view
|
||||
v-for="itemConsumed in model.resources.itemsConsumed"
|
||||
:key="itemConsumed._id"
|
||||
class="action-child"
|
||||
:model="itemConsumed"
|
||||
:action="model"
|
||||
/>
|
||||
<v-divider
|
||||
v-if="
|
||||
model.resources.attributesConsumed.length ||
|
||||
model.resources.itemsConsumed.length
|
||||
"
|
||||
class="my-2"
|
||||
/>
|
||||
<tree-node-view
|
||||
v-for="child in children"
|
||||
:key="child._id"
|
||||
class="action-child"
|
||||
:model="child"
|
||||
/>
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||
import doAction from '/imports/api/creature/actions/doAction.js';
|
||||
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import AttributeConsumedView from '/imports/ui/properties/components/actions/AttributeConsumedView.vue';
|
||||
import ItemConsumedView from '/imports/ui/properties/components/actions/ItemConsumedView.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeView,
|
||||
AttributeConsumedView,
|
||||
ItemConsumedView,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
},
|
||||
theme: {
|
||||
default: {
|
||||
isDark: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
@@ -63,48 +111,118 @@ export default {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
activated: undefined,
|
||||
doActionLoading: false,
|
||||
hovering: false,
|
||||
}},
|
||||
computed: {
|
||||
hasClickListener(){
|
||||
return this.$listeners && this.$listeners.click
|
||||
},
|
||||
rollBonus(){
|
||||
if (!this.attack) return;
|
||||
return numberToSignedString(this.model.rollBonusResult);
|
||||
},
|
||||
childText(){
|
||||
let scope = this.context.creature && this.context.creature.variables;
|
||||
if (!this.model.children || !this.model.children.length) return;
|
||||
let textArray = [];
|
||||
this.model.children.forEach(child => {
|
||||
if (child.type === 'damage'){
|
||||
let { result } = evaluateString(child.amount, scope);
|
||||
textArray.push(`${result} ${child.damageType}`);
|
||||
} else if (child.type === 'savingThrow'){
|
||||
textArray.push(`DC ${child.dcResult} ${child.name}`);
|
||||
}
|
||||
});
|
||||
return textArray.join(' ');
|
||||
rollBonusTooLong(){
|
||||
return this.rollBonus && this.rollBonus.length > 3;
|
||||
},
|
||||
totalUses(){
|
||||
return Math.max(this.model.usesResult, 0);
|
||||
},
|
||||
usesLeft(){
|
||||
return Math.max(this.model.usesResult - this.model.usesUsed, 0);
|
||||
},
|
||||
cardClasses() {
|
||||
return {
|
||||
'theme--dark': this.theme.isDark,
|
||||
'theme--light': !this.theme.isDark,
|
||||
'muted-text': this.model.insufficientResources,
|
||||
'shrink': this.activated,
|
||||
}
|
||||
},
|
||||
actionTypeIcon() {
|
||||
return `$vuetify.icons.${this.model.actionType}`;
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
children(){
|
||||
return getActiveProperties({
|
||||
ancestorId: this.model._id,
|
||||
filter: {'parent.id': this.model._id},
|
||||
options: {sort: {order: 1}},
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
click(e){
|
||||
this.$emit('click', e);
|
||||
},
|
||||
doAction(){
|
||||
this.doActionLoading = true;
|
||||
this.shwing();
|
||||
doAction.call({actionId: this.model._id}, error => {
|
||||
this.doActionLoading = false;
|
||||
if (error){
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
shwing(){
|
||||
this.activated = true;
|
||||
setTimeout(() => {
|
||||
this.activated = undefined;
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.action-title {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: .3s cubic-bezier(.25,.8,.5,1);
|
||||
width: 100%;
|
||||
}
|
||||
.action-sub-title {
|
||||
color: #9e9e9e;
|
||||
flex-grow: 0;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
height: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: .3s cubic-bezier(.25,.8,.5,1);
|
||||
width: 100%;
|
||||
}
|
||||
.action-child {
|
||||
height: 32px;
|
||||
}
|
||||
.theme--light.muted-text {
|
||||
color: rgba(0,0,0,.3) !important;
|
||||
}
|
||||
.theme--dark.muted-text {
|
||||
color: hsla(0,0%,100%,.3) !important;
|
||||
}
|
||||
.action-card {
|
||||
transition: transform 0.15s cubic;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="css">
|
||||
.action-card.theme--light.muted-text .v-icon {
|
||||
color: rgba(0,0,0,.3) !important;
|
||||
}
|
||||
.action-card.theme--dark.muted-text .v-icon {
|
||||
color: hsla(0,0%,100%,.3) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
<template lang="html">
|
||||
<v-list-tile
|
||||
class="ability-list-tile"
|
||||
v-on="hasClickListener ? {click} : {}"
|
||||
>
|
||||
<v-list-tile-action
|
||||
v-if="attack"
|
||||
>
|
||||
<v-btn
|
||||
flat
|
||||
icon
|
||||
class="headline"
|
||||
:disabled="model.insufficientResources"
|
||||
@click.stop="doAction"
|
||||
>
|
||||
{{ rollBonus }}
|
||||
</v-btn>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
{{ model.name }}
|
||||
</v-list-tile-title>
|
||||
<v-list-tile-sub-title
|
||||
v-if="childText"
|
||||
v-html="childText"
|
||||
/>
|
||||
</v-list-tile-content>
|
||||
<v-list-tile-action v-if="model.usesResult">
|
||||
<v-list-tile-action-text>
|
||||
{{ usesLeft }}/{{ totalUses }}
|
||||
</v-list-tile-action-text>
|
||||
</v-list-tile-action>
|
||||
</v-list-tile>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||
import doAction from '/imports/api/creature/actions/doAction.js';
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
attack: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasClickListener(){
|
||||
return this.$listeners && this.$listeners.click
|
||||
},
|
||||
rollBonus(){
|
||||
return numberToSignedString(this.model.rollBonusResult);
|
||||
},
|
||||
childText(){
|
||||
let scope = this.context.creature && this.context.creature.variables;
|
||||
if (!this.model.children || !this.model.children.length) return;
|
||||
let textArray = [];
|
||||
this.model.children.forEach(child => {
|
||||
if (child.type === 'damage'){
|
||||
let { result } = evaluateString(child.amount, scope);
|
||||
textArray.push(`${result} ${child.damageType}`);
|
||||
} else if (child.type === 'savingThrow'){
|
||||
textArray.push(`DC ${child.dcResult} ${child.name}`);
|
||||
}
|
||||
});
|
||||
return textArray.join(' ');
|
||||
},
|
||||
totalUses(){
|
||||
return Math.max(this.model.usesResult, 0);
|
||||
},
|
||||
usesLeft(){
|
||||
return Math.max(this.model.usesResult - this.model.usesUsed, 0);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
click(e){
|
||||
this.$emit('click', e);
|
||||
},
|
||||
doAction(){
|
||||
doAction.call({actionId: this.model._id}, error => {
|
||||
if (error){
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,34 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="layout row align-center justify-start"
|
||||
:class="insufficient && 'error--text'"
|
||||
>
|
||||
<div
|
||||
class="mr-2"
|
||||
style="width: 24px; text-align: center;"
|
||||
>
|
||||
{{ model.quantity }}
|
||||
</div>
|
||||
<div
|
||||
class="text-no-wrap text-truncate"
|
||||
>
|
||||
{{ model.statName }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
insufficient(){
|
||||
return this.model.quantity > this.model.available;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,108 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
:class="{
|
||||
'theme--dark': theme.isDark,
|
||||
'theme--light': !theme.isDark,
|
||||
}"
|
||||
>
|
||||
<v-menu
|
||||
transition="slide-y-transition"
|
||||
lazy
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<div
|
||||
class="layout row align-center justify-start"
|
||||
style="height: 100%;"
|
||||
:class="{
|
||||
'error--text': insufficient,
|
||||
'clickable': context.editPermission,
|
||||
}"
|
||||
v-on="on"
|
||||
>
|
||||
<div
|
||||
class="mr-2"
|
||||
style="width: 24px; text-align: center;"
|
||||
>
|
||||
<template v-if="model.quantity === 1">
|
||||
{{ model.available }}
|
||||
</template>
|
||||
<template v-else-if="model.quantity !== 0">
|
||||
{{ model.available }} / {{ model.quantity }}
|
||||
</template>
|
||||
</div>
|
||||
<svg-icon
|
||||
v-if="model.itemIcon"
|
||||
:shape="model.itemIcon.shape"
|
||||
:color="model.itemColor"
|
||||
/>
|
||||
<div
|
||||
class="text-no-wrap text-truncate flex"
|
||||
>
|
||||
<template v-if="model.itemId">
|
||||
{{ model.itemName }}
|
||||
</template>
|
||||
<span
|
||||
v-else
|
||||
class="error--text"
|
||||
>
|
||||
Select ammo
|
||||
</span>
|
||||
</div>
|
||||
<v-icon v-if="context.editPermission">
|
||||
arrow_drop_down
|
||||
</v-icon>
|
||||
</div>
|
||||
</template>
|
||||
<select-item-to-consume
|
||||
:action="action"
|
||||
:item-consumed="model"
|
||||
/>
|
||||
</v-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectItemToConsume from '/imports/ui/properties/components/actions/SelectItemToConsume.vue';
|
||||
export default {
|
||||
components: {
|
||||
SelectItemToConsume,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
},
|
||||
theme: {
|
||||
default: {
|
||||
isDark: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
action: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
insufficient(){
|
||||
return this.model.quantity > this.model.available;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.theme--light .clickable:hover {
|
||||
background: rgba(0,0,0,.04);
|
||||
}
|
||||
.theme--dark .clickable:hover {
|
||||
background: hsla(0,0%,100%,.08);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
<template lang="html">
|
||||
<v-list v-if="items.length">
|
||||
<v-list-tile
|
||||
v-for="item in items"
|
||||
:key="item._id"
|
||||
@click="selectItem(item._id)"
|
||||
>
|
||||
<item-tree-node
|
||||
:model="item"
|
||||
:selected="itemConsumed.itemId === item._id"
|
||||
/>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
<v-card v-else>
|
||||
<v-card-text>
|
||||
No equipped items found with the tag "{{ itemConsumed.tag }}"
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ItemTreeNode from '/imports/ui/properties/treeNodeViews/ItemTreeNode.vue';
|
||||
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
||||
import { selectAmmoItem } from '/imports/api/creature/CreatureProperties.js';
|
||||
import { findIndex } from 'lodash';
|
||||
export default {
|
||||
components: {
|
||||
ItemTreeNode
|
||||
},
|
||||
props: {
|
||||
action: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
itemConsumed: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
items(){
|
||||
return getActiveProperties({
|
||||
ancestorId: this.action.ancestors[0].id,
|
||||
filter: {
|
||||
tags: this.itemConsumed.tag,
|
||||
equipped: true,
|
||||
},
|
||||
options: {
|
||||
fields: {equipped: false},
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
selectItem(itemId){
|
||||
let itemConsumedIndex = findIndex(
|
||||
this.action.resources.itemsConsumed,
|
||||
item => item._id === this.itemConsumed._id
|
||||
);
|
||||
selectAmmoItem.call({
|
||||
actionId: this.action._id,
|
||||
itemId,
|
||||
itemConsumedIndex
|
||||
}, error => {
|
||||
if (error) console.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -83,7 +83,6 @@
|
||||
this.addResourceLoading = false;
|
||||
},
|
||||
addAttributesConsumed(){
|
||||
console.log(AttributeConsumedSchema.clean({}));
|
||||
this.addResourceLoading = true;
|
||||
this.$emit('push', {
|
||||
path: ['attributesConsumed'],
|
||||
|
||||
30
app/imports/ui/properties/treeNodeViews/TreeNodeView.vue
Normal file
30
app/imports/ui/properties/treeNodeViews/TreeNodeView.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template lang="html">
|
||||
<component
|
||||
:is="treeNodeView"
|
||||
:model="model"
|
||||
:selected="selected"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import treeNodeViewIndex from '/imports/ui/properties/treeNodeViews/treeNodeViewIndex.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
...treeNodeViewIndex
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
selected: Boolean,
|
||||
},
|
||||
computed: {
|
||||
treeNodeView(){
|
||||
let type = this.model.type;
|
||||
return treeNodeViewIndex[type] || treeNodeViewIndex.default;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user