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

View File

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

View File

@@ -1,27 +1,27 @@
<template lang="pug"> <template lang="pug">
.d-flex .d-flex
avatar#header-avatar(:user="user") avatar#header-avatar(:member="member")
div div(v-if="expanded")
h3.character-name h3.character-name
| {{user.profile.name}} | {{member.profile.name}}
.is-buffed(v-if="isBuffed") .is-buffed(v-if="isBuffed")
.svg-icon(v-html="icons.buff") .svg-icon(v-html="icons.buff")
span.small-text.character-level {{ characterLevel }} span.small-text.character-level {{ characterLevel }}
.progress-container.d-flex .progress-container.d-flex
.svg-icon(v-html="icons.health") .svg-icon(v-html="icons.health")
.progress .progress
.progress-bar.bg-health(:style="{width: `${percent(user.stats.hp, MAX_HEALTH)}%`}") .progress-bar.bg-health(:style="{width: `${percent(member.stats.hp, MAX_HEALTH)}%`}")
span.small-text {{user.stats.hp | round}} / {{MAX_HEALTH}} span.small-text {{member.stats.hp | round}} / {{MAX_HEALTH}}
.progress-container.d-flex .progress-container.d-flex
.svg-icon(v-html="icons.experience") .svg-icon(v-html="icons.experience")
.progress .progress
.progress-bar.bg-experience(:style="{width: `${percent(user.stats.exp, toNextLevel)}%`}") .progress-bar.bg-experience(:style="{width: `${percent(member.stats.exp, toNextLevel)}%`}")
span.small-text {{user.stats.exp | round}} / {{toNextLevel}} span.small-text {{member.stats.exp | round}} / {{toNextLevel}}
.progress-container.d-flex(v-if="user.flags.classSelected && !user.preferences.disableClasses") .progress-container.d-flex(v-if="hasClass")
.svg-icon(v-html="icons.mana") .svg-icon(v-html="icons.mana")
.progress .progress
.progress-bar.bg-mana(:style="{width: `${percent(user.stats.mp, maxMP)}%`}") .progress-bar.bg-mana(:style="{width: `${percent(member.stats.mp, maxMP)}%`}")
span.small-text {{user.stats.mp | round}} / {{maxMP}} span.small-text {{member.stats.mp | round}} / {{maxMP}}
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -82,7 +82,7 @@
} }
.progress-container > .progress { .progress-container > .progress {
width: 203px; width: 303px;
margin: 0px; margin: 0px;
border-radius: 2px; border-radius: 2px;
height: 16px; height: 16px;
@@ -98,7 +98,7 @@
<script> <script>
import Avatar from './avatar'; import Avatar from './avatar';
import { mapState, mapGetters } from 'client/libs/store'; import { mapState } from 'client/libs/store';
import { toNextLevel } from '../../common/script/statHelpers'; import { toNextLevel } from '../../common/script/statHelpers';
import statsComputed from '../../common/script/libs/statsComputed'; import statsComputed from '../../common/script/libs/statsComputed';
@@ -114,10 +114,11 @@ export default {
Avatar, Avatar,
}, },
props: { props: {
user: { member: {
type: Object, type: Object,
required: true, required: true,
}, },
expanded: Boolean,
}, },
data () { data () {
return { return {
@@ -131,23 +132,26 @@ export default {
}, },
methods: { methods: {
percent, percent,
hasClass () {
return this.$store.getters['members:hasClass'](this.member);
},
isBuffed () {
return this.$store.getters['members:isBuffed'](this.member);
},
}, },
computed: { computed: {
...mapState({ ...mapState({
MAX_HEALTH: 'constants.MAX_HEALTH', MAX_HEALTH: 'constants.MAX_HEALTH',
}), }),
...mapGetters({
isBuffed: 'user:isBuffed',
}),
maxMP () { maxMP () {
return statsComputed(this.user).maxMP; return statsComputed(this.member).maxMP;
}, },
toNextLevel () { // Exp to next level toNextLevel () { // Exp to next level
return toNextLevel(this.user.stats.lvl); return toNextLevel(this.member.stats.lvl);
}, },
characterLevel () { characterLevel () {
return `${this.$t('level')} ${this.user.stats.lvl} ${ return `${this.$t('level')} ${this.member.stats.lvl} ${
this.user.stats.class ? this.$t(this.user.stats.class) : '' 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 user from './user';
import * as tasks from './tasks'; import * as tasks from './tasks';
import * as guilds from './guilds'; import * as guilds from './guilds';
import * as party from './party';
import * as members from './members'; import * as members from './members';
// Actions should be named as 'actionName' and can be accessed as 'namespace:actionName' // Actions should be named as 'actionName' and can be accessed as 'namespace:actionName'
@@ -14,6 +15,7 @@ const actions = flattenAndNamespace({
user, user,
tasks, tasks,
guilds, guilds,
party,
members, 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 { flattenAndNamespace } from 'client/libs/store/helpers/internals';
import * as user from './user'; import * as user from './user';
import * as tasks from './tasks'; 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' // Getters should be named as 'getterName' and can be accessed as 'namespace:getterName'
// Example: gems in user.js -> 'user:gems' // Example: gems in user.js -> 'user:gems'
@@ -8,6 +10,8 @@ import * as tasks from './tasks';
const getters = flattenAndNamespace({ const getters = flattenAndNamespace({
user, user,
tasks, tasks,
party,
members,
}); });
export default getters; 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 gems (store) { export function data (store) {
return store.state.user.data.balance * 4; return store.state.user.data;
} }
export function isBuffed (store) { export function gems (store) {
const buffs = store.state.user.data.stats.buffs; return store.state.user.data.balance * 4;
return buffs.str || buffs.per || buffs.con || buffs.int;
} }

View File

@@ -17,9 +17,11 @@ export default function () {
title: 'Habitica', title: 'Habitica',
user: asyncResourceFactory(), user: asyncResourceFactory(),
tasks: asyncResourceFactory(), // user tasks tasks: asyncResourceFactory(), // user tasks
publicGuilds: [], party: {
members: asyncResourceFactory(),
},
myGuilds: [], myGuilds: [],
editingGroup: {}, editingGroup: {}, // TODO move to local state
// content data, frozen to prevent Vue from modifying it since it's static and never changes // 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? // 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 // NOTE this takes about 10-15ms on a fast computer