new client - quest / seasonal / time travelers shops (#8903)
* initial quests.vue - refactorings - add group to quests * shows quests by quest-group * buyQuestModal with rewards sidebar * store / actions to load seasonal/time-travelers shop data * buyModal buyPressed instead of buyAction - seasonal shop categories now with specialClass property - seasonal shop * time travelers vue - show hourglass in shopItem / buyDialog - fix banners * cleanup * show amount of already owned quests * show html notes in popovers / dialog * extract purchase-api to common.ops.purchaseWithSpell to call the same in the store / update the UI on purchases * add time-travelers sprites * fix lint * add last mystery set images * remove unused Page * remove equipment from newClient.json
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
@@ -166,3 +166,7 @@ $bg-disabled-control: #34303a;
|
||||
border-color: $gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-switch-container.no-margin {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
@@ -22,15 +22,18 @@
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.text {
|
||||
height: 60px;
|
||||
min-height: 60px;
|
||||
font-family: Roboto;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
text-align: center;
|
||||
color: $gray-100;
|
||||
overflow-y: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
10
website/client/assets/svg/hourglass.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="24" viewBox="0 0 25 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#A9DCF6" fill-opacity=".8" d="M5.105 18.122v2.555c0 .3.134.586.377.763 2.647 1.935 9.952 1.935 12.6 0a.941.941 0 0 0 .376-.763v-2.555c0-2.331-1.196-4.568-3.007-5.9a.957.957 0 0 1 0-1.553c1.81-1.333 3.007-3.57 3.007-5.9V2.213c0-.3-.134-.586-.375-.761-2.649-1.936-9.954-1.936-12.601 0a.935.935 0 0 0-.377.761V4.77c0 2.33 1.196 4.567 3.008 5.9a.957.957 0 0 1 0 1.552c-1.812 1.333-3.008 3.57-3.008 5.9"/>
|
||||
<path fill="#FFF" fill-opacity=".9" d="M11.782 1.907c2.169 0 3.857.394 4.768.844v2.017c0 1.68-.854 3.353-2.23 4.365a2.883 2.883 0 0 0-1.174 2.312c0 .907.438 1.771 1.173 2.312 1.377 1.013 2.231 2.685 2.231 4.364v2.019c-.91.45-2.6.843-4.768.843-2.17 0-3.858-.394-4.769-.843V18.12c0-1.679.854-3.351 2.23-4.363a2.883 2.883 0 0 0 1.174-2.313c0-.907-.438-1.77-1.173-2.312-1.377-1.012-2.23-2.685-2.23-4.365V2.751c.91-.45 2.599-.844 4.768-.844"/>
|
||||
<path fill="#9A62FF" d="M12 3.34c-1.626 0-3.35.151-3.367.554-.015.325-.007.955 1.314 1.624 1.283.652 1.524 1.843 2.222 1.843.81 0 .857-.685 1.846-1.366 1.02-.701 1.183-1.649 1.171-2.024-.016-.522-1.558-.63-3.185-.63"/>
|
||||
<path fill="#4F2A93" d="M14.016 5.995c1.019-.701 1.182-1.648 1.17-2.024-.012-.413-.982-.567-2.194-.614.34.175.781.542.672 1.266-.112.754-1.3 1.588-1.107 2.254.036.13.1.233.179.318.355-.244.586-.723 1.28-1.2"/>
|
||||
<path fill="#9A62FF" d="M11.782 13.181c-.495 0-.623.903-1.342 1.464-.881.687-1.719 1.534-1.949 2.751-.19 1.003-.018 1.128.212 1.2.229.07 1.992.37 3.72 0 1.73-.37 2.69-1.06 2.523-1.764-.16-.67-.644-1.243-1.535-1.905-.852-.633-1.134-1.746-1.63-1.746"/>
|
||||
<path fill="#4F2A93" d="M8.703 18.595c.23.071 1.993.37 3.721 0 1.374-.294 2.262-.789 2.485-1.335a.138.138 0 0 0-.007-.033c-.097-.368-1.543-.59-3.342-.298-1.714.277-3.102.843-3.096 1.48.053.116.14.156.239.186"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -1,7 +1,7 @@
|
||||
<template lang="pug">
|
||||
#app-header.row(:class='{sticky: user.preferences.stickyHeader}', v-if='showHeader')
|
||||
create-party-modal
|
||||
members-modal(:group='user.party', :hide-badge="true")
|
||||
members-modal(v-if="user.party._id", :group='user.party', :hide-badge="true")
|
||||
member-details(:member="user", @click="$router.push({name: 'avatar'})")
|
||||
.view-party(v-if="user.party && user.party._id")
|
||||
// TODO button should open the party members modal
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
div.form-group.clearfix
|
||||
h3.float-left Hide Missing
|
||||
toggle-switch.float-right.hideMissing(
|
||||
toggle-switch.float-right.no-margin(
|
||||
:label="''",
|
||||
:checked="hideMissing",
|
||||
@change="updateHideMissing"
|
||||
@@ -265,10 +265,6 @@
|
||||
top: -28px;
|
||||
}
|
||||
|
||||
.toggle-switch-container.hideMissing {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.hatchablePopover {
|
||||
width: 180px
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template lang="pug">
|
||||
div
|
||||
.svg-icon(v-if="withHourglass",v-html="icons.hourglasses")
|
||||
span(v-if="withHourglass") {{userHourglasses | roundBigNumber}}
|
||||
|
||||
.svg-icon(v-html="icons.gem")
|
||||
span {{userGems | roundBigNumber}}
|
||||
.svg-icon(v-html="icons.gold")
|
||||
@@ -32,15 +35,17 @@ span {
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'client/libs/store';
|
||||
import gemIcon from 'assets/svg/gem.svg';
|
||||
import goldIcon from 'assets/svg/gold.svg';
|
||||
import svgGem from 'assets/svg/gem.svg';
|
||||
import svgGold from 'assets/svg/gold.svg';
|
||||
import svgHourglasses from 'assets/svg/hourglass.svg';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
gem: gemIcon,
|
||||
gold: goldIcon,
|
||||
gem: svgGem,
|
||||
gold: svgGold,
|
||||
hourglasses: svgHourglasses,
|
||||
}),
|
||||
};
|
||||
},
|
||||
@@ -49,8 +54,14 @@ span {
|
||||
userGems: 'user:gems',
|
||||
}),
|
||||
...mapState({
|
||||
userHourglasses: 'user.data.purchased.plan.consecutive.trinkets',
|
||||
userGold: 'user.data.stats.gp',
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
withHourglass: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -20,19 +20,19 @@
|
||||
slot(name="item", :item="item")
|
||||
|
||||
h4.title {{ itemText }}
|
||||
div.text {{ itemNotes }}
|
||||
div.text(v-html="itemNotes")
|
||||
|
||||
slot(name="additionalInfo", :item="item")
|
||||
|
||||
div
|
||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="(priceType === 'gems') ? icons.gem : icons.gold")
|
||||
span.value(:class="priceType") {{ item.value }}
|
||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getSvgClass()]")
|
||||
span.value(:class="getSvgClass()") {{ item.value }}
|
||||
|
||||
button.btn.btn-primary(@click="buyItem()") {{ $t('buyNow') }}
|
||||
|
||||
div.clearfix(slot="modal-footer")
|
||||
span.balance.float-left {{ $t('yourBalance') }}
|
||||
balanceInfo.float-right
|
||||
balanceInfo(:withHourglass="getSvgClass() === 'hourglasses'").float-right
|
||||
|
||||
|
||||
</template>
|
||||
@@ -92,6 +92,10 @@
|
||||
&.gold {
|
||||
color: $yellow-10
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $blue-10;
|
||||
}
|
||||
}
|
||||
|
||||
button.btn.btn-primary {
|
||||
@@ -133,6 +137,7 @@
|
||||
import svgClose from 'assets/svg/close.svg';
|
||||
import svgGold from 'assets/svg/gold.svg';
|
||||
import svgGem from 'assets/svg/gem.svg';
|
||||
import svgHourglasses from 'assets/svg/hourglass.svg';
|
||||
import svgPin from 'assets/svg/pin.svg';
|
||||
|
||||
import BalanceInfo from './balanceInfo.vue';
|
||||
@@ -147,7 +152,8 @@
|
||||
icons: Object.freeze({
|
||||
close: svgClose,
|
||||
gold: svgGold,
|
||||
gem: svgGem,
|
||||
gems: svgGem,
|
||||
hourglasses: svgHourglasses,
|
||||
pin: svgPin,
|
||||
}),
|
||||
};
|
||||
@@ -173,12 +179,19 @@
|
||||
this.$emit('change', $event);
|
||||
},
|
||||
buyItem () {
|
||||
this.$store.dispatch('shops:buyItem', {key: this.item.key});
|
||||
this.$emit('buyPressed', this.item);
|
||||
this.hideDialog();
|
||||
},
|
||||
hideDialog () {
|
||||
this.$root.$emit('hide::modal', 'buy-modal');
|
||||
},
|
||||
getSvgClass () {
|
||||
if (this.priceType && this.icons[this.priceType]) {
|
||||
return this.priceType;
|
||||
} else {
|
||||
return 'gold';
|
||||
}
|
||||
},
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
@@ -17,14 +17,14 @@
|
||||
span.custom-control-description(v-once) {{ category.text }}
|
||||
|
||||
div.form-group.clearfix
|
||||
h3.float-left Hide locked
|
||||
toggle-switch.float-right.hideMissing(
|
||||
h3.float-left(v-once) {{ $t('hideLocked') }}
|
||||
toggle-switch.float-right.no-margin(
|
||||
:label="''",
|
||||
v-model="hideLocked",
|
||||
)
|
||||
div.form-group.clearfix
|
||||
h3.float-left Hide pinned
|
||||
toggle-switch.float-right.hideMissing(
|
||||
h3.float-left(v-once) {{ $t('hidePinned') }}
|
||||
toggle-switch.float-right.no-margin(
|
||||
:label="''",
|
||||
v-model="hidePinned",
|
||||
)
|
||||
@@ -95,8 +95,8 @@
|
||||
:items="filteredGear(selectedGroupGearByClass, searchTextThrottled, selectedSortGearBy, hideLocked, hidePinned)",
|
||||
:itemWidth=94,
|
||||
:itemMargin=24,
|
||||
:showAllLabel="$t('showAllEquipment', { classType: getClassName(selectedGroupGearByClass) })",
|
||||
:showLessLabel="$t('showLessEquipment', { classType: getClassName(selectedGroupGearByClass) })"
|
||||
:showAllLabel="$t('showAllGeneric', { type: getClassName(selectedGroupGearByClass) + ' '+$t('equipment') })",
|
||||
:showLessLabel="$t('showLessGeneric', { type: getClassName(selectedGroupGearByClass) + ' '+$t('equipment') })"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
shopItem(
|
||||
@@ -111,7 +111,6 @@
|
||||
)
|
||||
template(slot="popoverContent", scope="ctx")
|
||||
equipmentAttributesPopover(:item="ctx.item")
|
||||
div {{ ctx.item }}
|
||||
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
span.badge.badge-pill.badge-item.badge-svg(
|
||||
@@ -155,8 +154,7 @@
|
||||
)
|
||||
span(slot="popoverContent")
|
||||
h4.popover-content-title {{ item.text }}
|
||||
div {{ item }}
|
||||
div {{ userItems[item.purchaseType][item.key] }}
|
||||
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
countBadge(
|
||||
:show="true",
|
||||
@@ -227,7 +225,8 @@
|
||||
:item="selectedGearToBuy",
|
||||
priceType="gold",
|
||||
:withPin="true",
|
||||
@change="resetGearToBuy($event)"
|
||||
@change="resetGearToBuy($event)",
|
||||
@buyPressed="buyGear($event)"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
div
|
||||
@@ -244,7 +243,8 @@
|
||||
buyModal(
|
||||
:item="selectedItemToBuy",
|
||||
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
|
||||
@change="resetItemToBuy($event)"
|
||||
@change="resetItemToBuy($event)",
|
||||
@buyPressed="buyItem($event)"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
item.flat(
|
||||
@@ -293,11 +293,37 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.featured-label {
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border-radius: 2px;
|
||||
background-color: #f9f9f9;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px 24px 10px;
|
||||
}
|
||||
|
||||
.market {
|
||||
.avatar {
|
||||
cursor: default;
|
||||
margin: 0 auto;
|
||||
|
||||
.character-sprites span {
|
||||
left: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.standard-page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.featuredItems {
|
||||
height: 216px;
|
||||
|
||||
.background {
|
||||
background: url('~assets/images/market/shop_background.png');
|
||||
background: url('~assets/images/shops/shop_background.png');
|
||||
|
||||
background-repeat: repeat-x;
|
||||
|
||||
@@ -324,7 +350,7 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 216px;
|
||||
background: url('~assets/images/market/market_banner_web_alexnpc.png');
|
||||
background: url('~assets/images/shops/market_banner_web_alexnpc.png');
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.featured-label {
|
||||
@@ -336,30 +362,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.featured-label {
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border-radius: 2px;
|
||||
background-color: #f9f9f9;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px 24px 10px;
|
||||
}
|
||||
|
||||
.market {
|
||||
.avatar {
|
||||
cursor: default;
|
||||
margin: 0 auto;
|
||||
|
||||
.character-sprites span {
|
||||
left: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.standard-page {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -380,7 +382,7 @@
|
||||
import EquipmentAttributesPopover from 'client/components/inventory/equipment/attributesPopover';
|
||||
|
||||
import SellModal from './sellModal.vue';
|
||||
import BuyModal from './buyModal.vue';
|
||||
import BuyModal from '../buyModal.vue';
|
||||
import EquipmentAttributesGrid from './equipmentAttributesGrid.vue';
|
||||
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
@@ -657,9 +659,15 @@ export default {
|
||||
item.pinned = !isPinned;
|
||||
this.$store.dispatch(isPinned ? 'shops:unpinGear' : 'shops:pinGear', {key: item.key});
|
||||
},
|
||||
buyGear (item) {
|
||||
this.$store.dispatch('shops:buyItem', {key: item.key});
|
||||
},
|
||||
buyItem (item) {
|
||||
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('shops:fetch');
|
||||
this.$store.dispatch('shops:fetchMarket');
|
||||
|
||||
this.selectedGroupGearByClass = this.userStats.class;
|
||||
},
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
import svgGold from 'assets/svg/gold.svg';
|
||||
import svgGem from 'assets/svg/gem.svg';
|
||||
|
||||
import BalanceInfo from './balanceInfo.vue';
|
||||
import BalanceInfo from '../balanceInfo.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
290
website/client/components/shops/quests/buyQuestModal.vue
Normal file
@@ -0,0 +1,290 @@
|
||||
<template lang="pug">
|
||||
b-modal#buy-quest-modal(
|
||||
:visible="true",
|
||||
v-if="item != null",
|
||||
:hide-header="true",
|
||||
@change="onChange($event)"
|
||||
)
|
||||
span.badge.badge-pill.badge-dialog(
|
||||
:class="{'item-selected-badge': true}",
|
||||
v-if="withPin"
|
||||
)
|
||||
span.svg-icon.inline.color.icon-10(v-html="icons.pin")
|
||||
|
||||
div.close
|
||||
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close", @click="hideDialog()")
|
||||
|
||||
div.content(v-if="item != null")
|
||||
|
||||
div.inner-content
|
||||
slot(name="item", :item="item")
|
||||
|
||||
h4.title {{ itemText }}
|
||||
div.text(v-html="itemNotes")
|
||||
|
||||
slot(name="additionalInfo", :item="item")
|
||||
|
||||
div
|
||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="(priceType === 'gems') ? icons.gem : icons.gold")
|
||||
span.value(:class="priceType") {{ item.value }}
|
||||
|
||||
button.btn.btn-primary(@click="buyItem()") {{ $t('buyNow') }}
|
||||
|
||||
div.right-sidebar(v-if="item.drop")
|
||||
h3(v-once) {{ $t('rewards') }}
|
||||
div.reward-item
|
||||
span.svg-icon.inline.icon(v-html="icons.experience")
|
||||
span.reward-text {{ $t('amountExperience', { amount: item.drop.exp }) }}
|
||||
div.reward-item(v-if="item.drop.gp != 0")
|
||||
span.svg-icon.inline.icon(v-html="icons.gold")
|
||||
span.reward-text {{ $t('amountGold', { amount: item.drop.gp }) }}
|
||||
div.reward-item(v-for="drop in item.drop.items")
|
||||
span.icon
|
||||
div(:class="getDropIcon(drop)")
|
||||
span.reward-text {{ getDropName(drop) }}
|
||||
|
||||
div.clearfix(slot="modal-footer")
|
||||
span.balance.float-left {{ $t('yourBalance') }}
|
||||
balanceInfo.float-right
|
||||
|
||||
|
||||
</template>
|
||||
<style lang="scss">
|
||||
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/modal.scss';
|
||||
|
||||
#buy-quest-modal {
|
||||
@include centeredModal();
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.inner-content {
|
||||
margin: 33px auto auto;
|
||||
width: 282px;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
line-height: 1.43;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
position: absolute;
|
||||
right: -350px;
|
||||
top: 25px;
|
||||
border-radius: 8px;
|
||||
background-color: $gray-600;
|
||||
box-shadow: 0 2px 16px 0 rgba(26, 24, 29, 0.32);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 364px;
|
||||
z-index: -1;
|
||||
height: 100%;
|
||||
|
||||
|
||||
h3 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.reward-item {
|
||||
width: 306px;
|
||||
height: 84px;
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
margin-bottom: 8px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
margin: 18px;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.reward-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span.svg-icon.inline.icon-32 {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
|
||||
margin-right: 8px;
|
||||
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.value {
|
||||
width: 28px;
|
||||
height: 32px;
|
||||
font-family: Roboto;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
|
||||
vertical-align: middle;
|
||||
|
||||
&.gems {
|
||||
color: $green-10;
|
||||
}
|
||||
|
||||
&.gold {
|
||||
color: $yellow-10
|
||||
}
|
||||
}
|
||||
|
||||
button.btn.btn-primary {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.balance {
|
||||
width: 74px;
|
||||
height: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
height: 48px;
|
||||
background-color: $gray-700;
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.badge-dialog {
|
||||
color: $gray-300;
|
||||
position: absolute;
|
||||
left: -14px;
|
||||
padding: 8px 10px;
|
||||
top: -12px;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import {mapState} from 'client/libs/store';
|
||||
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
|
||||
import svgClose from 'assets/svg/close.svg';
|
||||
import svgGold from 'assets/svg/gold.svg';
|
||||
import svgGem from 'assets/svg/gem.svg';
|
||||
import svgPin from 'assets/svg/pin.svg';
|
||||
import svgExperience from 'assets/svg/experience.svg';
|
||||
|
||||
import BalanceInfo from '../balanceInfo.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
bModal,
|
||||
BalanceInfo,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
close: svgClose,
|
||||
gold: svgGold,
|
||||
gem: svgGem,
|
||||
pin: svgPin,
|
||||
experience: svgExperience,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
content: 'content',
|
||||
}),
|
||||
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;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange ($event) {
|
||||
this.$emit('change', $event);
|
||||
},
|
||||
buyItem () {
|
||||
this.$emit('buyPressed', this.item);
|
||||
this.hideDialog();
|
||||
},
|
||||
hideDialog () {
|
||||
this.$root.$emit('hide::modal', 'buy-quest-modal');
|
||||
},
|
||||
getDropIcon (drop) {
|
||||
switch (drop.type) {
|
||||
case 'gear':
|
||||
return `shop_${drop.key}`;
|
||||
case 'hatchingPotions':
|
||||
return `Pet_HatchingPotion_${drop.key}`;
|
||||
case 'food':
|
||||
return `Pet_Food_${drop.key}`;
|
||||
case 'eggs':
|
||||
return `Pet_Egg_${drop.key}`;
|
||||
case 'quests':
|
||||
return `inventory_quest_scroll_${drop.key}`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
},
|
||||
getDropName (drop) {
|
||||
switch (drop.type) {
|
||||
case 'gear':
|
||||
return this.content.gear.flat[drop.key].text();
|
||||
case 'quests':
|
||||
return this.content.quests[drop.key].text();
|
||||
case 'hatchingPotions':
|
||||
return this.$t('namedHatchingPotion', { type: this.content.hatchingPotions[drop.key].text() });
|
||||
case 'food':
|
||||
return this.content.food[drop.key].text();
|
||||
case 'eggs':
|
||||
return this.content.eggs[drop.key].text();
|
||||
default:
|
||||
return `Unknown type: ${drop.type}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
},
|
||||
priceType: {
|
||||
type: String,
|
||||
},
|
||||
withPin: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
468
website/client/components/shops/quests/index.vue
Normal file
@@ -0,0 +1,468 @@
|
||||
<template lang="pug">
|
||||
.row.quests
|
||||
.standard-sidebar
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
.form
|
||||
h2(v-once) {{ $t('filter') }}
|
||||
.form-group
|
||||
.form-check(
|
||||
v-for="category in categories",
|
||||
:key="category.identifier",
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="viewOptions[category.identifier].selected")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ category.text }}
|
||||
|
||||
div.form-group.clearfix
|
||||
h3.float-left(v-once) {{ $t('hideLocked') }}
|
||||
toggle-switch.float-right.no-margin(
|
||||
:label="''",
|
||||
v-model="hideLocked",
|
||||
)
|
||||
div.form-group.clearfix
|
||||
h3.float-left(v-once) {{ $t('hidePinned') }}
|
||||
toggle-switch.float-right.no-margin(
|
||||
:label="''",
|
||||
v-model="hidePinned",
|
||||
)
|
||||
.standard-page
|
||||
div.featuredItems
|
||||
.background
|
||||
div.npc
|
||||
div.featured-label
|
||||
span.rectangle
|
||||
span.text Ian
|
||||
span.rectangle
|
||||
div.content
|
||||
div.featured-label.with-border
|
||||
span.rectangle
|
||||
span.text(v-once) {{ $t('featuredQuests') }}
|
||||
span.rectangle
|
||||
|
||||
div.items.margin-center
|
||||
shopItem(
|
||||
v-for="item in featuredItems",
|
||||
:key="item.key",
|
||||
:item="item",
|
||||
:price="item.goldValue ? item.goldValue : item.value",
|
||||
:priceType="item.goldValue ? 'gold' : 'gem'",
|
||||
:itemContentClass="'inventory_quest_scroll_'+item.key",
|
||||
:emptyItem="false",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedItemToBuy = item"
|
||||
)
|
||||
template(slot="popoverContent", scope="ctx")
|
||||
div
|
||||
h4.popover-content-title {{ item.text() }}
|
||||
.popover-content-text(v-html="item.notes()")
|
||||
|
||||
h1.mb-0.page-header(v-once) {{ $t('quests') }}
|
||||
|
||||
.clearfix
|
||||
h2.float-left
|
||||
| {{ $t('items') }}
|
||||
|
||||
div.float-right
|
||||
span.dropdown-label {{ $t('sortBy') }}
|
||||
b-dropdown(:text="$t(selectedSortItemsBy)", right=true)
|
||||
b-dropdown-item(
|
||||
v-for="sort in sortItemsBy",
|
||||
@click="selectedSortItemsBy = sort",
|
||||
:active="selectedSortItemsBy === sort",
|
||||
:key="sort"
|
||||
) {{ $t(sort) }}
|
||||
|
||||
|
||||
div(
|
||||
v-for="category in categories",
|
||||
v-if="viewOptions[category.identifier].selected"
|
||||
)
|
||||
h2 {{ category.text }}
|
||||
|
||||
itemRows(
|
||||
v-if="category.identifier === 'pet'",
|
||||
:items="questItems(category, selectedSortItemsBy, searchTextThrottled, hideLocked, hidePinned)",
|
||||
:itemWidth=94,
|
||||
:itemMargin=24,
|
||||
:showAllLabel="$t('showAllGeneric', { type: category.text })",
|
||||
:showLessLabel="$t('showLessGeneric', { type: category.text })"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
shopItem(
|
||||
:key="ctx.item.key",
|
||||
:item="ctx.item",
|
||||
:price="ctx.item.value",
|
||||
:priceType="ctx.item.currency",
|
||||
:itemContentClass="ctx.item.class",
|
||||
:emptyItem="false",
|
||||
@click="selectedItemToBuy = ctx.item"
|
||||
)
|
||||
span(slot="popoverContent", scope="ctx")
|
||||
div
|
||||
h4.popover-content-title {{ ctx.item.text }}
|
||||
.popover-content-text(v-html="ctx.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")
|
||||
|
||||
countBadge(
|
||||
:show="userItems.quests[ctx.item.key] > 0",
|
||||
:count="userItems.quests[ctx.item.key] || 0"
|
||||
)
|
||||
|
||||
div.grouped-parent(v-else-if="category.identifier === 'unlockable' || category.identifier === 'gold'")
|
||||
div.group(v-for="(items, key) in getGrouped(questItems(category, selectedSortItemsBy, searchTextThrottled, hideLocked, hidePinned))")
|
||||
h3 {{ $t(key) }}
|
||||
div.items
|
||||
shopItem(
|
||||
v-for="item in items",
|
||||
: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(v-html="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")
|
||||
|
||||
countBadge(
|
||||
:show="userItems.quests[ctx.item.key] > 0",
|
||||
:count="userItems.quests[ctx.item.key] || 0"
|
||||
)
|
||||
|
||||
div.items(v-else)
|
||||
shopItem(
|
||||
v-for="item in questItems(category, selectedSortItemsBy, searchTextThrottled, hideLocked, 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(v-html="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")
|
||||
|
||||
countBadge(
|
||||
:show="userItems.quests[ctx.item.key] > 0",
|
||||
:count="userItems.quests[ctx.item.key] || 0"
|
||||
)
|
||||
|
||||
buyModal(
|
||||
:item="selectedItemToBuy",
|
||||
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
|
||||
:withPin="true",
|
||||
@change="resetItemToBuy($event)",
|
||||
@buyPressed="buyItem($event)"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
item.flat(
|
||||
:item="ctx.item",
|
||||
:itemContentClass="ctx.item.class",
|
||||
:showPopover="false"
|
||||
)
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.badge-svg {
|
||||
left: calc((100% - 18px) / 2);
|
||||
cursor: pointer;
|
||||
color: $gray-400;
|
||||
background: $white;
|
||||
padding: 4.5px 6px;
|
||||
|
||||
&.item-selected-badge {
|
||||
background: $purple-300;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
span.badge.badge-pill.badge-item.badge-svg:not(.item-selected-badge) {
|
||||
color: #a5a1ac;
|
||||
}
|
||||
|
||||
span.badge.badge-pill.badge-item.badge-svg.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
span.badge.badge-pill.badge-item.badge-svg.hide {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-12 {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.hand-cursor {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.featured-label {
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border-radius: 2px;
|
||||
background-color: #f9f9f9;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px 24px 10px;
|
||||
}
|
||||
|
||||
.group {
|
||||
display: inline-block;
|
||||
width: 33%;
|
||||
margin-bottom: 24px;
|
||||
|
||||
|
||||
.items {
|
||||
border-radius: 2px;
|
||||
background-color: #edecee;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.items > div:not(:last-of-type) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.quests {
|
||||
.standard-page {
|
||||
position: relative;
|
||||
}
|
||||
.featuredItems {
|
||||
height: 216px;
|
||||
|
||||
.background {
|
||||
background: url('~assets/images/shops/quest_shop__banner_background_web.png');
|
||||
|
||||
background-repeat: repeat-x;
|
||||
|
||||
width: 100%;
|
||||
height: 216px;
|
||||
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;
|
||||
width: 100%;
|
||||
height: 216px;
|
||||
background: url('~assets/images/shops/quest_shop__banner_web_iannpc.png');
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.featured-label {
|
||||
position: absolute;
|
||||
bottom: -14px;
|
||||
margin: 0;
|
||||
left: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import {mapState} from 'client/libs/store';
|
||||
|
||||
import ShopItem from '../shopItem';
|
||||
import Item from 'client/components/inventory/item';
|
||||
import CountBadge from 'client/components/ui/countBadge';
|
||||
import ItemRows from 'client/components/ui/itemRows';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
import Avatar from 'client/components/avatar';
|
||||
|
||||
import BuyModal from './buyQuestModal.vue';
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
|
||||
import svgPin from 'assets/svg/pin.svg';
|
||||
|
||||
import featuredItems from 'common/script/content/shop-featuredItems';
|
||||
|
||||
import _filter from 'lodash/filter';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _throttle from 'lodash/throttle';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ShopItem,
|
||||
Item,
|
||||
CountBadge,
|
||||
ItemRows,
|
||||
toggleSwitch,
|
||||
|
||||
bPopover,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
|
||||
Avatar,
|
||||
BuyModal,
|
||||
},
|
||||
watch: {
|
||||
searchText: _throttle(function throttleSearch () {
|
||||
this.searchTextThrottled = this.searchText.toLowerCase();
|
||||
}, 250),
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
viewOptions: {},
|
||||
|
||||
searchText: null,
|
||||
searchTextThrottled: null,
|
||||
|
||||
icons: Object.freeze({
|
||||
pin: svgPin,
|
||||
}),
|
||||
|
||||
sortItemsBy: ['AZ', 'sortByNumber'],
|
||||
selectedSortItemsBy: 'AZ',
|
||||
|
||||
selectedItemToBuy: null,
|
||||
|
||||
hideLocked: false,
|
||||
hidePinned: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
content: 'content',
|
||||
quests: 'shops.quests.data',
|
||||
user: 'user.data',
|
||||
userStats: 'user.data.stats',
|
||||
userItems: 'user.data.items',
|
||||
}),
|
||||
categories () {
|
||||
if (this.quests) {
|
||||
this.quests.categories.map((category) => {
|
||||
this.$set(this.viewOptions, category.identifier, {
|
||||
selected: true,
|
||||
});
|
||||
});
|
||||
|
||||
return this.quests.categories;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
featuredItems () {
|
||||
return featuredItems.quests.map(i => {
|
||||
return this.content.quests[i];
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
questItems (category, sortBy, searchBy, hideLocked, hidePinned) {
|
||||
let result = _filter(category.items, (i) => {
|
||||
if (hideLocked && i.locked) {
|
||||
return false;
|
||||
}
|
||||
if (hidePinned && i.pinned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !searchBy || i.text.toLowerCase().indexOf(searchBy) !== -1;
|
||||
});
|
||||
|
||||
switch (sortBy) {
|
||||
case 'AZ': {
|
||||
result = _sortBy(result, ['text']);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'sortByNumber': {
|
||||
result = _sortBy(result, ['value']);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
getGrouped (entries) {
|
||||
return _groupBy(entries, 'group');
|
||||
},
|
||||
resetItemToBuy ($event) {
|
||||
if (!$event) {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
},
|
||||
isGearLocked (gear) {
|
||||
if (gear.value > this.userStats.gp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
togglePinned (item) {
|
||||
let isPinned = Boolean(item.pinned);
|
||||
item.pinned = !isPinned;
|
||||
this.$store.dispatch(isPinned ? 'shops:unpinGear' : 'shops:pinGear', {key: item.key});
|
||||
},
|
||||
buyItem (item) {
|
||||
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('shops:fetchQuests');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
496
website/client/components/shops/seasonal/index.vue
Normal file
@@ -0,0 +1,496 @@
|
||||
<template lang="pug">
|
||||
.row.seasonal
|
||||
.standard-sidebar
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
.form
|
||||
h2(v-once) {{ $t('filter') }}
|
||||
.form-group
|
||||
.form-check(
|
||||
v-for="category in filterCategories",
|
||||
:key="category.key",
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="viewOptions[category.key].selected")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ $t(category.localeKey+'Capitalized') }}
|
||||
|
||||
div.form-group.clearfix
|
||||
h3.float-left(v-once) {{ $t('hidePinned') }}
|
||||
toggle-switch.float-right.no-margin(
|
||||
:label="''",
|
||||
v-model="hidePinned",
|
||||
)
|
||||
.standard-page
|
||||
div.featuredItems
|
||||
.background
|
||||
div.npc
|
||||
div.featured-label
|
||||
span.rectangle
|
||||
span.text Leslie
|
||||
span.rectangle
|
||||
div.content
|
||||
div.featured-label.with-border
|
||||
span.rectangle
|
||||
span.text(v-once) {{ $t('featuredset', { name: featuredSet.text }) }}
|
||||
span.rectangle
|
||||
|
||||
div.items.margin-center
|
||||
shopItem(
|
||||
v-for="item in featuredSet.items",
|
||||
:key="item.key",
|
||||
:item="item",
|
||||
:price="item.value",
|
||||
:priceType="item.currency",
|
||||
:itemContentClass="item.class",
|
||||
:emptyItem="false",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedItemToBuy = item"
|
||||
)
|
||||
template(slot="popoverContent", scope="ctx")
|
||||
div
|
||||
h4.popover-content-title {{ item.text }}
|
||||
.popover-content-text {{ item.notes }}
|
||||
|
||||
h1.mb-0.page-header(v-once) {{ $t('seasonalShop') }}
|
||||
|
||||
.clearfix
|
||||
h2.float-left
|
||||
| {{ $t('classArmor') }}
|
||||
|
||||
div.float-right
|
||||
span.dropdown-label {{ $t('sortBy') }}
|
||||
b-dropdown(:text="$t(selectedSortItemsBy)", right=true)
|
||||
b-dropdown-item(
|
||||
v-for="sort in sortItemsBy",
|
||||
@click="selectedSortItemsBy = sort",
|
||||
:active="selectedSortItemsBy === sort",
|
||||
:key="sort"
|
||||
) {{ $t(sort) }}
|
||||
|
||||
|
||||
div(
|
||||
v-for="(groupSets, categoryGroup) in getGroupedCategories(categories)",
|
||||
)
|
||||
h3.classgroup
|
||||
span.svg-icon.inline(v-html="icons[categoryGroup]")
|
||||
span.name(:class="categoryGroup") {{ getClassName(categoryGroup) }}
|
||||
|
||||
div.grouped-parent
|
||||
div.group(
|
||||
v-for="category in groupSets"
|
||||
)
|
||||
h3 {{ category.text }}
|
||||
div.items
|
||||
shopItem(
|
||||
v-for="item in seasonalItems(category, selectedSortItemsBy, searchTextThrottled, viewOptions, 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")
|
||||
|
||||
|
||||
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)"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
item.flat(
|
||||
:item="ctx.item",
|
||||
:itemContentClass="ctx.item.class",
|
||||
:showPopover="false"
|
||||
)
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.badge-svg {
|
||||
left: calc((100% - 18px) / 2);
|
||||
cursor: pointer;
|
||||
color: $gray-400;
|
||||
background: $white;
|
||||
padding: 4.5px 6px;
|
||||
|
||||
&.item-selected-badge {
|
||||
background: $purple-300;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
span.badge.badge-pill.badge-item.badge-svg:not(.item-selected-badge) {
|
||||
color: #a5a1ac;
|
||||
}
|
||||
|
||||
span.badge.badge-pill.badge-item.badge-svg.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
span.badge.badge-pill.badge-item.badge-svg.hide {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-12 {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.hand-cursor {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.featured-label {
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border-radius: 2px;
|
||||
background-color: #f9f9f9;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px 24px 10px;
|
||||
}
|
||||
|
||||
.group {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
margin-bottom: 24px;
|
||||
|
||||
|
||||
.items {
|
||||
border-radius: 2px;
|
||||
background-color: #edecee;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.items > div:not(:last-of-type) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.seasonal {
|
||||
.standard-page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
h3.classgroup {
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span.svg-icon.inline {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.healer {
|
||||
color: #cf8229;
|
||||
}
|
||||
|
||||
.rogue {
|
||||
color: #4f2a93;
|
||||
}
|
||||
|
||||
.warrior {
|
||||
color: #b01515;
|
||||
}
|
||||
|
||||
.wizard {
|
||||
color: #1f6ea2;
|
||||
}
|
||||
|
||||
.featuredItems {
|
||||
height: 216px;
|
||||
|
||||
.background {
|
||||
background: url('~assets/images/shops/seasonal_shop_closed_banner_web_background.png');
|
||||
|
||||
background-repeat: repeat-x;
|
||||
|
||||
width: 100%;
|
||||
height: 216px;
|
||||
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;
|
||||
width: 100%;
|
||||
height: 216px;
|
||||
background: url('~assets/images/shops/seasonal_shop_closed_banner_web_leslienpc.png');
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.featured-label {
|
||||
position: absolute;
|
||||
bottom: -14px;
|
||||
margin: 0;
|
||||
left: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import {mapState} from 'client/libs/store';
|
||||
|
||||
import ShopItem from '../shopItem';
|
||||
import Item from 'client/components/inventory/item';
|
||||
import CountBadge from 'client/components/ui/countBadge';
|
||||
import ItemRows from 'client/components/ui/itemRows';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
import Avatar from 'client/components/avatar';
|
||||
|
||||
import BuyModal from '../buyModal.vue';
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
|
||||
import svgPin from 'assets/svg/pin.svg';
|
||||
import svgWarrior from 'assets/svg/warrior.svg';
|
||||
import svgWizard from 'assets/svg/wizard.svg';
|
||||
import svgRogue from 'assets/svg/rogue.svg';
|
||||
import svgHealer from 'assets/svg/healer.svg';
|
||||
|
||||
import featuredItems from 'common/script/content/shop-featuredItems';
|
||||
|
||||
import _filter from 'lodash/filter';
|
||||
import _map from 'lodash/map';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _throttle from 'lodash/throttle';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ShopItem,
|
||||
Item,
|
||||
CountBadge,
|
||||
ItemRows,
|
||||
toggleSwitch,
|
||||
|
||||
bPopover,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
|
||||
Avatar,
|
||||
BuyModal,
|
||||
},
|
||||
watch: {
|
||||
searchText: _throttle(function throttleSearch () {
|
||||
this.searchTextThrottled = this.searchText.toLowerCase();
|
||||
}, 250),
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
viewOptions: {},
|
||||
|
||||
searchText: null,
|
||||
searchTextThrottled: null,
|
||||
|
||||
icons: Object.freeze({
|
||||
pin: svgPin,
|
||||
warrior: svgWarrior,
|
||||
wizard: svgWizard,
|
||||
rogue: svgRogue,
|
||||
healer: svgHealer,
|
||||
}),
|
||||
|
||||
sortItemsBy: ['AZ', 'sortByNumber'],
|
||||
selectedSortItemsBy: 'AZ',
|
||||
|
||||
selectedItemToBuy: null,
|
||||
|
||||
hidePinned: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
content: 'content',
|
||||
seasonal: 'shops.seasonal.data',
|
||||
user: 'user.data',
|
||||
userStats: 'user.data.stats',
|
||||
userItems: 'user.data.items',
|
||||
}),
|
||||
categories () {
|
||||
if (this.seasonal) {
|
||||
this.seasonal.categories.map((category) => {
|
||||
this.$set(this.viewOptions, category.identifier, {
|
||||
selected: true,
|
||||
});
|
||||
});
|
||||
|
||||
return this.seasonal.categories;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
filterCategories () {
|
||||
if (this.content) {
|
||||
let equipmentList = _filter(_map(this.content.itemList, (i, key) => {
|
||||
return {
|
||||
...i,
|
||||
key,
|
||||
};
|
||||
}), 'isEquipment');
|
||||
|
||||
equipmentList.map((category) => {
|
||||
this.$set(this.viewOptions, category.key, {
|
||||
selected: true,
|
||||
});
|
||||
});
|
||||
|
||||
return equipmentList;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
featuredSet () {
|
||||
return _filter(this.categories, (c) => {
|
||||
return c.identifier === featuredItems.seasonal;
|
||||
})[0];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getClassName (classType) {
|
||||
if (classType === 'wizard') {
|
||||
return this.$t('mage');
|
||||
} else {
|
||||
return this.$t(classType);
|
||||
}
|
||||
},
|
||||
seasonalItems (category, sortBy, searchBy, viewOptions, hidePinned) {
|
||||
let result = _filter(category.items, (i) => {
|
||||
if (hidePinned && i.pinned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (viewOptions[i.type] && !viewOptions[i.type].selected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !searchBy || i.text.toLowerCase().indexOf(searchBy) !== -1;
|
||||
});
|
||||
|
||||
switch (sortBy) {
|
||||
case 'AZ': {
|
||||
result = _sortBy(result, ['text']);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'sortByNumber': {
|
||||
result = _sortBy(result, ['value']);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
getGroupedCategories (categories) {
|
||||
let spellCategory = _filter(categories, (c) => {
|
||||
return c.identifier === 'spells';
|
||||
})[0];
|
||||
|
||||
let setCategories = _filter(categories, 'specialClass');
|
||||
|
||||
let result = _groupBy(setCategories, 'specialClass');
|
||||
result.spells = [
|
||||
spellCategory,
|
||||
];
|
||||
|
||||
return result;
|
||||
},
|
||||
resetItemToBuy ($event) {
|
||||
if (!$event) {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
},
|
||||
isGearLocked (gear) {
|
||||
if (gear.value > this.userStats.gp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
togglePinned (item) {
|
||||
let isPinned = Boolean(item.pinned);
|
||||
item.pinned = !isPinned;
|
||||
this.$store.dispatch(isPinned ? 'shops:unpinGear' : 'shops:pinGear', {key: item.key});
|
||||
},
|
||||
buyItem (item) {
|
||||
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('shops:fetchSeasonal');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -15,10 +15,11 @@ b-popover(
|
||||
span.svg-icon.inline.lock(v-if="item.locked" v-html="icons.lock")
|
||||
|
||||
|
||||
div.image(:class="itemContentClass")
|
||||
div.image
|
||||
div(:class="itemContentClass")
|
||||
|
||||
div.price
|
||||
span.svg-icon.inline.icon-16(v-html="(priceType === 'gems') ? icons.gem : icons.gold")
|
||||
span.svg-icon.inline.icon-16(v-html="icons[getSvgClass()]")
|
||||
|
||||
span.price-label(:class="priceType") {{ price }}
|
||||
|
||||
@@ -47,6 +48,11 @@ b-popover(
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
|
||||
.price {
|
||||
.svg-icon {
|
||||
padding-top: 2px;
|
||||
@@ -87,6 +93,7 @@ b-popover(
|
||||
|
||||
import svgGem from 'assets/svg/gem.svg';
|
||||
import svgGold from 'assets/svg/gold.svg';
|
||||
import svgHourglasses from 'assets/svg/hourglass.svg';
|
||||
import svgLock from 'assets/svg/lock.svg';
|
||||
|
||||
export default {
|
||||
@@ -96,9 +103,10 @@ b-popover(
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
gem: svgGem,
|
||||
gems: svgGem,
|
||||
gold: svgGold,
|
||||
lock: svgLock,
|
||||
hourglasses: svgHourglasses,
|
||||
}),
|
||||
};
|
||||
},
|
||||
@@ -137,6 +145,13 @@ b-popover(
|
||||
click () {
|
||||
this.$emit('click', {});
|
||||
},
|
||||
getSvgClass () {
|
||||
if (this.priceType && this.icons[this.priceType]) {
|
||||
return this.priceType;
|
||||
} else {
|
||||
return 'gold';
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
424
website/client/components/shops/timeTravelers/index.vue
Normal file
@@ -0,0 +1,424 @@
|
||||
<template lang="pug">
|
||||
.row.timeTravelers
|
||||
.standard-sidebar
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
.form
|
||||
h2(v-once) {{ $t('filter') }}
|
||||
.form-group
|
||||
.form-check(
|
||||
v-for="category in categories",
|
||||
:key="category.identifier",
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="viewOptions[category.identifier].selected")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ category.text }}
|
||||
|
||||
div.form-group.clearfix
|
||||
h3.float-left(v-once) {{ $t('hidePinned') }}
|
||||
toggle-switch.float-right.no-margin(
|
||||
:label="''",
|
||||
v-model="hidePinned",
|
||||
)
|
||||
.standard-page
|
||||
div.featuredItems
|
||||
.background
|
||||
div.npc
|
||||
div.featured-label
|
||||
span.rectangle
|
||||
span.text(v-once) {{ timeTravelers.text }}
|
||||
span.rectangle
|
||||
div.content(v-if="false")
|
||||
div.featured-label.with-border
|
||||
span.rectangle
|
||||
span.text(v-once) {{ $t('featuredQuests') }}
|
||||
span.rectangle
|
||||
|
||||
div.items.margin-center
|
||||
shopItem(
|
||||
v-for="item in featuredItems",
|
||||
:key="item.key",
|
||||
:item="item",
|
||||
:price="item.goldValue ? item.goldValue : item.value",
|
||||
:priceType="item.goldValue ? 'gold' : 'gem'",
|
||||
:itemContentClass="'inventory_quest_scroll_'+item.key",
|
||||
:emptyItem="false",
|
||||
:popoverPosition="'top'",
|
||||
@click="selectedItemToBuy = item"
|
||||
)
|
||||
template(slot="popoverContent", scope="ctx")
|
||||
div
|
||||
h4.popover-content-title {{ item.text() }}
|
||||
.popover-content-text {{ item.notes() }}
|
||||
|
||||
h1.mb-0.page-header(v-once) {{ timeTravelers.text }}
|
||||
|
||||
.clearfix
|
||||
div.float-right
|
||||
span.dropdown-label {{ $t('sortBy') }}
|
||||
b-dropdown(:text="$t(selectedSortItemsBy)", right=true)
|
||||
b-dropdown-item(
|
||||
v-for="sort in sortItemsBy",
|
||||
@click="selectedSortItemsBy = sort",
|
||||
:active="selectedSortItemsBy === sort",
|
||||
:key="sort"
|
||||
) {{ $t(sort) }}
|
||||
|
||||
|
||||
div(
|
||||
v-for="category in categories",
|
||||
v-if="viewOptions[category.identifier].selected",
|
||||
:class="category.identifier"
|
||||
)
|
||||
h2 {{ category.text }}
|
||||
|
||||
itemRows(
|
||||
:items="travelersItems(category, selectedSortItemsBy, searchTextThrottled, hidePinned)",
|
||||
:itemWidth=94,
|
||||
:itemMargin=24,
|
||||
:showAllLabel="$t('showAllGeneric', { type: category.text })",
|
||||
:showLessLabel="$t('showLessGeneric', { type: category.text })"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
shopItem(
|
||||
:key="ctx.item.key",
|
||||
:item="ctx.item",
|
||||
:price="ctx.item.value",
|
||||
:priceType="ctx.item.currency",
|
||||
:itemContentClass="getItemClass(ctx.item)",
|
||||
:emptyItem="false",
|
||||
@click="selectedItemToBuy = ctx.item"
|
||||
)
|
||||
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(
|
||||
: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)"
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
item.flat(
|
||||
:item="ctx.item",
|
||||
:itemContentClass="ctx.item.class",
|
||||
:showPopover="false"
|
||||
)
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.badge-svg {
|
||||
left: calc((100% - 18px) / 2);
|
||||
cursor: pointer;
|
||||
color: $gray-400;
|
||||
background: $white;
|
||||
padding: 4.5px 6px;
|
||||
|
||||
&.item-selected-badge {
|
||||
background: $purple-300;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
span.badge.badge-pill.badge-item.badge-svg:not(.item-selected-badge) {
|
||||
color: #a5a1ac;
|
||||
}
|
||||
|
||||
span.badge.badge-pill.badge-item.badge-svg.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
span.badge.badge-pill.badge-item.badge-svg.hide {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-12 {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.hand-cursor {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.featured-label {
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border-radius: 2px;
|
||||
background-color: #f9f9f9;
|
||||
margin-bottom: 24px;
|
||||
padding: 24px 24px 10px;
|
||||
}
|
||||
|
||||
.group {
|
||||
display: inline-block;
|
||||
width: 33%;
|
||||
margin-bottom: 24px;
|
||||
|
||||
|
||||
.items {
|
||||
border-radius: 2px;
|
||||
background-color: #edecee;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.items > div:not(:last-of-type) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.timeTravelers {
|
||||
.standard-page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mounts {
|
||||
.shop-content .image div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 7px;
|
||||
right: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.featuredItems {
|
||||
height: 216px;
|
||||
|
||||
.background {
|
||||
background: url('~assets/images/shops/shop_background.png');
|
||||
|
||||
background-repeat: repeat-x;
|
||||
|
||||
width: 100%;
|
||||
height: 216px;
|
||||
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;
|
||||
width: 100%;
|
||||
height: 216px;
|
||||
background: url('~assets/images/shops/time_travelers_open_banner_web_tylerandvickynpcs.png');
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.featured-label {
|
||||
position: absolute;
|
||||
bottom: -14px;
|
||||
margin: 0;
|
||||
left: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import {mapState} from 'client/libs/store';
|
||||
|
||||
import ShopItem from '../shopItem';
|
||||
import Item from 'client/components/inventory/item';
|
||||
import CountBadge from 'client/components/ui/countBadge';
|
||||
import ItemRows from 'client/components/ui/itemRows';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
import Avatar from 'client/components/avatar';
|
||||
|
||||
import BuyModal from '../buyModal.vue';
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
|
||||
import svgPin from 'assets/svg/pin.svg';
|
||||
import svgHourglass from 'assets/svg/hourglass.svg';
|
||||
|
||||
import featuredItems from 'common/script/content/shop-featuredItems';
|
||||
|
||||
import _filter from 'lodash/filter';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _throttle from 'lodash/throttle';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ShopItem,
|
||||
Item,
|
||||
CountBadge,
|
||||
ItemRows,
|
||||
toggleSwitch,
|
||||
|
||||
bPopover,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
|
||||
Avatar,
|
||||
BuyModal,
|
||||
},
|
||||
watch: {
|
||||
searchText: _throttle(function throttleSearch () {
|
||||
this.searchTextThrottled = this.searchText.toLowerCase();
|
||||
}, 250),
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
viewOptions: {},
|
||||
|
||||
searchText: null,
|
||||
searchTextThrottled: null,
|
||||
|
||||
icons: Object.freeze({
|
||||
pin: svgPin,
|
||||
hourglass: svgHourglass,
|
||||
}),
|
||||
|
||||
sortItemsBy: ['AZ', 'sortByNumber'],
|
||||
selectedSortItemsBy: 'AZ',
|
||||
|
||||
selectedItemToBuy: null,
|
||||
|
||||
hidePinned: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
content: 'content',
|
||||
quests: 'shops.quests.data',
|
||||
timeTravelers: 'shops.time-travelers.data',
|
||||
user: 'user.data',
|
||||
userStats: 'user.data.stats',
|
||||
userItems: 'user.data.items',
|
||||
}),
|
||||
categories () {
|
||||
if (this.timeTravelers) {
|
||||
let normalGroups = _filter(this.timeTravelers.categories, (c) => {
|
||||
return c.identifier === 'mounts' || c.identifier === 'pets';
|
||||
});
|
||||
|
||||
let setGroups = _filter(this.timeTravelers.categories, (c) => {
|
||||
return c.identifier !== 'mounts' && c.identifier !== 'pets';
|
||||
});
|
||||
|
||||
let setCategory = {
|
||||
identifier: 'sets',
|
||||
text: this.$t('mysterySets'),
|
||||
items: setGroups.map((c) => {
|
||||
return {
|
||||
...c,
|
||||
value: 1,
|
||||
currency: 'hourglasses',
|
||||
type: 'set_mystery',
|
||||
key: c.identifier,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
normalGroups.push(setCategory);
|
||||
|
||||
normalGroups.map((category) => {
|
||||
this.$set(this.viewOptions, category.identifier, {
|
||||
selected: true,
|
||||
});
|
||||
});
|
||||
|
||||
return normalGroups;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
featuredItems () {
|
||||
return featuredItems.quests.map(i => {
|
||||
return this.content.quests[i];
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
travelersItems (category, sortBy, searchBy, hidePinned) {
|
||||
let result = _filter(category.items, (i) => {
|
||||
if (hidePinned && i.pinned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !searchBy || i.text.toLowerCase().indexOf(searchBy) !== -1;
|
||||
});
|
||||
|
||||
switch (sortBy) {
|
||||
case 'AZ': {
|
||||
result = _sortBy(result, ['text']);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'sortByNumber': {
|
||||
result = _sortBy(result, ['value']);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
getGrouped (entries) {
|
||||
return _groupBy(entries, 'group');
|
||||
},
|
||||
resetItemToBuy ($event) {
|
||||
if (!$event) {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
},
|
||||
togglePinned (item) {
|
||||
let isPinned = Boolean(item.pinned);
|
||||
item.pinned = !isPinned;
|
||||
this.$store.dispatch(isPinned ? 'shops:unpinGear' : 'shops:pinGear', {key: item.key});
|
||||
},
|
||||
buyItem (item) {
|
||||
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
|
||||
},
|
||||
getItemClass (item) {
|
||||
return `shop_${item.type}_${item.key}`;
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('shops:fetchTimeTravelers');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -6,7 +6,6 @@ import getStore from 'client/store';
|
||||
|
||||
// TODO Dummy elements used as placeholder until real components are implemented
|
||||
import ParentPage from './components/parentPage';
|
||||
import Page from './components/page';
|
||||
|
||||
// Static Pages
|
||||
const AppPage = () => import(/* webpackChunkName: "static" */'./components/static/app');
|
||||
@@ -82,6 +81,9 @@ const ChallengeDetail = () => import(/* webpackChunkName: "challenges" */ './com
|
||||
// Shops
|
||||
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 SeasonalPage = () => import(/* webpackChunkName: "shops-seasonal" */'./components/shops/seasonal/index');
|
||||
const TimeTravelersPage = () => import(/* webpackChunkName: "shops-timetravelers" */'./components/shops/timeTravelers/index');
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
@@ -115,9 +117,9 @@ const router = new VueRouter({
|
||||
component: ShopsContainer,
|
||||
children: [
|
||||
{ name: 'market', path: 'market', component: MarketPage },
|
||||
{ name: 'quests', path: 'quests', component: Page },
|
||||
{ name: 'seasonal', path: 'seasonal', component: Page },
|
||||
{ name: 'time', path: 'time', component: Page },
|
||||
{ name: 'quests', path: 'quests', component: QuestsPage },
|
||||
{ name: 'seasonal', path: 'seasonal', component: SeasonalPage },
|
||||
{ name: 'time', path: 'time', component: TimeTravelersPage },
|
||||
],
|
||||
},
|
||||
{ name: 'party', path: '/party', component: GuildPage },
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import axios from 'axios';
|
||||
import { loadAsyncResource } from 'client/libs/asyncResource';
|
||||
import buyOp from 'common/script/ops/buy';
|
||||
import purchaseOp from 'common/script/ops/purchaseWithSpell';
|
||||
import sellOp from 'common/script/ops/sell';
|
||||
|
||||
export function fetch (store, forceLoad = false) { // eslint-disable-line no-shadow
|
||||
export function fetchMarket (store, forceLoad = false) { // eslint-disable-line no-shadow
|
||||
return loadAsyncResource({
|
||||
store,
|
||||
path: 'shops.market',
|
||||
@@ -15,6 +16,43 @@ export function fetch (store, forceLoad = false) { // eslint-disable-line no-sha
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchQuests (store, forceLoad = false) { // eslint-disable-line no-shadow
|
||||
return loadAsyncResource({
|
||||
store,
|
||||
path: 'shops.quests',
|
||||
url: '/api/v3/shops/quests',
|
||||
deserialize (response) {
|
||||
return response.data.data;
|
||||
},
|
||||
forceLoad,
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchSeasonal (store, forceLoad = false) { // eslint-disable-line no-shadow
|
||||
return loadAsyncResource({
|
||||
store,
|
||||
path: 'shops.seasonal',
|
||||
url: '/api/v3/shops/seasonal',
|
||||
deserialize (response) {
|
||||
return response.data.data;
|
||||
},
|
||||
forceLoad,
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchTimeTravelers (store, forceLoad = false) { // eslint-disable-line no-shadow
|
||||
return loadAsyncResource({
|
||||
store,
|
||||
path: 'shops.time-travelers',
|
||||
url: '/api/v3/shops/time-travelers',
|
||||
deserialize (response) {
|
||||
return response.data.data;
|
||||
},
|
||||
forceLoad,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function buyItem (store, params) {
|
||||
const user = store.state.user.data;
|
||||
buyOp(user, {params});
|
||||
@@ -25,6 +63,16 @@ export function buyItem (store, params) {
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
|
||||
export function purchase (store, params) {
|
||||
const user = store.state.user.data;
|
||||
purchaseOp(user, {params});
|
||||
axios
|
||||
.post(`/api/v3/user/purchase/${params.type}/${params.key}`);
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
|
||||
export function sellItems (store, params) {
|
||||
const user = store.state.user.data;
|
||||
sellOp(user, {params, query: {amount: params.amount}});
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
export function market (store) {
|
||||
return store.state.shops.market;
|
||||
}
|
||||
|
||||
export function quests (store) {
|
||||
return store.state.shops.market;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ export default function () {
|
||||
},
|
||||
shops: {
|
||||
market: asyncResourceFactory(),
|
||||
quests: asyncResourceFactory(),
|
||||
seasonal: asyncResourceFactory(),
|
||||
'time-travelers': asyncResourceFactory(),
|
||||
},
|
||||
myGuilds: [],
|
||||
publicGuilds: [],
|
||||
|
||||
@@ -221,17 +221,29 @@
|
||||
"sortByStr": "Str",
|
||||
"sortByInt": "Int",
|
||||
"classEquipment": "Class Equipment",
|
||||
"showAllEquipment": "Show All <%= classType %> Equipment",
|
||||
"showLessEquipment": "Show Less <%= classType %> Equipment",
|
||||
"showAllGeneric": "Show All <%= type %>",
|
||||
"showLessGeneric": "Show Less <%= type %>",
|
||||
"howManyToSell": "How many would you like to sell?",
|
||||
"yourBalance": "Your balance",
|
||||
"sell": "Sell",
|
||||
"buyNow": "Buy Now",
|
||||
"sortByNumber": "Number",
|
||||
"featuredItems": "Featured Items!",
|
||||
"hideLocked": "Hide locked",
|
||||
"hidePinned": "Hide pinned",
|
||||
"featuredQuests": "Featured Quests!",
|
||||
"amountExperience": "<%= amount %> Experience",
|
||||
"amountGold": "<%= amount %> Gold",
|
||||
"namedHatchingPotion": "<%= type %> Hatching Potion",
|
||||
"not_participating": "Not Participating",
|
||||
"owned": "Owned",
|
||||
"not_owned": "Not Owned",
|
||||
"participantsTitle": "Participants",
|
||||
"shortName": "Short Name"
|
||||
"shortName": "Short Name",
|
||||
"classArmor": "Class Armor",
|
||||
"backCapitalized": "Back Accessory",
|
||||
"bodyCapitalized": "Body Accessory",
|
||||
"eyewearCapitalized": "Eyewear",
|
||||
"featuredset": "Featured Set <%= name %>",
|
||||
"mysterySets": "Mystery Sets"
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"questSpiderDropSpiderEgg": "Spider (Egg)",
|
||||
"questSpiderUnlockText": "Unlocks purchasable Spider eggs in the Market",
|
||||
|
||||
"questGroupVice": "Vice",
|
||||
"questVice1Text": "Vice, Part 1: Free Yourself of the Dragon's Influence",
|
||||
"questVice1Notes": "<p>They say there lies a terrible evil in the caverns of Mt. Habitica. A monster whose presence twists the wills of the strong heroes of the land, turning them towards bad habits and laziness! The beast is a grand dragon of immense power and comprised of the shadows themselves: Vice, the treacherous Shadow Wyrm. Brave Habiteers, stand up and defeat this foul beast once and for all, but only if you believe you can stand against its immense power. </p><h3>Vice Part 1: </h3><p>How can you expect to fight the beast if it already has control over you? Don't fall victim to laziness and vice! Work hard to fight against the dragon's dark influence and dispel his hold on you!</p>",
|
||||
"questVice1Boss": "Vice's Shade",
|
||||
@@ -86,6 +87,7 @@
|
||||
"questVice3DropDragonEgg": "Dragon (Egg)",
|
||||
"questVice3DropShadeHatchingPotion": "Shade Hatching Potion",
|
||||
|
||||
"questGroupMoonstone": "Recidivate",
|
||||
"questMoonstone1Text": "Recidivate, Part 1: The Moonstone Chain",
|
||||
"questMoonstone1Notes": "A terrible affliction has struck Habiticans. Bad Habits thought long-dead are rising back up with a vengeance. Dishes lie unwashed, textbooks linger unread, and procrastination runs rampant!<br><br>You track some of your own returning Bad Habits to the Swamps of Stagnation and discover the culprit: the ghostly Necromancer, Recidivate. You rush in, weapons swinging, but they slide through her specter uselessly.<br><br>\"Don’t bother,\" she hisses with a dry rasp. \"Without a chain of moonstones, nothing can harm me – and master jeweler @aurakami scattered all the moonstones across Habitica long ago!\" Panting, you retreat... but you know what you must do.",
|
||||
"questMoonstone1CollectMoonstone": "Moonstones",
|
||||
@@ -103,6 +105,7 @@
|
||||
"questMoonstone3DropRottenMeat": "Rotten Meat (Food)",
|
||||
"questMoonstone3DropZombiePotion": "Zombie Hatching Potion",
|
||||
|
||||
"questGroupGoldenknight": "The Golden Knight",
|
||||
"questGoldenknight1Text": "The Golden Knight, Part 1: A Stern Talking-To",
|
||||
"questGoldenknight1Notes": "The Golden Knight has been getting on poor Habiticans' cases. Didn't do all of your Dailies? Checked off a negative Habit? She will use this as a reason to harass you about how you should follow her example. She is the shining example of a perfect Habitican, and you are naught but a failure. Well, that is not nice at all! Everyone makes mistakes. They should not have to be met with such negativity for it. Perhaps it is time you gather some testimonies from hurt Habiticans and give the Golden Knight a stern talking-to!",
|
||||
"questGoldenknight1CollectTestimony": "Testimonies",
|
||||
@@ -151,6 +154,7 @@
|
||||
"questSeahorseDropSeahorseEgg": "Seahorse (Egg)",
|
||||
"questSeahorseUnlockText": "Unlocks purchasable Seahorse eggs in the Market",
|
||||
|
||||
"questGroupAtom": "Attack of the Mundane",
|
||||
"questAtom1Text": "Attack of the Mundane, Part 1: Dish Disaster!",
|
||||
"questAtom1Notes": "You reach the shores of Washed-Up Lake for some well-earned relaxation... But the lake is polluted with unwashed dishes! How did this happen? Well, you simply cannot allow the lake to be in this state. There is only one thing you can do: clean the dishes and save your vacation spot! Better find some soap to clean up this mess. A lot of soap...",
|
||||
"questAtom1CollectSoapBars": "Bars of Soap",
|
||||
@@ -253,6 +257,7 @@
|
||||
"questWhaleDropWhaleEgg": "Whale (Egg)",
|
||||
"questWhaleUnlockText": "Unlocks purchasable Whale eggs in the Market",
|
||||
|
||||
"questGroupDilatoryDistress": "Dilatory Distress",
|
||||
"questDilatoryDistress1Text": "Dilatory Distress, Part 1: Message in a Bottle",
|
||||
"questDilatoryDistress1Notes": "A message in a bottle arrived from the newly rebuilt city of Dilatory! It reads: \"Dear Habiticans, we need your help once again. Our princess has disappeared and the city is under siege by some unknown watery demons! The mantis shrimps are holding the attackers at bay. Please aid us!\" To make the long journey to the sunken city, one must be able to breathe water. Fortunately, the alchemists @Benga and @hazel can make it all possible! You only have to find the proper ingredients.",
|
||||
"questDilatoryDistress1Completion": "You don the the finned armor and swim to Dilatory as quickly as you can. The merfolk and their mantis shrimp allies have managed to keep the monsters outside the city for the moment, but they are losing. No sooner are you within the castle walls than the horrifying siege descends!",
|
||||
@@ -412,6 +417,7 @@
|
||||
"questBeetleDropBeetleEgg": "Beetle (Egg)",
|
||||
"questBeetleUnlockText": "Unlocks purchasable Beetle eggs in the Market",
|
||||
|
||||
"questGroupTaskwoodsTerror": "Terror in the Taskwoods",
|
||||
"questTaskwoodsTerror1Text": "Terror in the Taskwoods, Part 1: The Blaze in the Taskwoods",
|
||||
"questTaskwoodsTerror1Notes": "You have never seen the Joyful Reaper so agitated. The ruler of the Flourishing Fields lands her skeleton gryphon mount right in the middle of Productivity Plaza and shouts without dismounting. \"Lovely Habiticans, we need your help! Something is starting fires in the Taskwoods, and we still haven't fully recovered from our battle against Burnout. If it's not halted, the flames could engulf all of our wild orchards and berry bushes!\"<br><br>You quickly volunteer, and hasten to the Taskwoods. As you creep into Habitica’s biggest fruit-bearing forest, you suddenly hear clanking and cracking voices from far ahead, and catch the faint smell of smoke. Soon enough, a horde of cackling, flaming skull-creatures flies by you, biting off branches and setting the treetops on fire!",
|
||||
"questTaskwoodsTerror1Completion": "With the help of the Joyful Reaper and the renowned pyromancer @Beffymaroo, you manage to drive back the swarm. In a show of solidarity, Beffymaroo offers you her Pyromancer's Turban as you move deeper into the forest.",
|
||||
@@ -450,6 +456,7 @@
|
||||
"questDustBunniesCompletion": "The dust bunnies vanish into a puff of... well, dust. As it clears, you look around. You'd forgotten how nice this place looks when it's clean. You spy a small pile of gold where the dust used to be. Huh, you'd been wondering where that was!",
|
||||
"questDustBunniesBoss": "Feral Dust Bunnies",
|
||||
|
||||
"questGroupMoon": "Lunar Battle",
|
||||
"questMoon1Text": "Lunar Battle, Part 1: Find the Mysterious Shards",
|
||||
"questMoon1Notes": "Habiticans have been distracted from their tasks by something strange: twisted shards of stone are appearing across the land. Worried, @Starsystemic the Seer summons you to her tower. She says, \"I've been reading alarming omens about these shards, which have been blighting the land and driving hardworking Habiticans to distraction. I can track the source, but first I'll need to examine the shards. Can you bring some to me?\"",
|
||||
"questMoon1Completion": "@Starsystemic disappears into her tower to examine the shards you gathered. \"This may be more complicated than we feared,\" says @Beffymaroo, her trusted assistant. \"It will take us some time to discover the cause. Keep checking in every day, and when we know more, we'll send you the next quest scroll.\"",
|
||||
@@ -482,6 +489,7 @@
|
||||
"questTriceratopsDropTriceratopsEgg": "Triceratops (Egg)",
|
||||
"questTriceratopsUnlockText": "Unlocks purchasable Triceratops eggs in the Market",
|
||||
|
||||
"questGroupStoikalmCalamity": "Stoïkalm Calamity",
|
||||
"questStoikalmCalamity1Text": "Stoïkalm Calamity, Part 1: Earthen Enemies",
|
||||
"questStoikalmCalamity1Notes": "A terse missive arrives from @Kiwibot, and the frost-crusted scroll chills your heart as well as your fingertips. \"Visiting Stoïkalm Steppes -- monsters bursting from earth -- send help!\" You gather your party and ride north, but as soon as you venture down from the mountains, the snow beneath your feet explodes and gruesomely grinning skulls surround you!<br><br>Suddenly, a spear sails past, burying itself in a skull that was burrowing through the snow in an attempt to catch you unawares. A tall woman in finely-crafted armor gallops into the fray on the back of a mastodon, her long braid swinging as she yanks the spear unceremoniously from the crushed beast. It's time to fight off these foes with the help of Lady Glaciate, the leader of the Mammoth Riders!",
|
||||
"questStoikalmCalamity1Completion": "As you deliver a final blow to the skulls, they dissipate in a puff of magic. \"The dratted swarm may be gone,\" Lady Glaciate says, \"but we have bigger problems. Follow me.\" She tosses you a cloak to protect you from the chill air, and you ride off after her.",
|
||||
@@ -528,6 +536,7 @@
|
||||
"questButterflyDropButterflyEgg": "Caterpillar (Egg)",
|
||||
"questButterflyUnlockText": "Unlocks purchasable Caterpillar eggs in the Market",
|
||||
|
||||
"questGroupMayhemMistiflying": "Mayhem in Mistiflying",
|
||||
"questMayhemMistiflying1Text": "Mayhem in Mistiflying, Part 1: In Which Mistiflying Experiences a Dreadful Bother",
|
||||
"questMayhemMistiflying1Notes": "Although local soothsayers predicted pleasant weather, the afternoon is extremely breezy, so you gratefully follow your friend @Kiwibot into their house to escape the blustery day.<br><br>Neither of you expects to find the April Fool lounging at the kitchen table.<br><br>“Oh, hello,” he says. “Fancy seeing you here. Please, let me offer you some of this delicious tea.”<br><br>“That’s…” @Kiwibot begins. “That’s MY—“<br><br>“Yes, yes, of course,” says the April Fool, helping himself to some cookies. “Just thought I’d pop indoors and get a nice reprieve from all the tornado-summoning skulls.” He takes a casual sip from his teacup. “Incidentally, the city of Mistiflying is under attack.”<br><br>Horrified, you and your friends race to the Stables and saddle your fastest winged mounts. As you soar towards the floating city, you see that a swarm of chattering, flying skulls are laying siege to the city… and several turn their attentions towards you!",
|
||||
"questMayhemMistiflying1Completion": "The final skull drops from the sky, a shimmering set of rainbow robes clasped in its jaws, but the steady wind has not slackened. Something else is at play here. And where is that slacking April Fool? You pick up the robes, then swoop into the city.",
|
||||
|
||||
@@ -411,6 +411,7 @@ let quests = {
|
||||
vice1: {
|
||||
text: t('questVice1Text'),
|
||||
notes: t('questVice1Notes'),
|
||||
group: 'questGroupVice',
|
||||
value: 4,
|
||||
lvl: 30,
|
||||
category: 'unlockable',
|
||||
@@ -435,6 +436,7 @@ let quests = {
|
||||
vice2: {
|
||||
text: t('questVice2Text'),
|
||||
notes: t('questVice2Notes'),
|
||||
group: 'questGroupVice',
|
||||
value: 4,
|
||||
lvl: 30,
|
||||
category: 'unlockable',
|
||||
@@ -461,6 +463,7 @@ let quests = {
|
||||
vice3: {
|
||||
text: t('questVice3Text'),
|
||||
notes: t('questVice3Notes'),
|
||||
group: 'questGroupVice',
|
||||
completion: t('questVice3Completion'),
|
||||
previous: 'vice2',
|
||||
value: 4,
|
||||
@@ -661,6 +664,7 @@ let quests = {
|
||||
atom1: {
|
||||
text: t('questAtom1Text'),
|
||||
notes: t('questAtom1Notes'),
|
||||
group: 'questGroupAtom',
|
||||
value: 4,
|
||||
lvl: 15,
|
||||
category: 'unlockable',
|
||||
@@ -686,6 +690,7 @@ let quests = {
|
||||
atom2: {
|
||||
text: t('questAtom2Text'),
|
||||
notes: t('questAtom2Notes'),
|
||||
group: 'questGroupAtom',
|
||||
previous: 'atom1',
|
||||
value: 4,
|
||||
lvl: 15,
|
||||
@@ -711,6 +716,7 @@ let quests = {
|
||||
atom3: {
|
||||
text: t('questAtom3Text'),
|
||||
notes: t('questAtom3Notes'),
|
||||
group: 'questGroupAtom',
|
||||
previous: 'atom2',
|
||||
completion: t('questAtom3Completion'),
|
||||
value: 4,
|
||||
@@ -840,6 +846,7 @@ let quests = {
|
||||
moonstone1: {
|
||||
text: t('questMoonstone1Text'),
|
||||
notes: t('questMoonstone1Notes'),
|
||||
group: 'questGroupMoonstone',
|
||||
value: 4,
|
||||
lvl: 60,
|
||||
category: 'unlockable',
|
||||
@@ -865,6 +872,7 @@ let quests = {
|
||||
moonstone2: {
|
||||
text: t('questMoonstone2Text'),
|
||||
notes: t('questMoonstone2Notes'),
|
||||
group: 'questGroupMoonstone',
|
||||
value: 4,
|
||||
lvl: 60,
|
||||
previous: 'moonstone1',
|
||||
@@ -890,6 +898,7 @@ let quests = {
|
||||
moonstone3: {
|
||||
text: t('questMoonstone3Text'),
|
||||
notes: t('questMoonstone3Notes'),
|
||||
group: 'questGroupMoonstone',
|
||||
completion: t('questMoonstone3Completion'),
|
||||
previous: 'moonstone2',
|
||||
value: 4,
|
||||
@@ -947,6 +956,7 @@ let quests = {
|
||||
goldenknight1: {
|
||||
text: t('questGoldenknight1Text'),
|
||||
notes: t('questGoldenknight1Notes'),
|
||||
group: 'questGroupGoldenknight',
|
||||
value: 4,
|
||||
lvl: 40,
|
||||
category: 'unlockable',
|
||||
@@ -972,6 +982,7 @@ let quests = {
|
||||
goldenknight2: {
|
||||
text: t('questGoldenknight2Text'),
|
||||
notes: t('questGoldenknight2Notes'),
|
||||
group: 'questGroupGoldenknight',
|
||||
value: 4,
|
||||
previous: 'goldenknight1',
|
||||
lvl: 40,
|
||||
@@ -997,6 +1008,7 @@ let quests = {
|
||||
goldenknight3: {
|
||||
text: t('questGoldenknight3Text'),
|
||||
notes: t('questGoldenknight3Notes'),
|
||||
group: 'questGroupGoldenknight',
|
||||
completion: t('questGoldenknight3Completion'),
|
||||
previous: 'goldenknight2',
|
||||
value: 4,
|
||||
@@ -1389,6 +1401,7 @@ let quests = {
|
||||
dilatoryDistress1: {
|
||||
text: t('questDilatoryDistress1Text'),
|
||||
notes: t('questDilatoryDistress1Notes'),
|
||||
group: 'questGroupDilatoryDistress',
|
||||
completion: t('questDilatoryDistress1Completion'),
|
||||
value: 4,
|
||||
goldValue: 200,
|
||||
@@ -1418,6 +1431,7 @@ let quests = {
|
||||
dilatoryDistress2: {
|
||||
text: t('questDilatoryDistress2Text'),
|
||||
notes: t('questDilatoryDistress2Notes'),
|
||||
group: 'questGroupDilatoryDistress',
|
||||
completion: t('questDilatoryDistress2Completion'),
|
||||
previous: 'dilatoryDistress1',
|
||||
value: 4,
|
||||
@@ -1457,6 +1471,7 @@ let quests = {
|
||||
dilatoryDistress3: {
|
||||
text: t('questDilatoryDistress3Text'),
|
||||
notes: t('questDilatoryDistress3Notes'),
|
||||
group: 'questGroupDilatoryDistress',
|
||||
completion: t('questDilatoryDistress3Completion'),
|
||||
previous: 'dilatoryDistress2',
|
||||
value: 4,
|
||||
@@ -2065,6 +2080,7 @@ let quests = {
|
||||
taskwoodsTerror1: {
|
||||
text: t('questTaskwoodsTerror1Text'),
|
||||
notes: t('questTaskwoodsTerror1Notes'),
|
||||
group: 'questGroupTaskwoodsTerror',
|
||||
completion: t('questTaskwoodsTerror1Completion'),
|
||||
value: 4,
|
||||
goldValue: 200,
|
||||
@@ -2103,6 +2119,7 @@ let quests = {
|
||||
taskwoodsTerror2: {
|
||||
text: t('questTaskwoodsTerror2Text'),
|
||||
notes: t('questTaskwoodsTerror2Notes'),
|
||||
group: 'questGroupTaskwoodsTerror',
|
||||
completion: t('questTaskwoodsTerror2Completion'),
|
||||
previous: 'taskwoodsTerror1',
|
||||
value: 4,
|
||||
@@ -2137,6 +2154,7 @@ let quests = {
|
||||
taskwoodsTerror3: {
|
||||
text: t('questTaskwoodsTerror3Text'),
|
||||
notes: t('questTaskwoodsTerror3Notes'),
|
||||
group: 'questGroupTaskwoodsTerror',
|
||||
completion: t('questTaskwoodsTerror3Completion'),
|
||||
previous: 'taskwoodsTerror2',
|
||||
value: 4,
|
||||
@@ -2226,6 +2244,7 @@ let quests = {
|
||||
moon1: {
|
||||
text: t('questMoon1Text'),
|
||||
notes: t('questMoon1Notes'),
|
||||
group: 'questGroupMoon',
|
||||
completion: t('questMoon1Completion'),
|
||||
value: 4,
|
||||
category: 'unlockable',
|
||||
@@ -2255,6 +2274,7 @@ let quests = {
|
||||
moon2: {
|
||||
text: t('questMoon2Text'),
|
||||
notes: t('questMoon2Notes'),
|
||||
group: 'questGroupMoon',
|
||||
completion: t('questMoon2Completion'),
|
||||
previous: 'moon1',
|
||||
value: 4,
|
||||
@@ -2284,6 +2304,7 @@ let quests = {
|
||||
moon3: {
|
||||
text: t('questMoon3Text'),
|
||||
notes: t('questMoon3Notes'),
|
||||
group: 'questGroupMoon',
|
||||
completion: t('questMoon3Completion'),
|
||||
previous: 'moon2',
|
||||
value: 4,
|
||||
@@ -2377,6 +2398,7 @@ let quests = {
|
||||
stoikalmCalamity1: {
|
||||
text: t('questStoikalmCalamity1Text'),
|
||||
notes: t('questStoikalmCalamity1Notes'),
|
||||
group: 'questGroupStoikalmCalamity',
|
||||
completion: t('questStoikalmCalamity1Completion'),
|
||||
value: 4,
|
||||
goldValue: 200,
|
||||
@@ -2415,6 +2437,7 @@ let quests = {
|
||||
stoikalmCalamity2: {
|
||||
text: t('questStoikalmCalamity2Text'),
|
||||
notes: t('questStoikalmCalamity2Notes'),
|
||||
group: 'questGroupStoikalmCalamity',
|
||||
completion: t('questStoikalmCalamity2Completion'),
|
||||
previous: 'stoikalmCalamity1',
|
||||
value: 4,
|
||||
@@ -2441,6 +2464,7 @@ let quests = {
|
||||
stoikalmCalamity3: {
|
||||
text: t('questStoikalmCalamity3Text'),
|
||||
notes: t('questStoikalmCalamity3Notes'),
|
||||
group: 'questGroupStoikalmCalamity',
|
||||
completion: t('questStoikalmCalamity3Completion'),
|
||||
previous: 'stoikalmCalamity2',
|
||||
value: 4,
|
||||
@@ -2578,6 +2602,7 @@ let quests = {
|
||||
mayhemMistiflying1: {
|
||||
text: t('questMayhemMistiflying1Text'),
|
||||
notes: t('questMayhemMistiflying1Notes'),
|
||||
group: 'questGroupMayhemMistiflying',
|
||||
completion: t('questMayhemMistiflying1Completion'),
|
||||
value: 4,
|
||||
goldValue: 200,
|
||||
@@ -2616,6 +2641,7 @@ let quests = {
|
||||
mayhemMistiflying2: {
|
||||
text: t('questMayhemMistiflying2Text'),
|
||||
notes: t('questMayhemMistiflying2Notes'),
|
||||
group: 'questGroupMayhemMistiflying',
|
||||
completion: t('questMayhemMistiflying2Completion'),
|
||||
previous: 'mayhemMistiflying1',
|
||||
value: 4,
|
||||
@@ -2650,6 +2676,7 @@ let quests = {
|
||||
mayhemMistiflying3: {
|
||||
text: t('questMayhemMistiflying3Text'),
|
||||
notes: t('questMayhemMistiflying3Notes'),
|
||||
group: 'questGroupMayhemMistiflying',
|
||||
completion: t('questMayhemMistiflying3Completion'),
|
||||
previous: 'mayhemMistiflying2',
|
||||
value: 4,
|
||||
|
||||
@@ -5,6 +5,15 @@ const featuredItems = {
|
||||
'shield_special_0',
|
||||
'armor_warrior_5',
|
||||
],
|
||||
quests: [
|
||||
'dilatoryDistress1',
|
||||
'dilatoryDistress2',
|
||||
'dilatoryDistress3',
|
||||
],
|
||||
seasonal: 'summerMage',
|
||||
timeTravelers: [
|
||||
// TODO
|
||||
],
|
||||
};
|
||||
|
||||
export default featuredItems;
|
||||
|
||||
@@ -143,6 +143,7 @@ import equip from './ops/equip';
|
||||
import changeClass from './ops/changeClass';
|
||||
import disableClasses from './ops/disableClasses';
|
||||
import purchase from './ops/purchase';
|
||||
import purchaseWithSpell from './ops/purchaseWithSpell';
|
||||
import purchaseHourglass from './ops/hourglassPurchase';
|
||||
import readCard from './ops/readCard';
|
||||
import openMysteryItem from './ops/openMysteryItem';
|
||||
@@ -179,6 +180,7 @@ api.ops = {
|
||||
changeClass,
|
||||
disableClasses,
|
||||
purchase,
|
||||
purchaseWithSpell,
|
||||
purchaseHourglass,
|
||||
readCard,
|
||||
openMysteryItem,
|
||||
|
||||
@@ -127,6 +127,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
|
||||
* key: 'bundleName',
|
||||
* text: t('bundleNameText'),
|
||||
* notes: t('bundleNameNotes'),
|
||||
* group: 'group',
|
||||
* bundleKeys: [
|
||||
* 'quest1',
|
||||
* 'quest2',
|
||||
@@ -154,6 +155,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
|
||||
* key: 'bundleName',
|
||||
* text: 'i18ned string for bundle title',
|
||||
* notes: 'i18ned string for bundle description',
|
||||
* group: 'group',
|
||||
* value: 7,
|
||||
* currency: 'gems',
|
||||
* class: 'quest_bundle_bundleName',
|
||||
@@ -205,6 +207,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
|
||||
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,
|
||||
@@ -404,6 +407,7 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
|
||||
});
|
||||
|
||||
if (category.items.length > 0) {
|
||||
category.specialClass = category.items[0].specialClass;
|
||||
categories.push(category);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import blockUser from './blockUser';
|
||||
import feed from './feed';
|
||||
import buySpecialSpell from './buySpecialSpell';
|
||||
import purchase from './purchase';
|
||||
import purchaseWithSpell from './purchaseWithSpell';
|
||||
import releasePets from './releasePets';
|
||||
import releaseMounts from './releaseMounts';
|
||||
import releaseBoth from './releaseBoth';
|
||||
@@ -61,6 +62,7 @@ module.exports = {
|
||||
feed,
|
||||
buySpecialSpell,
|
||||
purchase,
|
||||
purchaseWithSpell,
|
||||
releasePets,
|
||||
releaseMounts,
|
||||
releaseBoth,
|
||||
|
||||
9
website/common/script/ops/purchaseWithSpell.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import buySpecialSpellOp from './buySpecialSpell';
|
||||
import purchaseOp from './purchase';
|
||||
import get from 'lodash/get';
|
||||
|
||||
module.exports = function purchaseWithSpell (user, req = {}, analytics) {
|
||||
const type = get(req.params, 'type');
|
||||
|
||||
return type === 'spells' ? buySpecialSpellOp(user, req) : purchaseOp(user, req, analytics);
|
||||
};
|
||||
@@ -1241,7 +1241,7 @@ api.purchase = {
|
||||
if (!canGetGems) throw new NotAuthorized(res.t('groupPolicyCannotGetGems'));
|
||||
}
|
||||
|
||||
let purchaseRes = type === 'spells' ? common.ops.buySpecialSpell(user, req) : common.ops.purchase(user, req, res.analytics);
|
||||
let purchaseRes = common.ops.purchaseWithSpell(user, req, res.analytics);
|
||||
await user.save();
|
||||
res.respond(200, ...purchaseRes);
|
||||
},
|
||||
|
||||