Files
habitica/website/client/components/userMenu/profileStats.vue
aszlig 4f2d066d66 client: Fix display of class bonus for other users (#10376)
Whenever one is hovering an item from another user, the bonuses of these
items are shown for the own user. So for example if you're a mage and
view a Royal Magus Robe of another mage, the class bonus is 6.

However if you're a warrior, the class bonus displays as 0 because the
attributes grid is always using the stats for the own user even if
viewing equipment of a different user.

I've fixed this by moving the user object to the properties in
attributesGrid and passing the current user from every other Vue file
that's using attributesGrid.

Not sure whether this is the right approach, as I'm no expert in Vue.js
but some testing with the client now shows the correct values.

Signed-off-by: aszlig <aszlig@nix.build>
2018-05-25 12:38:02 +02:00

509 lines
14 KiB
Vue

<template lang="pug">
#stats.standard-page
.row
.col-12.col-md-6
h2.text-center {{$t('equipment')}}
.well
.col-12.col-md-4.item-wrapper(v-for="(label, key) in equipTypes")
.box(
:id="key",
v-if="label !== 'skip'",
:class='{white: equippedItems[key] && equippedItems[key].indexOf("base_0") === -1}'
)
div(:class="`shop_${equippedItems[key]}`")
b-popover(
v-if="label !== 'skip' && equippedItems[key] && equippedItems[key].indexOf('base_0') === -1",
:target="key",
triggers="hover",
:placement="'bottom'",
:preventOverflow="false",
)
h4.gearTitle {{ getGearTitle(equippedItems[key]) }}
attributesGrid.attributesGrid(
:item="content.gear.flat[equippedItems[key]]",
:user="user"
)
h3(v-if="label !== 'skip'") {{ label }}
.col-12.col-md-6
h2.text-center {{$t('costume')}}
.well
// Use similar for loop for costume items, except show background if label is 'skip'.
.col-12.col-md-4.item-wrapper(v-for="(label, key) in equipTypes")
// Append a "C" to the key name since HTML IDs have to be unique.
.box(
:id="key + 'C'",
v-if="label !== 'skip'",
:class='{white: costumeItems[key] && costumeItems[key].indexOf("base_0") === -1}'
)
div(:class="`shop_${costumeItems[key]}`")
// Show background on 8th tile rather than a piece of equipment.
.box(v-if="label === 'skip'",
:class='{white: user.preferences.background}', style="overflow:hidden"
)
div(:class="'icon_background_' + user.preferences.background")
b-popover(
v-if="label !== 'skip' && costumeItems[key] && costumeItems[key].indexOf('base_0') === -1",
:target="key + 'C'",
triggers="hover",
:placement="'bottom'",
:preventOverflow="false",
)
h4.gearTitle {{ getGearTitle(costumeItems[key]) }}
attributesGrid.attributesGrid(
:item="content.gear.flat[costumeItems[key]]",
:user="user"
)
h3(v-if="label !== 'skip'") {{ label }}
h3(v-else) {{ $t('background') }}
.row.pet-mount-row
.col-12.col-md-6
h2.text-center(v-once) {{ $t('pets') }}
.well.pet-mount-well
.row.col-12
.col-12.col-md-4
.box(:class='{white: user.items.currentPet}')
.Pet(:class="`Pet-${user.items.currentPet}`")
.col-12.col-md-8
div
| {{ formatAnimal(user.items.currentPet, 'pet') }}
div
strong {{ $t('petsFound') }}:
| {{ totalCount(user.items.pets) }}
div
strong {{ $t('beastMasterProgress') }}:
| {{ beastMasterProgress(user.items.pets) }}
.col-12.col-md-6
h2.text-center(v-once) {{ $t('mounts') }}
.well.pet-mount-well
.row.col-12
.col-12.col-md-4
.box(:class='{white: user.items.currentMount}')
.mount(:class="`Mount_Icon_${user.items.currentMount}`")
.col-12.col-md-8
div
| {{ formatAnimal(user.items.currentMount, 'mount') }}
div
strong {{ $t('mountsTamed') }}:
span {{ totalCount(user.items.mounts) }}
div
strong {{ $t('mountMasterProgress') }}:
span {{ mountMasterProgress(user.items.mounts) }}
#attributes.row
hr.col-12
h2.col-12 {{$t('attributes')}}
.col-12.col-md-6(v-for="(statInfo, stat) in stats")
.row.col-12.stats-column
.col-12.col-md-4.attribute-label
span.hint(:popover-title='$t(statInfo.title)', popover-placement='right',
:popover='$t(statInfo.popover)', popover-trigger='mouseenter')
.stat-title(:class='stat') {{ $t(statInfo.title) }}
strong.number {{ statsComputed[stat] | floorWholeNumber }}
.col-12.col-md-6
ul.bonus-stats
li
strong {{$t('level')}}:
| {{statsComputed.levelBonus[stat]}}
li
strong {{$t('equipment')}}:
| {{statsComputed.gearBonus[stat]}}
li
strong {{$t('class')}}:
| {{statsComputed.classBonus[stat]}}
li
strong {{$t('allocated')}}:
| {{user.stats[stat]}}
li
strong {{$t('buffs')}}:
| {{user.stats.buffs[stat]}}
#allocation(v-if='showAllocation')
.row.title-row
.col-12.col-md-6
h3(v-if='userLevel100Plus', v-once, v-html="$t('noMoreAllocate')")
h3
| {{$t('pointsAvailable')}}
.counter.badge(v-if='user.stats.points || userLevel100Plus')
| {{user.stats.points}}&nbsp;
.col-12.col-md-6
.float-right
toggle-switch(
:label="$t('autoAllocation')",
v-model='user.preferences.automaticAllocation',
@change='setAutoAllocate()'
)
.row
.col-12.col-md-3(v-for='(statInfo, stat) in allocateStatsList')
.box.white.row.col-12
.col-12.col-md-9
div(:class='stat') {{ $t(stats[stat].title) }}
.number {{ user.stats[stat] }}
.points {{$t('pts')}}
.col-12.col-md-3
div
.up(v-if='user.stats.points', @click='allocate(stat)')
div
.down(@click='deallocate(stat)', v-if='user.stats.points')
.row.save-row
.col-12.col-md-6.offset-md-3.text-center
button.btn.btn-primary(@click='saveAttributes()', :disabled='loading') {{ this.loading ? $t('loading') : $t('save') }}
</template>
<script>
import toggleSwitch from 'client/components/ui/toggleSwitch';
import attributesGrid from 'client/components/inventory/equipment/attributesGrid';
import { mapState } from 'client/libs/store';
import Content from '../../../common/script/content';
import { beastMasterProgress, mountMasterProgress } from '../../../common/script/count';
import autoAllocate from '../../../common/script/fns/autoAllocate';
import allocate from '../../../common/script/ops/stats/allocate';
import statsComputed from '../../../common/script/libs/statsComputed';
import axios from 'axios';
import size from 'lodash/size';
import keys from 'lodash/keys';
const DROP_ANIMALS = keys(Content.pets);
const TOTAL_NUMBER_OF_DROP_ANIMALS = DROP_ANIMALS.length;
export default {
props: ['user', 'showAllocation'],
components: {
toggleSwitch,
attributesGrid,
},
data () {
return {
loading: false,
equipTypes: {
eyewear: this.$t('eyewear'),
head: this.$t('headgearCapitalized'),
headAccessory: this.$t('headAccess'),
back: this.$t('backAccess'),
armor: this.$t('armorCapitalized'),
body: this.$t('bodyAccess'),
weapon: this.$t('mainHand'),
_skip: 'skip',
shield: this.$t('offHand'),
},
allocateStatsList: {
str: { title: 'allocateStr', popover: 'strengthText', allocatepop: 'allocateStrPop' },
int: { title: 'allocateInt', popover: 'intText', allocatepop: 'allocateIntPop' },
con: { title: 'allocateCon', popover: 'conText', allocatepop: 'allocateConPop' },
per: { title: 'allocatePer', popover: 'perText', allocatepop: 'allocatePerPop' },
},
stats: {
str: {
title: 'strength',
popover: 'strengthText',
},
int: {
title: 'intelligence',
popover: 'intText',
},
con: {
title: 'constitution',
popover: 'conText',
},
per: {
title: 'perception',
popover: 'perText',
},
},
statUpdates: {
str: 0,
int: 0,
con: 0,
per: 0,
},
content: Content,
};
},
computed: {
...mapState({
flatGear: 'content.gear.flat',
}),
equippedItems () {
return this.user.items.gear.equipped;
},
costumeItems () {
return this.user.items.gear.costume;
},
statsComputed () {
return statsComputed(this.user);
},
userLevel100Plus () {
return this.user.stats.lvl >= 100;
},
},
methods: {
getGearTitle (key) {
return this.flatGear[key].text();
},
totalCount (objectToCount) {
let total = size(objectToCount);
return total;
},
formatAnimal (animalName, type) {
if (type === 'pet') {
if (Content.petInfo.hasOwnProperty(animalName)) {
return Content.petInfo[animalName].text();
} else {
return this.$t('noActivePet');
}
} else if (type === 'mount') {
if (Content.mountInfo.hasOwnProperty(animalName)) {
return Content.mountInfo[animalName].text();
} else {
return this.$t('noActiveMount');
}
}
},
formatBackground (background) {
let bg = Content.appearances.background;
if (bg.hasOwnProperty(background)) {
return `${bg[background].text()} (${this.$t(bg[background].set.text)})`;
}
return this.$t('noBackground');
},
beastMasterProgress (pets) {
let dropPetsFound = beastMasterProgress(pets);
let display = this.formatOutOfTotalDisplay(dropPetsFound, TOTAL_NUMBER_OF_DROP_ANIMALS);
return display;
},
mountMasterProgress (mounts) {
let dropMountsFound = mountMasterProgress(mounts);
let display = this.formatOutOfTotalDisplay(dropMountsFound, TOTAL_NUMBER_OF_DROP_ANIMALS);
return display;
},
formatOutOfTotalDisplay (stat, totalStat) {
let display = `${stat}/${totalStat}`;
return display;
},
allocate (stat) {
allocate(this.user, {query: { stat }});
this.statUpdates[stat] += 1;
},
deallocate (stat) {
if (this.user.stats[stat] === 0) return;
this.user.stats[stat] -= 1;
this.user.stats.points += 1;
this.statUpdates[stat] -= 1;
},
async saveAttributes () {
this.loading = true;
const statUpdates = {};
['str', 'int', 'per', 'con'].forEach(stat => {
if (this.statUpdates[stat] > 0) statUpdates[stat] = this.statUpdates[stat];
});
await axios.post('/api/v3/user/allocate-bulk', {
stats: statUpdates,
});
this.statUpdates = {
str: 0,
int: 0,
con: 0,
per: 0,
};
this.loading = false;
},
allocateNow () {
autoAllocate(this.user);
},
setAutoAllocate () {
let settings = {
'preferences.automaticAllocation': Boolean(this.user.preferences.automaticAllocation),
'preferences.allocationMode': 'taskbased',
};
this.$store.dispatch('user:set', settings);
},
},
};
</script>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
#stats {
.box div {
margin: 0 auto;
margin-top: 1em;
}
}
.stats-column {
border-radius: 2px;
background-color: #ffffff;
padding: .5em;
margin-bottom: 1em;
ul {
list-style-type: none;
li strong {
margin-right: .3em;
}
}
}
.stat-title {
text-transform: uppercase;
}
.str {
color: #f74e52;
}
.int {
color: #2995cd;
}
.con {
color: #ffa623;
}
.per {
color: #4f2a93;
}
#allocation {
.title-row {
margin-top: 1em;
margin-bottom: 1em;
}
.counter.badge {
position: relative;
top: -0.25em;
left: 0.5em;
color: #fff;
background-color: #ff944c;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
width: 24px;
height: 24px;
border-radius: 50%;
}
.box {
width: 148px;
height: 84px;
padding: .5em;
margin: 0 auto;
div {
margin-top: 0;
}
.number {
font-size: 40px;
text-align: left;
color: #686274;
display: inline-block;
}
.points {
display: inline-block;
font-weight: bold;
line-height: 1.67;
text-align: left;
color: #878190;
margin-left: .5em;
}
.up, .down {
border: solid #a5a1ac;
border-width: 0 3px 3px 0;
display: inline-block;
padding: 3px;
}
.up:hover, .down:hover {
cursor: pointer;
}
.up {
transform: rotate(-135deg);
-webkit-transform: rotate(-135deg);
margin-top: 1em;
}
.down {
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
}
}
}
#attributes {
.number {
font-size: 64px;
font-weight: bold;
color: #686274;
}
.attribute-label {
text-align: center;
}
}
.well {
background-color: #edecee;
border-radius: 2px;
padding: 0.4em;
padding-top: 1em;
}
.well.pet-mount-well {
padding-bottom: 1em;
strong {
margin-right: .2em;
}
}
.box {
width: 94px;
height: 92px;
border-radius: 2px;
border: dotted 1px #c3c0c7;
}
.white {
border-radius: 2px;
background: #FFFFFF;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.15), 0 1px 4px 0 rgba(26, 24, 29, 0.1);
border: 1px solid transparent;
}
.item-wrapper {
h3 {
text-align: center;
}
}
.pet-mount-row {
margin-top: 2em;
margin-bottom: 2em;
}
.mount {
margin-top: -0.2em !important;
}
.save-row {
margin-top: 1em;
}
</style>