mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-05-20 12:48:52 +00:00
item pinning (#8918)
* toggle pinned state of items server + client * pin quests / add pin src * add officially pinned items and api to get in app rewards * update schema and get items deatils * update pin actions to the new logic * show countBadge only with a number * extract getPinKey - pin seasonal items * togglePinned in buy-dialogs * add pinKey to shop items * wip * wip * fix path * togglePinnedItem as common.op / use in client * fix linting * pinning: getItemInfo and save in db path and type * make api more consistent, fix bugs * updates * fix bugs * update actions to current api * marketGear * change to pinType * add mystery_set to getItemInfo * fix isPinned * ignore animals * list shopItems (initial) * shopItem now has default popoverconent, itemclass and price / currency - list pinned items as rewards - attributes to gear * show buyModal for the rewards * show mystery_set icon * add info whether item is suggested * write migration, fix style issues * pin potion and armoire * make potion, armoire not unpinnable * show notes for armoire and potion, add default items for new users * show unpin notification * add/remove pinned gear on class-change * remove pinned & add new gear on purchase - refactoring pinning methods - fixes * always allow to purchase armoire * highlight item if suggested
This commit is contained in:
parent
fcea1ecbc2
commit
87f39b4273
34 changed files with 955 additions and 274 deletions
108
migrations/20170811_pinned_items.js
Normal file
108
migrations/20170811_pinned_items.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
var updateStore = require('../website/common/script/libs/updateStore');
|
||||
var getItemInfo = require('../website/common/script/libs/getItemInfo');
|
||||
|
||||
var migrationName = '20170811_pinned_items.js';
|
||||
var authorName = 'paglias'; // in case script author needs to know when their ...
|
||||
var authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; //... own data is done
|
||||
|
||||
/*
|
||||
* Migrate existing in app rewards lists to pinned items
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
return dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
var set = {'migration': migrationName};
|
||||
|
||||
var oldRewardsList = updateStore(user);
|
||||
var newPinnedItems = [
|
||||
{
|
||||
type: 'armoire',
|
||||
path: 'armoire',
|
||||
},
|
||||
{
|
||||
type: 'potion',
|
||||
path: 'potion',
|
||||
},
|
||||
];
|
||||
|
||||
oldRewardsList.forEach(item => {
|
||||
var type = 'marketGear';
|
||||
|
||||
var itemInfo = getItemInfo(user, 'marketGear', item);
|
||||
newPinnedItems.push({
|
||||
type,
|
||||
path: itemInfo.path,
|
||||
})
|
||||
});
|
||||
|
||||
set.pinnedItems = newPinnedItems;
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
|
||||
return dbUsers.update({_id: user._id}, {$set:set});
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
|
|
@ -68,23 +68,6 @@ describe('shared.ops.buyArmoire', () => {
|
|||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not open without Ultimate Gear achievement', (done) => {
|
||||
user.achievements.ultimateGearSets = {healer: false, wizard: false, rogue: false, warrior: false};
|
||||
|
||||
try {
|
||||
buyArmoire(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('cannotBuyItem'));
|
||||
expect(user.items.gear.owned).to.eql({
|
||||
weapon_warrior_0: true,
|
||||
});
|
||||
expect(user.items.food).to.be.empty;
|
||||
expect(user.stats.exp).to.eql(0);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('non-gear awards', () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template lang="pug">
|
||||
div
|
||||
h4.popover-content-title {{ item.text() }}
|
||||
.popover-content-text {{ item.notes() }}
|
||||
h4.popover-content-title {{ itemText }}
|
||||
.popover-content-text {{ itemNotes }}
|
||||
.popover-content-attr(v-for="attr in ATTRIBUTES", :key="attr", v-once)
|
||||
span.popover-content-attr-key {{ `${$t(attr)}: ` }}
|
||||
span.popover-content-attr-val {{ `+${item[attr]}` }}
|
||||
|
|
@ -20,6 +20,20 @@ div
|
|||
...mapState({
|
||||
ATTRIBUTES: 'constants.ATTRIBUTES',
|
||||
}),
|
||||
itemText () {
|
||||
if (this.item.text instanceof Function) {
|
||||
return this.item.text();
|
||||
} else {
|
||||
return this.item.text;
|
||||
}
|
||||
},
|
||||
itemNotes () {
|
||||
if (this.item.notes instanceof Function) {
|
||||
return this.item.notes();
|
||||
} else {
|
||||
return this.item.notes;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
6
website/client/components/shops/_isPinned.js
Normal file
6
website/client/components/shops/_isPinned.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export default function _isPinned (user, item) {
|
||||
const isUnpinned = user.unpinnedItems.findIndex(unpinned => unpinned.path === item.path) > -1;
|
||||
const isPinned = user.pinnedItems.findIndex(pinned => pinned.path === item.path) > -1;
|
||||
|
||||
return isPinned && !isUnpinned;
|
||||
}
|
||||
|
|
@ -6,8 +6,9 @@
|
|||
@change="onChange($event)"
|
||||
)
|
||||
span.badge.badge-pill.badge-dialog(
|
||||
:class="{'item-selected-badge': true}",
|
||||
v-if="withPin"
|
||||
:class="{'item-selected-badge': item.pinned}",
|
||||
v-if="withPin",
|
||||
@click.prevent.stop="togglePinned()"
|
||||
)
|
||||
span.svg-icon.inline.color.icon-10(v-html="icons.pin")
|
||||
|
||||
|
|
@ -127,7 +128,15 @@
|
|||
padding: 8px 10px;
|
||||
top: -12px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
|
||||
&.item-selected-badge {
|
||||
background: $purple-300;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -182,6 +191,9 @@
|
|||
this.$emit('buyPressed', this.item);
|
||||
this.hideDialog();
|
||||
},
|
||||
togglePinned () {
|
||||
this.$emit('togglePinned', this.item);
|
||||
},
|
||||
hideDialog () {
|
||||
this.$root.$emit('hide::modal', 'buy-modal');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,10 +11,17 @@
|
|||
|
||||
<script>
|
||||
import SecondaryMenu from 'client/components/secondaryMenu';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
components: {
|
||||
SecondaryMenu,
|
||||
},
|
||||
methods: {
|
||||
showUnpinNotification (item) {
|
||||
this.text(this.$t('unpinnedItem', {item: item.text}));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -48,14 +48,11 @@
|
|||
:key="item.key",
|
||||
:item="item",
|
||||
:price="item.value",
|
||||
:priceType="item.currency",
|
||||
:itemContentClass="'shop_'+item.key",
|
||||
:emptyItem="false",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedGearToBuy = item"
|
||||
)
|
||||
template(slot="popoverContent", scope="ctx")
|
||||
equipmentAttributesPopover(:item="ctx.item")
|
||||
|
||||
h1.mb-0.page-header(v-once) {{ $t('market') }}
|
||||
|
||||
|
|
@ -102,15 +99,10 @@
|
|||
shopItem(
|
||||
:key="ctx.item.key",
|
||||
:item="ctx.item",
|
||||
:price="ctx.item.value",
|
||||
:priceType="ctx.item.currency",
|
||||
:itemContentClass="'shop_'+ctx.item.key",
|
||||
:emptyItem="userItems.gear[ctx.item.key] === undefined",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedGearToBuy = ctx.item"
|
||||
)
|
||||
template(slot="popoverContent", scope="ctx")
|
||||
equipmentAttributesPopover(:item="ctx.item")
|
||||
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
span.badge.badge-pill.badge-item.badge-svg(
|
||||
|
|
@ -142,12 +134,9 @@
|
|||
|
||||
div.items
|
||||
shopItem(
|
||||
v-for="item in sortedMarketItems(category, selectedSortItemsBy, searchTextThrottled)",
|
||||
v-for="item in sortedMarketItems(category, selectedSortItemsBy, searchTextThrottled, hidePinned)",
|
||||
:key="item.key",
|
||||
:item="item",
|
||||
:price="item.value",
|
||||
:priceType="item.currency",
|
||||
:itemContentClass="item.class",
|
||||
:emptyItem="false",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedItemToBuy = item"
|
||||
|
|
@ -161,6 +150,12 @@
|
|||
:count="userItems[item.purchaseType][item.key] || 0"
|
||||
)
|
||||
|
||||
span.badge.badge-pill.badge-item.badge-svg(
|
||||
:class="{'item-selected-badge': ctx.item.pinned, 'hide': !ctx.item.pinned}",
|
||||
@click.prevent.stop="togglePinned(ctx.item)"
|
||||
)
|
||||
span.svg-icon.inline.icon-12.color(v-html="icons.pin")
|
||||
|
||||
|
||||
drawer(
|
||||
:title="$t('quickInventory')"
|
||||
|
|
@ -226,7 +221,8 @@
|
|||
priceType="gold",
|
||||
:withPin="true",
|
||||
@change="resetGearToBuy($event)",
|
||||
@buyPressed="buyGear($event)"
|
||||
@buyPressed="buyGear($event)",
|
||||
@togglePinned="togglePinned($event)"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
div
|
||||
|
|
@ -244,7 +240,8 @@
|
|||
:item="selectedItemToBuy",
|
||||
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
|
||||
@change="resetItemToBuy($event)",
|
||||
@buyPressed="buyItem($event)"
|
||||
@buyPressed="buyItem($event)",
|
||||
@togglePinned="togglePinned($event)"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
item.flat(
|
||||
|
|
@ -380,8 +377,6 @@
|
|||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
import Avatar from 'client/components/avatar';
|
||||
|
||||
import EquipmentAttributesPopover from 'client/components/inventory/equipment/attributesPopover';
|
||||
|
||||
import SellModal from './sellModal.vue';
|
||||
import BuyModal from '../buyModal.vue';
|
||||
import EquipmentAttributesGrid from './equipmentAttributesGrid.vue';
|
||||
|
|
@ -398,12 +393,15 @@
|
|||
import svgHealer from 'assets/svg/healer.svg';
|
||||
|
||||
import featuredItems from 'common/script/content/shop-featuredItems';
|
||||
import getItemInfo from 'common/script/libs/getItemInfo';
|
||||
|
||||
import _filter from 'lodash/filter';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _map from 'lodash/map';
|
||||
import _throttle from 'lodash/throttle';
|
||||
|
||||
import _isPinned from '../_isPinned';
|
||||
|
||||
const sortGearTypes = ['sortByType', 'sortByPrice', 'sortByCon', 'sortByPer', 'sortByStr', 'sortByInt'];
|
||||
|
||||
const sortGearTypeMap = {
|
||||
|
|
@ -429,7 +427,6 @@ export default {
|
|||
bDropdown,
|
||||
bDropdownItem,
|
||||
|
||||
EquipmentAttributesPopover,
|
||||
SellModal,
|
||||
BuyModal,
|
||||
EquipmentAttributesGrid,
|
||||
|
|
@ -523,7 +520,7 @@ export default {
|
|||
|
||||
featuredItems () {
|
||||
return featuredItems.market.map(i => {
|
||||
return this.content.gear.flat[i];
|
||||
return getItemInfo(this.user, 'marketGear', this.content.gear.flat[i]);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
@ -577,11 +574,12 @@ export default {
|
|||
filteredGear (groupByClass, searchBy, sortBy, hideLocked, hidePinned) {
|
||||
let result = _filter(this.content.gear.flat, ['klass', groupByClass]);
|
||||
result = _map(result, (e) => {
|
||||
return {
|
||||
...e,
|
||||
pinned: false, // TODO read pinned state
|
||||
locked: this.isGearLocked(e),
|
||||
};
|
||||
let newItem = getItemInfo(this.user, 'marketGear', e);
|
||||
|
||||
newItem.pinned = _isPinned(this.user, newItem);
|
||||
newItem.locked = this.isGearLocked(newItem);
|
||||
|
||||
return newItem;
|
||||
});
|
||||
|
||||
result = _filter(result, (gear) => {
|
||||
|
|
@ -607,9 +605,27 @@ export default {
|
|||
|
||||
return result;
|
||||
},
|
||||
sortedMarketItems (category, sortBy, searchBy) {
|
||||
let result = _filter(category.items, (i) => {
|
||||
return !searchBy || i.text.toLowerCase().indexOf(searchBy) !== -1;
|
||||
sortedMarketItems (category, sortBy, searchBy, hidePinned) {
|
||||
let result = _map(category.items, (e) => {
|
||||
return {
|
||||
...e,
|
||||
pinned: _isPinned(this.user, e),
|
||||
};
|
||||
});
|
||||
|
||||
result = _filter(result, (item) => {
|
||||
if (hidePinned && item.pinned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (searchBy) {
|
||||
let foundPosition = item.text().toLowerCase().indexOf(searchBy);
|
||||
if (foundPosition === -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
switch (sortBy) {
|
||||
|
|
@ -656,9 +672,9 @@ export default {
|
|||
};
|
||||
},
|
||||
togglePinned (item) {
|
||||
let isPinned = Boolean(item.pinned);
|
||||
item.pinned = !isPinned;
|
||||
this.$store.dispatch(isPinned ? 'shops:unpinGear' : 'shops:pinGear', {key: item.key});
|
||||
if (!this.$store.dispatch('user:togglePinnedItem', {type: item.pinType, path: item.path})) {
|
||||
this.$parent.showUnpinNotification(item);
|
||||
}
|
||||
},
|
||||
buyGear (item) {
|
||||
this.$store.dispatch('shops:buyItem', {key: item.key});
|
||||
|
|
@ -669,7 +685,6 @@ export default {
|
|||
},
|
||||
created () {
|
||||
this.$store.dispatch('shops:fetchMarket');
|
||||
|
||||
this.selectedGroupGearByClass = this.userStats.class;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
@change="onChange($event)"
|
||||
)
|
||||
span.badge.badge-pill.badge-dialog(
|
||||
:class="{'item-selected-badge': true}",
|
||||
v-if="withPin"
|
||||
:class="{'item-selected-badge': item.pinned}",
|
||||
v-if="withPin",
|
||||
@click.prevent.stop="togglePinned()"
|
||||
)
|
||||
span.svg-icon.inline.color.icon-10(v-html="icons.pin")
|
||||
|
||||
|
|
@ -178,6 +179,12 @@
|
|||
padding: 8px 10px;
|
||||
top: -12px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
|
||||
&.item-selected-badge {
|
||||
background: $purple-300;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -239,6 +246,9 @@
|
|||
this.$emit('buyPressed', this.item);
|
||||
this.hideDialog();
|
||||
},
|
||||
togglePinned () {
|
||||
this.$emit('togglePinned', this.item);
|
||||
},
|
||||
hideDialog () {
|
||||
this.$root.$emit('hide::modal', 'buy-quest-modal');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@
|
|||
)
|
||||
template(slot="popoverContent", scope="ctx")
|
||||
div
|
||||
h4.popover-content-title {{ item.text() }}
|
||||
.popover-content-text(v-html="item.notes()")
|
||||
h4.popover-content-title {{ item.text }}
|
||||
.popover-content-text(v-html="item.notes")
|
||||
|
||||
h1.mb-0.page-header(v-once) {{ $t('quests') }}
|
||||
|
||||
|
|
@ -124,8 +124,6 @@
|
|||
:key="item.key",
|
||||
:item="item",
|
||||
:price="item.value",
|
||||
:priceType="item.currency",
|
||||
:itemContentClass="item.class",
|
||||
:emptyItem="false",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedItemToBuy = item"
|
||||
|
|
@ -153,8 +151,6 @@
|
|||
:key="item.key",
|
||||
:item="item",
|
||||
:price="item.value",
|
||||
:priceType="item.currency",
|
||||
:itemContentClass="item.class",
|
||||
:emptyItem="false",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedItemToBuy = item"
|
||||
|
|
@ -181,7 +177,8 @@
|
|||
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
|
||||
:withPin="true",
|
||||
@change="resetItemToBuy($event)",
|
||||
@buyPressed="buyItem($event)"
|
||||
@buyPressed="buyItem($event)",
|
||||
@togglePinned="togglePinned($event)"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
item.flat(
|
||||
|
|
@ -335,11 +332,15 @@
|
|||
import svgPin from 'assets/svg/pin.svg';
|
||||
|
||||
import featuredItems from 'common/script/content/shop-featuredItems';
|
||||
import getItemInfo from 'common/script/libs/getItemInfo';
|
||||
|
||||
import _isPinned from '../_isPinned';
|
||||
|
||||
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';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -405,13 +406,20 @@ export default {
|
|||
|
||||
featuredItems () {
|
||||
return featuredItems.quests.map(i => {
|
||||
return this.content.quests[i];
|
||||
return getItemInfo(this.user, 'quest', this.content.quests[i]);
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
questItems (category, sortBy, searchBy, hideLocked, hidePinned) {
|
||||
let result = _filter(category.items, (i) => {
|
||||
let result = _map(category.items, (e) => {
|
||||
return {
|
||||
...e,
|
||||
pinned: _isPinned(this.user, e),
|
||||
};
|
||||
});
|
||||
|
||||
result = _filter(result, (i) => {
|
||||
if (hideLocked && i.locked) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -453,9 +461,9 @@ export default {
|
|||
return false;
|
||||
},
|
||||
togglePinned (item) {
|
||||
let isPinned = Boolean(item.pinned);
|
||||
item.pinned = !isPinned;
|
||||
this.$store.dispatch(isPinned ? 'shops:unpinGear' : 'shops:pinGear', {key: item.key});
|
||||
if (!this.$store.dispatch('user:togglePinnedItem', {type: item.pinType, path: item.path})) {
|
||||
this.$parent.showUnpinNotification(item);
|
||||
}
|
||||
},
|
||||
buyItem (item) {
|
||||
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
span.rectangle
|
||||
span.text Leslie
|
||||
span.rectangle
|
||||
div.content
|
||||
div.content(v-if="featuredSet")
|
||||
div.featured-label.with-border
|
||||
span.rectangle
|
||||
span.text(v-once) {{ $t('featuredset', { name: featuredSet.text }) }}
|
||||
|
|
@ -42,8 +42,6 @@
|
|||
:key="item.key",
|
||||
:item="item",
|
||||
:price="item.value",
|
||||
:priceType="item.currency",
|
||||
:itemContentClass="item.class",
|
||||
:emptyItem="false",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedItemToBuy = item"
|
||||
|
|
@ -88,8 +86,6 @@
|
|||
:key="item.key",
|
||||
:item="item",
|
||||
:price="item.value",
|
||||
:priceType="item.currency",
|
||||
:itemContentClass="item.class",
|
||||
:emptyItem="false",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedItemToBuy = item"
|
||||
|
|
@ -106,37 +102,13 @@
|
|||
)
|
||||
span.svg-icon.inline.icon-12.color(v-html="icons.pin")
|
||||
|
||||
|
||||
div.items(v-if="false")
|
||||
shopItem(
|
||||
v-for="item in seasonalItems(category, selectedSortItemsBy, searchTextThrottled, hidePinned)",
|
||||
:key="item.key",
|
||||
:item="item",
|
||||
:price="item.value",
|
||||
:priceType="item.currency",
|
||||
:itemContentClass="item.class",
|
||||
:emptyItem="false",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedItemToBuy = item"
|
||||
)
|
||||
span(slot="popoverContent")
|
||||
div
|
||||
h4.popover-content-title {{ item.text }}
|
||||
.popover-content-text {{ item.notes }}
|
||||
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
span.badge.badge-pill.badge-item.badge-svg(
|
||||
:class="{'item-selected-badge': ctx.item.pinned, 'hide': !ctx.item.pinned}",
|
||||
@click.prevent.stop="togglePinned(ctx.item)"
|
||||
)
|
||||
span.svg-icon.inline.icon-12.color(v-html="icons.pin")
|
||||
|
||||
buyModal(
|
||||
:item="selectedItemToBuy",
|
||||
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
|
||||
:withPin="true",
|
||||
@change="resetItemToBuy($event)",
|
||||
@buyPressed="buyItem($event)"
|
||||
@buyPressed="buyItem($event)",
|
||||
@togglePinned="togglePinned($event)"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
item.flat(
|
||||
|
|
@ -328,7 +300,9 @@
|
|||
import _throttle from 'lodash/throttle';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
|
||||
export default {
|
||||
import _isPinned from '../_isPinned';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ShopItem,
|
||||
Item,
|
||||
|
|
@ -377,7 +351,6 @@ export default {
|
|||
seasonal: 'shops.seasonal.data',
|
||||
user: 'user.data',
|
||||
userStats: 'user.data.stats',
|
||||
userItems: 'user.data.items',
|
||||
}),
|
||||
categories () {
|
||||
if (this.seasonal) {
|
||||
|
|
@ -428,7 +401,14 @@ export default {
|
|||
}
|
||||
},
|
||||
seasonalItems (category, sortBy, searchBy, viewOptions, hidePinned) {
|
||||
let result = _filter(category.items, (i) => {
|
||||
let result = _map(category.items, (e) => {
|
||||
return {
|
||||
...e,
|
||||
pinned: _isPinned(this.user, e),
|
||||
};
|
||||
});
|
||||
|
||||
result = _filter(result, (i) => {
|
||||
if (hidePinned && i.pinned) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -482,9 +462,9 @@ export default {
|
|||
return false;
|
||||
},
|
||||
togglePinned (item) {
|
||||
let isPinned = Boolean(item.pinned);
|
||||
item.pinned = !isPinned;
|
||||
this.$store.dispatch(isPinned ? 'shops:unpinGear' : 'shops:pinGear', {key: item.key});
|
||||
if (!this.$store.dispatch('user:togglePinnedItem', {type: item.pinType, path: item.path})) {
|
||||
this.$parent.showUnpinNotification(item);
|
||||
}
|
||||
},
|
||||
buyItem (item) {
|
||||
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
|
||||
|
|
|
|||
|
|
@ -5,31 +5,41 @@ b-popover(
|
|||
)
|
||||
span(slot="content")
|
||||
slot(name="popoverContent", :item="item")
|
||||
equipmentAttributesPopover(
|
||||
v-if="item.purchaseType==='gear'",
|
||||
:item="item"
|
||||
)
|
||||
div(v-else)
|
||||
h4.popover-content-title {{ item.text }}
|
||||
.popover-content-text(v-if="showNotes") {{ item.notes }}
|
||||
|
||||
.item-wrapper(@click="click()")
|
||||
.item(
|
||||
:class="{'item-empty': emptyItem, 'highlight': highlightBorder}",
|
||||
:class="{'item-empty': emptyItem, 'highlight-border': highlightBorder}",
|
||||
)
|
||||
slot(name="itemBadge", :item="item", :emptyItem="emptyItem")
|
||||
div.shop-content
|
||||
span.svg-icon.inline.lock(v-if="item.locked" v-html="icons.lock")
|
||||
|
||||
|
||||
div.image
|
||||
div(:class="itemContentClass")
|
||||
div(:class="item.class")
|
||||
|
||||
div.price
|
||||
span.svg-icon.inline.icon-16(v-html="icons[getSvgClass()]")
|
||||
|
||||
span.price-label(:class="getSvgClass()") {{ price }}
|
||||
span.price-label(:class="getSvgClass()") {{ getPrice() }}
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.item {
|
||||
min-height: 106px;
|
||||
.item-wrapper {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.item {
|
||||
min-height: 106px;
|
||||
}
|
||||
|
||||
.item.item-empty {
|
||||
|
|
@ -100,9 +110,12 @@ b-popover(
|
|||
import svgHourglasses from 'assets/svg/hourglass.svg';
|
||||
import svgLock from 'assets/svg/lock.svg';
|
||||
|
||||
import EquipmentAttributesPopover from 'client/components/inventory/equipment/attributesPopover';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
bPopover,
|
||||
EquipmentAttributesPopover,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
@ -118,16 +131,10 @@ b-popover(
|
|||
item: {
|
||||
type: Object,
|
||||
},
|
||||
itemContentClass: {
|
||||
type: String,
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
priceType: {
|
||||
type: String,
|
||||
},
|
||||
emptyItem: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
|
@ -145,17 +152,29 @@ b-popover(
|
|||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showNotes () {
|
||||
if (['armoire', 'potion'].indexOf(this.item.path) > -1) return true;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
click () {
|
||||
this.$emit('click', {});
|
||||
},
|
||||
getSvgClass () {
|
||||
if (this.priceType && this.icons[this.priceType]) {
|
||||
return this.priceType;
|
||||
if (this.item.currency && this.icons[this.item.currency]) {
|
||||
return this.item.currency;
|
||||
} else {
|
||||
return 'gold';
|
||||
}
|
||||
},
|
||||
getPrice () {
|
||||
if (this.price === -1) {
|
||||
return this.item.value;
|
||||
} else {
|
||||
return this.price;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -92,11 +92,10 @@
|
|||
span(slot="popoverContent", scope="ctx")
|
||||
div
|
||||
h4.popover-content-title {{ ctx.item.text }}
|
||||
.popover-content-text {{ ctx.item.notes }}
|
||||
div {{ ctx.item }}
|
||||
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
span.badge.badge-pill.badge-item.badge-svg(
|
||||
v-if="ctx.item.pinType !== 'IGNORE'",
|
||||
:class="{'item-selected-badge': ctx.item.pinned, 'hide': !ctx.item.pinned}",
|
||||
@click.prevent.stop="togglePinned(ctx.item)"
|
||||
)
|
||||
|
|
@ -277,6 +276,9 @@
|
|||
import _sortBy from 'lodash/sortBy';
|
||||
import _throttle from 'lodash/throttle';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
import _map from 'lodash/map';
|
||||
|
||||
import _isPinned from '../_isPinned';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -345,8 +347,8 @@ export default {
|
|||
...c,
|
||||
value: 1,
|
||||
currency: 'hourglasses',
|
||||
type: 'set_mystery',
|
||||
key: c.identifier,
|
||||
class: `shop_set_mystery_${c.identifier}`,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
|
@ -373,7 +375,14 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
travelersItems (category, sortBy, searchBy, hidePinned) {
|
||||
let result = _filter(category.items, (i) => {
|
||||
let result = _map(category.items, (e) => {
|
||||
return {
|
||||
...e,
|
||||
pinned: _isPinned(this.user, e),
|
||||
};
|
||||
});
|
||||
|
||||
result = _filter(result, (i) => {
|
||||
if (hidePinned && i.pinned) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -405,9 +414,9 @@ export default {
|
|||
}
|
||||
},
|
||||
togglePinned (item) {
|
||||
let isPinned = Boolean(item.pinned);
|
||||
item.pinned = !isPinned;
|
||||
this.$store.dispatch(isPinned ? 'shops:unpinGear' : 'shops:pinGear', {key: item.key});
|
||||
if (!this.$store.dispatch('user:togglePinnedItem', {type: item.pinType, path: item.path})) {
|
||||
this.$parent.showUnpinNotification(item);
|
||||
}
|
||||
},
|
||||
buyItem (item) {
|
||||
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,17 @@
|
|||
:isUser="isUser",
|
||||
@editTask="editTask",
|
||||
)
|
||||
template(v-if="isUser === true && type === 'reward' && activeFilter.label !== 'custom'")
|
||||
.reward-items
|
||||
shopItem(
|
||||
v-for="reward in inAppRewards",
|
||||
:item="reward",
|
||||
:key="reward.key",
|
||||
:highlightBorder="reward.isSuggested",
|
||||
@click="openBuyDialog(reward)"
|
||||
)
|
||||
|
||||
.bottom-gradient
|
||||
.column-background(
|
||||
v-if="isUser === true",
|
||||
:class="{'initial-description': tasks[`${type}s`].length === 0}",
|
||||
|
|
@ -34,6 +45,12 @@
|
|||
height: 556px;
|
||||
}
|
||||
|
||||
.reward-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tasks-list {
|
||||
border-radius: 4px;
|
||||
background: $gray-600;
|
||||
|
|
@ -137,17 +154,20 @@
|
|||
import Task from './task';
|
||||
import { mapState, mapActions } from 'client/libs/store';
|
||||
import { shouldDo } from 'common/script/cron';
|
||||
import inAppRewards from 'common/script/libs/inAppRewards';
|
||||
import habitIcon from 'assets/svg/habit.svg';
|
||||
import dailyIcon from 'assets/svg/daily.svg';
|
||||
import todoIcon from 'assets/svg/todo.svg';
|
||||
import rewardIcon from 'assets/svg/reward.svg';
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
import shopItem from '../shops/shopItem';
|
||||
import throttle from 'lodash/throttle';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Task,
|
||||
bModal,
|
||||
shopItem,
|
||||
},
|
||||
props: ['type', 'isUser', 'searchText', 'selectedTags', 'taskListOverride'],
|
||||
data () {
|
||||
|
|
@ -203,12 +223,16 @@ export default {
|
|||
computed: {
|
||||
...mapState({
|
||||
tasks: 'tasks.data',
|
||||
user: 'user.data',
|
||||
userPreferences: 'user.data.preferences',
|
||||
}),
|
||||
taskList () {
|
||||
if (this.taskListOverride) return this.taskListOverride;
|
||||
return this.tasks[`${this.type}s`];
|
||||
},
|
||||
inAppRewards () {
|
||||
return inAppRewards(this.user);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
taskList: {
|
||||
|
|
@ -240,6 +264,12 @@ export default {
|
|||
Array.from(taskListEl.getElementsByClassName('task')).forEach(el => {
|
||||
combinedTasksHeights += el.offsetHeight;
|
||||
});
|
||||
|
||||
const rewardsList = taskListEl.getElementsByClassName('reward-items')[0];
|
||||
if (rewardsList) {
|
||||
combinedTasksHeights += rewardsList.offsetHeight;
|
||||
}
|
||||
|
||||
const columnBackgroundStyle = this.$refs.columnBackground.style;
|
||||
|
||||
if (tasklistHeight - combinedTasksHeights < 150) {
|
||||
|
|
@ -279,6 +309,9 @@ export default {
|
|||
return checklistItemIndex !== -1;
|
||||
}
|
||||
},
|
||||
openBuyDialog (rewardItem) {
|
||||
this.$emit('openBuyDialog', rewardItem);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -74,7 +74,38 @@
|
|||
:isUser="true", :searchText="searchTextThrottled",
|
||||
:selectedTags="selectedTags",
|
||||
@editTask="editTask",
|
||||
@openBuyDialog="openBuyDialog($event)"
|
||||
)
|
||||
|
||||
buyModal(
|
||||
:item="selectedItemToBuy",
|
||||
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
|
||||
@change="resetItemToBuy($event)",
|
||||
@buyPressed="buyItem($event)",
|
||||
@togglePinned="togglePinned($event)"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
div(v-if="ctx.item.purchaseType === 'gear'")
|
||||
avatar.inline(
|
||||
:member="user",
|
||||
:avatarOnly="true",
|
||||
:withBackground="true",
|
||||
:overrideAvatarGear="memberOverrideAvatarGear(ctx.item)"
|
||||
)
|
||||
|
||||
item.flat(
|
||||
:item="ctx.item",
|
||||
:itemContentClass="ctx.item.class",
|
||||
:showPopover="false",
|
||||
v-else
|
||||
)
|
||||
|
||||
template(slot="additionalInfo", scope="ctx")
|
||||
equipmentAttributesGrid.bordered(
|
||||
:item="ctx.item",
|
||||
v-if="ctx.item.purchaseType === 'gear'"
|
||||
)
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
@ -246,12 +277,21 @@ import cloneDeep from 'lodash/cloneDeep';
|
|||
import { mapState, mapActions } from 'client/libs/store';
|
||||
import taskDefaults from 'common/script/libs/taskDefaults';
|
||||
|
||||
import BuyModal from 'client/components/shops/buyModal.vue';
|
||||
import Item from 'client/components/inventory/item.vue';
|
||||
import Avatar from 'client/components/avatar';
|
||||
import EquipmentAttributesGrid from 'client/components/shops/market/equipmentAttributesGrid.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TaskColumn,
|
||||
TaskModal,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
BuyModal,
|
||||
Item,
|
||||
Avatar,
|
||||
EquipmentAttributesGrid,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
@ -275,6 +315,8 @@ export default {
|
|||
newTag: null,
|
||||
editingTask: null,
|
||||
creatingTask: null,
|
||||
|
||||
selectedItemToBuy: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -393,6 +435,26 @@ export default {
|
|||
if (this.temporarilySelectedTags.indexOf(tagId) !== -1) return true;
|
||||
return false;
|
||||
},
|
||||
resetItemToBuy ($event) {
|
||||
if (!$event) {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
},
|
||||
memberOverrideAvatarGear (gear) {
|
||||
return {
|
||||
[gear.type]: gear.key,
|
||||
};
|
||||
},
|
||||
buyItem (item) {
|
||||
if (item.currency === 'gold') {
|
||||
this.$store.dispatch('shops:buyItem', {key: item.key});
|
||||
} else {
|
||||
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
|
||||
}
|
||||
},
|
||||
openBuyDialog (rewardItem) {
|
||||
this.selectedItemToBuy = rewardItem;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template lang="pug">
|
||||
span.badge.badge-pill.badge-item.badge-count(
|
||||
v-if="show",
|
||||
v-if="show && count != 0",
|
||||
) {{ count }}
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -82,20 +82,3 @@ export function sellItems (store, params) {
|
|||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
|
||||
|
||||
export function pinGear () {
|
||||
// axios
|
||||
// .post(`/api/v3/user/pin/${params.key}`);
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
|
||||
export function unpinGear () {
|
||||
// axios
|
||||
// .post(`/api/v3/user/unpin/${params.key}`);
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { loadAsyncResource } from 'client/libs/asyncResource';
|
|||
import setProps from 'lodash/set';
|
||||
import axios from 'axios';
|
||||
|
||||
import { togglePinnedItem as togglePinnedItemOp } from 'common/script/ops/pinnedGearUtils';
|
||||
|
||||
export function fetch (store, forceLoad = false) { // eslint-disable-line no-shadow
|
||||
return loadAsyncResource({
|
||||
store,
|
||||
|
|
@ -55,3 +57,17 @@ export async function deleteWebhook (store, payload) {
|
|||
let response = await axios.delete(`/api/v3/user/webhook/${payload.webhook.id}`);
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
|
||||
export function togglePinnedItem (store, params) {
|
||||
const user = store.state.user.data;
|
||||
|
||||
let addedItem = togglePinnedItemOp(user, params);
|
||||
|
||||
axios.get(`/api/v3/user/toggle-pinned-item/${params.type}/${params.path}`);
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
|
||||
return addedItem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,6 +275,10 @@
|
|||
"spirituality": "Spirituality",
|
||||
"time_management": "Time-Management + Accountability",
|
||||
"recovery_support_groups": "Recovery + Support Groups",
|
||||
"wrongItemType": "The item type \"<%= type %>\" is not valid.",
|
||||
"unpinnedItem": "You unpinned <%= item %>! It will no longer display in your Rewards column.",
|
||||
"cannotUpinArmoirPotion": "The Health Potion and Enchanted Armoire cannot be unpinned.",
|
||||
"recovery_support_groups": "Recovery + Support Groups",
|
||||
"equip": "Equip",
|
||||
"unequip": "Unequip",
|
||||
"sortByName": "Name",
|
||||
|
|
|
|||
|
|
@ -584,9 +584,11 @@ let backgrounds = {
|
|||
};
|
||||
/* eslint-enable quote-props */
|
||||
|
||||
forOwn(backgrounds, function prefillBackgroundSet (value) {
|
||||
forOwn(value, function prefillBackground (bgObject) {
|
||||
bgObject.price = 7;
|
||||
forOwn(backgrounds, function prefillBackgroundSet (backgroundsInSet, set) {
|
||||
forOwn(backgroundsInSet, function prefillBackground (background, bgKey) {
|
||||
background.key = bgKey;
|
||||
background.set = set;
|
||||
background.price = 7;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import defaults from 'lodash/defaults';
|
||||
import each from 'lodash/each';
|
||||
import includes from 'lodash/includes';
|
||||
import moment from 'moment';
|
||||
import t from './translation';
|
||||
|
||||
|
|
@ -33,6 +32,8 @@ import timeTravelers from './time-travelers';
|
|||
|
||||
import loginIncentives from './loginIncentives';
|
||||
|
||||
import officialPinnedItems from './officialPinnedItems';
|
||||
|
||||
api.achievements = achievements;
|
||||
|
||||
api.quests = quests;
|
||||
|
|
@ -48,6 +49,8 @@ api.subscriptionBlocks = subscriptionBlocks;
|
|||
api.mystery = timeTravelers.mystery;
|
||||
api.timeTravelerStore = timeTravelers.timeTravelerStore;
|
||||
|
||||
api.officialPinnedItems = officialPinnedItems;
|
||||
|
||||
/*
|
||||
---------------------------------------------------------------
|
||||
Discounted Item Bundles
|
||||
|
|
@ -112,8 +115,8 @@ api.armoire = {
|
|||
},
|
||||
value: 100,
|
||||
key: 'armoire',
|
||||
canOwn (u) {
|
||||
return includes(u.achievements.ultimateGearSets, true);
|
||||
canOwn () {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ let mysterySets = {
|
|||
each(mysterySets, (value, key) => {
|
||||
value.key = key;
|
||||
value.text = t(`mysterySet${key}`);
|
||||
value.class = `shop_set_mystery_${key}`;
|
||||
});
|
||||
|
||||
module.exports = mysterySets;
|
||||
|
|
|
|||
1
website/common/script/content/officialPinnedItems.js
Normal file
1
website/common/script/content/officialPinnedItems.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export default [];
|
||||
|
|
@ -64,6 +64,9 @@ api.preenTodos = preenTodos;
|
|||
import updateStore from './libs/updateStore';
|
||||
api.updateStore = updateStore;
|
||||
|
||||
import inAppRewards from './libs/inAppRewards';
|
||||
api.inAppRewards = inAppRewards;
|
||||
|
||||
import uuid from './libs/uuid';
|
||||
api.uuid = uuid;
|
||||
|
||||
|
|
@ -161,6 +164,7 @@ import deletePM from './ops/deletePM';
|
|||
import reroll from './ops/reroll';
|
||||
import reset from './ops/reset';
|
||||
import markPmsRead from './ops/markPMSRead';
|
||||
import pinnedGearUtils from './ops/pinnedGearUtils';
|
||||
|
||||
api.ops = {
|
||||
scoreTask,
|
||||
|
|
@ -198,6 +202,7 @@ api.ops = {
|
|||
reroll,
|
||||
reset,
|
||||
markPmsRead,
|
||||
pinnedGearUtils,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
247
website/common/script/libs/getItemInfo.js
Normal file
247
website/common/script/libs/getItemInfo.js
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
import i18n from '../i18n';
|
||||
import content from '../content/index';
|
||||
import { BadRequest } from './errors';
|
||||
import count from '../count';
|
||||
|
||||
function lockQuest (quest, user) {
|
||||
if (quest.lvl && user.stats.lvl < quest.lvl) return true;
|
||||
if (quest.unlockCondition && (quest.key === 'moon1' || quest.key === 'moon2' || quest.key === 'moon3')) {
|
||||
return user.loginIncentives < quest.unlockCondition.incentiveThreshold;
|
||||
}
|
||||
if (user.achievements.quests) return quest.previous && !user.achievements.quests[quest.previous];
|
||||
return quest.previous;
|
||||
}
|
||||
|
||||
const officialPinnedItems = content.officialPinnedItems;
|
||||
|
||||
function isItemSuggested (itemInfo) {
|
||||
return officialPinnedItems.findIndex(officialItem => {
|
||||
return officialItem.type === itemInfo.pinType && officialItem.path === itemInfo.path;
|
||||
}) > -1;
|
||||
}
|
||||
|
||||
function getDefaultGearProps (item, language) {
|
||||
return {
|
||||
key: item.key,
|
||||
text: item.text(language),
|
||||
notes: item.notes(language),
|
||||
type: item.type,
|
||||
specialClass: item.specialClass,
|
||||
locked: false,
|
||||
purchaseType: 'gear',
|
||||
class: `shop_${item.key}`,
|
||||
path: `gear.flat.${item.key}`,
|
||||
str: item.str,
|
||||
int: item.int,
|
||||
per: item.per,
|
||||
con: item.con,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = function getItemInfo (user, type, item, language = 'en') {
|
||||
let itemInfo;
|
||||
|
||||
switch (type) {
|
||||
case 'egg':
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: i18n.t('egg', {eggType: item.text(language)}, language),
|
||||
notes: item.notes(language),
|
||||
value: item.value,
|
||||
class: `Pet_Egg_${item.key}`,
|
||||
locked: false,
|
||||
currency: 'gems',
|
||||
purchaseType: 'eggs',
|
||||
path: `eggs.${item.key}`,
|
||||
pinType: 'egg',
|
||||
};
|
||||
break;
|
||||
case 'hatchingPotion':
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: i18n.t('potion', {potionType: item.text(language)}),
|
||||
notes: item.notes(language),
|
||||
class: `Pet_HatchingPotion_${item.key}`,
|
||||
value: item.value,
|
||||
locked: false,
|
||||
currency: 'gems',
|
||||
purchaseType: 'hatchingPotions',
|
||||
path: `hatchingPotions.${item.key}`,
|
||||
pinType: 'hatchingPotion',
|
||||
};
|
||||
break;
|
||||
case 'premiumHatchingPotion':
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: i18n.t('potion', {potionType: item.text(language)}),
|
||||
notes: `${item.notes(language)} ${item._addlNotes(language)}`,
|
||||
class: `Pet_HatchingPotion_${item.key}`,
|
||||
value: item.value,
|
||||
locked: false,
|
||||
currency: 'gems',
|
||||
purchaseType: 'hatchingPotions',
|
||||
path: `premiumHatchingPotions.${item.key}`,
|
||||
pinType: 'premiumHatchingPotion',
|
||||
};
|
||||
break;
|
||||
case 'food':
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: item.text(language),
|
||||
notes: item.notes(language),
|
||||
class: `Pet_Food_${item.key}`,
|
||||
value: item.value,
|
||||
locked: false,
|
||||
currency: 'gems',
|
||||
purchaseType: 'food',
|
||||
path: `food.${item.key}`,
|
||||
pinType: 'food',
|
||||
};
|
||||
break;
|
||||
case 'questBundle':
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: item.text(language),
|
||||
notes: item.notes(language),
|
||||
value: item.value,
|
||||
currency: 'gems',
|
||||
class: `quest_bundle_${item.key}`,
|
||||
purchaseType: 'bundles',
|
||||
path: `bundles.${item.key}`,
|
||||
pinType: 'questBundle',
|
||||
};
|
||||
break;
|
||||
case 'quest': // eslint-disable-line no-case-declarations
|
||||
const locked = lockQuest(item, user);
|
||||
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: item.text(language),
|
||||
notes: item.notes(language),
|
||||
group: item.group,
|
||||
value: item.goldValue ? item.goldValue : item.value,
|
||||
currency: item.goldValue ? 'gold' : 'gems',
|
||||
locked,
|
||||
unlockCondition: item.unlockCondition,
|
||||
drop: item.drop,
|
||||
boss: item.boss,
|
||||
collect: item.collect,
|
||||
lvl: item.lvl,
|
||||
class: locked ? `inventory_quest_scroll_locked inventory_quest_scroll_${item.key}_locked` : `inventory_quest_scroll inventory_quest_scroll_${item.key}`,
|
||||
purchaseType: 'quests',
|
||||
path: `quests.${item.key}`,
|
||||
pinType: 'quest',
|
||||
};
|
||||
break;
|
||||
case 'timeTravelers':
|
||||
// TODO
|
||||
itemInfo = {};
|
||||
break;
|
||||
case 'seasonalSpell':
|
||||
itemInfo = {
|
||||
key: item.keyspellKey,
|
||||
text: item.text(language),
|
||||
notes: item.notes(language),
|
||||
value: item.value,
|
||||
type: 'special',
|
||||
currency: 'gold',
|
||||
locked: false,
|
||||
purchaseType: 'spells',
|
||||
class: `inventory_special_${item.key}`,
|
||||
path: `spells.special.${item.key}`,
|
||||
pinType: 'seasonalSpell',
|
||||
};
|
||||
break;
|
||||
case 'seasonalQuest':
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: item.text(language),
|
||||
notes: item.notes(language),
|
||||
value: item.value,
|
||||
type: 'quests',
|
||||
currency: 'gems',
|
||||
locked: false,
|
||||
drop: item.drop,
|
||||
boss: item.boss,
|
||||
collect: item.collect,
|
||||
class: `inventory_quest_scroll_${item.key}`,
|
||||
purchaseType: 'quests',
|
||||
path: `quests.${item.key}`,
|
||||
pinType: 'seasonalQuest',
|
||||
};
|
||||
break;
|
||||
case 'gear':
|
||||
// spread operator not available
|
||||
itemInfo = Object.assign(getDefaultGearProps(item, language), {
|
||||
value: item.twoHanded ? 2 : 1,
|
||||
currency: 'gems',
|
||||
pinType: 'gear',
|
||||
});
|
||||
break;
|
||||
case 'marketGear':
|
||||
itemInfo = Object.assign(getDefaultGearProps(item, language), {
|
||||
value: item.value,
|
||||
currency: 'gold',
|
||||
pinType: 'marketGear',
|
||||
});
|
||||
break;
|
||||
case 'background':
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: item.text(language),
|
||||
notes: item.notes(language),
|
||||
class: `icon_background_${item.key}`,
|
||||
value: item.price,
|
||||
currency: item.currency || 'gems',
|
||||
purchaseType: 'backgrounds',
|
||||
path: `backgrounds.${item.set}.${item.key}`,
|
||||
pinType: 'background',
|
||||
};
|
||||
break;
|
||||
case 'mystery_set':
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: item.text(language),
|
||||
value: 1,
|
||||
currency: 'hourglasses',
|
||||
purchaseType: 'mystery_set',
|
||||
class: `shop_set_mystery_${item.key}`,
|
||||
path: `mystery.${item.key}`,
|
||||
pinType: 'mystery_set',
|
||||
};
|
||||
break;
|
||||
case 'potion':
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: item.text(language),
|
||||
notes: item.notes(language),
|
||||
value: item.value,
|
||||
currency: 'gold',
|
||||
purchaseType: 'potions',
|
||||
class: `shop_${item.key}`,
|
||||
path: 'potion',
|
||||
pinType: 'potion',
|
||||
};
|
||||
break;
|
||||
case 'armoire':
|
||||
itemInfo = {
|
||||
key: item.key,
|
||||
text: item.text(language),
|
||||
notes: item.notes(user, count.remainingGearInSet(user.items.gear.owned, 'armoire')), // TODO count
|
||||
value: item.value,
|
||||
currency: 'gold',
|
||||
purchaseType: 'armoire',
|
||||
class: `shop_${item.key}`,
|
||||
path: 'armoire',
|
||||
pinType: 'armoire',
|
||||
};
|
||||
}
|
||||
|
||||
if (itemInfo) {
|
||||
itemInfo.isSuggested = isItemSuggested(itemInfo);
|
||||
} else {
|
||||
throw new BadRequest(i18n.t('wrongItemType', {type}, language));
|
||||
}
|
||||
|
||||
return itemInfo;
|
||||
};
|
||||
18
website/common/script/libs/inAppRewards.js
Normal file
18
website/common/script/libs/inAppRewards.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import content from '../content/index';
|
||||
import get from 'lodash/get';
|
||||
import getItemInfo from './getItemInfo';
|
||||
|
||||
const officialPinnedItems = content.officialPinnedItems;
|
||||
|
||||
module.exports = function getPinnedItems (user) {
|
||||
const officialPinnedItemsNotUnpinned = officialPinnedItems.filter(officialPin => {
|
||||
const isUnpinned = user.unpinnedItems.findIndex(unpinned => unpinned.path === officialPin.path) > -1;
|
||||
return !isUnpinned;
|
||||
});
|
||||
|
||||
const pinnedItems = officialPinnedItemsNotUnpinned.concat(user.pinnedItems);
|
||||
|
||||
return pinnedItems.map(({type, path}) => {
|
||||
return getItemInfo(user, type, get(content, path));
|
||||
});
|
||||
};
|
||||
|
|
@ -8,18 +8,10 @@ import pickBy from 'lodash/pickBy';
|
|||
import sortBy from 'lodash/sortBy';
|
||||
import content from '../content/index';
|
||||
import i18n from '../i18n';
|
||||
import getItemInfo from './getItemInfo';
|
||||
|
||||
let shops = {};
|
||||
|
||||
function lockQuest (quest, user) {
|
||||
if (quest.lvl && user.stats.lvl < quest.lvl) return true;
|
||||
if (quest.unlockCondition && (quest.key === 'moon1' || quest.key === 'moon2' || quest.key === 'moon3')) {
|
||||
return user.loginIncentives < quest.unlockCondition.incentiveThreshold;
|
||||
}
|
||||
if (user.achievements.quests) return quest.previous && !user.achievements.quests[quest.previous];
|
||||
return quest.previous;
|
||||
}
|
||||
|
||||
shops.getMarketCategories = function getMarket (user, language) {
|
||||
let categories = [];
|
||||
let eggsCategory = {
|
||||
|
|
@ -32,16 +24,7 @@ shops.getMarketCategories = function getMarket (user, language) {
|
|||
.filter(egg => egg.canBuy(user))
|
||||
.concat(values(content.dropEggs))
|
||||
.map(egg => {
|
||||
return {
|
||||
key: egg.key,
|
||||
text: i18n.t('egg', {eggType: egg.text()}, language),
|
||||
notes: egg.notes(language),
|
||||
value: egg.value,
|
||||
class: `Pet_Egg_${egg.key}`,
|
||||
locked: false,
|
||||
currency: 'gems',
|
||||
purchaseType: 'eggs',
|
||||
};
|
||||
return getItemInfo(user, 'egg', egg, language);
|
||||
}), 'key');
|
||||
categories.push(eggsCategory);
|
||||
|
||||
|
|
@ -53,16 +36,7 @@ shops.getMarketCategories = function getMarket (user, language) {
|
|||
hatchingPotionsCategory.items = sortBy(values(content.hatchingPotions)
|
||||
.filter(hp => !hp.limited)
|
||||
.map(hatchingPotion => {
|
||||
return {
|
||||
key: hatchingPotion.key,
|
||||
text: i18n.t('potion', {potionType: hatchingPotion.text(language)}),
|
||||
notes: hatchingPotion.notes(language),
|
||||
class: `Pet_HatchingPotion_${hatchingPotion.key}`,
|
||||
value: hatchingPotion.value,
|
||||
locked: false,
|
||||
currency: 'gems',
|
||||
purchaseType: 'hatchingPotions',
|
||||
};
|
||||
return getItemInfo(user, 'hatchingPotion', hatchingPotion, language);
|
||||
}), 'key');
|
||||
categories.push(hatchingPotionsCategory);
|
||||
|
||||
|
|
@ -74,16 +48,7 @@ shops.getMarketCategories = function getMarket (user, language) {
|
|||
premiumHatchingPotionsCategory.items = sortBy(values(content.hatchingPotions)
|
||||
.filter(hp => hp.limited && hp.canBuy())
|
||||
.map(premiumHatchingPotion => {
|
||||
return {
|
||||
key: premiumHatchingPotion.key,
|
||||
text: i18n.t('potion', {potionType: premiumHatchingPotion.text(language)}),
|
||||
notes: `${premiumHatchingPotion.notes(language)} ${premiumHatchingPotion._addlNotes(language)}`,
|
||||
class: `Pet_HatchingPotion_${premiumHatchingPotion.key}`,
|
||||
value: premiumHatchingPotion.value,
|
||||
locked: false,
|
||||
currency: 'gems',
|
||||
purchaseType: 'hatchingPotions',
|
||||
};
|
||||
return getItemInfo(user, 'premiumHatchingPotion', premiumHatchingPotion, language);
|
||||
}), 'key');
|
||||
if (premiumHatchingPotionsCategory.items.length > 0) {
|
||||
categories.push(premiumHatchingPotionsCategory);
|
||||
|
|
@ -97,16 +62,7 @@ shops.getMarketCategories = function getMarket (user, language) {
|
|||
foodCategory.items = sortBy(values(content.food)
|
||||
.filter(food => food.canDrop || food.key === 'Saddle')
|
||||
.map(foodItem => {
|
||||
return {
|
||||
key: foodItem.key,
|
||||
text: foodItem.text(language),
|
||||
notes: foodItem.notes(language),
|
||||
class: `Pet_Food_${foodItem.key}`,
|
||||
value: foodItem.value,
|
||||
locked: false,
|
||||
currency: 'gems',
|
||||
purchaseType: 'food',
|
||||
};
|
||||
return getItemInfo(user, 'food', foodItem, language);
|
||||
}), 'key');
|
||||
categories.push(foodCategory);
|
||||
|
||||
|
|
@ -178,15 +134,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
|
|||
bundleCategory.items = sortBy(values(content.bundles)
|
||||
.filter(bundle => bundle.type === 'quests' && bundle.canBuy())
|
||||
.map(bundle => {
|
||||
return {
|
||||
key: bundle.key,
|
||||
text: bundle.text(language),
|
||||
notes: bundle.notes(language),
|
||||
value: bundle.value,
|
||||
currency: 'gems',
|
||||
class: `quest_bundle_${bundle.key}`,
|
||||
purchaseType: 'bundles',
|
||||
};
|
||||
return getItemInfo(user, 'questBundle', bundle, language);
|
||||
}));
|
||||
|
||||
if (bundleCategory.items.length > 0) {
|
||||
|
|
@ -202,23 +150,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
|
|||
category.items = content.questsByLevel
|
||||
.filter(quest => quest.canBuy(user) && quest.category === type)
|
||||
.map(quest => {
|
||||
let locked = lockQuest(quest, user);
|
||||
return {
|
||||
key: quest.key,
|
||||
text: quest.text(language),
|
||||
notes: quest.notes(language),
|
||||
group: quest.group,
|
||||
value: quest.goldValue ? quest.goldValue : quest.value,
|
||||
currency: quest.goldValue ? 'gold' : 'gems',
|
||||
locked,
|
||||
unlockCondition: quest.unlockCondition,
|
||||
drop: quest.drop,
|
||||
boss: quest.boss,
|
||||
collect: quest.collect,
|
||||
lvl: quest.lvl,
|
||||
class: locked ? `inventory_quest_scroll_locked inventory_quest_scroll_${quest.key}_locked` : `inventory_quest_scroll inventory_quest_scroll_${quest.key}`,
|
||||
purchaseType: 'quests',
|
||||
};
|
||||
return getItemInfo(user, 'quest', quest, language);
|
||||
});
|
||||
|
||||
categories.push(category);
|
||||
|
|
@ -251,6 +183,7 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la
|
|||
notes: '',
|
||||
locked: false,
|
||||
currency: 'hourglasses',
|
||||
pinType: 'IGNORE',
|
||||
};
|
||||
category.items.push(item);
|
||||
}
|
||||
|
|
@ -269,6 +202,8 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la
|
|||
let category = {
|
||||
identifier: set.key,
|
||||
text: set.text(language),
|
||||
path: `mystery.${set.key}`,
|
||||
pinType: 'mystery_set',
|
||||
purchaseAll: true,
|
||||
};
|
||||
|
||||
|
|
@ -283,6 +218,7 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la
|
|||
locked: false,
|
||||
currency: 'hourglasses',
|
||||
class: `shop_${item.key}`,
|
||||
pinKey: `timeTravelers!gear.flat.${item.key}`,
|
||||
};
|
||||
});
|
||||
if (category.items.length > 0) {
|
||||
|
|
@ -322,18 +258,8 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
|
|||
text: i18n.t('seasonalItems', language),
|
||||
};
|
||||
|
||||
category.items = map(spells, (spell, key) => {
|
||||
return {
|
||||
key,
|
||||
text: spell.text(language),
|
||||
notes: spell.notes(language),
|
||||
value: spell.value,
|
||||
type: 'special',
|
||||
currency: 'gold',
|
||||
locked: false,
|
||||
purchaseType: 'spells',
|
||||
class: `inventory_special_${key}`,
|
||||
};
|
||||
category.items = map(spells, (spell) => {
|
||||
return getItemInfo(user, 'seasonalSpell', spell, language);
|
||||
});
|
||||
|
||||
categories.push(category);
|
||||
|
|
@ -349,21 +275,8 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
|
|||
text: i18n.t('quests', language),
|
||||
};
|
||||
|
||||
category.items = map(quests, (quest, key) => {
|
||||
return {
|
||||
key,
|
||||
text: quest.text(language),
|
||||
notes: quest.notes(language),
|
||||
value: quest.value,
|
||||
type: 'quests',
|
||||
currency: 'gems',
|
||||
locked: false,
|
||||
drop: quest.drop,
|
||||
boss: quest.boss,
|
||||
collect: quest.collect,
|
||||
class: `inventory_quest_scroll_${key}`,
|
||||
purchaseType: 'quests',
|
||||
};
|
||||
category.items = map(quests, (quest) => {
|
||||
return getItemInfo(user, 'seasonalQuest', quest, language);
|
||||
});
|
||||
|
||||
categories.push(category);
|
||||
|
|
@ -379,18 +292,7 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
|
|||
category.items = flatGearArray.filter((gear) => {
|
||||
return user.items.gear.owned[gear.key] === undefined && gear.index === key;
|
||||
}).map(gear => {
|
||||
return {
|
||||
key: gear.key,
|
||||
text: gear.text(language),
|
||||
notes: gear.notes(language),
|
||||
value: gear.twoHanded ? 2 : 1,
|
||||
type: gear.type,
|
||||
specialClass: gear.specialClass,
|
||||
locked: false,
|
||||
currency: 'gems',
|
||||
purchaseType: 'gear',
|
||||
class: `shop_${gear.key}`,
|
||||
};
|
||||
return getItemInfo(null, 'gear', gear, language);
|
||||
});
|
||||
|
||||
if (category.items.length > 0) {
|
||||
|
|
@ -412,15 +314,8 @@ shops.getBackgroundShopSets = function getBackgroundShopSets (language) {
|
|||
text: i18n.t(key, language),
|
||||
};
|
||||
|
||||
set.items = map(group, (background, bgKey) => {
|
||||
return {
|
||||
key: bgKey,
|
||||
text: background.text(language),
|
||||
notes: background.notes(language),
|
||||
value: background.price,
|
||||
currency: background.currency || 'gems',
|
||||
purchaseType: 'backgrounds',
|
||||
};
|
||||
set.items = map(group, (background) => {
|
||||
return getItemInfo(null, 'background', background, language);
|
||||
});
|
||||
|
||||
sets.push(set);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import {
|
|||
import handleTwoHanded from '../fns/handleTwoHanded';
|
||||
import ultimateGear from '../fns/ultimateGear';
|
||||
|
||||
import { removePinnedGearAddPossibleNewOnes } from './pinnedGearUtils';
|
||||
|
||||
module.exports = function buyGear (user, req = {}, analytics) {
|
||||
let key = get(req, 'params.key');
|
||||
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
|
||||
|
|
@ -38,6 +40,7 @@ module.exports = function buyGear (user, req = {}, analytics) {
|
|||
message = handleTwoHanded(user, item, undefined, req);
|
||||
}
|
||||
|
||||
removePinnedGearAddPossibleNewOnes(user, `gear.flat.${item.key}`);
|
||||
user.items.gear.owned[item.key] = true;
|
||||
|
||||
if (item.last) ultimateGear(user);
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@ import {
|
|||
NotAuthorized,
|
||||
BadRequest,
|
||||
} from '../libs/errors';
|
||||
import { removePinnedGearByClass, addPinnedGearByClass } from './pinnedGearUtils';
|
||||
|
||||
function resetClass (user, req = {}) {
|
||||
removePinnedGearByClass(user);
|
||||
|
||||
if (user.preferences.disableClasses) {
|
||||
user.preferences.disableClasses = false;
|
||||
user.preferences.autoAllocate = false;
|
||||
|
|
@ -41,6 +44,8 @@ module.exports = function changeClass (user, req = {}, analytics) {
|
|||
user.stats.class = klass;
|
||||
user.flags.classSelected = true;
|
||||
|
||||
addPinnedGearByClass(user);
|
||||
|
||||
user.items.gear.owned[`weapon_${klass}_0`] = true;
|
||||
if (klass === 'rogue') user.items.gear.owned[`shield_${klass}_0`] = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import readCard from './readCard';
|
|||
import openMysteryItem from './openMysteryItem';
|
||||
import scoreTask from './scoreTask';
|
||||
import markPmsRead from './markPMSRead';
|
||||
import * as pinnedGearUtils from './pinnedGearUtils';
|
||||
|
||||
module.exports = {
|
||||
sleep,
|
||||
|
|
@ -84,4 +85,5 @@ module.exports = {
|
|||
openMysteryItem,
|
||||
scoreTask,
|
||||
markPmsRead,
|
||||
pinnedGearUtils,
|
||||
};
|
||||
|
|
|
|||
119
website/common/script/ops/pinnedGearUtils.js
Normal file
119
website/common/script/ops/pinnedGearUtils.js
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import content from '../content/index';
|
||||
import getItemInfo from '../libs/getItemInfo';
|
||||
import get from 'lodash/get';
|
||||
import { BadRequest } from '../libs/errors';
|
||||
import i18n from '../i18n';
|
||||
|
||||
const officialPinnedItems = content.officialPinnedItems;
|
||||
|
||||
import updateStore from '../libs/updateStore';
|
||||
|
||||
function addPinnedGearByClass (user) {
|
||||
if (user.flags.classSelected) {
|
||||
let newPinnedItems = updateStore(user);
|
||||
|
||||
for (let item of newPinnedItems) {
|
||||
let itemInfo = getItemInfo(user, 'marketGear', item);
|
||||
|
||||
user.pinnedItems.push({
|
||||
type: 'marketGear',
|
||||
path: itemInfo.path,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeItemByPath (user, path) {
|
||||
const foundIndex = user.pinnedItems.findIndex(pinnedItem => {
|
||||
return pinnedItem.path === path;
|
||||
});
|
||||
|
||||
if (foundIndex >= 0) {
|
||||
user.pinnedItems.splice(foundIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function removePinnedGearByClass (user) {
|
||||
if (user.flags.classSelected) {
|
||||
let currentPinnedItems = updateStore(user);
|
||||
|
||||
for (let item of currentPinnedItems) {
|
||||
let itemInfo = getItemInfo(user, 'marketGear', item);
|
||||
|
||||
removeItemByPath(user, itemInfo.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removePinnedGearAddPossibleNewOnes (user, itemPath) {
|
||||
let currentPinnedItems = updateStore(user);
|
||||
let removeAndAddAllItems = false;
|
||||
|
||||
for (let item of currentPinnedItems) {
|
||||
let itemInfo = getItemInfo(user, 'marketGear', item);
|
||||
|
||||
if (itemInfo.path === itemPath) {
|
||||
removeAndAddAllItems = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
removeItemByPath(user, itemPath);
|
||||
|
||||
if (removeAndAddAllItems) {
|
||||
// an item of the users current "new" gear was bought
|
||||
// remove the old pinned gear items and add the new gear back
|
||||
removePinnedGearByClass(user);
|
||||
addPinnedGearByClass(user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} TRUE added the item / FALSE removed it
|
||||
*/
|
||||
function togglePinnedItem (user, {item, type, path}, req = {}) {
|
||||
let arrayToChange;
|
||||
|
||||
if (!path) { // If path isn't passed it means an item was passed
|
||||
path = getItemInfo(user, type, item, req.language).path;
|
||||
}
|
||||
|
||||
if (!item) item = get(content, path);
|
||||
|
||||
if (path === 'armoire' || path === 'potion') {
|
||||
throw new BadRequest(i18n.t('cannotUpinArmoirPotion', req.language));
|
||||
}
|
||||
|
||||
let isOfficialPinned = officialPinnedItems.find(officialPinnedItem => {
|
||||
return officialPinnedItem.path === path;
|
||||
}) !== undefined;
|
||||
|
||||
if (isOfficialPinned) {
|
||||
arrayToChange = user.unpinnedItems;
|
||||
} else {
|
||||
arrayToChange = user.pinnedItems;
|
||||
}
|
||||
|
||||
const foundIndex = arrayToChange.findIndex(pinnedItem => {
|
||||
return pinnedItem.path === path;
|
||||
});
|
||||
|
||||
if (foundIndex >= 0) {
|
||||
arrayToChange.splice(foundIndex, 1);
|
||||
return isOfficialPinned;
|
||||
} else {
|
||||
arrayToChange.push({path, type});
|
||||
return !isOfficialPinned;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addPinnedGearByClass,
|
||||
removePinnedGearByClass,
|
||||
removePinnedGearAddPossibleNewOnes,
|
||||
togglePinnedItem,
|
||||
removeItemByPath,
|
||||
};
|
||||
|
|
@ -134,6 +134,49 @@ api.getBuyList = {
|
|||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {get} /api/v3/user/in-app-rewards Get the in app items appaearing in the user's reward column
|
||||
* @apiName UserGetInAppRewards
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "success": true,
|
||||
* "data": [
|
||||
* {
|
||||
* "key":"weapon_armoire_battleAxe",
|
||||
* "text":"Battle Axe",
|
||||
* "notes":"This fine iron axe is well-suited to battling your fiercest foes or your most difficult tasks. Increases Intelligence by 6 and Constitution by 8. Enchanted Armoire: Independent Item.",
|
||||
* "value":1,
|
||||
* "type":"weapon",
|
||||
* "locked":false,
|
||||
* "currency":"gems",
|
||||
* "purchaseType":"gear",
|
||||
* "class":"shop_weapon_armoire_battleAxe",
|
||||
* "path":"gear.flat.weapon_armoire_battleAxe",
|
||||
* "pinType":"gear"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
api.getInAppRewardsList = {
|
||||
method: 'GET',
|
||||
middlewares: [authWithHeaders()],
|
||||
url: '/user/in-app-rewards',
|
||||
async handler (req, res) {
|
||||
let list = common.inAppRewards(res.locals.user);
|
||||
|
||||
// return text and notes strings
|
||||
_.each(list, item => {
|
||||
_.each(item, (itemPropVal, itemPropKey) => {
|
||||
if (_.isFunction(itemPropVal) && itemPropVal.i18nLangFunc) item[itemPropKey] = itemPropVal(req.language);
|
||||
});
|
||||
});
|
||||
|
||||
res.respond(200, list);
|
||||
},
|
||||
};
|
||||
|
||||
let updatablePaths = [
|
||||
'_ABtests.counter',
|
||||
|
||||
|
|
@ -1958,4 +2001,45 @@ api.setCustomDayStart = {
|
|||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {get} /user/toggle-pinned-item/:key Toggle an item to be pinned
|
||||
* @apiName togglePinnedItem
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiSuccess {Object} data Pinned items array
|
||||
*
|
||||
* @apiSuccessExample {json} Result:
|
||||
* {
|
||||
* "success": true,
|
||||
* "data": {
|
||||
* "pinnedItems": [
|
||||
* "type": "gear",
|
||||
* "path": "gear.flat.weapon_1"
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
api.togglePinnedItem = {
|
||||
method: 'GET',
|
||||
middlewares: [authWithHeaders()],
|
||||
url: '/user/toggle-pinned-item/:type/:path',
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
const path = get(req.params, 'path');
|
||||
const type = get(req.params, 'type');
|
||||
|
||||
common.ops.pinnedGearUtils.togglePinnedItem(user, {type, path}, req);
|
||||
|
||||
await user.save();
|
||||
|
||||
let userJson = user.toJSON();
|
||||
|
||||
res.respond(200, {
|
||||
pinnedItems: userJson.pinnedItems,
|
||||
unpinnedItems: userJson.unpinnedItems,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = api;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import {
|
||||
findIndex,
|
||||
isPlainObject,
|
||||
} from 'lodash';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
|
||||
export function removeFromArray (array, element) {
|
||||
let elementIndex;
|
||||
|
|
|
|||
|
|
@ -90,6 +90,23 @@ function _populateDefaultTasks (user, taskTypes) {
|
|||
});
|
||||
}
|
||||
|
||||
function pinBaseItems (user) {
|
||||
const itemsPaths = [
|
||||
'weapon_warrior_0', 'armor_warrior_1',
|
||||
'shield_warrior_1', 'head_warrior_1',
|
||||
];
|
||||
|
||||
itemsPaths.map(p => user.pinnedItems.push({
|
||||
type: 'marketGear',
|
||||
path: `gear.flat.${p}`,
|
||||
}));
|
||||
|
||||
user.pinnedItems.push(
|
||||
{type: 'potion', path: 'potion'},
|
||||
{type: 'armoire', path: 'armoire'},
|
||||
);
|
||||
}
|
||||
|
||||
function _setUpNewUser (user) {
|
||||
let taskTypes;
|
||||
let iterableFlags = user.flags.toObject();
|
||||
|
|
@ -137,6 +154,7 @@ function _setUpNewUser (user) {
|
|||
}
|
||||
}
|
||||
|
||||
pinBaseItems(user);
|
||||
return _populateDefaultTasks(user, taskTypes);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -581,6 +581,17 @@ let schema = new Schema({
|
|||
webhooks: [WebhookSchema],
|
||||
loginIncentives: {type: Number, default: 0},
|
||||
invitesSent: {type: Number, default: 0},
|
||||
|
||||
// Items manually pinned by the user
|
||||
pinnedItems: [{
|
||||
path: {type: String},
|
||||
type: {type: String},
|
||||
}],
|
||||
// Items the user manually unpinned from the ones suggested by Habitica
|
||||
unpinnedItems: [{
|
||||
path: {type: String},
|
||||
type: {type: String},
|
||||
}],
|
||||
}, {
|
||||
strict: true,
|
||||
minimize: false, // So empty objects are returned
|
||||
|
|
|
|||
Loading…
Reference in a new issue