Improved item viewer significantly, including increment button.

This commit is contained in:
Stefan Zermatten
2020-05-30 23:36:27 +02:00
parent db652ac47f
commit a305929b29
14 changed files with 552 additions and 168 deletions

View File

@@ -264,6 +264,48 @@ const damageProperty = new ValidatedMethod({
},
});
const adjustQuantity = new ValidatedMethod({
name: 'CreatureProperties.methods.adjustQuantity',
validate: new SimpleSchema({
_id: SimpleSchema.RegEx.Id,
operation: {
type: String,
allowedValues: ['set', 'increment']
},
value: Number,
}).validator(),
run({_id, operation, value}) {
let currentProperty = CreatureProperties.findOne(_id);
// Check permissions
assertPropertyEditPermission(currentProperty, this.userId);
// Check if property can take damage
let schema = CreatureProperties.simpleSchema(currentProperty);
if (!schema.allowsKey('quantity')){
throw new Meteor.Error(
'Adjust quantity failed',
`Property of type "${currentProperty.type}" doesn't have a quantity`
);
}
if (operation === 'set'){
CreatureProperties.update(_id, {
$set: {quantity: value}
}, {
selector: currentProperty
});
} else if (operation === 'increment'){
// value here is 'damage'
value = -value;
let currentQuantity = currentProperty.quantity;
if (currentQuantity + value < 0) value = -currentQuantity;
CreatureProperties.update(_id, {
$inc: {quantity: value}
}, {
selector: currentProperty
});
}
},
});
const pushToProperty = new ValidatedMethod({
name: 'CreatureProperties.methods.push',
validate: null,
@@ -317,6 +359,7 @@ export {
insertPropertyFromLibraryNode,
updateProperty,
damageProperty,
adjustQuantity,
pushToProperty,
pullFromProperty,
softRemoveProperty,

View File

@@ -72,6 +72,9 @@ const calculationPropertyTypes = [
* - Write the computed results back to the database
*/
export function recomputeCreatureById(creatureId){
// Skipping computation on the client can result in the server being made to
// do work that isn't possible, in exchange for dramatic performance gains
if (Meteor.isClient) return;
let props = getActiveProperties({
ancestorId: creatureId,
filter: {type: {$in: calculationPropertyTypes}},
@@ -81,7 +84,6 @@ export function recomputeCreatureById(creatureId){
computeMemo(computationMemo);
writeAlteredProperties(computationMemo);
writeCreatureVariables(computationMemo, creatureId);
// if(Meteor.isClient) console.log(computationMemo);
recomputeDamageMultipliersById(creatureId);
return computationMemo;
}

View File

@@ -24,13 +24,13 @@ const ItemSchema = new SimpleSchema({
weight: {
type: Number,
min: 0,
defaultValue: 0,
optional: true,
},
// Value per item in the stack, in gold pieces
value: {
type: Number,
min: 0,
defaultValue: 0,
optional: true,
},
// If this item is equipped, it requires attunement
// Being equipped is `enabled === true`

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
<template lang="html">
<div>
<span
v-if="coinValue.gp || value === 0"
>
{{ coinValue.gp }} gp
</span>
<span
v-if="coinValue.sp || (coinValue.gp && coinValue.cp)"
>
{{ coinValue.sp }} sp
</span>
<span
v-if="coinValue.cp"
>
{{ coinValue.cp }} cp
</span>
</div>
</template>
<script>
import valueToCoins from '/imports/ui/utility/valueToCoins.js';
export default {
props:{
value: {
type: Number,
default: undefined,
},
},
computed:{
coinValue(){
return valueToCoins(this.value);
}
},
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,58 @@
<template lang="html">
<v-menu
v-model="open"
origin="center center"
transition="scale-transition"
:nudge-left="130"
:min-width="305"
:close-on-content-click="false"
>
<template #activator="{ on }">
<v-btn
v-bind="$attrs"
v-on="on"
>
<slot>
<v-icon>add</v-icon>
</slot>
</v-btn>
</template>
<v-card>
<increment-menu
flat
:value="value"
:open="open"
@change="changeIncrementMenu"
@close="open = false"
/>
</v-card>
</v-menu>
</template>
<script>
import IncrementMenu from '/imports/ui/components/IncrementMenu.vue';
export default {
components: {
IncrementMenu,
},
props: {
value: {
type: Number,
required: true,
},
},
data(){return {
open: false
}},
methods: {
changeIncrementMenu(e){
this.$emit('change', e);
this.open = false;
},
},
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,166 @@
<template>
<v-layout
row
align-center
justify-center
class="increment-menu"
>
<v-spacer />
<v-btn-toggle
:value="operation === 'add' ? 0: operation === 'subtract' ? 1 : null"
class="mx-2"
@click="$refs.editInput.focus()"
>
<v-btn
:disabled="context.editPermission === false"
class="filled"
@click="toggleAdd(); $nextTick(() => $refs.editInput.focus())"
>
<v-icon>add</v-icon>
</v-btn>
<v-btn
:disabled="context.editPermission === false"
class="filled"
@click="toggleSubtract(); $nextTick(() => $refs.editInput.focus())"
>
<v-icon>remove</v-icon>
</v-btn>
</v-btn-toggle>
<v-text-field
ref="editInput"
:solo="!flat"
:class="flat && 'ma-0 pa-0'"
hide-details
type="number"
style="max-width: 120px;"
min="0"
:value="editValue"
:prepend-inner-icon="operationIcon(operation)"
:disabled="context.editPermission === false"
@focus="$event.target.select()"
@keypress="keypress"
@input="input"
/>
<v-btn
:small="!flat"
:fab="!flat"
:flat="flat"
:icon="flat"
class="filled"
@click="commitEdit"
>
<v-icon>done</v-icon>
</v-btn>
<v-btn
:small="!flat"
:fab="!flat"
:flat="flat"
:icon="flat"
class="mx-0 filled"
@click="cancelEdit"
>
<v-icon>close</v-icon>
</v-btn>
<v-spacer />
</v-layout>
</template>
<script>
export default {
inject: {
context: { default: {} }
},
props: {
value: {
type: Number,
required: true,
},
open: Boolean,
flat: Boolean,
},
data() {
return {
editValue: 0,
operation: 'set',
hover: false,
};
},
watch: {
open(newValue){
if (newValue){
this.resetData();
}
}
},
methods: {
resetData(){
this.editValue = this.value;
this.operation = 'set';
// this.$nextTick didn't work, using timeout instead did
setTimeout(() => {
if (this.$refs.editInput){
this.$refs.editInput.focus();
}
}, 100);
},
cancelEdit() {
this.$emit('close');
},
commitEdit() {
this.editing = false;
let value = +this.$refs.editInput.lazyValue;
if (this.operation === 'add') {
value = -value;
}
let type = this.operation === 'set' ? 'set' : 'increment';
this.$emit('change', { type, value });
},
operationIcon(operation) {
switch (operation) {
case 'set':
return 'forward';
case 'add':
return 'add';
case 'subtract':
return 'remove';
}
},
toggleAdd(){
this.operation = (this.operation === 'add') ? 'set': 'add';
},
toggleSubtract(){
this.operation = (this.operation === 'subtract') ? 'set': 'subtract';
},
keypress(event) {
let digitsOnly = /[0-9]/;
let key = event.key;
if (key === '+') {
this.toggleAdd();
event.preventDefault();
} else if (key === '-') {
this.toggleSubtract();
event.preventDefault();
} else if (key === 'Enter') {
this.commitEdit();
} else if (!digitsOnly.test(key)){
event.preventDefault();
}
},
input(value){
if (+value < 0){
this.editValue = -value;
this.operation = 'subtract';
}
}
}
};
</script>
<style scoped>
.filled.theme--light {
background: #fff !important;
}
.filled.theme--dark {
background: #424242 !important;
}
</style>

View File

@@ -9,7 +9,7 @@
class="mr-2"
/>
<v-toolbar-title v-if="model">
{{ model.name || getPropertyName(model.type) }}
{{ title }}
</v-toolbar-title>
<v-spacer />
<v-slide-y-transition
@@ -141,13 +141,25 @@ export default {
},
color(){
return this.model && this.model.color || this.$vuetify.theme.secondary;
},
title(){
let model = this.model;
if (model.quantity !== 1 && model.quantity !== undefined){
if (model.plural){
return `${model.quantity} ${model.plural}`;
} else if (model.name) {
return `${model.quantity} ${model.name}`;
} else {
return `${model.quantity} × ${getPropertyName(model.type)}`
}
}
return model.name || getPropertyName(model.type);
}
},
methods: {
colorChanged(value){
this.$emit('color-changed', value);
},
getPropertyName,
}
}
</script>

View File

@@ -51,82 +51,31 @@
</span>
</v-layout>
<transition name="transition">
<v-toolbar
<increment-menu
v-show="editing"
:value="value"
:open="editing"
@change="changeIncrementMenu"
@close="cancelEdit"
/>
</transition>
<transition name="background-transition">
<div
v-if="editing"
justify-center
height="48"
flat
class="transparent toolbar"
>
<v-spacer />
<v-btn-toggle
:value="operation === 'add' ? 0: operation === 'subtract' ? 1 : null"
class="mr-2"
@click="$refs.editInput.focus()"
>
<v-btn
:disabled="context.editPermission === false"
class="filled"
@click="toggleAdd(); $nextTick(() => $refs.editInput.focus())"
>
<v-icon>add</v-icon>
</v-btn>
<v-btn
:disabled="context.editPermission === false"
class="filled"
@click="toggleSubtract(); $nextTick(() => $refs.editInput.focus())"
>
<v-icon>remove</v-icon>
</v-btn>
</v-btn-toggle>
<v-text-field
v-if="editing"
ref="editInput"
solo
hide-details
type="number"
style="max-width: 120px;"
min="0"
:value="editValue"
:prepend-inner-icon="operationIcon(operation)"
:disabled="context.editPermission === false"
@focus="$event.target.select()"
@keypress="keypress"
@input="input"
/>
<v-btn
small
fab
class="filled"
color="red"
@click="commitEdit"
>
<v-icon>done</v-icon>
</v-btn>
<v-btn
small
fab
class="mx-0 filled"
@click="cancelEdit"
>
<v-icon>close</v-icon>
</v-btn>
<v-spacer />
</v-toolbar>
class="page-tint"
@click="cancelEdit"
/>
</transition>
</v-flex>
<transition name="background-transition">
<div
v-if="editing"
class="page-tint"
@click="cancelEdit"
/>
</transition>
</v-layout>
</template>
<script>
import IncrementMenu from '/imports/ui/components/IncrementMenu.vue';
export default {
components: {
IncrementMenu
},
inject: {
context: { default: {} }
},
@@ -139,73 +88,35 @@
data() {
return {
editing: false,
editValue: 0,
operation: 'set',
hover: false,
};
},
methods: {
edit() {
this.editing = true;
this.operation = 'set';
this.editValue = this.value;
this.$nextTick(function() {
this.$refs.editInput.focus();
});
},
cancelEdit() {
this.editing = false;
},
commitEdit() {
this.editing = false;
let value = +this.$refs.editInput.lazyValue;
if (this.operation === 'add') {
value = -value;
}
let type = this.operation === 'set' ? 'set' : 'increment';
this.$emit('change', { type, value });
},
operationIcon(operation) {
switch (operation) {
case 'set':
return 'forward';
case 'add':
return 'add';
case 'subtract':
return 'remove';
}
},
toggleAdd(){
this.operation = (this.operation === 'add') ? 'set': 'add';
},
toggleSubtract(){
this.operation = (this.operation === 'subtract') ? 'set': 'subtract';
},
keypress(event) {
let digitsOnly = /[0-9]/;
let key = event.key;
if (key === '+') {
this.toggleAdd();
event.preventDefault();
} else if (key === '-') {
this.toggleSubtract();
event.preventDefault();
} else if (key === 'Enter') {
this.commitEdit();
} else if (!digitsOnly.test(key)){
event.preventDefault();
}
},
input(value){
if (+value < 0){
this.editValue = -value;
this.operation = 'subtract';
}
changeIncrementMenu(e){
this.$emit('change', e);
this.editing = false;
}
},
};
</script>
<style>
.health-bar .increment-menu {
margin-left: -50%;
margin-right: -50%;
width: 200%;
margin-top: -34px !important;
z-index: 4;
position: relative;
}
</style>
<style scoped>
.health-bar {
background: inherit;
@@ -228,13 +139,6 @@
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12) !important;
}
.toolbar {
margin-left: -50%;
margin-right: -50%;
width: 200%;
margin-top: -34px !important;
z-index: 4;
}
.hover {
background: #f5f5f5 !important;
}

View File

@@ -1,27 +1,27 @@
<template lang="html">
<v-card class="pa-2">
<health-bar
v-for="attribute in attributes"
:key="attribute._id"
:value="attribute.value - (attribute.damage || 0)"
:maxValue="attribute.value"
:name="attribute.name"
:_id="attribute._id"
@change="e => $emit('change', {_id: attribute._id, change: e})"
@click="e => $emit('click', {_id: attribute._id})"
/>
</v-card>
<v-card class="pa-2">
<health-bar
v-for="attribute in attributes"
:key="attribute._id"
:value="attribute.currentValue"
:max-value="attribute.value"
:name="attribute.name"
:_id="attribute._id"
@change="e => $emit('change', {_id: attribute._id, change: e})"
@click="e => $emit('click', {_id: attribute._id})"
/>
</v-card>
</template>
<script>
import HealthBar from '/imports/ui/properties/components/attributes/HealthBar.vue';
export default {
props: {
attributes: Array,
},
components: {
HealthBar,
},
props: {
attributes: Array,
},
}
</script>

View File

@@ -75,14 +75,12 @@
name="Advanced"
standalone
>
<!--
<smart-switch
label="Show increment buttons"
label="Show increment button"
:value="model.showIncrement"
:error-messages="errors.showIncrement"
@change="change('showIncrement', ...arguments)"
/>
-->
<smart-combobox
label="Tags"
class="mr-2"

View File

@@ -33,7 +33,7 @@ export default {
title(){
let model = this.model;
if (!model) return;
if (model.quantity > 1){
if (model.quantity !== 1){
if (model.plural){
return `${model.quantity} ${model.plural}`;
} else if (model.name){

View File

@@ -1,22 +1,116 @@
<template lang="html">
<div class="item-viewer">
<property-name :value="model.name" />
<property-field
name="Plural name"
:value="model.plural"
/>
<property-field
name="Quantity"
:value="model.quantity"
/>
<property-field
name="Weight"
:value="`${model.weight} lbs`"
/>
<property-field
name="Value"
:value="`${model.value} gp`"
/>
<div
v-if="tagString"
class="tags ma-3"
>
{{ tagString }}
</div>
<div
v-if="model.quantity > 1 || model.showIncrement"
class="layout column justify-center align-center"
>
<div class="display-1">
{{ model.quantity }}
</div>
<increment-button
v-if="context.creature && model.showIncrement"
icon
large
outline
color="primary"
:value="model.quantity"
@change="changeQuantity"
>
<svg-icon
:shape="getIcon('abacus').shape"
/>
</increment-button>
</div>
<div class="layout row wrap justify-space-around">
<div
v-if="model.value !== undefined"
class="mr-3 my-3"
>
<v-layout
v-if="model.quantity > 1"
row
align-center
class="mb-2"
>
<svg-icon
:shape="getIcon('cash').shape"
class="mr-2"
x-large
/>
<coin-value
class="title"
:value="totalValue"
/>
</v-layout>
<v-layout
row
align-center
>
<svg-icon
:shape="getIcon('two-coins').shape"
class="mr-2"
x-large
/>
<coin-value
class="title mr-2"
:value="model.value"
/>
<span
v-if="model.quantity > 1"
class="title"
>
each
</span>
</v-layout>
</div>
<div
v-if="model.weight !== undefined"
class="my-3"
>
<v-layout
v-if="model.quantity > 1"
row
align-center
justify-end
class="mb-2"
>
<span class="title">
{{ totalWeight }} lb
</span>
<svg-icon
:shape="getIcon('injustice').shape"
class="ml-2"
x-large
/>
</v-layout>
<v-layout
row
align-center
justify-end
>
<span class="title mr-2">
{{ model.weight }} lb
</span>
<span
v-if="model.quantity > 1"
class="title"
>
each
</span>
<svg-icon
:shape="getIcon('weight').shape"
class="ml-2"
x-large
/>
</v-layout>
</div>
</div>
<property-description
v-if="model.description"
:value="model.description"
@@ -25,9 +119,44 @@
</template>
<script>
import SVG_ICONS from '/imports/constants/SVG_ICONS.js';
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
import CoinValue from '/imports/ui/components/CoinValue.vue';
import IncrementButton from '/imports/ui/components/IncrementButton.vue';
import { adjustQuantity } from '/imports/api/creature/CreatureProperties.js';
export default {
components:{
IncrementButton,
CoinValue,
},
mixins: [propertyViewerMixin],
inject: {
context: { default: {} }
},
computed:{
tagString(){
return this.model.tags && this.model.tags.join(', ');
},
totalValue(){
return this.model.value * this.model.quantity;
},
totalWeight(){
return this.model.weight * this.model.quantity;
},
},
methods: {
getIcon(name){
return SVG_ICONS[name];
},
changeQuantity({type, value}) {
adjustQuantity.call({
_id: this.model._id,
operation: type,
value: value
});
}
}
}
</script>

View File

@@ -0,0 +1,8 @@
export default function valueToCoins(value = 0){
let totalCopperValue = Math.round(value * 100);
let copper = totalCopperValue % 10;
let totalSilverValue = Math.floor(totalCopperValue / 10);
let silver = (totalSilverValue % 10);
let totalGoldValue = Math.floor(totalSilverValue / 10);
return {gp: totalGoldValue, sp: silver, cp: copper};
}