mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-14 21:27:23 +01:00
wip client/header-party-members (#8803)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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) : ''
|
||||
}`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
13
website/client/store/actions/party.js
Normal file
13
website/client/store/actions/party.js
Normal 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,
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
12
website/client/store/getters/members.js
Normal file
12
website/client/store/getters/members.js
Normal 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;
|
||||
};
|
||||
}
|
||||
3
website/client/store/getters/party.js
Normal file
3
website/client/store/getters/party.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export function members (store) {
|
||||
return store.state.party.members.data;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user