wip client/header-party-members (#8803)

This commit is contained in:
Matteo Pagliazzi
2017-06-08 14:33:23 -07:00
committed by GitHub
parent 52edb8a8da
commit 138b5c4bdb
10 changed files with 124 additions and 64 deletions

View File

@@ -1,12 +1,18 @@
<template lang="pug">
#app-header.row
user-list-detail(:user="user")
user-list-detail(:member="user", :expanded="true")
.no-party.d-flex.justify-content-center.text-center(v-if="!user.party._id")
.align-self-center(v-once)
h3 {{ $t('battleWithFriends') }}
span.small-text(v-html="$t('inviteFriendsParty')")
br
button.btn.btn-primary {{ $t('startAParty') }}
div(v-else)
user-list-detail(
v-for="member in partyMembers",
:key="member._id",
:member="member",
)
</template>
<style lang="scss" scoped>
@@ -39,7 +45,7 @@
</style>
<script>
import { mapState } from 'client/libs/store';
import { mapGetters, mapActions } from 'client/libs/store';
import UserListDetail from './userListDetail';
export default {
@@ -47,9 +53,18 @@ export default {
UserListDetail,
},
computed: {
...mapState({
user: 'user.data',
...mapGetters({
user: 'user:data',
partyMembers: 'party:members',
}),
},
methods: {
...mapActions({
getPartyMembers: 'party:getMembers',
}),
},
created () {
this.getPartyMembers();
},
};
</script>

View File

@@ -3,45 +3,45 @@
.character-sprites
template(v-if="!avatarOnly" v-once)
// Mount Body
span(v-if="user.items.currentMount", :class="'Mount_Body_' + user.items.currentMount")
span(v-if="member.items.currentMount", :class="'Mount_Body_' + member.items.currentMount")
// Buffs that cause visual changes to avatar: Snowman, Ghost, Flower, etc
template(v-for="(klass, item) in visualBuffs")
span(v-if="user.stats.buffs[item]", :class="klass")
span(v-if="member.stats.buffs[item]", :class="klass")
// Show flower ALL THE TIME!!!
// See https://github.com/HabitRPG/habitica/issues/7133
span(:class="'hair_flower_' + user.preferences.hair.flower")
span(:class="'hair_flower_' + member.preferences.hair.flower")
// Show avatar only if not currently affected by visual buff
template(v-if!="!user.stats.buffs.snowball && !user.stats.buffs.spookySparkles && !user.stats.buffs.shinySeed && !user.stats.buffs.seafoam")
span(:class="'chair_' + user.preferences.chair")
span(:class="user.items.gear[costumeClass].back")
template(v-if!="!member.stats.buffs.snowball && !member.stats.buffs.spookySparkles && !member.stats.buffs.shinySeed && !member.stats.buffs.seafoam")
span(:class="'chair_' + member.preferences.chair")
span(:class="member.items.gear[costumeClass].back")
span(:class="skinClass")
span(:class="user.preferences.size + '_shirt_' + user.preferences.shirt")
span(:class="user.preferences.size + '_' + user.items.gear[costumeClass].armor")
span(:class="user.items.gear[costumeClass].back_collar")
span(:class="user.items.gear[costumeClass].body")
span(:class="member.preferences.size + '_shirt_' + member.preferences.shirt")
span(:class="member.preferences.size + '_' + member.items.gear[costumeClass].armor")
span(:class="member.items.gear[costumeClass].back_collar")
span(:class="member.items.gear[costumeClass].body")
span.head_0
template(v-for="type in ['base', 'bangs', 'mustache', 'beard']")
span(:class="'hair_' + type + '_' + user.preferences.hair[type] + '_' + user.preferences.hair.color")
span(:class="user.items.gear[costumeClass].eyewear")
span(:class="user.items.gear[costumeClass].head")
span(:class="user.items.gear[costumeClass].headAccessory")
span(:class="'hair_flower_' + user.preferences.hair.flower")
span(:class="user.items.gear[costumeClass].shield")
span(:class="user.items.gear[costumeClass].weapon")
span(:class="'hair_' + type + '_' + member.preferences.hair[type] + '_' + member.preferences.hair.color")
span(:class="member.items.gear[costumeClass].eyewear")
span(:class="member.items.gear[costumeClass].head")
span(:class="member.items.gear[costumeClass].headAccessory")
span(:class="'hair_flower_' + member.preferences.hair.flower")
span(:class="member.items.gear[costumeClass].shield")
span(:class="member.items.gear[costumeClass].weapon")
// Resting
span.zzz(v-if="user.preferences.sleep")
span.zzz(v-if="member.preferences.sleep")
template(v-if="!avatarOnly" v-once)
// Mount Head
span(v-if="user.items.currentMount", :class="'Mount_Head_' + user.items.currentMount")
span(v-if="member.items.currentMount", :class="'Mount_Head_' + member.items.currentMount")
// Pet
span.current-pet(v-if="user.items.currentPet", :class="'Pet-' + user.items.currentPet")
.class-badge.d-flex.justify-content-center(v-if="user.flags.classSelected")
.align-self-center.svg-icon(v-html="icons[user.stats.class]")
span.current-pet(v-if="member.items.currentPet", :class="'Pet-' + member.items.currentPet")
.class-badge.d-flex.justify-content-center(v-if="hasClass")
.align-self-center.svg-icon(v-html="icons[member.stats.class]")
</template>
<style lang="scss" scoped>
@@ -96,7 +96,7 @@ import wizardIcon from 'assets/svg/wizard.svg';
export default {
props: {
user: {
member: {
type: Object,
required: true,
},
@@ -124,21 +124,27 @@ export default {
};
},
computed: {
hasClass () {
return this.$store.getters['members:hasClass'](this.member);
},
isBuffed () {
return this.$store.getters['members:isBuffed'](this.member);
},
paddingTop () {
let val = '28px';
if (!this.avatarOnly) {
if (this.user.items.currentPet) val = '24.5px';
if (this.user.items.currentMount) val = '0px';
if (this.member.items.currentPet) val = '24.5px';
if (this.member.items.currentMount) val = '0px';
}
return val;
},
backgroundClass () {
let background = this.user.preferences.background;
let background = this.member.preferences.background;
if (background && !this.avatarOnly) {
return `background_${this.user.preferences.background}`;
return `background_${this.member.preferences.background}`;
}
return '';
@@ -147,17 +153,17 @@ export default {
return {
snowball: 'snowman',
spookySparkles: 'ghost',
shinySeed: `avatar_floral_${this.user.stats.class}`,
shinySeed: `avatar_floral_${this.member.stats.class}`,
seafoam: 'seafoam_star',
};
},
skinClass () {
let baseClass = `skin_${this.user.preferences.skin}`;
let baseClass = `skin_${this.member.preferences.skin}`;
return `${baseClass}${this.user.preferences.sleep ? '_sleep' : ''}`;
return `${baseClass}${this.member.preferences.sleep ? '_sleep' : ''}`;
},
costumeClass () {
return this.user.preferences.costume ? 'costume' : 'equipped';
return this.member.preferences.costume ? 'costume' : 'equipped';
},
},
};

View File

@@ -1,27 +1,27 @@
<template lang="pug">
.d-flex
avatar#header-avatar(:user="user")
div
avatar#header-avatar(:member="member")
div(v-if="expanded")
h3.character-name
| {{user.profile.name}}
| {{member.profile.name}}
.is-buffed(v-if="isBuffed")
.svg-icon(v-html="icons.buff")
span.small-text.character-level {{ characterLevel }}
.progress-container.d-flex
.svg-icon(v-html="icons.health")
.progress
.progress-bar.bg-health(:style="{width: `${percent(user.stats.hp, MAX_HEALTH)}%`}")
span.small-text {{user.stats.hp | round}} / {{MAX_HEALTH}}
.progress-bar.bg-health(:style="{width: `${percent(member.stats.hp, MAX_HEALTH)}%`}")
span.small-text {{member.stats.hp | round}} / {{MAX_HEALTH}}
.progress-container.d-flex
.svg-icon(v-html="icons.experience")
.progress
.progress-bar.bg-experience(:style="{width: `${percent(user.stats.exp, toNextLevel)}%`}")
span.small-text {{user.stats.exp | round}} / {{toNextLevel}}
.progress-container.d-flex(v-if="user.flags.classSelected && !user.preferences.disableClasses")
.progress-bar.bg-experience(:style="{width: `${percent(member.stats.exp, toNextLevel)}%`}")
span.small-text {{member.stats.exp | round}} / {{toNextLevel}}
.progress-container.d-flex(v-if="hasClass")
.svg-icon(v-html="icons.mana")
.progress
.progress-bar.bg-mana(:style="{width: `${percent(user.stats.mp, maxMP)}%`}")
span.small-text {{user.stats.mp | round}} / {{maxMP}}
.progress-bar.bg-mana(:style="{width: `${percent(member.stats.mp, maxMP)}%`}")
span.small-text {{member.stats.mp | round}} / {{maxMP}}
</template>
<style lang="scss" scoped>
@@ -82,7 +82,7 @@
}
.progress-container > .progress {
width: 203px;
width: 303px;
margin: 0px;
border-radius: 2px;
height: 16px;
@@ -98,7 +98,7 @@
<script>
import Avatar from './avatar';
import { mapState, mapGetters } from 'client/libs/store';
import { mapState } from 'client/libs/store';
import { toNextLevel } from '../../common/script/statHelpers';
import statsComputed from '../../common/script/libs/statsComputed';
@@ -114,10 +114,11 @@ export default {
Avatar,
},
props: {
user: {
member: {
type: Object,
required: true,
},
expanded: Boolean,
},
data () {
return {
@@ -131,23 +132,26 @@ export default {
},
methods: {
percent,
hasClass () {
return this.$store.getters['members:hasClass'](this.member);
},
isBuffed () {
return this.$store.getters['members:isBuffed'](this.member);
},
},
computed: {
...mapState({
MAX_HEALTH: 'constants.MAX_HEALTH',
}),
...mapGetters({
isBuffed: 'user:isBuffed',
}),
maxMP () {
return statsComputed(this.user).maxMP;
return statsComputed(this.member).maxMP;
},
toNextLevel () { // Exp to next level
return toNextLevel(this.user.stats.lvl);
return toNextLevel(this.member.stats.lvl);
},
characterLevel () {
return `${this.$t('level')} ${this.user.stats.lvl} ${
this.user.stats.class ? this.$t(this.user.stats.class) : ''
return `${this.$t('level')} ${this.member.stats.lvl} ${
this.member.stats.class ? this.$t(this.member.stats.class) : ''
}`;
},
},

View File

@@ -4,6 +4,7 @@ import * as common from './common';
import * as user from './user';
import * as tasks from './tasks';
import * as guilds from './guilds';
import * as party from './party';
import * as members from './members';
// Actions should be named as 'actionName' and can be accessed as 'namespace:actionName'
@@ -14,6 +15,7 @@ const actions = flattenAndNamespace({
user,
tasks,
guilds,
party,
members,
});

View File

@@ -0,0 +1,13 @@
import { loadAsyncResource } from 'client/libs/asyncResource';
export function getMembers (store, forceLoad = false) {
return loadAsyncResource({
store,
path: 'party.members',
url: '/api/v3/groups/party/members?includeAllPublicFields=true',
deserialize (response) {
return response.data.data;
},
forceLoad,
});
}

View File

@@ -1,6 +1,8 @@
import { flattenAndNamespace } from 'client/libs/store/helpers/internals';
import * as user from './user';
import * as tasks from './tasks';
import * as party from './party';
import * as members from './members';
// Getters should be named as 'getterName' and can be accessed as 'namespace:getterName'
// Example: gems in user.js -> 'user:gems'
@@ -8,6 +10,8 @@ import * as tasks from './tasks';
const getters = flattenAndNamespace({
user,
tasks,
party,
members,
});
export default getters;

View File

@@ -0,0 +1,12 @@
export function isBuffed () {
return (member) => {
const buffs = member.stats.buffs;
return buffs.str || buffs.per || buffs.con || buffs.int;
};
}
export function hasClass () {
return (member) => {
return member.stats.lvl >= 10 && !member.preferences.disableClasses;
};
}

View File

@@ -0,0 +1,3 @@
export function members (store) {
return store.state.party.members.data;
}

View File

@@ -1,8 +1,7 @@
export function data (store) {
return store.state.user.data;
}
export function gems (store) {
return store.state.user.data.balance * 4;
}
export function isBuffed (store) {
const buffs = store.state.user.data.stats.buffs;
return buffs.str || buffs.per || buffs.con || buffs.int;
}

View File

@@ -17,9 +17,11 @@ export default function () {
title: 'Habitica',
user: asyncResourceFactory(),
tasks: asyncResourceFactory(), // user tasks
publicGuilds: [],
party: {
members: asyncResourceFactory(),
},
myGuilds: [],
editingGroup: {},
editingGroup: {}, // TODO move to local state
// content data, frozen to prevent Vue from modifying it since it's static and never changes
// TODO apply freezing to the entire codebase (the server) and not only to the client side?
// NOTE this takes about 10-15ms on a fast computer