mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-07-14 07:52:15 +00:00
Merge branch 'sabrecat/customizations' into subs-private
This commit is contained in:
commit
69c0488335
23 changed files with 494 additions and 652 deletions
|
|
@ -217,16 +217,13 @@
|
|||
|
||||
.btn-show-more {
|
||||
display: block;
|
||||
width: 50%;
|
||||
max-width: 448px;
|
||||
margin: 0 auto;
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
background: $gray-600;
|
||||
background: $gray-500;
|
||||
color: $gray-200 !important; // Otherwise it gets ignored when used on an A element
|
||||
box-shadow: none;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
// TODO move to item component?
|
||||
|
||||
.item, .item-wrapper, .item > div > div {
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.items > div {
|
||||
display: inline-block;
|
||||
margin-right: 24px;
|
||||
|
|
@ -9,6 +15,15 @@
|
|||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid $purple-400;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
|
||||
}
|
||||
}
|
||||
|
||||
.items-one-line .item-wrapper {
|
||||
|
|
@ -56,11 +71,6 @@
|
|||
background: $purple-500;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
|
||||
border-color: $purple-400;
|
||||
}
|
||||
|
||||
&.highlight {
|
||||
box-shadow: 0 0 8px 8px rgba($black, 0.16), 0 5px 10px 0 rgba($black, 0.12) !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<span :class="[skinClass, specialMountClass]"></span>
|
||||
<!-- eslint-disable max-len-->
|
||||
<span
|
||||
:class="[member.preferences.size + '_shirt_' + member.preferences.shirt, specialMountClass]"
|
||||
:class="[shirtClass, specialMountClass]"
|
||||
></span>
|
||||
<!-- eslint-enable max-len-->
|
||||
<span :class="['head_0', specialMountClass]"></span>
|
||||
|
|
@ -46,12 +46,10 @@
|
|||
<template
|
||||
v-for="type in ['bangs', 'base', 'mustache', 'beard']"
|
||||
>
|
||||
<!-- eslint-disable max-len-->
|
||||
<span
|
||||
:key="type"
|
||||
:class="['hair_' + type + '_' + member.preferences.hair[type] + '_' + member.preferences.hair.color, specialMountClass]"
|
||||
:class="[hairClass(type), specialMountClass]"
|
||||
></span>
|
||||
<!-- eslint-enable max-len-->
|
||||
</template>
|
||||
<span :class="[getGearClass('body'), specialMountClass]"></span>
|
||||
<span :class="[getGearClass('eyewear'), specialMountClass]"></span>
|
||||
|
|
@ -233,10 +231,20 @@ export default {
|
|||
},
|
||||
skinClass () {
|
||||
if (!this.member) return '';
|
||||
if (this.overrideAvatarGear?.skin) {
|
||||
return `skin_${this.overrideAvatarGear.skin}`;
|
||||
}
|
||||
const baseClass = `skin_${this.member.preferences.skin}`;
|
||||
|
||||
return `${baseClass}${this.member.preferences.sleep ? '_sleep' : ''}`;
|
||||
},
|
||||
shirtClass () {
|
||||
if (!this.member) return '';
|
||||
if (this.overrideAvatarGear?.shirt) {
|
||||
return `${this.member.preferences.size}_shirt_${this.overrideAvatarGear.shirt}`;
|
||||
}
|
||||
return `${this.member.preferences.size}_shirt_${this.member.preferences.shirt}`;
|
||||
},
|
||||
costumeClass () {
|
||||
return this.member?.preferences.costume ? 'costume' : 'equipped';
|
||||
},
|
||||
|
|
@ -269,6 +277,17 @@ export default {
|
|||
|
||||
return result;
|
||||
},
|
||||
hairClass (slot) {
|
||||
if (this.overrideAvatarGear?.hair) {
|
||||
if (this.overrideAvatarGear.hair[slot]) {
|
||||
return `hair_${slot}_${this.overrideAvatarGear.hair[slot]}_${this.member.preferences.hair.color}`;
|
||||
}
|
||||
if (this.overrideAvatarGear.hair.color) {
|
||||
return `hair_${slot}_${this.member.preferences.hair[slot]}_${this.overrideAvatarGear.hair.color}`;
|
||||
}
|
||||
}
|
||||
return `hair_${slot}_${this.member.preferences.hair[slot]}_${this.member.preferences.hair.color}`;
|
||||
},
|
||||
hideGear (gearType) {
|
||||
if (!this.member) return true;
|
||||
if (gearType === 'weapon') {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
import appearance from '@/../../common/script/content/appearance';
|
||||
import { subPageMixin } from '../../mixins/subPage';
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities';
|
||||
import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities';
|
||||
import subMenu from './sub-menu';
|
||||
import customizeOptions from './customize-options';
|
||||
import gem from '@/assets/svg/gem.svg';
|
||||
|
|
@ -51,7 +51,7 @@ export default {
|
|||
mixins: [
|
||||
subPageMixin,
|
||||
userStateMixin,
|
||||
avatarEditorUtilies,
|
||||
avatarEditorUtilities,
|
||||
],
|
||||
props: [
|
||||
'editing',
|
||||
|
|
|
|||
|
|
@ -67,11 +67,11 @@
|
|||
<script>
|
||||
import gem from '@/assets/svg/gem.svg';
|
||||
import gold from '@/assets/svg/gold.svg';
|
||||
import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities';
|
||||
import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
avatarEditorUtilies,
|
||||
avatarEditorUtilities,
|
||||
],
|
||||
props: ['items', 'currentValue', 'fullSet'],
|
||||
data () {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
import appearance from '@/../../common/script/content/appearance';
|
||||
import { subPageMixin } from '../../mixins/subPage';
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities';
|
||||
import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities';
|
||||
import subMenu from './sub-menu';
|
||||
import customizeOptions from './customize-options';
|
||||
import gem from '@/assets/svg/gem.svg';
|
||||
|
|
@ -76,7 +76,7 @@ export default {
|
|||
mixins: [
|
||||
subPageMixin,
|
||||
userStateMixin,
|
||||
avatarEditorUtilies,
|
||||
avatarEditorUtilities,
|
||||
],
|
||||
props: [
|
||||
'editing',
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ import appearance from '@/../../common/script/content/appearance';
|
|||
import appearanceSets from '@/../../common/script/content/appearance/sets';
|
||||
import { subPageMixin } from '../../mixins/subPage';
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities';
|
||||
import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities';
|
||||
import subMenu from './sub-menu';
|
||||
import customizeOptions from './customize-options';
|
||||
import gem from '@/assets/svg/gem.svg';
|
||||
|
|
@ -97,7 +97,7 @@ export default {
|
|||
mixins: [
|
||||
subPageMixin,
|
||||
userStateMixin,
|
||||
avatarEditorUtilies,
|
||||
avatarEditorUtilities,
|
||||
],
|
||||
props: [
|
||||
'editing',
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import appearance from '@/../../common/script/content/appearance';
|
|||
import appearanceSets from '@/../../common/script/content/appearance/sets';
|
||||
import { subPageMixin } from '../../mixins/subPage';
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities';
|
||||
import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities';
|
||||
import subMenu from './sub-menu';
|
||||
import customizeOptions from './customize-options';
|
||||
import gem from '@/assets/svg/gem.svg';
|
||||
|
|
@ -56,7 +56,7 @@ export default {
|
|||
mixins: [
|
||||
subPageMixin,
|
||||
userStateMixin,
|
||||
avatarEditorUtilies,
|
||||
avatarEditorUtilities,
|
||||
],
|
||||
props: [
|
||||
'editing',
|
||||
|
|
|
|||
|
|
@ -1187,7 +1187,7 @@ import gold from '@/assets/svg/gold.svg';
|
|||
import arrowRight from '@/assets/svg/arrow_right.svg';
|
||||
import arrowLeft from '@/assets/svg/arrow_left.svg';
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
import { avatarEditorUtilies } from '../mixins/avatarEditUtilities';
|
||||
import { avatarEditorUtilities } from '../mixins/avatarEditUtilities';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -1201,7 +1201,7 @@ export default {
|
|||
toggleSwitch,
|
||||
usernameForm,
|
||||
},
|
||||
mixins: [guide, notifications, avatarEditorUtilies],
|
||||
mixins: [guide, notifications, avatarEditorUtilities],
|
||||
data () {
|
||||
const backgroundShopSets = shops.getBackgroundShopSets();
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,12 @@
|
|||
>
|
||||
{{ $t('quests') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
:to="{name: 'customizations'}"
|
||||
>
|
||||
{{ $t('customizations') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
:to="{name: 'seasonal'}"
|
||||
|
|
|
|||
|
|
@ -89,19 +89,19 @@
|
|||
v-if="item.value > 0 && !(item.key === 'gem' && gemsLeft < 1)"
|
||||
class="purchase-amount"
|
||||
>
|
||||
<!-- this is where the pretty item cost element lives -->
|
||||
<div class="item-cost">
|
||||
<div class="item-cost justify-content-center my-3">
|
||||
<span
|
||||
class="cost"
|
||||
class="cost d-flex mx-auto"
|
||||
:class="getPriceClass()"
|
||||
>
|
||||
<span
|
||||
class="svg-icon inline icon-24"
|
||||
class="svg-icon icon-24 my-auto mr-1"
|
||||
aria-hidden="true"
|
||||
v-html="icons[getPriceClass()]"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="my-auto"
|
||||
:class="getPriceClass()"
|
||||
>{{ item.value }}</span>
|
||||
</span>
|
||||
|
|
@ -452,14 +452,11 @@
|
|||
}
|
||||
|
||||
.item-cost {
|
||||
display: inline-flex;
|
||||
margin: 16px 0;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.cost {
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
font-family: sans-serif;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
|
|
@ -470,19 +467,16 @@
|
|||
&.gems {
|
||||
color: $green-10;
|
||||
background-color: rgba(36, 204, 143, 0.15);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.gold {
|
||||
color: $yellow-5;
|
||||
background-color: rgba(255, 190, 93, 0.15);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $hourglass-color;
|
||||
background-color: rgba(41, 149, 205, 0.15);
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -637,7 +631,7 @@ const amountOfDropPotions = size(dropPotions);
|
|||
const hideAmountSelectionForPurchaseTypes = [
|
||||
'gear', 'backgrounds', 'mystery_set', 'card',
|
||||
'rebirth_orb', 'fortify', 'armoire', 'keys',
|
||||
'debuffPotion', 'pets', 'mounts',
|
||||
'debuffPotion', 'pets', 'mounts', 'customization',
|
||||
];
|
||||
|
||||
export default {
|
||||
|
|
@ -690,7 +684,8 @@ export default {
|
|||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
showAvatar () {
|
||||
return ['backgrounds', 'gear', 'mystery_set'].includes(this.item.purchaseType);
|
||||
return ['backgrounds', 'gear', 'mystery_set', 'customization']
|
||||
.includes(this.item.purchaseType);
|
||||
},
|
||||
|
||||
preventHealthPotion () {
|
||||
|
|
@ -891,6 +886,27 @@ export default {
|
|||
|
||||
return gear;
|
||||
}
|
||||
case 'customization': {
|
||||
if (item.type === 'skin') {
|
||||
return {
|
||||
skin: item.key,
|
||||
};
|
||||
}
|
||||
if (item.type === 'shirt') {
|
||||
return {
|
||||
shirt: item.key,
|
||||
armor: 'armor_base_0',
|
||||
};
|
||||
}
|
||||
if (['base', 'beard', 'color', 'mustache'].includes(item.type)) {
|
||||
return {
|
||||
hair: {
|
||||
[item.type]: item.option,
|
||||
},
|
||||
head: 'head_base_0',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<div class="row customizations">
|
||||
<div
|
||||
class="standard-sidebar d-none d-sm-block">
|
||||
<div class="row market">
|
||||
<div class="standard-sidebar">
|
||||
<filter-sidebar>
|
||||
<div
|
||||
slot="search"
|
||||
|
|
@ -16,388 +15,146 @@
|
|||
</div>
|
||||
<filter-group>
|
||||
<checkbox
|
||||
v-for="category in categories"
|
||||
v-for="category in unfilteredCategories"
|
||||
:id="`category-${category.identifier}`"
|
||||
:key="category.identifier"
|
||||
:checked.sync="viewOptions[category.identifier].selected"
|
||||
:text="category.text"
|
||||
/>
|
||||
</filter-group>
|
||||
<div class="form-group clearfix">
|
||||
<h3
|
||||
v-once
|
||||
class="float-left"
|
||||
>
|
||||
{{ $t('hidePinned') }}
|
||||
</h3>
|
||||
<toggle-switch
|
||||
v-model="hidePinned"
|
||||
class="float-right"
|
||||
/>
|
||||
</div>
|
||||
</filter-sidebar>
|
||||
</div>
|
||||
<div class="standard-page">
|
||||
<div class="featuredItems">
|
||||
<div
|
||||
class="background"
|
||||
:style="{'background-image': imageURLs.background}"
|
||||
>
|
||||
<div
|
||||
class="npc"
|
||||
:style="{'background-image': imageURLs.npc}"
|
||||
>
|
||||
<div class="featured-label">
|
||||
<span class="rectangle"></span><span
|
||||
v-once
|
||||
class="text"
|
||||
>Beffy</span><span class="rectangle"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
<div class="float-right">
|
||||
<span class="dropdown-label">{{ $t('sortBy') }}</span>
|
||||
<select-translated-array
|
||||
:right="true"
|
||||
:value="selectedSortItemsBy"
|
||||
:items="sortItemsBy"
|
||||
:inline-dropdown="false"
|
||||
class="inline"
|
||||
@select="selectedSortItemsBy = $event"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- eslint-disable vue/no-use-v-if-with-v-for -->
|
||||
<div class="standard-page p-0">
|
||||
<div
|
||||
v-for="category in categories"
|
||||
v-if="!anyFilterSelected || viewOptions[category.identifier].selected"
|
||||
:key="category.identifier"
|
||||
:class="category.identifier"
|
||||
class="background"
|
||||
:style="{'background-image': imageURLs.background}"
|
||||
>
|
||||
<!-- eslint-enable vue/no-use-v-if-with-v-for -->
|
||||
<h2 class="mb-3">
|
||||
{{ category.text }}
|
||||
</h2>
|
||||
<itemRows
|
||||
v-if="category.identifier === 'backgrounds'"
|
||||
:items="customizationItems(category, selectedSortItemsBy,
|
||||
searchTextThrottled, hidePinned)"
|
||||
:item-width="94"
|
||||
:item-margin="24"
|
||||
:type="category.identifier"
|
||||
>
|
||||
<template
|
||||
slot="item"
|
||||
slot-scope="ctx"
|
||||
>
|
||||
<shopItem
|
||||
:key="ctx.item.key"
|
||||
:item="ctx.item"
|
||||
:price="ctx.item.value"
|
||||
:price-type="ctx.item.currency"
|
||||
:empty-item="false"
|
||||
@click="selectItemToBuy(ctx.item)"
|
||||
>
|
||||
<span
|
||||
v-if="ctx.item.text != ''"
|
||||
slot="popoverContent"
|
||||
slot-scope="ctx"
|
||||
><div><h4 class="popover-content-title">{{ ctx.item.text }}</h4></div></span>>
|
||||
<template
|
||||
slot="itemBadge"
|
||||
slot-scope="ctx"
|
||||
>
|
||||
<span
|
||||
v-if="ctx.item.pinType !== 'IGNORE'"
|
||||
class="badge-top"
|
||||
@click.prevent.stop="togglePinned(ctx.item)"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="ctx.item.pinned"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</shopItem>
|
||||
</template>
|
||||
</itemRows>
|
||||
<div v-else
|
||||
v-for="items in getGrouped(customizationItems(category, selectedSortItemsBy,
|
||||
searchTextThrottled, hidePinned))"
|
||||
v-bind:key="items[0].set.key"
|
||||
class="mb-5">
|
||||
<h3>{{ items[0].set.text() }}</h3>
|
||||
<itemRows
|
||||
:items="items"
|
||||
:item-width="94"
|
||||
:item-margin="24"
|
||||
:type="category.identifier">
|
||||
<template
|
||||
slot="item"
|
||||
slot-scope="ctx"
|
||||
>
|
||||
<shopItem
|
||||
:key="ctx.item.path"
|
||||
:item="ctx.item"
|
||||
:price="ctx.item.value"
|
||||
:price-type="ctx.item.currency"
|
||||
:empty-item="false"
|
||||
@click="selectItemToBuy(ctx.item)"
|
||||
>
|
||||
<span
|
||||
v-if="ctx.item.text != ''"
|
||||
slot="popoverContent"
|
||||
slot-scope="ctx"
|
||||
><div><h4 class="popover-content-title">{{ ctx.item.text }}</h4></div></span>>
|
||||
<template
|
||||
slot="itemBadge"
|
||||
slot-scope="ctx"
|
||||
>
|
||||
<span
|
||||
v-if="ctx.item.pinType !== 'IGNORE'"
|
||||
class="badge-top"
|
||||
@click.prevent.stop="togglePinned(ctx.item)"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="ctx.item.pinned"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</shopItem>
|
||||
</template>
|
||||
</itemRows>
|
||||
<div
|
||||
v-if="items[0].set"
|
||||
:style="'width: ' + (items.length * (94 + 24) - 24) + 'px'"
|
||||
class="purchase-set"
|
||||
@click="unlock()"
|
||||
class="npc"
|
||||
:style="{'background-image': imageURLs.npc}"
|
||||
>
|
||||
<div class="purchase-set-content mx-auto">
|
||||
<span class="label">{{ $t('purchaseAll') }}</span>
|
||||
<div
|
||||
class="svg-icon gem"
|
||||
v-html="icons.gem"
|
||||
></div>
|
||||
<span class="price">{{ items[0].set.setPrice }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<h1
|
||||
v-once
|
||||
>
|
||||
{{ $t('customizations') }}
|
||||
</h1>
|
||||
<div
|
||||
v-for="category in categories"
|
||||
:key="category.identifier"
|
||||
>
|
||||
<h2 class="mb-3 mt-4">
|
||||
{{ category.text }}
|
||||
</h2>
|
||||
<item-rows
|
||||
:items="customizationsItems({category, searchBy: searchTextThrottled})"
|
||||
:type="category.identifier"
|
||||
:item-width="94"
|
||||
:item-margin="24"
|
||||
:max-items-per-row="8"
|
||||
>
|
||||
<template
|
||||
slot="item"
|
||||
slot-scope="ctx"
|
||||
>
|
||||
<shop-item
|
||||
:item="ctx.item"
|
||||
:key="ctx.item.key"
|
||||
:price="ctx.item.value"
|
||||
:price-type="ctx.item.currency"
|
||||
:empty-item="false"
|
||||
:show-popover="Boolean(ctx.item.text)"
|
||||
@click="selectItem(ctx.item)"
|
||||
/>
|
||||
</template>
|
||||
</item-rows>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
// these styles may be applied to other pages too
|
||||
h1 {
|
||||
color: $purple-200;
|
||||
}
|
||||
|
||||
.group {
|
||||
display: inline-block;
|
||||
width: 33%;
|
||||
margin-bottom: 24px;
|
||||
.background, .npc {
|
||||
height: 216px;
|
||||
}
|
||||
|
||||
.items {
|
||||
border-radius: 2px;
|
||||
background-color: #edecee;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
.item-rows {
|
||||
max-width: 920px;
|
||||
|
||||
.item-wrapper {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.items > div:not(:last-of-type) {
|
||||
margin-right: 16px;
|
||||
.items > div:nth-of-type(8n) {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.customizations {
|
||||
.standard-page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.badge-pin:not(.pinned) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:hover .badge-pin {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
cursor: default;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.featuredItems {
|
||||
height: 216px;
|
||||
|
||||
.background {
|
||||
height: 216px;
|
||||
background-repeat: repeat-x;
|
||||
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.npc {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 216px;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.featured-label {
|
||||
position: absolute;
|
||||
bottom: -14px;
|
||||
margin: 0;
|
||||
left: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.npc {
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
.purchase-set {
|
||||
background: #fff;
|
||||
padding: 0.5em;
|
||||
border-radius: 0 0 2px 2px;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
margin-top: -28px;
|
||||
width: 180px;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
span.price {
|
||||
color: #24cc8f;
|
||||
}
|
||||
|
||||
.gem, .coin {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
&.single {
|
||||
width: 141px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.gem, .coin {
|
||||
width: 20px;
|
||||
margin: 0 .5em;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.purchase-set-content {
|
||||
display: block;
|
||||
width: 150px;
|
||||
}
|
||||
</style>
|
||||
<!-- eslint-enable max-len -->
|
||||
|
||||
<script>
|
||||
import _filter from 'lodash/filter';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _throttle from 'lodash/throttle';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
import _map from 'lodash/map';
|
||||
import _find from 'lodash/find';
|
||||
import isPinned from '@/../../common/script/libs/isPinned';
|
||||
import shops from '@/../../common/script/libs/shops';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import ShopItem from '../shopItem';
|
||||
import ItemRows from '@/components/ui/itemRows';
|
||||
import PinBadge from '@/components/ui/pinBadge';
|
||||
import toggleSwitch from '@/components/ui/toggleSwitch';
|
||||
|
||||
import svgHourglass from '@/assets/svg/hourglass.svg';
|
||||
import gem from '@/assets/svg/gem.svg';
|
||||
|
||||
import pinUtils from '@/mixins/pinUtils';
|
||||
import FilterSidebar from '@/components/ui/filterSidebar';
|
||||
import FilterGroup from '@/components/ui/filterGroup';
|
||||
import Checkbox from '@/components/ui/checkbox';
|
||||
import SelectTranslatedArray from '@/components/tasks/modal-controls/selectTranslatedArray';
|
||||
import FilterGroup from '@/components/ui/filterGroup';
|
||||
import FilterSidebar from '@/components/ui/filterSidebar';
|
||||
import ItemRows from '@/components/ui/itemRows';
|
||||
import ShopItem from '../shopItem';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SelectTranslatedArray,
|
||||
Checkbox,
|
||||
FilterGroup,
|
||||
FilterSidebar,
|
||||
ShopItem,
|
||||
ItemRows,
|
||||
PinBadge,
|
||||
toggleSwitch,
|
||||
ShopItem,
|
||||
},
|
||||
mixins: [pinUtils],
|
||||
data () {
|
||||
return {
|
||||
viewOptions: {},
|
||||
|
||||
searchText: null,
|
||||
searchTextThrottled: null,
|
||||
|
||||
icons: Object.freeze({
|
||||
hourglass: svgHourglass,
|
||||
gem,
|
||||
}),
|
||||
|
||||
sortItemsBy: ['AZ', 'sortByNumber'],
|
||||
selectedSortItemsBy: 'AZ',
|
||||
|
||||
selectedItemToBuy: null,
|
||||
|
||||
hidePinned: false,
|
||||
|
||||
backgroundUpdate: new Date(),
|
||||
|
||||
currentEvent: null,
|
||||
viewOptions: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
content: 'content',
|
||||
// content: 'content',
|
||||
user: 'user.data',
|
||||
userStats: 'user.data.stats',
|
||||
userItems: 'user.data.items',
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
|
||||
shop () {
|
||||
return shops.getCustomizationShop(this.user);
|
||||
anyFilterSelected () {
|
||||
return Object.values(this.viewOptions).some(g => g.selected);
|
||||
},
|
||||
imageURLs () {
|
||||
const currentEvent = this.currentEventList?.find(event => Boolean(event.season));
|
||||
if (!currentEvent) {
|
||||
return {
|
||||
background: 'url(/static/npc/normal/market_background.png)',
|
||||
npc: 'url(/static/npc/normal/market_banner_npc.png)',
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: `url(/static/npc/${currentEvent.season}/market_background.png)`,
|
||||
npc: `url(/static/npc/${currentEvent.season}/market_banner_npc.png)`,
|
||||
};
|
||||
},
|
||||
shop () {
|
||||
return shops.getCustomizationsShop(this.user);
|
||||
},
|
||||
unfilteredCategories () {
|
||||
const apiCategories = this.shop.categories;
|
||||
|
||||
categories () {
|
||||
const { categories } = this.shop;
|
||||
|
||||
categories.forEach(category => {
|
||||
apiCategories.forEach(category => {
|
||||
// do not reset the viewOptions if already set once
|
||||
if (typeof this.viewOptions[category.identifier] === 'undefined') {
|
||||
this.$set(this.viewOptions, category.identifier, {
|
||||
|
|
@ -406,93 +163,35 @@ export default {
|
|||
}
|
||||
});
|
||||
|
||||
return categories;
|
||||
return apiCategories;
|
||||
},
|
||||
anyFilterSelected () {
|
||||
return Object.values(this.viewOptions).some(g => g.selected);
|
||||
},
|
||||
imageURLs () {
|
||||
if (!this.currentEvent || !this.currentEvent.season || this.currentEvent.season === 'thanksgiving') {
|
||||
return {
|
||||
background: 'url(/static/npc/normal/market_background.png)',
|
||||
npc: 'url(/static/npc/normal/market_banner_npc.png)',
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: `url(/static/npc/${this.currentEvent.season}/market_background.png)`,
|
||||
npc: `url(/static/npc/${this.currentEvent.season}/market_banner_npc.png)`,
|
||||
};
|
||||
categories () {
|
||||
const { unfilteredCategories } = this;
|
||||
|
||||
return unfilteredCategories.filter(category => !this.anyFilterSelected
|
||||
|| this.viewOptions[category.identifier].selected);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
searchText: _throttle(function throttleSearch () {
|
||||
// TODO mixin?
|
||||
searchText: throttle(function throttleSearch () {
|
||||
this.searchTextThrottled = this.searchText.toLowerCase();
|
||||
}, 250),
|
||||
},
|
||||
mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
subSection: this.$t('titleCustomizations'),
|
||||
subSection: this.$t('customizations'),
|
||||
section: this.$t('shops'),
|
||||
});
|
||||
this.$root.$on('buyModal::boughtItem', () => {
|
||||
this.backgroundUpdate = new Date();
|
||||
});
|
||||
this.$root.$on('bv::modal::hidden', event => {
|
||||
if (event.componentId === 'buy-quest-modal') {
|
||||
this.$root.$emit('buyModal::hidden', this.selectedItemToBuy.key);
|
||||
}
|
||||
});
|
||||
this.currentEvent = _find(this.currentEventList, event => Boolean(['winter', 'spring', 'summer', 'fall'].includes(event.season)));
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('buyModal::boughtItem');
|
||||
},
|
||||
methods: {
|
||||
customizationItems (category, sortBy, searchBy, hidePinned) {
|
||||
let result = _map(category.items, e => ({
|
||||
...e,
|
||||
pinned: isPinned(this.user, e),
|
||||
}));
|
||||
|
||||
result = _filter(result, i => {
|
||||
if (hidePinned && i.pinned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !searchBy || i.text.toLowerCase().indexOf(searchBy) !== -1;
|
||||
});
|
||||
|
||||
switch (sortBy) { // eslint-disable-line default-case
|
||||
case 'AZ': {
|
||||
result = _sortBy(result, ['set.key', 'text']);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'sortByNumber': {
|
||||
result = _sortBy(result, ['set.key', 'value']);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
customizationsItems (options = {}) {
|
||||
const { category, searchBy } = options;
|
||||
return category.items.filter(item => !searchBy
|
||||
|| item.text.toLowerCase().includes(searchBy));
|
||||
},
|
||||
getGrouped (entries) {
|
||||
return _groupBy(entries, 'set.key');
|
||||
},
|
||||
selectItemToBuy (item) {
|
||||
if (item.purchaseType === 'quests') {
|
||||
this.selectedItemToBuy = item;
|
||||
|
||||
this.$root.$emit('bv::show::modal', 'buy-quest-modal');
|
||||
} else {
|
||||
this.$root.$emit('buyModal::showItem', item);
|
||||
}
|
||||
},
|
||||
resetItemToBuy ($event) {
|
||||
if (!$event) {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
selectItem (item) {
|
||||
this.$root.$emit('buyModal::showItem', item);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,26 +3,32 @@
|
|||
<secondary-menu class="col-12">
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'market'}"
|
||||
:to="{ name: 'market' }"
|
||||
exact="exact"
|
||||
>
|
||||
{{ $t('market') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'quests'}"
|
||||
:to="{ name: 'quests' }"
|
||||
>
|
||||
{{ $t('quests') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'seasonal'}"
|
||||
:to="{ name: 'customizations' }"
|
||||
>
|
||||
{{ $t('customizations') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{ name: 'seasonal' }"
|
||||
>
|
||||
{{ $t('titleSeasonalShop') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'time'}"
|
||||
:to="{ name: 'time' }"
|
||||
>
|
||||
{{ $t('titleTimeTravelers') }}
|
||||
</router-link>
|
||||
|
|
|
|||
|
|
@ -142,6 +142,22 @@
|
|||
&.locked .price {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.hair, .facial-hair, .shirt, .skin {
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
.hair {
|
||||
background-position: -24px -2px;
|
||||
}
|
||||
|
||||
.facial-hair, .skin {
|
||||
background-position: -24px -10px;
|
||||
}
|
||||
|
||||
.shirt {
|
||||
background-position: -23px -32px;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
|
|
@ -177,6 +193,7 @@
|
|||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
margin-bottom: 1px;
|
||||
|
||||
&.gems {
|
||||
color: $green-1;
|
||||
|
|
@ -351,6 +368,7 @@ export default {
|
|||
this.$emit('click', {});
|
||||
},
|
||||
blur () {
|
||||
if (!this.$refs?.popover) return;
|
||||
this.$refs.popover.$emit('enable');
|
||||
},
|
||||
getPrice () {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<div class="row timeTravelers">
|
||||
<div
|
||||
v-if="!closed"
|
||||
class="standard-sidebar d-none d-sm-block"
|
||||
>
|
||||
<filter-sidebar>
|
||||
|
|
@ -88,7 +87,7 @@
|
|||
<!-- eslint-disable vue/no-use-v-if-with-v-for -->
|
||||
<div
|
||||
v-for="category in categories"
|
||||
v-if="!anyFilterSelected || (!closed && viewOptions[category.identifier].selected)"
|
||||
v-if="!closed && (!anyFilterSelected || viewOptions[category.identifier].selected)"
|
||||
:key="category.identifier"
|
||||
:class="category.identifier"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -22,20 +22,12 @@
|
|||
:show-all="showAll"
|
||||
@click="toggleItemsToShow()"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="fill-height"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.fill-height {
|
||||
height: 38px; // button + margin + padding
|
||||
}
|
||||
|
||||
.item-rows {
|
||||
margin-right: -1.5rem;
|
||||
}
|
||||
|
|
@ -69,6 +61,9 @@ export default {
|
|||
noItemsLabel: {
|
||||
type: String,
|
||||
},
|
||||
maxItemsPerRow: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
@ -80,6 +75,9 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
itemsPerRow () {
|
||||
if (this.maxItemsPerRow > 0) {
|
||||
return this.maxItemsPerRow;
|
||||
}
|
||||
return Math.floor(this.currentWidth / (this.itemWidth + this.itemMargin));
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<button
|
||||
class="btn btn-flat btn-show-more mb-4"
|
||||
class="btn btn-flat btn-show-more mt-2"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<span class="button-text">
|
||||
|
|
@ -38,5 +38,9 @@ export default {
|
|||
color: $purple-300;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid $purple-400;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { getScheduleMatchingGroup } from '@/../../common/script/content/constant
|
|||
|
||||
import { userStateMixin } from './userState';
|
||||
|
||||
export const avatarEditorUtilies = { // eslint-disable-line import/prefer-default-export
|
||||
export const avatarEditorUtilities = { // eslint-disable-line import/prefer-default-export
|
||||
mixins: [userStateMixin],
|
||||
data () {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ const ChallengeDetail = () => import(/* webpackChunkName: "challenges" */ '@/com
|
|||
const ShopsContainer = () => import(/* webpackChunkName: "shops" */'@/components/shops/index');
|
||||
const MarketPage = () => import(/* webpackChunkName: "shops-market" */'@/components/shops/market/index');
|
||||
const QuestsPage = () => import(/* webpackChunkName: "shops-quest" */'@/components/shops/quests/index');
|
||||
const CustomizationsPage = () => import(/* webpackChunkName: "shops-customizations" */'@/components/shops/customizations/index');
|
||||
const SeasonalPage = () => import(/* webpackChunkName: "shops-seasonal" */'@/components/shops/seasonal/index');
|
||||
const TimeTravelersPage = () => import(/* webpackChunkName: "shops-timetravelers" */'@/components/shops/timeTravelers/index');
|
||||
const CustomizationsShopPage = () => import(/* webpackChunkName: "shops-customizations" */'@/components/shops/customizations/index');
|
||||
|
|
@ -112,6 +113,7 @@ const router = new VueRouter({
|
|||
children: [
|
||||
{ name: 'market', path: 'market', component: MarketPage },
|
||||
{ name: 'quests', path: 'quests', component: QuestsPage },
|
||||
{ name: 'customizations', path: 'customizations', component: CustomizationsPage },
|
||||
{ name: 'seasonal', path: 'seasonal', component: SeasonalPage },
|
||||
{ name: 'time', path: 'time', component: TimeTravelersPage },
|
||||
{ name: 'customizations', path: 'customizations', component: CustomizationsShopPage },
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the <a href='https://habitica.com/static/community-guidelines' target='_blank'>Community Guidelines</a> (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email <%= hrefBlankCommunityManagerEmail %>!",
|
||||
"profile": "Profile",
|
||||
"avatar": "Customize Avatar",
|
||||
"editAvatar": "Edit Avatar",
|
||||
"editAvatar": "Customize Avatar",
|
||||
"noDescription": "This Habitican hasn't added a description.",
|
||||
"noPhoto": "This Habitican hasn't added a photo.",
|
||||
"other": "Other",
|
||||
|
|
@ -190,5 +190,10 @@
|
|||
"mainHand": "Main-Hand",
|
||||
"offHand": "Off-Hand",
|
||||
"statPoints": "Stat Points",
|
||||
"pts": "pts"
|
||||
"pts": "pts",
|
||||
"customizations": "Customizations",
|
||||
"hairColors": "Hair Colors",
|
||||
"hairStyles": "Hair Styles",
|
||||
"skins": "Skins",
|
||||
"facialHairs": "Facial Hair"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -236,7 +236,6 @@
|
|||
"questionDescriptionText": "It's okay to ask your questions in your primary language if you aren't comfortable speaking in English.",
|
||||
"questionPlaceholder": "Ask your question here",
|
||||
"submitQuestion": "Submit Question",
|
||||
"reportPlayer": "Report Player",
|
||||
"whyReportingPlayer": "Why are you reporting this player?",
|
||||
"whyReportingPlayerPlaceholder": "Reason for report",
|
||||
"playerReportModalBody": "You should only report a player who violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Submitting a false report is a violation of Habitica’s Community Guidelines."
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ function getDefaultGearProps (item, language) {
|
|||
};
|
||||
}
|
||||
|
||||
export default function getItemInfo (user, type, item, officialPinnedItems, language = 'en', matcher = null) {
|
||||
export default function getItemInfo (user, type, item, officialPinnedItems, language = 'en') {
|
||||
if (officialPinnedItems === undefined) {
|
||||
officialPinnedItems = getOfficialPinnedItems(user); // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
|
@ -235,7 +235,7 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
|
|||
case 'gear':
|
||||
// spread operator not available
|
||||
itemInfo = Object.assign(getDefaultGearProps(item, language), {
|
||||
value: item.twoHanded ? 2 : 1,
|
||||
value: item.twoHanded || item.gearSet === 'animal' ? 2 : 1,
|
||||
currency: 'gems',
|
||||
pinType: 'gear',
|
||||
});
|
||||
|
|
@ -379,93 +379,73 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
|
|||
};
|
||||
break;
|
||||
}
|
||||
case 'haircolor': {
|
||||
case 'hairColor': {
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
class: `hair_bangs_${user.preferences.hair.bangs}_${item.key}`,
|
||||
text: item.key,
|
||||
notes: '',
|
||||
value: item.price,
|
||||
set: item.set,
|
||||
locked: false,
|
||||
class: `hair hair_bangs_${user.preferences.hair.bangs}_${item.key}`,
|
||||
currency: 'gems',
|
||||
path: `customizations.hair.color.${item.key}`,
|
||||
pinType: 'timeTravelersStable',
|
||||
option: item.key,
|
||||
purchaseType: 'customization',
|
||||
type: 'color',
|
||||
value: item.price,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'hairbase': {
|
||||
case 'hairBase': {
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
class: `hair_base_${item.key}_${user.preferences.hair.color}`,
|
||||
text: item.key,
|
||||
notes: '',
|
||||
value: item.price,
|
||||
set: item.set,
|
||||
locked: false,
|
||||
key: `hair-base-${item.key}`,
|
||||
class: `hair hair_base_${item.key}_${user.preferences.hair.color}`,
|
||||
currency: 'gems',
|
||||
path: `customizations.hair.base.${item.key}`,
|
||||
pinType: 'timeTravelersStable',
|
||||
option: item.key,
|
||||
purchaseType: 'customization',
|
||||
type: 'base',
|
||||
value: item.price,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'hairmustache': {
|
||||
case 'mustache': {
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
class: `hair_mustache_${item.key}_${user.preferences.hair.color}`,
|
||||
text: item.key,
|
||||
notes: '',
|
||||
value: item.price,
|
||||
set: item.set,
|
||||
locked: false,
|
||||
key: `mustache-${item.key}`,
|
||||
class: `facial-hair hair_mustache_${item.key}_${user.preferences.hair.color}`,
|
||||
currency: 'gems',
|
||||
path: `customizations.hair.mustache.${item.key}`,
|
||||
pinType: 'timeTravelersStable',
|
||||
option: item.key,
|
||||
purchaseType: 'customization',
|
||||
type: 'mustache',
|
||||
value: item.price,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'hairbeard': {
|
||||
case 'beard': {
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
class: `hair_beard_${item.key}_${user.preferences.hair.color}`,
|
||||
text: item.key,
|
||||
notes: '',
|
||||
value: item.price,
|
||||
set: item.set,
|
||||
locked: false,
|
||||
key: `beard-${item.key}`,
|
||||
class: `facial-hair hair_beard_${item.key}_${user.preferences.hair.color}`,
|
||||
currency: 'gems',
|
||||
path: `customizations.hair.beard.${item.key}`,
|
||||
pinType: 'timeTravelersStable',
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'shirt': {
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
class: `${user.preferences.size}_shirt_${item.key}`,
|
||||
text: item.key,
|
||||
notes: '',
|
||||
option: item.key,
|
||||
purchaseType: 'customization',
|
||||
type: 'beard',
|
||||
value: item.price,
|
||||
set: item.set,
|
||||
locked: false,
|
||||
currency: 'gems',
|
||||
path: `customizations.shirt.${item.key}`,
|
||||
pinType: 'timeTravelersStable',
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'skin': {
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
class: `skin_${item.key}`,
|
||||
text: item.key,
|
||||
notes: '',
|
||||
value: item.price,
|
||||
set: item.set,
|
||||
locked: false,
|
||||
class: `skin skin_${item.key}`,
|
||||
currency: 'gems',
|
||||
path: `customizations.skin.${item.key}`,
|
||||
pinType: 'timeTravelersStable',
|
||||
purchaseType: 'customization',
|
||||
type: 'skin',
|
||||
value: item.price,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'shirt': {
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
class: `shirt ${user.preferences.size}_shirt_${item.key}`,
|
||||
currency: 'gems',
|
||||
purchaseType: 'customization',
|
||||
type: 'shirt',
|
||||
value: item.price,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
|
@ -478,10 +458,5 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
|
|||
throw new BadRequest(i18n.t('wrongItemType', { type }, language));
|
||||
}
|
||||
|
||||
if (matcher) {
|
||||
itemInfo.end = matcher.getEndDate();
|
||||
console.log(itemInfo);
|
||||
}
|
||||
|
||||
return itemInfo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import moment from 'moment';
|
||||
import values from 'lodash/values';
|
||||
import map from 'lodash/map';
|
||||
import keys from 'lodash/keys';
|
||||
|
|
@ -17,7 +18,6 @@ import featuredItems from '../content/shop-featuredItems';
|
|||
|
||||
import getOfficialPinnedItems from './getOfficialPinnedItems';
|
||||
import { getClassName } from './getClassName';
|
||||
import { getScheduleMatchingGroup } from '../content/constants/schedule';
|
||||
|
||||
const shops = {};
|
||||
|
||||
|
|
@ -71,11 +71,9 @@ shops.getMarketCategories = function getMarket (user, language) {
|
|||
text: i18n.t('magicHatchingPotions', language),
|
||||
notes: i18n.t('premiumPotionNoDropExplanation', language),
|
||||
};
|
||||
const matchers = getScheduleMatchingGroup('premiumHatchingPotions');
|
||||
premiumHatchingPotionsCategory.items = sortBy(values(content.hatchingPotions)
|
||||
.filter(hp => hp.limited
|
||||
&& matchers.match(hp.key))
|
||||
.map(premiumHatchingPotion => getItemInfo(user, 'premiumHatchingPotion', premiumHatchingPotion, officialPinnedItems, language, matchers)), 'key');
|
||||
.filter(hp => hp.limited && hp.canBuy(user))
|
||||
.map(premiumHatchingPotion => getItemInfo(user, 'premiumHatchingPotion', premiumHatchingPotion, officialPinnedItems, language)), 'key');
|
||||
if (premiumHatchingPotionsCategory.items.length > 0) {
|
||||
categories.push(premiumHatchingPotionsCategory);
|
||||
}
|
||||
|
|
@ -273,11 +271,9 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
|
|||
text: i18n.t('questBundles', language),
|
||||
};
|
||||
|
||||
const bundleMatchers = getScheduleMatchingGroup('bundles');
|
||||
bundleCategory.items = sortBy(values(content.bundles)
|
||||
.filter(bundle => bundle.type === 'quests'
|
||||
&& bundleMatchers.match(bundle.key))
|
||||
.map(bundle => getItemInfo(user, 'bundles', bundle, officialPinnedItems, language, bundleMatchers)));
|
||||
.filter(bundle => bundle.type === 'quests' && bundle.canBuy())
|
||||
.map(bundle => getItemInfo(user, 'bundles', bundle, officialPinnedItems, language)));
|
||||
|
||||
if (bundleCategory.items.length > 0) {
|
||||
categories.push(bundleCategory);
|
||||
|
|
@ -289,15 +285,9 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
|
|||
text: i18n.t(`${type}Quests`, language),
|
||||
};
|
||||
|
||||
let filteredQuests = content.questsByLevel
|
||||
.filter(quest => quest.canBuy(user) && quest.category === type);
|
||||
|
||||
if (type === 'pet' || type === 'hatchingPotion') {
|
||||
const matchers = getScheduleMatchingGroup(`${type}Quests`);
|
||||
filteredQuests = filteredQuests.filter(quest => matchers.match(quest.key));
|
||||
}
|
||||
|
||||
category.items = filteredQuests.map(quest => getItemInfo(user, 'quests', quest, officialPinnedItems, language));
|
||||
category.items = content.questsByLevel
|
||||
.filter(quest => quest.canBuy(user) && quest.category === type)
|
||||
.map(quest => getItemInfo(user, 'quests', quest, officialPinnedItems, language));
|
||||
|
||||
categories.push(category);
|
||||
});
|
||||
|
|
@ -380,7 +370,7 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la
|
|||
}
|
||||
}
|
||||
|
||||
const sets = content.timeTravelerStore(user, new Date());
|
||||
const sets = content.timeTravelerStore(user);
|
||||
for (const setKey of Object.keys(sets)) {
|
||||
const set = sets[setKey];
|
||||
const category = {
|
||||
|
|
@ -449,8 +439,8 @@ shops.getSeasonalShop = function getSeasonalShop (user, language) {
|
|||
identifier: 'seasonalShop',
|
||||
text: i18n.t('seasonalShop'),
|
||||
notes: i18n.t(`seasonalShop${seasonalShopConfig.currentSeason}Text`),
|
||||
imageName: 'seasonalshop_open',
|
||||
opened: true,
|
||||
imageName: seasonalShopConfig.opened ? 'seasonalshop_open' : 'seasonalshop_closed',
|
||||
opened: seasonalShopConfig.opened,
|
||||
categories: this.getSeasonalShopCategories(user, language),
|
||||
featured: {
|
||||
text: i18n.t(seasonalShopConfig.featuredSet),
|
||||
|
|
@ -467,18 +457,26 @@ shops.getSeasonalShop = function getSeasonalShop (user, language) {
|
|||
return resObject;
|
||||
};
|
||||
|
||||
// To switch seasons/available inventory, edit the AVAILABLE_SETS object to whatever should be sold.
|
||||
// let AVAILABLE_SETS = {
|
||||
// setKey: i18n.t('setTranslationString', language),
|
||||
// };
|
||||
shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, language) {
|
||||
const officialPinnedItems = getOfficialPinnedItems(user);
|
||||
|
||||
const spellMatcher = getScheduleMatchingGroup('seasonalSpells');
|
||||
const questMatcher = getScheduleMatchingGroup('seasonalQuests');
|
||||
const gearMatcher = getScheduleMatchingGroup('seasonalGear');
|
||||
const AVAILABLE_SPELLS = [
|
||||
...seasonalShopConfig.availableSpells,
|
||||
];
|
||||
|
||||
const AVAILABLE_QUESTS = [
|
||||
...seasonalShopConfig.availableQuests,
|
||||
];
|
||||
|
||||
const categories = [];
|
||||
|
||||
const spells = pickBy(
|
||||
content.spells.special,
|
||||
(spell, key) => spellMatcher.match(key),
|
||||
(spell, key) => AVAILABLE_SPELLS.indexOf(key) !== -1,
|
||||
);
|
||||
|
||||
if (keys(spells).length > 0) {
|
||||
|
|
@ -489,13 +487,13 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
|
|||
|
||||
category.items = map(
|
||||
spells,
|
||||
spell => getItemInfo(user, 'seasonalSpell', spell, officialPinnedItems, language, spellMatcher),
|
||||
spell => getItemInfo(user, 'seasonalSpell', spell, officialPinnedItems, language),
|
||||
);
|
||||
|
||||
categories.push(category);
|
||||
}
|
||||
|
||||
const quests = pickBy(content.quests, (quest, key) => questMatcher.match(key));
|
||||
const quests = pickBy(content.quests, (quest, key) => AVAILABLE_QUESTS.indexOf(key) !== -1);
|
||||
|
||||
if (keys(quests).length > 0) {
|
||||
const category = {
|
||||
|
|
@ -503,12 +501,12 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
|
|||
text: i18n.t('quests', language),
|
||||
};
|
||||
|
||||
category.items = map(quests, quest => getItemInfo(user, 'seasonalQuest', quest, officialPinnedItems, language, questMatcher));
|
||||
category.items = map(quests, quest => getItemInfo(user, 'seasonalQuest', quest, officialPinnedItems, language));
|
||||
|
||||
categories.push(category);
|
||||
}
|
||||
|
||||
for (const set of gearMatcher.items) {
|
||||
for (const set of seasonalShopConfig.availableSets) {
|
||||
const category = {
|
||||
identifier: set,
|
||||
text: i18n.t(set),
|
||||
|
|
@ -532,124 +530,215 @@ shops.getBackgroundShopSets = function getBackgroundShopSets (language) {
|
|||
const sets = [];
|
||||
const officialPinnedItems = getOfficialPinnedItems();
|
||||
|
||||
const matchers = getScheduleMatchingGroup('backgrounds');
|
||||
eachRight(content.backgrounds, (group, key) => {
|
||||
if (matchers.match(key)) {
|
||||
const set = {
|
||||
identifier: key,
|
||||
text: i18n.t(key, language),
|
||||
};
|
||||
const set = {
|
||||
identifier: key,
|
||||
text: i18n.t(key, language),
|
||||
};
|
||||
|
||||
set.items = map(group, background => getItemInfo(null, 'background', background, officialPinnedItems, language, matchers));
|
||||
set.items = map(group, background => getItemInfo(null, 'background', background, officialPinnedItems, language));
|
||||
|
||||
sets.push(set);
|
||||
}
|
||||
sets.push(set);
|
||||
});
|
||||
|
||||
return sets;
|
||||
};
|
||||
|
||||
/* Customization Shop */
|
||||
|
||||
shops.getCustomizationShop = function getCustomizationShop (user, language) {
|
||||
return {
|
||||
identifier: 'customizationShop',
|
||||
text: i18n.t('titleCustomizations'),
|
||||
notes: i18n.t('timeTravelersPopover'),
|
||||
imageName: 'npc_timetravelers_active',
|
||||
categories: shops.getCustomizationShopCategories(user, language),
|
||||
};
|
||||
};
|
||||
|
||||
shops.getCustomizationShopCategories = function getCustomizationShopCategories (user, language) {
|
||||
shops.getCustomizationsShopCategories = function getCustomizationsShopCategories (user, language) {
|
||||
const categories = [];
|
||||
const officialPinnedItems = getOfficialPinnedItems(user);
|
||||
const officialPinnedItems = getOfficialPinnedItems();
|
||||
|
||||
const backgroundCategory = {
|
||||
identifier: 'backgrounds',
|
||||
const backgroundsCategory = {
|
||||
identifier: 'background',
|
||||
text: i18n.t('backgrounds', language),
|
||||
items: [],
|
||||
};
|
||||
backgroundsCategory.items = values(content.backgroundsFlat)
|
||||
.filter(bg => !user.purchased.background[bg.key] && (!bg.currency || bg.currency === 'gems')
|
||||
&& !(bg.price === 0))
|
||||
.map(bg => getItemInfo(user, 'background', bg, officialPinnedItems, language));
|
||||
categories.push(backgroundsCategory);
|
||||
|
||||
const matchers = getScheduleMatchingGroup('backgrounds');
|
||||
eachRight(content.backgrounds, (group, key) => {
|
||||
if (matchers.match(key)) {
|
||||
each(group, bg => {
|
||||
if (!user.purchased.background[bg.key]) {
|
||||
const item = getItemInfo(
|
||||
user,
|
||||
'background',
|
||||
bg,
|
||||
officialPinnedItems,
|
||||
language,
|
||||
);
|
||||
backgroundCategory.items.push(item);
|
||||
const hairColorsCategory = {
|
||||
identifier: 'hairColors',
|
||||
text: i18n.t('hairColors', language),
|
||||
};
|
||||
hairColorsCategory.items = values(content.appearances.hair.color)
|
||||
.filter(color => {
|
||||
const { hair } = user.purchased;
|
||||
if (hair && hair.color && hair.color[color.key]) {
|
||||
return false;
|
||||
}
|
||||
if (color.set) {
|
||||
if (color.set.availableFrom) {
|
||||
return moment().isBetween(color.set.availableFrom, color.set.availableUntil);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
categories.push(backgroundCategory);
|
||||
if (color.set.availableUntil) {
|
||||
return moment().isBefore(color.set.availableUntil);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map(color => getItemInfo(user, 'hairColor', color, officialPinnedItems, language));
|
||||
categories.push(hairColorsCategory);
|
||||
|
||||
const hairStylesCategory = {
|
||||
identifier: 'hairStyles',
|
||||
text: i18n.t('hairStyles', language),
|
||||
};
|
||||
hairStylesCategory.items = values(content.appearances.hair.base)
|
||||
.filter(style => {
|
||||
const { hair } = user.purchased;
|
||||
if (hair && hair.base && hair.base[style.key]) {
|
||||
return false;
|
||||
}
|
||||
if (style.set) {
|
||||
if (style.set.availableFrom) {
|
||||
return moment().isBetween(style.set.availableFrom, style.set.availableUntil);
|
||||
}
|
||||
if (style.set.availableUntil) {
|
||||
return moment().isBefore(style.set.availableUntil);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map(style => getItemInfo(user, 'hairBase', style, officialPinnedItems, language));
|
||||
categories.push(hairStylesCategory);
|
||||
|
||||
const facialHairCategory = {
|
||||
identifier: 'facialHair',
|
||||
text: i18n.t('titleFacialHair', language),
|
||||
items: [],
|
||||
text: i18n.t('facialHairs', language),
|
||||
};
|
||||
const customizationMatcher = getScheduleMatchingGroup('customizations');
|
||||
each(['color', 'base', 'mustache', 'beard'], hairType => {
|
||||
let category;
|
||||
if (hairType === 'beard' || hairType === 'mustache') {
|
||||
category = facialHairCategory;
|
||||
} else {
|
||||
category = {
|
||||
identifier: hairType,
|
||||
text: i18n.t(`titleHair${hairType}`, language),
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
eachRight(content.appearances.hair[hairType], (hairStyle, key) => {
|
||||
if (hairStyle.price > 0 && (!user.purchased.hair || !user.purchased.hair[hairType]
|
||||
|| !user.purchased.hair[hairType][key])
|
||||
&& customizationMatcher.match(hairStyle.set.key)) {
|
||||
const item = getItemInfo(
|
||||
user,
|
||||
`hair${hairType}`,
|
||||
hairStyle,
|
||||
officialPinnedItems,
|
||||
language,
|
||||
);
|
||||
category.items.push(item);
|
||||
facialHairCategory.items = values(content.appearances.hair.mustache)
|
||||
.filter(style => {
|
||||
const { hair } = user.purchased;
|
||||
if (hair && hair.mustache && hair.mustache[style.key]) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// only add the facial hair category once
|
||||
if (hairType !== 'beard') {
|
||||
categories.push(category);
|
||||
}
|
||||
});
|
||||
if (style.set) {
|
||||
if (style.set.availableFrom) {
|
||||
return moment().isBetween(style.set.availableFrom, style.set.availableUntil);
|
||||
}
|
||||
if (style.set.availableUntil) {
|
||||
return moment().isBefore(style.set.availableUntil);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map(style => getItemInfo(user, 'mustache', style, officialPinnedItems, language))
|
||||
.concat(
|
||||
values(content.appearances.hair.beard)
|
||||
.filter(style => {
|
||||
const { hair } = user.purchased;
|
||||
if (hair && hair.beard && hair.beard[style.key]) {
|
||||
return false;
|
||||
}
|
||||
if (style.set) {
|
||||
if (style.set.availableFrom) {
|
||||
return moment().isBetween(style.set.availableFrom, style.set.availableUntil);
|
||||
}
|
||||
if (style.set.availableUntil) {
|
||||
return moment().isBefore(style.set.availableUntil);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map(style => getItemInfo(user, 'beard', style, officialPinnedItems, language)),
|
||||
);
|
||||
categories.push(facialHairCategory);
|
||||
|
||||
each(['shirt', 'skin'], type => {
|
||||
const category = {
|
||||
identifier: type,
|
||||
text: i18n.t(type, language),
|
||||
items: [],
|
||||
};
|
||||
eachRight(content.appearances[type], (appearance, key) => {
|
||||
if (appearance.price > 0 && (!user.purchased[type] || !user.purchased[type][key])
|
||||
&& customizationMatcher.match(appearance.set.key)) {
|
||||
const item = getItemInfo(
|
||||
user,
|
||||
type,
|
||||
appearance,
|
||||
officialPinnedItems,
|
||||
language,
|
||||
);
|
||||
category.items.push(item);
|
||||
const skinsCategory = {
|
||||
identifier: 'skins',
|
||||
text: i18n.t('skins', language),
|
||||
};
|
||||
skinsCategory.items = values(content.appearances.skin)
|
||||
.filter(color => {
|
||||
const { skin } = user.purchased;
|
||||
if (skin && skin[color.key]) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
categories.push(category);
|
||||
});
|
||||
if (color.set) {
|
||||
if (color.set.availableFrom) {
|
||||
return moment().isBetween(color.set.availableFrom, color.set.availableUntil);
|
||||
}
|
||||
if (color.set.availableUntil) {
|
||||
return moment().isBefore(color.set.availableUntil);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map(color => getItemInfo(user, 'skin', color, officialPinnedItems, language));
|
||||
categories.push(skinsCategory);
|
||||
|
||||
const animalEarsCategory = {
|
||||
identifier: 'animalEars',
|
||||
text: i18n.t('animalEars', language),
|
||||
};
|
||||
animalEarsCategory.items = values(content.gear.tree.headAccessory.special)
|
||||
.filter(gearItem => {
|
||||
const { owned } = user.items.gear;
|
||||
if (typeof owned[gearItem.key] !== 'undefined') {
|
||||
return false;
|
||||
}
|
||||
return gearItem.gearSet === 'animal';
|
||||
})
|
||||
.map(gearItem => getItemInfo(user, 'gear', gearItem, officialPinnedItems, language));
|
||||
categories.push(animalEarsCategory);
|
||||
|
||||
const animalTailsCategory = {
|
||||
identifier: 'animalTails',
|
||||
text: i18n.t('animalTails', language),
|
||||
};
|
||||
animalTailsCategory.items = values(content.gear.tree.back.special)
|
||||
.filter(gearItem => {
|
||||
const { owned } = user.items.gear;
|
||||
if (typeof owned[gearItem.key] !== 'undefined') {
|
||||
return false;
|
||||
}
|
||||
return gearItem.gearSet === 'animal';
|
||||
})
|
||||
.map(gearItem => getItemInfo(user, 'gear', gearItem, officialPinnedItems, language));
|
||||
categories.push(animalTailsCategory);
|
||||
|
||||
const shirtsCategory = {
|
||||
identifier: 'shirts',
|
||||
text: i18n.t('shirts', language),
|
||||
};
|
||||
shirtsCategory.items = values(content.appearances.shirt)
|
||||
.filter(color => {
|
||||
const { shirt } = user.purchased;
|
||||
if (shirt && shirt[color.key]) {
|
||||
return false;
|
||||
}
|
||||
if (color.set) {
|
||||
if (color.set.availableFrom) {
|
||||
return moment().isBetween(color.set.availableFrom, color.set.availableUntil);
|
||||
}
|
||||
if (color.set.availableUntil) {
|
||||
return moment().isBefore(color.set.availableUntil);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map(color => getItemInfo(user, 'shirt', color, officialPinnedItems, language));
|
||||
categories.push(shirtsCategory);
|
||||
|
||||
return categories;
|
||||
};
|
||||
|
||||
shops.getCustomizationsShop = function getCustomizationsShop (user, language) {
|
||||
return {
|
||||
identifier: 'customizations',
|
||||
text: i18n.t('customizations'),
|
||||
// notes: i18n.t('customizations'),
|
||||
imageName: 'npc_alex',
|
||||
categories: shops.getCustomizationsShopCategories(user, language),
|
||||
};
|
||||
};
|
||||
|
||||
export default shops;
|
||||
|
|
|
|||
Loading…
Reference in a new issue