Compare commits

..

1 Commits

Author SHA1 Message Date
Sabe Jones
0ffd3e5883 4.0.6 2017-09-29 20:35:02 +00:00
23 changed files with 136 additions and 476 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "habitica",
"version": "4.0.8",
"version": "4.0.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.0.8",
"version": "4.0.6",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",

View File

@@ -138,21 +138,12 @@ export default {
// @TODO split up this file, it's too big
// Set up Error interceptors
axios.interceptors.response.use((response) => {
if (this.user && response.data && response.data.notifications) {
if (this.user) {
this.$set(this.user, 'notifications', response.data.notifications);
}
return response;
}, (error) => {
if (error.response.status >= 400) {
// Don't show errors from getting user details. These users have delete their account,
// but their chat message still exists.
let configExists = Boolean(error.response) && Boolean(error.response.config);
if (configExists && error.response.config.method === 'get' && error.response.config.url.indexOf('/api/v3/members/') !== -1) {
// @TODO: We resolve the promise because we need our caching to cache this user as tried
// Chat paging should help this, but maybe we can also find another solution..
return Promise.resolve(error);
}
this.$store.state.notificationStore.push({
title: 'Habitica',
text: error.response.data.message,

View File

@@ -1,20 +1,14 @@
<template lang="pug">
b-modal#quest-completed(v-if='user.party.quest.completed', :title="title",
size='md', :hide-footer="true")
b-modal#quest-completed(v-if='user.party.quest.completed', :title="quests[user.party.quest.completed].text() + '' + $t('completed')",
size='lg', :hide-footer="true")
.modal-body.text-center
.quest(:class='`quest_${user.party.quest.completed}`')
p(v-html='this.questData.completion()')
div(:class='`quest_${user.party.quest.completed}`')
p(v-html='quests[user.party.quest.completed].completion()')
.quest-rewards(key='user.party.quest.completed', header-participant="$t('youReceived')", header-quest-owner="$t('questOwnerReceived')")
.modal-footer
button.btn.btn-primary(@click='setQuestCompleted()') {{ $t('ok') }}
</template>
<style scoped>
.quest {
margin: 0 auto;
}
</style>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import quests from 'common/script/content/quests';
@@ -35,12 +29,6 @@ export default {
},
computed: {
...mapState({user: 'user.data'}),
questData () {
return this.quests.quests[this.user.party.quest.completed];
},
title () {
return `${this.questData.text()} ${this.$t('completed')}`;
},
barStyle () {
return {
width: `${percent(this.user.stats.hp, maxHealth)}%`,

View File

@@ -4,12 +4,12 @@
modify-inventory(v-if="isUserLoaded")
footer.container-fluid
.row
.col-12.col-md-2
.col-2
h3
a(href='https://itunes.apple.com/us/app/habitica/id994882113?ls=1&mt=8', target='_blank') {{ $t('mobileIOS') }}
h3
a(href='https://play.google.com/store/apps/details?id=com.habitrpg.android.habitica', target='_blank') {{ $t('mobileAndroid') }}
.col-12.col-md-2
.col-2
h3 Company
ul
li
@@ -28,7 +28,7 @@
a(href='/static/press-kit') {{ $t('presskit') }}
li
a(href='/static/contact') {{ $t('contactUs') }}
.col-12.col-md-2
.col-2
h3 Community
ul
li
@@ -47,7 +47,7 @@
a(href='https://www.facebook.com/Habitica', target='_blank') {{ $t('communityFacebook') }}
li
a(href='https://www.reddit.com/r/habitrpg/', target='_blank') {{ $t('communityReddit') }}
.col-12.col-md-6
.col-6
.row
.col-6
h3 Developers
@@ -55,7 +55,7 @@
li
a(href='/apidoc', target='_blank') {{ $t('APIv3') }}
li
a(href='https://oldgods.net/habitrpg/habitrpg_user_data_display.html', target='_blank') {{ $t('dataDisplayTool') }}
a(href='http://data.habitrpg.com/?uuid=', target='_blank') {{ $t('dataDisplayTool') }}
li
a(href='http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths', target='_blank') {{ $t('guidanceForBlacksmiths') }}
li

View File

@@ -52,7 +52,7 @@ div
a.dropdown-item(href="https://trello.com/c/odmhIqyW/440-read-first-table-of-contents", target='_blank') {{ $t('requestAF') }}
a.dropdown-item(href="http://habitica.wikia.com/wiki/Contributing_to_Habitica", target='_blank') {{ $t('contributing') }}
a.dropdown-item(href="http://habitica.wikia.com/wiki/Habitica_Wiki", target='_blank') {{ $t('wiki') }}
.item-with-icon(v-if="userHourglasses > 0")
.item-with-icon(v-if="userHourglasses != 0")
.svg-icon(v-html="icons.hourglasses")
span {{ userHourglasses }}
.item-with-icon

View File

@@ -51,7 +51,7 @@
)
.row
task-column.col-12.col-sm-6(
task-column.col-6(
v-for="column in columns",
:type="column",
:key="column",

View File

@@ -16,7 +16,7 @@
.svg-icon.positive-icon(v-html="icons.positiveIcon")
span(v-once) {{$t('createChallenge')}}
.row
.col-12.col-md-6(v-for='challenge in filteredChallenges', v-if='!memberOf(challenge)')
.col-6(v-for='challenge in filteredChallenges', v-if='!memberOf(challenge)')
challenge-item(:challenge='challenge')
</template>

View File

@@ -23,7 +23,7 @@
p(v-once) {{$t('challengeDescription2')}}
.row
.col-12.col-md-6(v-for='challenge in filteredChallenges')
.col-6(v-for='challenge in filteredChallenges')
challenge-item(:challenge='challenge')
</template>

View File

@@ -1,5 +1,5 @@
<template lang="pug">
.col-2.standard-sidebar.hidden-xs-down
.col-2.standard-sidebar
.form-group
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')

View File

@@ -5,14 +5,14 @@
copy-as-todo-modal(:copying-message='copyingMessage', :group-name='groupName', :group-id='groupId')
report-flag-modal
div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)')
div(v-for="(msg, index) in chat", v-if='chat && canViewFlag(msg)')
// @TODO: is there a different way to do these conditionals? This creates an infinite loop
//.hr(v-if='displayDivider(msg)')
.hr-middle(v-once) {{ msg.timestamp }}
.row(v-if='user._id !== msg.uuid')
div(:class='inbox ? "col-4" : "col-2"')
avatar(
v-if='cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected',
v-if='cachedProfileData[msg.uuid]',
:member="cachedProfileData[msg.uuid]",
:avatarOnly="true",
:hideClassBadge='true',
@@ -36,15 +36,13 @@
.svg-icon(v-html="icons.like")
span(v-if='!msg.likes[user._id]') {{ $t('like') }}
span(v-if='msg.likes[user._id]') {{ $t('liked') }}
span.action(v-if='!inbox', @click='copyAsTodo(msg)')
span.action( @click='copyAsTodo(msg)')
.svg-icon(v-html="icons.copy")
| {{$t('copyAsTodo')}}
// @TODO make copyAsTodo work in the inbox
span.action(v-if='!inbox && user.flags.communityGuidelinesAccepted', @click='report(msg)')
span.action(v-if='user.contributor.admin || (msg.uuid !== user._id && user.flags.communityGuidelinesAccepted)', @click='report(msg)')
.svg-icon(v-html="icons.report")
| {{$t('report')}}
// @TODO make flagging/reporting work in the inbox. NOTE: it must work even if the communityGuidelines are not accepted and it MUST work for messages that you have SENT as well as received. -- Alys
span.action(v-if='msg.uuid === user._id || inbox || user.contributor.admin', @click='remove(msg, index)')
span.action(v-if='msg.uuid === user._id || inbox', @click='remove(msg, index)')
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
span.action.float-right.liked(v-if='likeCount(msg) > 0')
@@ -72,15 +70,12 @@
.svg-icon(v-html="icons.like")
span(v-if='!msg.likes[user._id]') {{ $t('like') }}
span(v-if='msg.likes[user._id]') {{ $t('liked') }}
span.action(v-if='!inbox', @click='copyAsTodo(msg)')
span.action( @click='copyAsTodo(msg)')
.svg-icon(v-html="icons.copy")
| {{$t('copyAsTodo')}}
// @TODO make copyAsTodo work in the inbox
span.action(v-if='user.flags.communityGuidelinesAccepted', @click='report(msg)')
span.action(v-if='!inbox && user.flags.communityGuidelinesAccepted', @click='report(msg)')
.svg-icon(v-html="icons.report")
| {{$t('report')}}
// @TODO make flagging/reporting work in the inbox. NOTE: it must work even if the communityGuidelines are not accepted and it MUST work for messages that you have SENT as well as received. -- Alys
span.action(v-if='msg.uuid === user._id', @click='remove(msg, index)')
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
@@ -89,7 +84,7 @@
| + {{ likeCount(msg) }}
div(:class='inbox ? "col-4" : "col-2"')
avatar(
v-if='cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected',
v-if='cachedProfileData[msg.uuid]',
:member="cachedProfileData[msg.uuid]",
:avatarOnly="true",
:hideClassBadge='true',
@@ -243,7 +238,7 @@ import axios from 'axios';
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import { mapState } from 'client/libs/store';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import markdownDirective from 'client/directives/markdown';
import Avatar from '../avatar';
import styleHelper from 'client/mixins/styleHelper';
@@ -282,10 +277,12 @@ export default {
this.loadProfileCache();
},
created () {
window.addEventListener('scroll', this.handleScroll);
window.addEventListener('scroll', throttle(() => {
this.loadProfileCache(window.scrollY / 1000);
}, 1000));
},
destroyed () {
window.removeEventListener('scroll', this.handleScroll);
// window.removeEventListener('scroll', this.handleScroll);
},
data () {
return {
@@ -311,7 +308,6 @@ export default {
cachedProfileData: {},
currentProfileLoadedCount: 0,
currentProfileLoadedEnd: 10,
loading: false,
};
},
filters: {
@@ -325,22 +321,17 @@ export default {
},
computed: {
...mapState({user: 'user.data'}),
// @TODO: We need a different lazy load mechnism.
// But honestly, adding a paging route to chat would solve this
messages () {
return this.chat;
},
},
watch: {
messages (oldValue, newValue) {
if (newValue.length === oldValue.length) return;
messages () {
// @TODO: MAybe we should watch insert and remove?
this.loadProfileCache();
},
},
methods: {
handleScroll () {
this.loadProfileCache(window.scrollY / 1000);
},
isUserMentioned (message) {
let user = this.user;
@@ -367,10 +358,7 @@ export default {
if (!message.flagCount || message.flagCount < 2) return true;
return this.user.contributor.admin;
},
loadProfileCache: debounce(function loadProfileCache (screenPosition) {
this._loadProfileCache(screenPosition);
}, 1000),
async _loadProfileCache (screenPosition) {
async loadProfileCache (screenPosition) {
let promises = [];
// @TODO: write an explination
@@ -380,10 +368,11 @@ export default {
return;
}
// @TODO: Not sure we need this hash
let aboutToCache = {};
this.messages.forEach(message => {
let uuid = message.uuid;
if (Boolean(uuid) && !this.cachedProfileData[uuid] && !aboutToCache[uuid]) {
if (uuid && !this.cachedProfileData[uuid] && !aboutToCache[uuid]) {
if (uuid === 'system' || this.currentProfileLoadedCount === this.currentProfileLoadedEnd) return;
aboutToCache[uuid] = {};
promises.push(axios.get(`/api/v3/members/${uuid}`));
@@ -393,21 +382,9 @@ export default {
let results = await Promise.all(promises);
results.forEach(result => {
// We could not load the user. Maybe they were deleted. So, let's cache empty so we don't try again
if (!result || !result.data || result.status >= 400) {
return;
}
let userData = result.data.data;
this.$set(this.cachedProfileData, userData._id, userData);
});
// Merge in any attempts that were rejected so we don't attempt again
for (let uuid in aboutToCache) {
if (!this.cachedProfileData[uuid]) {
this.$set(this.cachedProfileData, uuid, {rejected: true});
}
}
},
displayDivider (message) {
if (this.currentDayDividerDisplay !== moment(message.timestamp).day()) {

View File

@@ -72,7 +72,7 @@
v-on:taskEdited='taskEdited',
)
.row
task-column.col-12.col-sm-6.col-3(
task-column.col-3(
v-for="column in columns",
:type="column",
:key="column",

View File

@@ -3,8 +3,7 @@
group-form-modal(v-if='isParty')
invite-modal(:group='this.group')
start-quest-modal(:group='this.group')
quest-details-modal(:group='this.group')
.col-12.col-sm-8.standard-page
.col-8.standard-page
.row
.col-6.title-details
h1 {{group.name}}
@@ -46,7 +45,7 @@
.col-12.hr
chat-message(:chat.sync='group.chat', :group-id='group._id', group-name='group.name')
.col-12.col-sm-4.sidebar
.col-4.sidebar
.row(:class='{"guild-background": !isParty}')
.col-6
.col-6
@@ -81,13 +80,9 @@
p(v-once) {{ $t('questDescription') }}
button.btn.btn-secondary(v-once, @click="openStartQuestModal()", v-if='isLeader') {{ $t('startAQuest') }}
.row.quest-active-section(v-if='isParty && onPendingQuest && !onActiveQuest')
.col-2
.quest(:class='`inventory_quest_scroll_${questData.key}`')
.col-6.titles
strong {{ questData.text() }}
p {{acceptedCount}} / {{group.memberCount}}
.col-4
button.btn.btn-secondary(@click="openQuestDetails()") {{ $t('details') }}
h2 Pending quest
button.btn.btn-secondary(v-once, @click="questForceStart()") {{ $t('begin') }}
button.btn.btn-secondary(v-once, @click="questCancel()") {{ $t('cancel') }}
.row.quest-active-section(v-if='isParty && !onPendingQuest && onActiveQuest')
.col-12.text-center
.quest-boss(:class="'quest_' + questData.key")
@@ -358,10 +353,6 @@
}
.quest-active-section {
.titles {
padding-top: .5em;
}
.quest-box {
background-image: url('~client/assets/svg/for-css/quest-border.svg');
background-size: 100% 100%;
@@ -440,9 +431,6 @@
</style>
<script>
// @TODO: Break this down into components
import debounce from 'lodash/debounce';
import extend from 'lodash/extend';
import groupUtilities from 'client/mixins/groupsUtilities';
import styleHelper from 'client/mixins/styleHelper';
@@ -450,7 +438,6 @@ import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import membersModal from './membersModal';
import startQuestModal from './startQuestModal';
import questDetailsModal from './questDetailsModal';
import quests from 'common/script/content/quests';
import percent from 'common/script/libs/percent';
import groupFormModal from './groupFormModal';
@@ -494,7 +481,6 @@ export default {
inviteModal,
groupChallenges,
autocomplete,
questDetailsModal,
},
directives: {
bToggle,
@@ -536,17 +522,6 @@ export default {
},
computed: {
...mapState({user: 'user.data'}),
acceptedCount () {
let count = 0;
if (!this.group || !this.group.quest) return count;
for (let uuid in this.group.quest.members) {
if (this.group.quest.members[uuid]) count += 1;
}
return count;
},
communityGuidelinesAccepted () {
return this.user.flags.communityGuidelinesAccepted;
},
@@ -680,10 +655,7 @@ export default {
};
document.body.removeChild(div);
},
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
updateCarretPosition (eventUpdate) {
if (eventUpdate.metaKey && eventUpdate.keyCode === 13) {
this.sendMessage();
return;
@@ -745,9 +717,6 @@ export default {
openStartQuestModal () {
this.$root.$emit('show::modal', 'start-quest-modal');
},
openQuestDetails () {
this.$root.$emit('show::modal', 'quest-details');
},
checkForAchievements () {
// Checks if user's party has reached 2 players for the first time.
if (!this.user.achievements.partyUp && this.group.memberCount >= 2) {
@@ -842,6 +811,11 @@ export default {
this.$store.state.profileOptions.startingPage = 'profile';
this.$root.$emit('show::modal', 'profile');
},
async questCancel () {
if (!confirm(this.$t('sureCancel'))) return;
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/cancel'});
this.group.quest = quest;
},
async questAbort () {
if (!confirm(this.$t('sureAbort'))) return;
if (!confirm(this.$t('doubleSureAbort'))) return;
@@ -857,6 +831,10 @@ export default {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/accept'});
this.group.quest = quest;
},
async questForceStart () {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/force-start'});
this.group.quest = quest;
},
// @TODO: Move to notificaitons component?
async questReject () {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/reject'});

View File

@@ -1,210 +0,0 @@
<template lang="pug">
b-modal#quest-details(title="Empty", size='md', :hide-footer="true", :hide-header="true")
.left-panel.content
h3.text-center {{ $t('participantsTitle') }}
.row
.col-10.offset-1.text-center
span.description(v-once) {{ $t('participantDesc') }}
.row
.col-12.member(v-for='member in members')
strong(:class="{'declined-name': member.accepted === false}") {{member.name}}
.accepted.float-right(v-if='member.accepted === true') {{ $t('accepted') }}
.declined.float-right(v-if='member.accepted === false') {{ $t('declined') }}
.pending.float-right(v-if='member.accepted === null') {{ $t('pending') }}
div(v-if='questData')
questDialogContent(:item="questData")
div.text-center.actions
div
button.btn.btn-secondary(v-once, @click="questForceStart()") {{ $t('begin') }}
div
.cancel(v-once, @click="questCancel()") {{ $t('cancel') }}
.side-panel(v-if='questData')
questDialogDrops(:item="questData")
</template>
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
header {
background-color: $white !important;
border: none !important;
h5 {
text-indent: -99999px;
}
}
.quest-details {
margin: 0 auto;
text-align: left;
width: 180px;
}
.btn-primary {
margin: 1em 0;
}
.left-panel {
background: #4e4a57;
color: $white;
position: absolute;
height: 460px;
width: 320px;
top: 2.5em;
left: -22em;
z-index: -1;
padding: 2em;
overflow: scroll;
h3 {
color: $white;
}
.selected .quest-wrapper {
border: solid 1.5px #9a62ff;
}
.quest-wrapper:hover {
cursor: pointer;
}
.quest-col .quest-wrapper {
background: $white;
padding: .2em;
margin-bottom: 1em;
border-radius: 3px;
}
.description {
text-align: center;
color: #a5a1ac;
font-size: 12px;
}
}
.side-panel {
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: 93%;
}
.member {
padding: 1em .5em;
border-top: 1px solid #686274;
.declined-name {
color: #878190;
}
.accepted {
color: #1ed3a0;
}
.declined {
color: #f19595;
}
.pending {
color: #c3c0c7;
}
}
.actions {
padding-top: 2em;
padding-bottom: 2em;
.cancel {
color: #f74e52;
margin-top: 3em;
}
.cancel:hover {
cursor: pointer;
}
}
</style>
<script>
import { mapState } from 'client/libs/store';
import bModal from 'bootstrap-vue/lib/components/modal';
import quests from 'common/script/content/quests';
import copyIcon from 'assets/svg/copy.svg';
import greyBadgeIcon from 'assets/svg/grey-badge.svg';
import qrCodeIcon from 'assets/svg/qrCode.svg';
import facebookIcon from 'assets/svg/facebook.svg';
import twitterIcon from 'assets/svg/twitter.svg';
import starIcon from 'assets/svg/star.svg';
import goldIcon from 'assets/svg/gold.svg';
import difficultyStarIcon from 'assets/svg/difficulty-star.svg';
import questDialogDrops from '../shops/quests/questDialogDrops';
import questDialogContent from '../shops/quests/questDialogContent';
export default {
props: ['group'],
components: {
bModal,
questDialogDrops,
questDialogContent,
},
data () {
return {
loading: false,
selectedQuest: {},
icons: Object.freeze({
copy: copyIcon,
greyBadge: greyBadgeIcon,
qrCode: qrCodeIcon,
facebook: facebookIcon,
twitter: twitterIcon,
starIcon,
goldIcon,
difficultyStarIcon,
}),
};
},
computed: {
...mapState({
user: 'user.data',
partyMembers: 'party.members.data',
}),
questData () {
return quests.quests[this.group.quest.key];
},
members () {
return this.partyMembers.map(member => {
return {
name: member.profile.name,
accepted: this.group.quest.members[member._id],
};
});
},
},
methods: {
async questForceStart () {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/force-start'});
this.group.quest = quest;
this.close();
},
async questCancel () {
if (!confirm(this.$t('sureCancel'))) return;
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/cancel'});
this.group.quest = quest;
this.close();
},
close () {
this.$root.$emit('hide::modal', 'quest-details');
},
},
};
</script>

View File

@@ -1,5 +1,5 @@
<template lang="pug">
.standard-sidebar.hidden-xs-down
.standard-sidebar
.form-group
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')

View File

@@ -1,6 +1,6 @@
<template lang="pug">
.row
.col-12.col-sm-8.clearfix.standard-page
.clearfix.col-8.standard-page
.row
.col-6.title-details
h1(v-once) {{ $t('welcomeToTavern') }}
@@ -29,7 +29,7 @@
.hr.col-12
chat-message(:chat.sync='group.chat', :group-id='group._id', group-name='group.name')
.col-12.col-sm-4.sidebar
.col-md-4.sidebar
.section
.grassy-meadow-backdrop
.daniel_front
@@ -350,7 +350,6 @@
</style>
<script>
import debounce from 'lodash/debounce';
import { mapState } from 'client/libs/store';
import { TAVERN_ID } from '../../../common/script/constants';
@@ -528,10 +527,7 @@ export default {
};
document.body.removeChild(div);
},
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
updateCarretPosition (eventUpdate) {
if (eventUpdate.metaKey && eventUpdate.keyCode === 13) {
this.sendMessage();
return;

View File

@@ -1,7 +1,6 @@
<template lang="pug">
// @TODO: breakdown to componentes and use some SOLID
.row.stable(v-mousePosition="30", @mouseMoved="mouseMoved($event)")
.standard-sidebar.col-3.hidden-xs-down
.standard-sidebar
div
#npmMattStable.npc_matt
b-popover(
@@ -54,7 +53,7 @@
@change="updateHideMissing"
)
.standard-page.col-12.col-sm-9
.standard-page
.clearfix
h1.float-left.mb-0.page-header(v-once) {{ $t('stable') }}
@@ -74,49 +73,55 @@
span.badge.badge-pill.badge-default {{countOwnedAnimals(petGroups[0], 'pet')}}
div(
v-for="(petGroup, index) in petGroups",
v-for="petGroup in petGroups",
v-if="viewOptions[petGroup.key].selected",
:key="petGroup.key"
)
h4(v-if="viewOptions[petGroup.key].animalCount !== 0") {{ petGroup.label }}
h4(v-if="viewOptions[petGroup.key].animalCount != 0") {{ petGroup.label }}
.pet-row.d-flex(
v-for="(group, key, index) in pets(petGroup, hideMissing, selectedSortBy, searchTextThrottled)",
v-if='index === 0 || showMore === petGroup.key')
.pet-group(
v-for='item in group'
v-drag.drop.food="item.key",
@itemDragOver="onDragOver($event, item)",
@itemDropped="onDrop($event, item)",
@itemDragLeave="onDragLeave()",
:class="{'last': item.isLastInRow}"
)
petItem(
:item="item",
:itemContentClass="getPetItemClass(item)",
:popoverPosition="'top'",
:progress="item.progress",
:emptyItem="!item.isOwned()",
:showPopover="currentDraggingFood == null",
:highlightBorder="highlightPet == item.key",
@click="petClicked(item)"
itemRows(
:items="pets(petGroup, hideMissing, selectedSortBy, searchTextThrottled)",
:itemWidth=94,
:itemMargin=24,
:type="petGroup.key",
)
template(slot="item", scope="context")
div(
v-drag.drop.food="context.item.key",
@itemDragOver="onDragOver($event, context.item)",
@itemDropped="onDrop($event, context.item)",
@itemDragLeave="onDragLeave()",
:class="{'last': context.item.isLastInRow}"
)
span(slot="popoverContent")
div.hatchablePopover(v-if="item.isHatchable()")
h4.popover-content-title {{ item.name }}
div.popover-content-text(v-html="$t('haveHatchablePet', { potion: item.potionName, egg: item.eggName })")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+item.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+item.eggKey")
div(v-else)
h4.popover-content-title {{ item.name }}
template(slot="itemBadge", scope="context")
starBadge(:selected="item.key === currentPet", :show="item.isOwned()", @click="selectPet(item)")
petItem(
:item="context.item",
:itemContentClass="getPetItemClass(context.item)",
:popoverPosition="'top'",
:progress="context.item.progress",
:emptyItem="!context.item.isOwned()",
:showPopover="currentDraggingFood == null",
:highlightBorder="highlightPet == context.item.key",
@click="petClicked(context.item)"
)
span(slot="popoverContent")
div.hatchablePopover(v-if="context.item.isHatchable()")
h4.popover-content-title {{ context.item.name }}
div.popover-content-text(v-html="$t('haveHatchablePet', { potion: context.item.potionName, egg: context.item.eggName })")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+context.item.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+context.item.eggKey")
.btn.btn-flat.btn-show-more(@click="setShowMore(petGroup.key)", v-if='petGroup.key !== "specialPets"')
| {{ showMore === petGroup.key ? $t('showLess') : $t('showMore') }}
div(v-else)
h4.popover-content-title {{ context.item.name }}
template(slot="itemBadge", scope="context")
starBadge(
:selected="context.item.key === currentPet",
:show="context.item.isOwned()",
@click="selectPet(context.item)",
)
h2
| {{ $t('mounts') }}
@@ -130,30 +135,31 @@
)
h4(v-if="viewOptions[mountGroup.key].animalCount != 0") {{ mountGroup.label }}
.pet-row.d-flex(v-for="(group, key, index) in mounts(mountGroup, hideMissing, selectedSortBy, searchTextThrottled)"
v-if='index === 0 || showMore === mountGroup.key')
.pet-group(v-for='item in group')
itemRows(
:items="mounts(mountGroup, hideMissing, selectedSortBy, searchTextThrottled)",
:itemWidth=94,
:itemMargin=24,
:type="mountGroup.key",
)
template(slot="item", scope="context")
mountItem(
:item="item",
:itemContentClass="item.isOwned() ? ('Mount_Icon_' + item.key) : 'PixelPaw GreyedOut'",
:key="item.key",
:item="context.item",
:itemContentClass="context.item.isOwned() ? ('Mount_Icon_' + context.item.key) : 'PixelPaw GreyedOut'",
:key="context.item.key",
:popoverPosition="'top'",
:emptyItem="!item.isOwned()",
:emptyItem="!context.item.isOwned()",
:showPopover="true",
@click="selectMount(item)"
@click="selectMount(context.item)"
)
span(slot="popoverContent")
h4.popover-content-title {{ item.name }}
h4.popover-content-title {{ context.item.name }}
template(slot="itemBadge", scope="context")
starBadge(
:selected="item.key === currentMount",
:show="item.isOwned()",
@click="selectMount(item)",
:selected="context.item.key === currentMount",
:show="context.item.isOwned()",
@click="selectMount(context.item)",
)
.btn.btn-flat.btn-show-more(@click="setShowMore(mountGroup.key)", v-if='mountGroup.key !== "specialMounts"')
| {{ showMore === mountGroup.key ? $t('showLess') : $t('showMore') }}
drawer(
:title="$t('quickInventory')",
:errorMessage="(!hasDrawerTabItems(selectedDrawerTab)) ? ((selectedDrawerTab === 0) ? $t('noFoodAvailable') : $t('noSaddlesAvailable')) : null"
@@ -213,6 +219,7 @@
:visible="hatchablePet != null",
@change="resetHatchablePet($event)"
)
div.content(v-if="hatchablePet")
div.potionEggGroup
div.potionEggBackground
@@ -246,9 +253,11 @@
div.food-icon(:class="'Pet_Food_'+currentDraggingFood.key")
div.popover
div.popover-content {{ $t('clickOnPetToFeed', {foodName: currentDraggingFood.text() }) }}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/modal.scss';
@@ -274,20 +283,6 @@
top: -16px !important;
}
.group {
height: 130px;
overflow: hidden;
}
.pet-row {
max-width: 100%;
flex-wrap: wrap;
.item {
margin-right: .5em;
}
}
.hatchablePopover {
width: 180px
}
@@ -505,7 +500,6 @@
import _filter from 'lodash/filter';
import _flatMap from 'lodash/flatMap';
import _throttle from 'lodash/throttle';
import groupBy from 'lodash/groupBy';
import Item from '../item';
import ItemRows from 'client/components/ui/itemRows';
@@ -591,7 +585,6 @@
currentDraggingFood: null,
selectedDrawerTab: 0,
showMore: '',
};
},
watch: {
@@ -719,13 +712,7 @@
},
},
methods: {
setShowMore (key) {
if (this.showMore === key) {
this.showMore = '';
return;
}
this.showMore = key;
},
getAnimalList (animalGroup, type) {
let key = animalGroup.key;
@@ -846,39 +833,11 @@
},
pets (animalGroup, hideMissing, sortBy, searchText) {
let pets = this.listAnimals(animalGroup, 'pet', hideMissing, sortBy, searchText);
// Don't group special
if (animalGroup.key === 'specialPets') {
return {none: pets};
}
let groupKey = 'eggKey';
if (sortBy === 'sortByColor') {
groupKey = 'potionKey';
} else if (sortBy === 'AZ') {
groupKey = '';
}
return groupBy(pets, groupKey);
return this.listAnimals(animalGroup, 'pet', hideMissing, sortBy, searchText);
},
mounts (animalGroup, hideMissing, sortBy, searchText) {
let mounts = this.listAnimals(animalGroup, 'mount', hideMissing, sortBy, searchText);
// Don't group special
if (animalGroup.key === 'specialMounts') {
return {none: mounts};
}
let groupKey = 'eggKey';
if (sortBy === 'sortByColor') {
groupKey = 'potionKey';
} else if (sortBy === 'AZ') {
groupKey = '';
}
return groupBy(mounts, groupKey);
return this.listAnimals(animalGroup, 'mount', hideMissing, sortBy, searchText);
},
getPetItemClass (pet) {

View File

@@ -37,11 +37,11 @@ div.item-with-icon.item-notifications.dropdown
@click='go("/user/profile")')
span.glyphicon.glyphicon-plus-sign
span {{ $t('haveUnallocated', {points: user.stats.points}) }}
a.dropdown-item(v-for='message in userNewMessages')
span(@click='navigateToGroup(message.key)')
a.dropdown-item(v-for='(message, key) in user.newMessages', v-if='message.value')
span(@click='navigateToGroup(key)')
span.glyphicon.glyphicon-comment
span {{message.name}}
span.clear-button(@click='clearMessages(message.key)', :popover="$t('clear')",
span.clear-button(@click='clearMessages(key)', :popover="$t('clear')",
popover-placement='right', popover-trigger='mouseenter', popover-append-to-body='true') Clear
a.dropdown-item(v-for='(notification, index) in user.groupNotifications', @click='viewGroupApprovalNotification(notification, index, true)')
span(:class="groupApprovalNotificationIcon(notification)")
@@ -156,18 +156,6 @@ export default {
return {name: ''};
// return this.user.party;
},
userNewMessages () {
// @TODO: For some reason data becomes corrupted. We should fix this on the server
let userNewMessages = [];
for (let key in this.user.newMessages) {
let message = this.user.newMessages[key];
if (message && message.name && message.value) {
message.key = key;
userNewMessages.push(message);
}
}
return userNewMessages;
},
},
methods: {
// @TODO: I hate this function, we can do better with a hashmap
@@ -256,8 +244,8 @@ export default {
count += this.user.stats.points > 0 ? 1 : 0;
}
if (this.userNewMessages) {
count += Object.keys(this.userNewMessages).length;
if (this.user.newMessages) {
count += Object.keys(this.user.newMessages).length;
}
return count;

View File

@@ -303,10 +303,6 @@ export default {
this.$root.$emit('show::modal', 'avatar-modal');
}
if (this.questCompleted) {
this.$root.$emit('show::modal', 'quest-completed');
}
// @TODO: This is a timeout to ensure dom is loaded
window.setTimeout(() => {
this.initTour();

View File

@@ -36,9 +36,9 @@
h6(v-once) {{ $t('class') + ': ' }}
// @TODO: what is classText
span(v-if='classText') {{ classText }}&nbsp;
button.btn.btn-danger.btn-xs(@click='changeClassForUser(true)', v-once) {{ $t('changeClass') }}
small.cost &nbsp; 3 {{ $t('gems') }}
// @TODO add icon span.Pet_Currency_Gem1x.inline-gems
button.btn.btn-danger.btn-xs(@click='changeClass(null)', v-once) {{ $t('changeClass') }}
small.cost 3
span.Pet_Currency_Gem1x.inline-gems
hr
div
@@ -82,7 +82,7 @@
button.btn.btn-primary(@click='showBailey()', popover-trigger='mouseenter', popover-placement='right', :popover="$t('showBaileyPop')") {{ $t('showBailey') }}
button.btn.btn-primary(@click='openRestoreModal()', popover-trigger='mouseenter', popover-placement='right', :popover="$t('fixValPop')") {{ $t('fixVal') }}
button.btn.btn-primary(v-if='user.preferences.disableClasses == true', @click='changeClassForUser(false)',
button.btn.btn-primary(v-if='user.preferences.disableClasses == true', @click='changeClass({})',
popover-trigger='mouseenter', popover-placement='right', :popover="$t('enableClassPop')") {{ $t('enableClass') }}
hr
@@ -378,8 +378,8 @@ export default {
this.$router.go('/tasks');
},
async changeClassForUser (confirmationNeeded) {
if (confirmationNeeded && !confirm(this.$t('changeClassConfirmCost'))) return;
async changeClass () {
if (!confirm('Are you sure you want to change your class for 3 gems?')) return;
try {
changeClass(this.user);
await axios.post('/api/v3/user/change-class');

View File

@@ -5,7 +5,7 @@ div
.profile-actions
button.btn.btn-secondary(@click='sendMessage()')
.svg-icon.message-icon(v-html="icons.message")
button.btn.btn-secondary(v-if='user._id !== this.userLoggedIn._id && userLoggedIn.inbox.blocks.indexOf(user._id) === -1', :tooltip="$t('unblock')",
button.btn.btn-secondary(v-if='userLoggedIn.inbox.blocks.indexOf(user._id) === -1', :tooltip="$t('unblock')",
@click="blockUser()", tooltip-placement='right')
span.glyphicon.glyphicon-plus
| {{$t('block')}}
@@ -795,7 +795,7 @@ export default {
return display;
},
allocate (stat) {
allocate(this.user, {query: { stat }});
allocate(this.user, stat);
axios.post(`/api/v3/user/allocate?stat=${stat}`);
},
allocateNow () {

View File

@@ -125,7 +125,6 @@
"mystery": "Mystery",
"changeClass": "Change Class, Refund Attribute Points",
"lvl10ChangeClass": "To change class you must be at least level 10.",
"changeClassConfirmCost": "Are you sure you want to change your class for 3 Gems?",
"invalidClass":"Invalid class. Please specify 'warrior', 'rogue', 'wizard', or 'healer'.",
"levelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options.",
"unallocated": "Unallocated Attribute Points",
@@ -159,7 +158,7 @@
"respawn": "Respawn!",
"youDied": "You Died!",
"dieText": "You've lost a Level, all your Gold, and a random piece of Equipment. Arise, Habiteer, and try again! Curb those negative Habits, be vigilant in completion of Dailies, and hold death at arm's length with a Health Potion if you falter!",
"sureReset": "Are you sure? This will reset your character's class and allocated points (you'll get them all back to re-allocate), and costs 3 Gems.",
"sureReset": "Are you sure? This will reset your character's class and allocated points (you'll get them all back to re-allocate), and costs 3 gems.",
"purchaseFor": "Purchase for <%= cost %> Gems?",
"notEnoughMana": "Not enough mana.",
"invalidTarget": "You can't cast a skill on that.",

View File

@@ -388,7 +388,5 @@
"selectPartyMember": "Select a Party Member",
"areYouSureDeleteMessage": "Are you sure you want to delete this message?",
"reverseChat": "Reverse Chat",
"invites": "Invites",
"details": "Details",
"participantDesc": "Once all members have either accepted or declined, the Quest begins. Only those that clicked 'accept' will be able to participate in the Quest and receive the drops."
"invites": "Invites"
}