diff --git a/website/client/components/creatorIntro.vue b/website/client/components/creatorIntro.vue index 4cff8ea8b2..19ca0efc5e 100644 --- a/website/client/components/creatorIntro.vue +++ b/website/client/components/creatorIntro.vue @@ -163,32 +163,27 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true :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})') #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') .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}') .base.sprite.customize-option(:class="`hair_mustache_${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", 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 .svg-icon.gem(v-html='icons.gem') 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"') .row.sub-menu .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], baseHair3Keys: [9, 10, 11, 12, 13, 14], baseHair4Keys: [15, 16, 17, 18, 19, 20], - baseHair5Keys: [1, 2, 3], - baseHair6Keys: [1, 2], + baseHair5Keys: [1, 2], + baseHair6Keys: [1, 2, 3], animalEarsKeys: ['bearEars', 'cactusEars', 'foxEars', 'lionEars', 'pandaEars', 'pigEars', 'tigerEars', 'wolfEars'], icons: Object.freeze({ logoPurple, @@ -1228,7 +1223,7 @@ export default { let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line let keys = this.baseHair5Keys; let options = keys.map(key => { - return this.mapKeysToOption(key, 'hair', 'beard'); + return this.mapKeysToOption(key, 'hair', 'mustache'); }); return options; }, @@ -1237,7 +1232,7 @@ export default { let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line let keys = this.baseHair6Keys; let options = keys.map(key => { - return this.mapKeysToOption(key, 'hair', 'mustache'); + return this.mapKeysToOption(key, 'hair', 'beard'); }); return options; }, @@ -1335,6 +1330,68 @@ export default { 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 () { this.modalPage -= 1; }, diff --git a/website/client/components/header/menu.vue b/website/client/components/header/menu.vue index 09400e80d4..22800b5dc9 100644 --- a/website/client/components/header/menu.vue +++ b/website/client/components/header/menu.vue @@ -3,7 +3,7 @@ div inbox-modal creator-intro 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 .logo.svg-icon.d-none.d-xl-block(v-html="icons.logo") .svg-icon.gryphon.d-md-block.d-none.d-xl-none @@ -145,7 +145,16 @@ div padding-right: 12.5px; height: 56px; 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 { @@ -327,7 +336,14 @@ export default { user: 'user.data', userHourglasses: 'user.data.purchased.plan.consecutive.trinkets', groupPlans: 'groupPlans', + modalStack: 'modalStack', }), + navbarZIndexClass () { + if (this.modalStack.length > 0) { + return 'navbar-z-index-modal'; + } + return 'navbar-z-index-normal'; + }, }, mounted () { this.getUserGroupPlans(); diff --git a/website/client/components/snackbars/notifications.vue b/website/client/components/snackbars/notifications.vue index 18705af494..22b0d78030 100644 --- a/website/client/components/snackbars/notifications.vue +++ b/website/client/components/snackbars/notifications.vue @@ -1,6 +1,6 @@ @@ -8,13 +8,23 @@ .notifications { position: fixed; right: 10px; - top: 65px; 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; + } + } }