fix most customizations not being pinnable (#15578)
* fix most customizations not being pinnable * set correct pinTypes * fix(pinning): correct purchase types for base hair and mustaches * automatically unpin purchased customizations * ability to pin customization items * Fix pin not showing on buy modal * Pin on buy modal tweak --------- Co-authored-by: Kalista Payne <kalista@habitica.com> Co-authored-by: Hafiz <hafizbhamidi@gmail.com>
This commit is contained in:
@@ -20,6 +20,9 @@ describe('shared.ops.unlock', () => {
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user.balance = usersStartingGems;
|
||||
user.pinnedItems.push({ type: 'background', path: 'backgrounds.backgrounds042016.giant_florals' });
|
||||
user.pinnedItems.push({ type: 'haircolor', path: 'hair.color.rainbow' });
|
||||
user.pinnedItems.push({ type: 'shirt', path: 'shirt.convict' });
|
||||
clock = sandbox.useFakeTimers(new Date('2024-04-10'));
|
||||
});
|
||||
|
||||
@@ -272,6 +275,7 @@ describe('shared.ops.unlock', () => {
|
||||
});
|
||||
|
||||
it('unlocks an item (appearance)', async () => {
|
||||
expect(user.pinnedItems.findIndex(item => item.type === 'shirt')).to.not.equal(-1);
|
||||
const path = unlockPath.split(',')[0];
|
||||
const initialShirts = Object.keys(user.purchased.shirt).length;
|
||||
const [, message] = await unlock(user, { query: { path } });
|
||||
@@ -282,11 +286,12 @@ describe('shared.ops.unlock', () => {
|
||||
);
|
||||
expect(get(user.purchased, path)).to.be.true;
|
||||
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
||||
expect(user.pinnedItems.findIndex(item => item.type === 'shirt')).to.equal(-1);
|
||||
});
|
||||
|
||||
it('unlocks an item (hair color)', async () => {
|
||||
user.purchased.hair.color = {};
|
||||
|
||||
expect(user.pinnedItems.findIndex(item => item.type === 'haircolor')).to.not.equal(-1);
|
||||
const path = hairUnlockPath.split(',')[0];
|
||||
const initialColorHair = Object.keys(user.purchased.hair.color).length;
|
||||
const [, message] = await unlock(user, { query: { path } });
|
||||
@@ -297,6 +302,7 @@ describe('shared.ops.unlock', () => {
|
||||
);
|
||||
expect(get(user.purchased, path)).to.be.true;
|
||||
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
||||
expect(user.pinnedItems.findIndex(item => item.type === 'haircolor')).to.equal(-1);
|
||||
});
|
||||
|
||||
it('unlocks an item (facial hair)', async () => {
|
||||
@@ -334,6 +340,7 @@ describe('shared.ops.unlock', () => {
|
||||
|
||||
it('unlocks an item (background)', async () => {
|
||||
const initialBackgrounds = Object.keys(user.purchased.background).length;
|
||||
expect(user.pinnedItems.findIndex(item => item.type === 'background')).to.not.equal(-1);
|
||||
const [, message] = await unlock(user, {
|
||||
query: { path: backgroundUnlockPath },
|
||||
});
|
||||
@@ -344,6 +351,7 @@ describe('shared.ops.unlock', () => {
|
||||
);
|
||||
expect(get(user.purchased, backgroundUnlockPath)).to.be.true;
|
||||
expect(user.balance).to.equal(usersStartingGems - 1.75);
|
||||
expect(user.pinnedItems.findIndex(item => item.type === 'background')).to.equal(-1);
|
||||
});
|
||||
|
||||
it('handles an invalid hair path gracefully', async () => {
|
||||
|
||||
@@ -281,6 +281,11 @@
|
||||
.badge-dialog {
|
||||
left: -8px;
|
||||
top: -8px;
|
||||
|
||||
.badge-pin {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
@@ -903,8 +908,8 @@ export default {
|
||||
purchaseGems () {
|
||||
this.$root.$emit('bv::show::modal', 'buy-gems');
|
||||
},
|
||||
togglePinned () {
|
||||
this.isPinned = this.$store.dispatch('user:togglePinnedItem', { type: this.item.pinType, path: this.item.path });
|
||||
async togglePinned () {
|
||||
this.isPinned = await this.$store.dispatch('user:togglePinnedItem', { type: this.item.pinType, path: this.item.path });
|
||||
|
||||
if (!this.isPinned) {
|
||||
this.text(this.$t('unpinnedItem', { item: this.item.text }));
|
||||
|
||||
@@ -76,7 +76,21 @@
|
||||
:empty-item="false"
|
||||
:show-popover="Boolean(ctx.item.text)"
|
||||
@click="selectItem(ctx.item)"
|
||||
/>
|
||||
>
|
||||
<template
|
||||
slot="itemBadge"
|
||||
slot-scope="slotProps"
|
||||
>
|
||||
<span
|
||||
class="badge-top"
|
||||
@click.prevent.stop="togglePinned(slotProps.item)"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="slotProps.item.pinned"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</shop-item>
|
||||
</template>
|
||||
</item-rows>
|
||||
</div>
|
||||
@@ -108,6 +122,16 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.market .badge-pin:not(.pinned) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.market .item:hover .badge-pin {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find';
|
||||
import shops from '@/../../common/script/libs/shops';
|
||||
@@ -118,7 +142,9 @@ import Checkbox from '@/components/ui/checkbox';
|
||||
import FilterGroup from '@/components/ui/filterGroup';
|
||||
import FilterSidebar from '@/components/ui/filterSidebar';
|
||||
import ItemRows from '@/components/ui/itemRows';
|
||||
import PinBadge from '@/components/ui/pinBadge';
|
||||
import ShopItem from '../shopItem';
|
||||
import pinUtils from '@/mixins/pinUtils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -126,8 +152,10 @@ export default {
|
||||
FilterGroup,
|
||||
FilterSidebar,
|
||||
ItemRows,
|
||||
PinBadge,
|
||||
ShopItem,
|
||||
},
|
||||
mixins: [pinUtils],
|
||||
data () {
|
||||
return {
|
||||
searchText: null,
|
||||
@@ -184,8 +212,12 @@ export default {
|
||||
methods: {
|
||||
customizationsItems (options = {}) {
|
||||
const { category, searchBy } = options;
|
||||
return category.items.filter(item => !searchBy
|
||||
|| item.text.toLowerCase().includes(searchBy));
|
||||
return category.items
|
||||
.filter(item => !searchBy || item.text.toLowerCase().includes(searchBy))
|
||||
.map(item => ({
|
||||
...item,
|
||||
pinned: this.isPinned(item),
|
||||
}));
|
||||
},
|
||||
emptyClick (identifier, event) {
|
||||
if (event.target.tagName !== 'A') return;
|
||||
|
||||
@@ -180,6 +180,11 @@
|
||||
.badge-dialog {
|
||||
left: -8px;
|
||||
top: -8px;
|
||||
|
||||
.badge-pin {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
.badge-pin {
|
||||
background-color: $white;
|
||||
color: $gray-200;
|
||||
color: $gray-100;
|
||||
transition: none;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
@@ -32,8 +32,8 @@
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,19 @@ import get from 'lodash/get';
|
||||
import content from '../content/index';
|
||||
|
||||
export default function getItemByPathAndType (type, path) {
|
||||
let item = get(content, path);
|
||||
let item;
|
||||
if ([
|
||||
'haircolor',
|
||||
'hairbase',
|
||||
'hairmustache',
|
||||
'hairbeard',
|
||||
'shirt',
|
||||
'skin',
|
||||
].indexOf(type) !== -1) {
|
||||
item = get(content, `appearances.${path}`);
|
||||
} else {
|
||||
item = get(content, path);
|
||||
}
|
||||
|
||||
if (type === 'timeTravelersStable') {
|
||||
const [, animalType, key] = path.split('.');
|
||||
|
||||
@@ -384,8 +384,8 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
|
||||
locked: false,
|
||||
notes: '',
|
||||
path: `hair.color.${item.key}`,
|
||||
pinType: 'haircolor',
|
||||
purchaseType: 'customization',
|
||||
pinType: 'timeTravelersStable',
|
||||
set: item.set,
|
||||
text: item.text(language),
|
||||
type: 'color',
|
||||
@@ -401,7 +401,7 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
|
||||
locked: false,
|
||||
notes: '',
|
||||
path: `hair.base.${item.key}`,
|
||||
pinType: 'timeTravelersStable',
|
||||
pinType: 'hairbase',
|
||||
purchaseType: 'customization',
|
||||
set: item.set,
|
||||
text: item.text(language),
|
||||
@@ -418,7 +418,7 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
|
||||
locked: false,
|
||||
notes: '',
|
||||
path: `hair.mustache.${item.key}`,
|
||||
pinType: 'timeTravelersStable',
|
||||
pinType: 'hairmustache',
|
||||
purchaseType: 'customization',
|
||||
set: item.set,
|
||||
text: item.text(language),
|
||||
@@ -435,7 +435,7 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
|
||||
locked: false,
|
||||
notes: '',
|
||||
path: `hair.beard.${item.key}`,
|
||||
pinType: 'timeTravelersStable',
|
||||
pinType: 'hairbeard',
|
||||
purchaseType: 'customization',
|
||||
set: item.set,
|
||||
text: item.text(language),
|
||||
@@ -452,7 +452,7 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
|
||||
locked: false,
|
||||
notes: '',
|
||||
path: `shirt.${item.key}`,
|
||||
pinType: 'timeTravelersStable',
|
||||
pinType: 'shirt',
|
||||
purchaseType: 'customization',
|
||||
set: item.set,
|
||||
text: item.text(language),
|
||||
@@ -469,7 +469,7 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
|
||||
locked: false,
|
||||
path: `skin.${item.key}`,
|
||||
notes: '',
|
||||
pinType: 'timeTravelersStable',
|
||||
pinType: 'skin',
|
||||
purchaseType: 'customization',
|
||||
set: item.set,
|
||||
text: item.text(language),
|
||||
|
||||
@@ -288,14 +288,16 @@ export default async function unlock (user, req = {}, analytics) {
|
||||
if (isFullSet) {
|
||||
paths.forEach(pathPart => purchaseItem(pathPart, setType, user));
|
||||
|
||||
if (isBackground) {
|
||||
paths.forEach(pathPart => {
|
||||
paths.forEach(pathPart => {
|
||||
if (isBackground) {
|
||||
const [key, value] = splitPathItem(pathPart);
|
||||
const backgroundContent = content.backgroundsFlat[value];
|
||||
const itemInfo = getItemInfo(user, key, backgroundContent);
|
||||
removeItemByPath(user, itemInfo.path);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
removeItemByPath(user, path);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const [key, value] = splitPathItem(path);
|
||||
|
||||
@@ -309,6 +311,8 @@ export default async function unlock (user, req = {}, analytics) {
|
||||
const backgroundContent = content.backgroundsFlat[value];
|
||||
const itemInfo = getItemInfo(user, 'background', backgroundContent);
|
||||
removeItemByPath(user, itemInfo.path);
|
||||
} else {
|
||||
removeItemByPath(user, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user