Merge branch 'develop' into issue-11615
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user