mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
issue(11266) - Restyle level-up modal with sparkles (#12486)
* issue(11266) - Restyle level-up modal with sparkles * issue(11266) - Add reward display to level up modal At levels 15, 30, 40 and 60 the earned quests are now shown in the level-up modal. * issue(11266) - Simplify css and don't use custom footer * issue(11266) - Don't show pink bars and use colour variables
This commit is contained in:
@@ -36,7 +36,7 @@ import BootstrapVue from 'bootstrap-vue';
|
|||||||
import StoreModule from '@/libs/store';
|
import StoreModule from '@/libs/store';
|
||||||
|
|
||||||
// couldn't inject the languages easily,
|
// couldn't inject the languages easily,
|
||||||
// so just a "$t()" string to show that this will be translated
|
// so just a "$t()" string to show that this will be translated
|
||||||
Vue.prototype.$t = function translateString (...args) {
|
Vue.prototype.$t = function translateString (...args) {
|
||||||
return `$t(${JSON.stringify(args)})`;
|
return `$t(${JSON.stringify(args)})`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -206,5 +206,4 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
7
website/client/src/assets/svg/sparkles-left.svg
Normal file
7
website/client/src/assets/svg/sparkles-left.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="15" viewBox="0 0 32 15">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path fill="#FFA624" d="M9.074 8.485L13.333 6.364 9.074 4.242 6.944 0 4.815 4.242 0.556 6.364 4.815 8.485 6.944 12.727z" transform="rotate(90 16 16)"/>
|
||||||
|
<path fill="#FFBE5D" d="M12.634 19.632L15.19 18.359 12.634 17.087 11.356 14.541 10.079 17.087 7.523 18.359 10.079 19.632 11.356 22.177z" transform="rotate(90 16 16) rotate(53 11.356 18.36)"/>
|
||||||
|
<path fill="#FEDEAD" d="M7.111 29.091L9.667 27.818 7.111 26.545 5.833 24 4.556 26.545 2 27.818 4.556 29.091 5.833 31.636z" transform="rotate(90 16 16)"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 662 B |
9
website/client/src/assets/svg/star-group.svg
Normal file
9
website/client/src/assets/svg/star-group.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="64" viewBox="0 0 40 64">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path fill="#77F4C7" d="M35.667 8.667L40 6.5 35.667 4.333 33.5 0 31.333 4.333 27 6.5 31.333 8.667 33.5 13z" transform="matrix(-1 0 0 1 40 0)"/>
|
||||||
|
<path fill="#BDA8FF" d="M24.667 35.667L30 33 24.667 30.333 22 25 19.333 30.333 14 33 19.333 35.667 22 41z" transform="matrix(-1 0 0 1 40 0)"/>
|
||||||
|
<path fill="#8EEDF6" d="M35.667 60.667L39 59 35.667 57.333 34 54 32.333 57.333 29 59 32.333 60.667 34 64z" transform="matrix(-1 0 0 1 40 0)"/>
|
||||||
|
<path fill="#FFBE5D" d="M6.667 46.667L10 45 6.667 43.333 5 40 3.333 43.333 0 45 3.333 46.667 5 50z" transform="matrix(-1 0 0 1 40 0)"/>
|
||||||
|
<path fill="#FFB6B8" d="M5.667 17.667L8 16.5 5.667 15.333 4.5 13 3.333 15.333 1 16.5 3.333 17.667 4.5 20z" transform="matrix(-1 0 0 1 40 0)"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 889 B |
@@ -1,109 +1,200 @@
|
|||||||
<template>
|
<template>
|
||||||
<b-modal
|
<b-modal
|
||||||
id="level-up"
|
id="level-up"
|
||||||
:title="$t('levelUpShare')"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
:hide-footer="true"
|
:title="title"
|
||||||
:hide-header="true"
|
ok-only
|
||||||
|
:ok-title="$t('onwards')"
|
||||||
|
v-bind:footer-class="{ greyed: displayRewardQuest }"
|
||||||
>
|
>
|
||||||
<div class="modal-body text-center">
|
<section class="d-flex">
|
||||||
<h2>{{ $t('reachedLevel', {level: user.stats.lvl}) }}</h2>
|
<span
|
||||||
|
class="star-group mirror"
|
||||||
|
v-html="icons.starGroup"
|
||||||
|
></span>
|
||||||
<avatar
|
<avatar
|
||||||
class="avatar"
|
class="avatar"
|
||||||
:member="user"
|
:member="user"
|
||||||
/>
|
/>
|
||||||
<p class="text">
|
<span
|
||||||
{{ $t('levelup') }}
|
class="star-group"
|
||||||
</p>
|
v-html="icons.starGroup"
|
||||||
<button
|
></span>
|
||||||
class="btn btn-primary"
|
</section>
|
||||||
@click="close()"
|
|
||||||
|
<p
|
||||||
|
class="text"
|
||||||
|
v-once
|
||||||
|
>
|
||||||
|
{{ $t('levelup') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<section
|
||||||
|
v-if="displayRewardQuest"
|
||||||
|
class="greyed"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="your-rewards d-flex"
|
||||||
|
v-once
|
||||||
>
|
>
|
||||||
{{ $t('onwards') }}
|
<span
|
||||||
</button>
|
class="sparkles"
|
||||||
<br>
|
v-html="icons.sparkles"
|
||||||
<!-- @TODO: Keep this? .checkboxinput(type='checkbox', v-model=
|
></span>
|
||||||
|
<span class="text">{{ $t('yourRewards') }}</span>
|
||||||
|
<span
|
||||||
|
class="sparkles mirror"
|
||||||
|
v-html="icons.sparkles"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="questClass"></div>
|
||||||
|
</section>
|
||||||
|
<!-- @TODO: Keep this? .checkboxinput(type='checkbox', v-model=
|
||||||
'user.preferences.suppressModals.levelUp', @change='changeLevelupSuppress()')
|
'user.preferences.suppressModals.levelUp', @change='changeLevelupSuppress()')
|
||||||
label(style='display:inline-block') {{ $t('dontShowAgain') }}
|
label(style='display:inline-block') {{ $t('dontShowAgain') }}
|
||||||
-->
|
-->
|
||||||
</div>
|
|
||||||
</b-modal>
|
</b-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
#level-up {
|
#level-up {
|
||||||
h2 {
|
.modal-content {
|
||||||
color: #4f2a93;
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 14px 28px 0 rgba($black, 0.24), 0 10px 10px 0 rgba($black, 0.28);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
@media (min-width: 576px) {
|
||||||
min-width: 28em;
|
.modal-sm {
|
||||||
|
max-width: 330px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin: 31px auto 16px;
|
||||||
|
color: $purple-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
position: absolute;
|
||||||
|
right: 18px;
|
||||||
|
top: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0 auto 32px auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.greyed {
|
||||||
|
background-color: $gray-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
padding-top: 1em;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-footer {
|
.mirror {
|
||||||
margin-top: 0;
|
transform: scaleX(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.herobox {
|
.star-group {
|
||||||
margin: auto 8.3em;
|
margin: auto;
|
||||||
width: 6em;
|
|
||||||
height: 9em;
|
svg {
|
||||||
padding-top: 0;
|
height: 64px;
|
||||||
cursor: default;
|
width: 40px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-sprites {
|
.avatar {
|
||||||
margin: 0;
|
cursor: auto;
|
||||||
width: 0;
|
}
|
||||||
|
|
||||||
|
.class-badge {
|
||||||
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
font-size: 14px;
|
margin: 25px 24px 24px;
|
||||||
text-align: center;
|
min-height: auto;
|
||||||
color: #686274;
|
|
||||||
margin-top: 1em;
|
|
||||||
min-height: 0px;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style scoped>
|
.your-rewards {
|
||||||
.avatar {
|
margin: 0 auto;
|
||||||
margin-left: 6.8em;
|
width: fit-content;
|
||||||
|
|
||||||
|
.sparkles {
|
||||||
|
width: 32px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 16px;
|
||||||
|
color: $gray-50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.greyed {
|
||||||
|
padding-bottom: 17px
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
margin: -11px auto 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Avatar from '../avatar';
|
import Avatar from '../avatar';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants';
|
import starGroup from '@/assets/svg/star-group.svg';
|
||||||
import styleHelper from '@/mixins/styleHelper';
|
import sparkles from '@/assets/svg/sparkles-left.svg';
|
||||||
|
|
||||||
|
const levelQuests = {
|
||||||
|
15: 'atom1',
|
||||||
|
30: 'vice1',
|
||||||
|
40: 'goldenknight1',
|
||||||
|
60: 'moonstone1',
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Avatar,
|
Avatar,
|
||||||
},
|
},
|
||||||
mixins: [styleHelper],
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
statsAllocationBoxIsOpen: true,
|
icons: Object.freeze({
|
||||||
maxHealth,
|
starGroup,
|
||||||
|
sparkles,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({ user: 'user.data' }),
|
...mapState({ user: 'user.data' }),
|
||||||
showAllocation () {
|
title () {
|
||||||
return this.$store.getters['members:hasClass'](this.user) && !this.user.preferences.automaticAllocation;
|
return this.$t('reachedLevel', { level: this.user.stats.lvl });
|
||||||
|
},
|
||||||
|
displayRewardQuest () {
|
||||||
|
return this.user.stats.lvl in levelQuests;
|
||||||
|
},
|
||||||
|
questClass () {
|
||||||
|
return `scroll inventory_quest_scroll_${levelQuests[this.user.stats.lvl]}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
close () {
|
|
||||||
this.$root.$emit('bv::hide::modal', 'level-up');
|
|
||||||
},
|
|
||||||
changeLevelupSuppress () {
|
changeLevelupSuppress () {
|
||||||
// @TODO: dispatch set({"preferences.suppressModals.levelUp":
|
// @TODO: dispatch set({"preferences.suppressModals.levelUp":
|
||||||
// user.preferences.suppressModals.levelUp?true: false})
|
// user.preferences.suppressModals.levelUp?true: false})
|
||||||
|
|||||||
@@ -46,7 +46,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '~@/assets/scss/colors.scss';
|
|
||||||
@import '~@/assets/scss/modal.scss';
|
@import '~@/assets/scss/modal.scss';
|
||||||
|
|
||||||
#hatchedPet-modal {
|
#hatchedPet-modal {
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
// Levels that already display modals and should not trigger generic Level Up
|
// Levels that already display modals and should not trigger generic Level Up
|
||||||
const unlockLevels = {
|
const unlockLevels = {
|
||||||
10: 'class system',
|
10: 'Class system',
|
||||||
50: 'Orb of Rebirth',
|
50: 'Orb of Rebirth',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -661,13 +661,7 @@ export default {
|
|||||||
showLevelUpNotifications (newlevel) {
|
showLevelUpNotifications (newlevel) {
|
||||||
this.lvl();
|
this.lvl();
|
||||||
this.playSound('Level_Up');
|
this.playSound('Level_Up');
|
||||||
// NOTE this code isn't actually used because no modal is shown when a quest is dropped
|
if (this.unlockLevels[newlevel]) return;
|
||||||
// In case it's added again it should keep in mind that it will not work
|
|
||||||
// when the user progress to the next level using the RYA modal
|
|
||||||
// as it doesn't score the tasks on the client side and thus this.user._tmp is not filled
|
|
||||||
// with any value
|
|
||||||
// if (this.user._tmp && this.user._tmp.drop && this.user._tmp.drop.type === 'Quest') return;
|
|
||||||
if (this.unlockLevels[`${newlevel}`]) return;
|
|
||||||
if (!this.user.preferences.suppressModals.levelUp) this.$root.$emit('bv::show::modal', 'level-up');
|
if (!this.user.preferences.suppressModals.levelUp) this.$root.$emit('bv::show::modal', 'level-up');
|
||||||
},
|
},
|
||||||
playSound (sound) {
|
playSound (sound) {
|
||||||
|
|||||||
@@ -696,12 +696,6 @@
|
|||||||
box-shadow: 0 14px 28px 0 rgba($black, 0.24), 0 10px 10px 0 rgba($black, 0.28);
|
box-shadow: 0 14px 28px 0 rgba($black, 0.24), 0 10px 10px 0 rgba($black, 0.28);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-body {
|
|
||||||
// the body has a margin/padding that can't be found
|
|
||||||
// if found please remove that padding and this style
|
|
||||||
// margin-bottom: -2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-header, .modal-body, .modal-footer {
|
.modal-header, .modal-body, .modal-footer {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -711,10 +705,6 @@
|
|||||||
cursor: auto;
|
cursor: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cursor-auto {
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-modal-header {
|
.task-modal-header {
|
||||||
color: $white;
|
color: $white;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import LevelUp from '@/components/achievements/levelUp.vue';
|
||||||
|
|
||||||
|
/*
|
||||||
|
// I couldn't get rendering to work for the modal, this is what I tried.
|
||||||
|
// Now the testing is done by overriding `this` for the exported computed
|
||||||
|
// functions directly. I intend to come back to this later to test it
|
||||||
|
// properly in a rendered component.
|
||||||
|
|
||||||
|
import { mount, createLocalVue } from '@vue/test-utils';
|
||||||
|
import BootstrapVue from 'bootstrap-vue';
|
||||||
|
import Store from '@/libs/store';
|
||||||
|
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
localVue.use(Store);
|
||||||
|
localVue.use(BootstrapVue);
|
||||||
|
|
||||||
|
function createContainer () {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('LevelUp', () => {
|
||||||
|
function testFunction (name, level = 10) {
|
||||||
|
return LevelUp.computed[name].bind({
|
||||||
|
user: { stats: { lvl: level } },
|
||||||
|
$t: (...args) => args.map(JSON.stringify).join(' '),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// More potential rendering code
|
||||||
|
let wrapper;
|
||||||
|
beforeEach(async () => {
|
||||||
|
wrapper = mount(LevelUp, {
|
||||||
|
store: new Store({
|
||||||
|
state: { user: { data: createUser() } },
|
||||||
|
getters: {},
|
||||||
|
actions: {},
|
||||||
|
}),
|
||||||
|
propsData: {
|
||||||
|
static: true,
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
localVue,
|
||||||
|
mocks: { $t: string => string },
|
||||||
|
attachTo: createContainer(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
it('displays the right level in the title', () => {
|
||||||
|
const title = testFunction('title', 12);
|
||||||
|
|
||||||
|
expect(title()).to.equal('"reachedLevel" {"level":12}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not display rewards for level 10', () => {
|
||||||
|
const displayRewardQuest = testFunction('displayRewardQuest', 10);
|
||||||
|
|
||||||
|
expect(displayRewardQuest()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
[15, 30, 40, 60].forEach(level => {
|
||||||
|
it(`does display rewards for level ${level}`, () => {
|
||||||
|
const displayRewardQuest = testFunction('displayRewardQuest', level);
|
||||||
|
|
||||||
|
expect(displayRewardQuest()).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates the right test class for level 15', () => {
|
||||||
|
const questClass = testFunction('questClass', 15);
|
||||||
|
|
||||||
|
expect(questClass()).to.equal('scroll inventory_quest_scroll_atom1');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
"onwards": "Onwards!",
|
"onwards": "Onwards!",
|
||||||
"levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!",
|
"levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!",
|
||||||
"reachedLevel": "You Reached Level <%= level %>",
|
"reachedLevel": "You Reached Level <%= level %>",
|
||||||
|
"yourRewards": "Your Rewards",
|
||||||
"gettingStartedDesc": "Complete these onboarding tasks and you’ll earn <strong>5 Achievements</strong> and <strong class=\"gold-amount\">100 Gold</strong> once you’re done!",
|
"gettingStartedDesc": "Complete these onboarding tasks and you’ll earn <strong>5 Achievements</strong> and <strong class=\"gold-amount\">100 Gold</strong> once you’re done!",
|
||||||
"onboardingProgress": "<%= percentage %>% progress",
|
"onboardingProgress": "<%= percentage %>% progress",
|
||||||
"yourProgress": "Your Progress",
|
"yourProgress": "Your Progress",
|
||||||
|
|||||||
Reference in New Issue
Block a user