Merge branch 'develop' into issue-11615

This commit is contained in:
alexthomson1
2020-01-12 00:20:13 -07:00
762 changed files with 47411 additions and 37824 deletions
@@ -1,8 +1,9 @@
<template functional>
<span
class="message-count"
class="message-count d-flex align-items-center justify-content-center"
:class="{'top-count': props.top === true, 'top-count-gray': props.gray === true}"
> {{ props.count }} </span>
v-html="props.badge || props.count"
></span>
</template>
<style lang="scss">
@@ -18,6 +19,11 @@
text-align: center;
font-weight: bold;
font-size: 12px;
svg {
width: 12px;
height: 12px;
}
}
.message-count.top-count {
@@ -10,7 +10,10 @@
>
<slot name="icon"></slot>
</div>
<div class="notification-content">
<div
class="notification-content"
:class="{'has-text': hasText}"
>
<slot name="content"></slot>
</div>
<div
@@ -107,17 +110,19 @@
line-height: 1.43;
color: $gray-50;
max-width: calc(100% - 26px); // to make space for the close icon
max-width: 100%;
&.has-text {
padding-right: 12px;
}
}
.notification-remove {
// total distance from the notification top edge is 20 pixels
margin-top: 7px;
position: absolute;
width: 18px;
height: 18px;
margin-left: 12px;
padding: 4px;
right: 24px;
.svg-icon {
width: 10px;
@@ -131,7 +136,25 @@ import closeIcon from '@/assets/svg/close.svg';
import { mapActions, mapState } from '@/libs/store';
export default {
props: ['notification', 'canRemove', 'hasIcon', 'readAfterClick'],
props: {
notification: {
type: Object,
required: true,
},
canRemove: {
type: Boolean,
},
hasIcon: {
type: Boolean,
},
readAfterClick: {
type: Boolean,
},
hasText: {
type: Boolean,
default: true,
},
},
data () {
return {
icons: Object.freeze({
@@ -3,7 +3,7 @@
:can-remove="canRemove"
:has-icon="true"
:notification="notification"
:read-after-click="true"
:read-after-click="false"
@click="action"
>
<div
@@ -0,0 +1,74 @@
<template>
<base-notification
:can-remove="canRemove"
:has-icon="false"
:read-after-click="true"
:notification="notification"
:has-text="false"
@click="action"
>
<div
slot="content"
class="d-flex flex-column align-items-center onboarding-complete-content"
>
<img
class="onboarding-complete-banner d-block"
src="~@/assets/images/onboarding-complete-banner@2x.png"
>
<h3>{{ $t('congratulations') }}</h3>
<p
class="onboarding-complete-text"
v-html="$t('onboardingCompleteDesc')"
></p>
<div class="notifications-buttons">
<div
class="btn btn-small btn-primary btn-block"
>
{{ $t('viewAchievements') }}
</div>
</div>
</div>
</base-notification>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.onboarding-complete-content {
text-align: center;
}
.onboarding-complete-banner {
width: 282px;
margin-top: 12px;
margin-bottom: 12px;
}
.onboarding-complete-text {
margin-bottom: 4px;
}
.onboarding-complete-text ::v-deep .gold-amount {
color: $yellow-5;
}
.notifications-buttons {
width: 100%;
}
</style>
<script>
import BaseNotification from './base';
export default {
components: {
BaseNotification,
},
props: ['notification', 'canRemove'],
methods: {
action () {
this.$router.push({ name: 'achievements' });
},
},
};
</script>
@@ -11,10 +11,11 @@
:aria-label="$t('notifications')"
>
<message-count
v-if="notificationsCount > 0"
v-if="notificationsCount > 0 || hasSpecialBadge"
:count="notificationsCount"
:top="true"
:gray="!hasUnseenNotifications"
:gray="!hasUnseenNotifications && !hasSpecialBadge"
:badge="hasSpecialBadge ? icons.starBadge : null"
/>
<div
class="top-menu-icon svg-icon notifications"
@@ -22,7 +23,10 @@
></div>
</div>
</div>
<div slot="dropdown-content">
<div
v-if="openStatus === 1"
slot="dropdown-content"
>
<div
class="dropdown-item dropdown-separated
d-flex justify-content-between dropdown-inactive align-items-center"
@@ -41,6 +45,10 @@
>{{ $t('dismissAll') }}</a>
</div>
<world-boss />
<onboarding-guide
v-if="showOnboardingGuide"
:never-seen="hasSpecialBadge"
/>
<component
:is="notification.type"
v-for="notification in notifications"
@@ -49,7 +57,7 @@
:can-remove="!isActionable(notification)"
/>
<div
v-if="notificationsCount === 0"
v-if="notificationsCount === 0 && !showOnboardingGuide"
class="dropdown-item dropdown-separated
d-flex justify-content-center dropdown-inactive no-notifications flex-column"
>
@@ -106,10 +114,16 @@
<script>
import { mapState, mapActions } from '@/libs/store';
import * as quests from '@/../../common/script/content/quests';
import {
hasCompletedOnboarding,
hasActiveOnboarding,
} from '@/../../common/script/libs/onboarding';
import notificationsIcon from '@/assets/svg/notifications.svg';
import MenuDropdown from '../ui/customMenuDropdown';
import MessageCount from './messageCount';
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
import successImage from '@/assets/svg/success.svg';
import starBadge from '@/assets/svg/star-badge.svg';
// Notifications
import NEW_STUFF from './notifications/newStuff';
@@ -132,6 +146,8 @@ import VERIFY_USERNAME from './notifications/verifyUsername';
import ACHIEVEMENT_JUST_ADD_WATER from './notifications/justAddWater';
import ACHIEVEMENT_LOST_MASTERCLASSER from './notifications/lostMasterclasser';
import ACHIEVEMENT_MIND_OVER_MATTER from './notifications/mindOverMatter';
import ONBOARDING_COMPLETE from './notifications/onboardingComplete';
import OnboardingGuide from './onboardingGuide';
export default {
components: {
@@ -158,18 +174,22 @@ export default {
ACHIEVEMENT_MIND_OVER_MATTER,
WorldBoss: WORLD_BOSS,
VERIFY_USERNAME,
OnboardingGuide,
ONBOARDING_COMPLETE,
},
data () {
return {
icons: Object.freeze({
notifications: notificationsIcon,
success: successImage,
starBadge,
}),
hasSpecialBadge: false,
quests,
openStatus: undefined,
actionableNotifications: [
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
'QUEST_INVITATION', 'GROUP_TASK_NEEDS_WORK', 'GROUP_TASK_APPROVAL',
'QUEST_INVITATION', 'GROUP_TASK_NEEDS_WORK',
],
// A list of notifications handled by this component,
// listed in the order they should appear in the notifications panel.
@@ -177,11 +197,11 @@ export default {
handledNotifications: [
'NEW_STUFF', 'GROUP_TASK_NEEDS_WORK',
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED', 'GROUP_TASK_CLAIMED',
'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
'GROUP_TASK_CLAIMED', 'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
'ACHIEVEMENT_JUST_ADD_WATER', 'ACHIEVEMENT_LOST_MASTERCLASSER', 'ACHIEVEMENT_MIND_OVER_MATTER',
'VERIFY_USERNAME',
'VERIFY_USERNAME', 'ONBOARDING_COMPLETE',
],
};
},
@@ -275,6 +295,20 @@ export default {
hasClass () {
return this.$store.getters['members:hasClass'](this.user);
},
showOnboardingGuide () {
return hasActiveOnboarding(this.user) && !hasCompletedOnboarding(this.user);
},
},
mounted () {
const onboardingPanelState = getLocalSetting(CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE);
if (
onboardingPanelState !== CONSTANTS.onboardingPanelValues.PANEL_OPENED
&& this.showOnboardingGuide
) {
// The first time the onboarding panel is opened a special
// badge for notifications should be used
this.hasSpecialBadge = true;
}
},
methods: {
...mapActions({
@@ -286,6 +320,18 @@ export default {
// Mark notifications as seen when the menu is opened
if (openStatus) this.markAllAsSeen();
// Reset the special notification badge as soon as it's opened
if (this.hasSpecialBadge) {
setLocalSetting(
CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE,
CONSTANTS.onboardingPanelValues.PANEL_OPENED,
);
setTimeout(() => {
this.hasSpecialBadge = false;
}, 100);
}
},
markAllAsSeen () {
const idsToSee = this.notifications.map(notification => {
@@ -318,5 +364,6 @@ export default {
return this.actionableNotifications.indexOf(notification.type) !== -1;
},
},
};
</script>
@@ -0,0 +1,259 @@
<template>
<div
class="onboarding-guide-panel d-flex align-items-center flex-column p-4 dropdown-separated"
@click.stop.prevent="action"
>
<div
class="svg-icon onboarding-toggle"
:class="{'onboarding-toggle-open': open}"
@click="openPanel"
v-html="icons.down"
></div>
<div
class="svg-icon onboarding-guide-banner"
v-html="icons.onboardingGuideBanner"
></div>
<h3 class="getting-started">
{{ $t('gettingStarted') }}
</h3>
<span
class="getting-started-desc"
v-html="$t('gettingStartedDesc')"
></span>
<div
class="onboarding-progress-box d-flex flex-row justify-content-between small-text mb-2"
>
<strong>Your Progress</strong>
<span :class="{'has-progress': progress > 0}">{{ progressText }}</span>
</div>
<div class="onboarding-progress-bar mb-3">
<div
class="onboarding-progress-bar-fill"
:style="{width: `${progress}%`}"
></div>
</div>
<b-collapse
id="onboardingPanelCollapse"
v-model="open"
>
<div
v-for="(achievement, key) in onboardingAchievements"
:key="key"
:class="{
'achievement-earned': achievement.earned
}"
class="achievement-box d-flex flex-row"
>
<div class="achievement-icon-wrapper">
<div :class="`achievement-icon ${getAchievementIcon(achievement)}`"></div>
</div>
<div class="achievement-info d-flex flex-column">
<strong class="achievement-title">{{ achievement.title }}</strong>
<span class="small-text achievement-desc">{{ getAchievementText(key) }}</span>
</div>
</div>
</b-collapse>
</div>
</template>
<style lang='scss' scoped>
@import '~@/assets/scss/colors.scss';
.onboarding-guide-panel {
white-space: normal;
text-align: center;
}
.onboarding-toggle {
cursor: pointer;
position: absolute;
top: 75px;
right: 24px;
width: 16px;
& ::v-deep svg path {
stroke: $gray-200;
}
&:hover ::v-deep svg path {
stroke: $gray-100;
}
&-open {
transform: rotate(-180deg);
}
}
.onboarding-guide-banner {
margin-top: -8px;
margin-bottom: 12px;
width: 154px;
height: 48px;
}
.getting-started {
margin-bottom: 4px;
}
.getting-started-desc {
font-size: 14px;
padding-bottom: 12px;
}
.getting-started-desc ::v-deep .gold-amount {
color: $yellow-5;
}
.onboarding-progress-box {
width: 100%;
font-style: normal;
strong {
color: $gray-50;
}
.has-progress {
color: $yellow-5;
}
}
.onboarding-progress-bar {
width: 100%;
height: 4px;
border-radius: 2px;
background-color: $gray-600;
.onboarding-progress-bar-fill {
height: 100%;
border-radius: 2px;
background: $yellow-5;
}
}
.achievement-box {
padding-top: 11px;
padding-bottom: 12px;
border-bottom: 1px dashed $gray-500;
width: 100%;
&:last-child {
padding-bottom: 0px;
border-bottom: none;
}
}
.achievement-earned {
.achievement-icon-wrapper {
opacity: 1;
}
color: $gray-200;
.achievement-title {
text-decoration: line-through;
}
}
.achievement-icon-wrapper {
opacity: 0.5;
width: 40px;
.achievement-icon {
margin-left: -12px;
transform: scale(0.5);
}
}
.achievement-info {
width: 100%;
text-align: left;
font-size: 14px;
}
.achievement-title {
margin-bottom: 4px;
}
.achievement-desc {
font-style: normal;
}
</style>
<script>
import achievs from '@/../../common/script/libs/achievements';
import { mapState } from '@/libs/store';
import onboardingGuideBanner from '@/assets/svg/onboarding-guide-banner.svg';
import downIcon from '@/assets/svg/down.svg';
export default {
props: {
neverSeen: { // whether it's ever been seen by the user
type: Boolean,
default: false,
},
},
data () {
return {
icons: Object.freeze({
onboardingGuideBanner,
down: downIcon,
}),
open: false,
};
},
computed: {
...mapState({ user: 'user.data' }),
onboardingAchievements () {
return achievs.getAchievementsForProfile(this.user).onboarding.achievements;
},
progress () {
const keys = Object.keys(this.onboardingAchievements);
let nEarned = 0;
keys.forEach(key => {
if (this.onboardingAchievements[key].earned) nEarned += 1;
});
return (nEarned / keys.length) * 100;
},
progressText () {
if (this.progress === 0) {
return this.$t('letsGetStarted');
}
return this.$t('onboardingProgress', { percentage: this.progress });
},
},
created () {
// null means it's never been automatically toggled
if (this.neverSeen === true) {
// The first time the panel should be automatically opened
this.open = true;
}
},
methods: {
getAchievementIcon (achievement) {
if (achievement.earned) {
return `${achievement.icon}2x`;
}
return 'achievement-unearned2x';
},
getAchievementText (key) {
let stringKey = 'achievement';
stringKey += key.charAt(0).toUpperCase() + key.slice(1);
stringKey += 'ModalText';
return this.$t(stringKey);
},
openPanel (e) {
e.preventDefault();
e.stopPropagation();
this.open = !this.open;
},
action () {
// Do nothing, used to prevent closure on click.
},
},
};
</script>