mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
Beard and mustache facial hairs now can be bought as a full set for 5 gems (#10338)
* Purchasing All Facial Hairs Fixed * Notifications z-index fixed * Notifications z-index fixed x2 * Z-indexes fixed, facial hairs buying corrected * isPurchaseAllNeeded refactored * isPurchaseAllNeeded is more generic now * Linting Passed
This commit is contained in:
committed by
Matteo Pagliazzi
parent
128ec5a1b1
commit
6c64a1cd8c
@@ -163,32 +163,27 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
:class='{active: user.preferences.hair.bangs === option}')
|
:class='{active: user.preferences.hair.bangs === option}')
|
||||||
.bangs.sprite.customize-option(:class="`hair_bangs_${option}_${user.preferences.hair.color}`", @click='set({"preferences.hair.bangs": option})')
|
.bangs.sprite.customize-option(:class="`hair_bangs_${option}_${user.preferences.hair.color}`", @click='set({"preferences.hair.bangs": option})')
|
||||||
#facialhair.row(v-if='activeSubPage === "facialhair"')
|
#facialhair.row(v-if='activeSubPage === "facialhair"')
|
||||||
.col-12.customize-options(v-if='editing')
|
|
||||||
.head_0.option(@click='set({"preferences.hair.beard": 0})', :class="[{ active: user.preferences.hair.beard === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
|
|
||||||
.option(v-for='option in baseHair5',
|
|
||||||
:class='{active: option.active, locked: option.locked}')
|
|
||||||
.base.sprite.customize-option(:class="`hair_beard_${option.key}_${user.preferences.hair.color}`", @click='option.click')
|
|
||||||
.gem-lock(v-if='option.locked')
|
|
||||||
.svg-icon.gem(v-html='icons.gem')
|
|
||||||
span 2
|
|
||||||
.col-12.text-center(v-if='!userOwnsSet("hair", baseHair5Keys, "beard")')
|
|
||||||
.gem-lock
|
|
||||||
.svg-icon.gem(v-html='icons.gem')
|
|
||||||
span 5
|
|
||||||
button.btn.btn-secondary.purchase-all(@click='unlock(`hair.beard.${baseHair5Keys.join(",hair.beard.")}`)') {{ $t('purchaseAll') }}
|
|
||||||
.col-12.customize-options(v-if='editing')
|
.col-12.customize-options(v-if='editing')
|
||||||
.head_0.option(@click='set({"preferences.hair.mustache": 0})', :class="[{ active: user.preferences.hair.mustache === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
|
.head_0.option(@click='set({"preferences.hair.mustache": 0})', :class="[{ active: user.preferences.hair.mustache === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
|
||||||
.option(v-for='option in baseHair6',
|
.option(v-for='option in baseHair5',
|
||||||
:class='{active: option.active, locked: option.locked}')
|
:class='{active: option.active, locked: option.locked}')
|
||||||
.base.sprite.customize-option(:class="`hair_mustache_${option.key}_${user.preferences.hair.color}`", @click='option.click')
|
.base.sprite.customize-option(:class="`hair_mustache_${option.key}_${user.preferences.hair.color}`", @click='option.click')
|
||||||
.gem-lock(v-if='option.locked')
|
.gem-lock(v-if='option.locked')
|
||||||
.svg-icon.gem(v-html='icons.gem')
|
.svg-icon.gem(v-html='icons.gem')
|
||||||
span 2
|
span 2
|
||||||
.col-12.text-center(v-if='!userOwnsSet("hair", baseHair6Keys, "mustache")')
|
.col-12.customize-options(v-if='editing')
|
||||||
|
.head_0.option(@click='set({"preferences.hair.beard": 0})', :class="[{ active: user.preferences.hair.beard === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
|
||||||
|
.option(v-for='option in baseHair6',
|
||||||
|
:class='{active: option.active, locked: option.locked}')
|
||||||
|
.base.sprite.customize-option(:class="`hair_beard_${option.key}_${user.preferences.hair.color}`", @click='option.click')
|
||||||
|
.gem-lock(v-if='option.locked')
|
||||||
|
.svg-icon.gem(v-html='icons.gem')
|
||||||
|
span 2
|
||||||
|
.col-12.text-center(v-if="isPurchaseAllNeeded('hair', ['baseHair5', 'baseHair6'], ['mustache', 'beard'])")
|
||||||
.gem-lock
|
.gem-lock
|
||||||
.svg-icon.gem(v-html='icons.gem')
|
.svg-icon.gem(v-html='icons.gem')
|
||||||
span 5
|
span 5
|
||||||
button.btn.btn-secondary.purchase-all(@click='unlock(`hair.mustache.${baseHair6Keys.join(",hair.mustache.")}`)') {{ $t('purchaseAll') }}
|
button.btn.btn-secondary.purchase-all(@click='unlock(`hair.mustache.${baseHair5Keys.join(",hair.mustache.")},hair.beard.${baseHair6Keys.join(",hair.beard.")}`)') {{ $t('purchaseAll') }}
|
||||||
#extra.section.container.customize-section(v-if='activeTopPage === "extra"')
|
#extra.section.container.customize-section(v-if='activeTopPage === "extra"')
|
||||||
.row.sub-menu
|
.row.sub-menu
|
||||||
.col-3.offset-1.text-center.sub-menu-item(@click='changeSubPage("glasses")', :class='{active: activeSubPage === "glasses"}')
|
.col-3.offset-1.text-center.sub-menu-item(@click='changeSubPage("glasses")', :class='{active: activeSubPage === "glasses"}')
|
||||||
@@ -1010,8 +1005,8 @@ export default {
|
|||||||
baseHair2Keys: [2, 4, 5, 6, 7, 8],
|
baseHair2Keys: [2, 4, 5, 6, 7, 8],
|
||||||
baseHair3Keys: [9, 10, 11, 12, 13, 14],
|
baseHair3Keys: [9, 10, 11, 12, 13, 14],
|
||||||
baseHair4Keys: [15, 16, 17, 18, 19, 20],
|
baseHair4Keys: [15, 16, 17, 18, 19, 20],
|
||||||
baseHair5Keys: [1, 2, 3],
|
baseHair5Keys: [1, 2],
|
||||||
baseHair6Keys: [1, 2],
|
baseHair6Keys: [1, 2, 3],
|
||||||
animalEarsKeys: ['bearEars', 'cactusEars', 'foxEars', 'lionEars', 'pandaEars', 'pigEars', 'tigerEars', 'wolfEars'],
|
animalEarsKeys: ['bearEars', 'cactusEars', 'foxEars', 'lionEars', 'pandaEars', 'pigEars', 'tigerEars', 'wolfEars'],
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
logoPurple,
|
logoPurple,
|
||||||
@@ -1228,7 +1223,7 @@ export default {
|
|||||||
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
||||||
let keys = this.baseHair5Keys;
|
let keys = this.baseHair5Keys;
|
||||||
let options = keys.map(key => {
|
let options = keys.map(key => {
|
||||||
return this.mapKeysToOption(key, 'hair', 'beard');
|
return this.mapKeysToOption(key, 'hair', 'mustache');
|
||||||
});
|
});
|
||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
@@ -1237,7 +1232,7 @@ export default {
|
|||||||
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
|
||||||
let keys = this.baseHair6Keys;
|
let keys = this.baseHair6Keys;
|
||||||
let options = keys.map(key => {
|
let options = keys.map(key => {
|
||||||
return this.mapKeysToOption(key, 'hair', 'mustache');
|
return this.mapKeysToOption(key, 'hair', 'beard');
|
||||||
});
|
});
|
||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
@@ -1335,6 +1330,68 @@ export default {
|
|||||||
|
|
||||||
return owns;
|
return owns;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Allows you to find out whether you need the "Purchase All" button or not. If there are more than 2 unpurchased items, returns true, otherwise returns false.
|
||||||
|
* @param {string} category - The selected category.
|
||||||
|
* @param {string[]} keySets - The items keySets.
|
||||||
|
* @param {string[]} [types] - The items types (subcategories). Optional.
|
||||||
|
* @returns {boolean} - Determines whether the "Purchase All" button is needed (true) or not (false).
|
||||||
|
*/
|
||||||
|
isPurchaseAllNeeded (category, keySets, types) {
|
||||||
|
const purchasedItemsLengths = [];
|
||||||
|
// If item types are specified, count them
|
||||||
|
if (types && types.length > 0) {
|
||||||
|
// Types can be undefined, so we must check them.
|
||||||
|
types.forEach((type) => {
|
||||||
|
if (this.user.purchased[category][type]) {
|
||||||
|
purchasedItemsLengths
|
||||||
|
.push(Object.keys(this.user.purchased[category][type]).length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let purchasedItemsCounter = 0;
|
||||||
|
|
||||||
|
// If types are not specified, recursively
|
||||||
|
// search for purchased items in the category
|
||||||
|
const findPurchasedItems = (item) => {
|
||||||
|
if (typeof item === 'object') {
|
||||||
|
Object.values(item)
|
||||||
|
.forEach((innerItem) => {
|
||||||
|
if (typeof innerItem === 'boolean' && innerItem === true) {
|
||||||
|
purchasedItemsCounter += 1;
|
||||||
|
}
|
||||||
|
return findPurchasedItems(innerItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return purchasedItemsCounter;
|
||||||
|
};
|
||||||
|
|
||||||
|
findPurchasedItems(this.user.purchased[category]);
|
||||||
|
if (purchasedItemsCounter > 0) {
|
||||||
|
purchasedItemsLengths.push(purchasedItemsCounter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need to count the key sets (below)
|
||||||
|
// if there are no purchased items at all.
|
||||||
|
if (purchasedItemsLengths.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allItemsLengths = [];
|
||||||
|
// Key sets must be specify correctly.
|
||||||
|
keySets.forEach((keySet) => {
|
||||||
|
allItemsLengths.push(Object.keys(this[keySet]).length);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simply sum all the length values and
|
||||||
|
// write them into variables for the convenience.
|
||||||
|
const allItems = allItemsLengths.reduce((acc, val) => acc + val);
|
||||||
|
const purchasedItems = purchasedItemsLengths.reduce((acc, val) => acc + val);
|
||||||
|
|
||||||
|
const unpurchasedItems = allItems - purchasedItems;
|
||||||
|
return unpurchasedItems > 2;
|
||||||
|
},
|
||||||
prev () {
|
prev () {
|
||||||
this.modalPage -= 1;
|
this.modalPage -= 1;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ div
|
|||||||
inbox-modal
|
inbox-modal
|
||||||
creator-intro
|
creator-intro
|
||||||
profile
|
profile
|
||||||
b-navbar.navbar.navbar-inverse.fixed-top.navbar-expand-lg(type="dark")
|
b-navbar.navbar.navbar-inverse.fixed-top.navbar-expand-lg(type="dark", :class="navbarZIndexClass")
|
||||||
.navbar-header
|
.navbar-header
|
||||||
.logo.svg-icon.d-none.d-xl-block(v-html="icons.logo")
|
.logo.svg-icon.d-none.d-xl-block(v-html="icons.logo")
|
||||||
.svg-icon.gryphon.d-md-block.d-none.d-xl-none
|
.svg-icon.gryphon.d-md-block.d-none.d-xl-none
|
||||||
@@ -145,7 +145,16 @@ div
|
|||||||
padding-right: 12.5px;
|
padding-right: 12.5px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
box-shadow: 0 1px 2px 0 rgba($black, 0.24);
|
box-shadow: 0 1px 2px 0 rgba($black, 0.24);
|
||||||
z-index: 1042; // To stay above snakbar notifications and modals
|
}
|
||||||
|
|
||||||
|
.navbar-z-index {
|
||||||
|
&-normal {
|
||||||
|
z-index: 1080;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-modal {
|
||||||
|
z-index: 1035;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-header {
|
.navbar-header {
|
||||||
@@ -327,7 +336,14 @@ export default {
|
|||||||
user: 'user.data',
|
user: 'user.data',
|
||||||
userHourglasses: 'user.data.purchased.plan.consecutive.trinkets',
|
userHourglasses: 'user.data.purchased.plan.consecutive.trinkets',
|
||||||
groupPlans: 'groupPlans',
|
groupPlans: 'groupPlans',
|
||||||
|
modalStack: 'modalStack',
|
||||||
}),
|
}),
|
||||||
|
navbarZIndexClass () {
|
||||||
|
if (this.modalStack.length > 0) {
|
||||||
|
return 'navbar-z-index-modal';
|
||||||
|
}
|
||||||
|
return 'navbar-z-index-normal';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.getUserGroupPlans();
|
this.getUserGroupPlans();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
.notifications
|
.notifications(:class="notificationsTopPos")
|
||||||
div(v-for='notification in notifications', :key='notification.uuid')
|
div(v-for='notification in notificationStore', :key='notification.uuid')
|
||||||
notification(:notification='notification')
|
notification(:notification='notification')
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -8,13 +8,23 @@
|
|||||||
.notifications {
|
.notifications {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: 65px;
|
|
||||||
width: 350px;
|
width: 350px;
|
||||||
z-index: 1041; // 1041 is above modal backgrounds
|
z-index: 1070; // 1070 is above modal backgrounds
|
||||||
|
|
||||||
|
&-top-pos {
|
||||||
|
&-normal {
|
||||||
|
top: 65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-sleeping {
|
||||||
|
top: 105px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState } from 'client/libs/store';
|
||||||
import notification from './notification';
|
import notification from './notification';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -22,8 +32,19 @@ export default {
|
|||||||
notification,
|
notification,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
notifications () {
|
...mapState({
|
||||||
return this.$store.state.notificationStore;
|
notificationStore: 'notificationStore',
|
||||||
|
userSleeping: 'user.data.preferences.sleep',
|
||||||
|
}),
|
||||||
|
notificationsTopPos () {
|
||||||
|
const base = 'notifications-top-pos-';
|
||||||
|
let modifier = '';
|
||||||
|
if (this.userSleeping) {
|
||||||
|
modifier = 'sleeping';
|
||||||
|
} else {
|
||||||
|
modifier = 'normal';
|
||||||
|
}
|
||||||
|
return `${base}${modifier}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user