mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-04-14 19:47:03 +00:00
Choose Class modal (#8976)
* feat(modals): near-complete Choose Class modal Also fixes avatar alignment in the Equip modal. * feat(classes): send API requests from choose modal * feat(classes): full functionality on choose modal * WIP(classes): refactor class functions * refactor(avatar): more intelligible sprites margin * fix(imports): correct import syntax
This commit is contained in:
parent
7d0ab1ba25
commit
69662f84df
5 changed files with 206 additions and 88 deletions
|
|
@ -1,119 +1,203 @@
|
|||
<template lang="pug">
|
||||
b-modal#choose-class(:title="$t('chooseClassHeading')", size='lg', :hide-footer="true")
|
||||
b-modal#choose-class(
|
||||
size='lg',
|
||||
:hide-header='true',
|
||||
:hide-footer='true',
|
||||
:no-close-on-esc='true',
|
||||
:no-close-on-backdrop='true',
|
||||
)
|
||||
.modal-body.select-class
|
||||
h1.header-purple.text-center {{ $t('chooseClass') }}
|
||||
.container-fluid
|
||||
.row
|
||||
.col-md-3(@click='selectedClass = "warrior"')
|
||||
h5 {{ $t('warriorWiki') }}
|
||||
figure.herobox(:class='{"selected-class": selectedClass === "warrior"}')
|
||||
.character-sprites
|
||||
span(:class='`skin_${user.preferences.skin}`')
|
||||
span(class='head_0')
|
||||
span(:class='`${user.preferences.size}_armor_warrior_5`')
|
||||
span(:class='`hair_base_${user.preferences.hair.base}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_bangs_${user.preferences.hair.bangs}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_beard_${user.preferences.hair.beard}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_mustache_${user.preferences.hair.mustache}_${user.preferences.hair.color}`')
|
||||
span(class='head_warrior_5')
|
||||
span(class='shield_warrior_5')
|
||||
span(class='weapon_warrior_6')
|
||||
.col-md-3(@click='selectedClass = "wizard"')
|
||||
h5 {{ $t('mageWiki') }}
|
||||
figure.herobox(:class='{"selected-class": selectedClass === "wizard"}')
|
||||
.character-sprites
|
||||
span(class='`skin_${user.preferences.skin}`')
|
||||
span(class='head_0')
|
||||
span(:class='`${user.preferences.size}_armor_wizard_5`')
|
||||
span(:class='`hair_base_${user.preferences.hair.base}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_bangs_${user.preferences.hair.bangs}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_beard_${user.preferences.hair.beard}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_mustache_${user.preferences.hair.mustache}_${user.preferences.hair.color}`')
|
||||
span(class='head_wizard_5')
|
||||
span(class='shield_wizard_5')
|
||||
span(class='weapon_wizard_6')
|
||||
.col-md-3(@click='selectedClass = "rogue"')
|
||||
h5 {{ $t('rogueWiki') }}
|
||||
figure.herobox(:class='{"selected-class": selectedClass === "rogue"}')
|
||||
.character-sprites
|
||||
span(:class='`skin_${user.preferences.skin}`')
|
||||
span(class='head_0')
|
||||
span(:class='`${user.preferences.size}_armor_rogue_5`')
|
||||
span(:class='`hair_base_${user.preferences.hair.base}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_bangs_${user.preferences.hair.bangs}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_beard_${user.preferences.hair.beard}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_mustache_${user.preferences.hair.mustache}_${user.preferences.hair.color}`')
|
||||
span(class='head_rogue_5')
|
||||
span(class='shield_rogue_6')
|
||||
span(class='weapon_rogue_6')
|
||||
.col-md-3(@click='selectedClass = "healer"')
|
||||
h5 {{ $t('healerWiki') }}
|
||||
figure.herobox(ng-class='{"selected-class": selectedClass === "healer"}')
|
||||
.character-sprites
|
||||
span(:class='`skin_${user.preferences.skin}`')
|
||||
span(class='head_0')
|
||||
span(:class='`${user.preferences.size}_armor_healer_5`')
|
||||
span(:class='`hair_base_${user.preferences.hair.base}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_bangs_${user.preferences.hair.bangs}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_beard_${user.preferences.hair.beard}_${user.preferences.hair.color}`')
|
||||
span(:class='`hair_mustache_${user.preferences.hair.mustache}_${user.preferences.hair.color}`')
|
||||
span(class='head_healer_5')
|
||||
span(class='shield_healer_5')
|
||||
span(class='weapon_healer_6')
|
||||
br
|
||||
.well(v-if='selectedClass === "warrior"') {{ $t('warriorText') }}
|
||||
.well(v-if='selectedClass === "wizard"') {{ $t('mageText') }}
|
||||
.well(v-if='selectedClass === "rogue"') {{ $t('rogueText') }}
|
||||
.well(v-if='selectedClass === "healer"') {{ $t('healerText') }}
|
||||
|
||||
.modal-footer
|
||||
span(popover-placement='left', popover-trigger='mouseenter', :popover="$t('optOutOfClassesText')")
|
||||
button.btn.btn-danger(@click='disableClasses({}); close()') {{ $t('optOutOfClasses') }}
|
||||
button.btn.btn-primary(:disabled='!selectedClass' @click='changeClass(selectedClass); selectedClass = undefined; close()') {{ $t('select') }}
|
||||
.pull-left {{ $t('chooseClassLearn') }}
|
||||
.row
|
||||
.col-md-3(v-for='heroClass in classes')
|
||||
div(@click='selectedClass = heroClass')
|
||||
avatar(
|
||||
:member='user',
|
||||
:avatarOnly='true',
|
||||
:withBackground='false',
|
||||
:overrideAvatarGear='classGear(heroClass)',
|
||||
:hideClassBadge='true',
|
||||
:spritesMargin='"1.8em 1.5em"',
|
||||
:overrideTopPadding='"0px"',
|
||||
:class='selectionBox(selectedClass, heroClass)',
|
||||
)
|
||||
br
|
||||
.d-flex.justify-content-center(v-for='heroClass in classes')
|
||||
.d-inline-flex(v-if='selectedClass === heroClass')
|
||||
.class-badge.d-flex.justify-content-center
|
||||
.svg-icon.align-self-center(v-html='icons[heroClass]')
|
||||
.class-name(:class='`${heroClass}-color`') {{ $t(heroClass) }}
|
||||
div(v-for='heroClass in classes')
|
||||
.class-explanation.text-center(v-if='selectedClass === heroClass') {{ $t(`${heroClass}Text`) }}
|
||||
.text-center(v-markdown='$t("chooseClassLearnMarkdown")')
|
||||
.modal-actions.text-center
|
||||
button.btn.btn-primary.d-inline-block(v-if='!selectedClass', :disabled='true') {{ $t('select') }}
|
||||
button.btn.btn-primary.d-inline-block(v-else, @click='clickSelectClass(selectedClass); close();') {{ $t('selectClass', {heroClass: $t(selectedClass)}) }}
|
||||
b-popover(
|
||||
:triggers="['hover']",
|
||||
:placement="'top'",
|
||||
).d-inline-block
|
||||
span(slot="content")
|
||||
div.popover-content-text {{ $t('optOutOfClassesText') }}
|
||||
.danger(@click='clickDisableClasses(); close();') {{ $t('optOutOfClasses') }}
|
||||
</template>
|
||||
|
||||
<style scope>
|
||||
.dont-despair, .death-penalty {
|
||||
margin-top: 1.5em;
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.btn-primary {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.class-badge {
|
||||
$badge-size: 32px;
|
||||
|
||||
width: $badge-size;
|
||||
height: $badge-size;
|
||||
background: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
border-radius: 100px;
|
||||
|
||||
.svg-icon {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
}
|
||||
}
|
||||
|
||||
.class-explanation {
|
||||
font-size: 16px;
|
||||
margin: 1.5em auto;
|
||||
}
|
||||
|
||||
.class-name {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: auto 0.33333em;
|
||||
}
|
||||
|
||||
.danger {
|
||||
color: $red-50;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
.header-purple {
|
||||
color: $purple-200;
|
||||
margin-top: 1.33333em;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
margin: 2em auto;
|
||||
}
|
||||
|
||||
.selection-box {
|
||||
width: 140px;
|
||||
height: 148px;
|
||||
border-radius: 16px;
|
||||
border: solid 4px $purple-300;
|
||||
}
|
||||
|
||||
.healer-color {
|
||||
color: $yellow-10;
|
||||
}
|
||||
|
||||
.rogue-color {
|
||||
color: $purple-200;
|
||||
}
|
||||
|
||||
.warrior-color {
|
||||
color: $red-50;
|
||||
}
|
||||
|
||||
.wizard-color {
|
||||
color: $blue-10;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
|
||||
import Avatar from '../avatar';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import percent from '../../../common/script/libs/percent';
|
||||
import {maxHealth} from '../../../common/script/index';
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
import warriorIcon from 'assets/svg/warrior.svg';
|
||||
import rogueIcon from 'assets/svg/rogue.svg';
|
||||
import healerIcon from 'assets/svg/healer.svg';
|
||||
import wizardIcon from 'assets/svg/wizard.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
bModal,
|
||||
bPopover,
|
||||
Avatar,
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
classes: 'content.classes',
|
||||
}),
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
maxHealth,
|
||||
icons: Object.freeze({
|
||||
warrior: warriorIcon,
|
||||
rogue: rogueIcon,
|
||||
healer: healerIcon,
|
||||
wizard: wizardIcon,
|
||||
}),
|
||||
selectedClass: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
barStyle () {
|
||||
return {
|
||||
width: `${percent(this.user.stats.hp, maxHealth)}%`,
|
||||
};
|
||||
},
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('hide::modal', 'choose-class');
|
||||
},
|
||||
disableClasses () {
|
||||
// @TODO:
|
||||
clickSelectClass (heroClass) {
|
||||
this.$store.dispatch('user:changeClass', {query: {class: heroClass}});
|
||||
},
|
||||
changeClass () {
|
||||
// @TODO:
|
||||
clickDisableClasses () {
|
||||
this.$store.dispatch('user:disableClasses');
|
||||
},
|
||||
classGear (heroClass) {
|
||||
if (heroClass === 'rogue') {
|
||||
return {
|
||||
armor: 'armor_rogue_5',
|
||||
head: 'head_rogue_5',
|
||||
shield: 'shield_rogue_6',
|
||||
weapon: 'weapon_rogue_6',
|
||||
};
|
||||
} else if (heroClass === 'wizard') {
|
||||
return {
|
||||
armor: 'armor_wizard_5',
|
||||
head: 'head_wizard_5',
|
||||
weapon: 'weapon_wizard_6',
|
||||
};
|
||||
} else if (heroClass === 'healer') {
|
||||
return {
|
||||
armor: 'armor_healer_5',
|
||||
head: 'head_healer_5',
|
||||
shield: 'shield_healer_5',
|
||||
weapon: 'weapon_healer_6',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
head: 'head_warrior_5',
|
||||
weapon: 'weapon_warrior_6',
|
||||
shield: 'shield_warrior_5',
|
||||
armor: 'armor_warrior_5',
|
||||
};
|
||||
}
|
||||
},
|
||||
selectionBox (selectedClass, heroClass) {
|
||||
if (selectedClass === heroClass) {
|
||||
return 'selection-box';
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template lang="pug">
|
||||
.avatar(:style="{width, height, paddingTop}", :class="backgroundClass", @click.prevent='castEnd()')
|
||||
.character-sprites
|
||||
.character-sprites(:style='{margin: spritesMargin}')
|
||||
template(v-if="!avatarOnly")
|
||||
// Mount Body
|
||||
span(v-if="member.items.currentMount", :class="'Mount_Body_' + member.items.currentMount")
|
||||
|
|
@ -56,7 +56,6 @@
|
|||
}
|
||||
|
||||
.character-sprites {
|
||||
margin: 0 auto 0 24px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
|
@ -123,6 +122,13 @@ export default {
|
|||
type: Number,
|
||||
default: 147,
|
||||
},
|
||||
spritesMargin: {
|
||||
type: String,
|
||||
default: '0 auto 0 24px',
|
||||
},
|
||||
overrideTopPadding: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
@ -142,6 +148,10 @@ export default {
|
|||
return this.$store.getters['members:isBuffed'](this.member);
|
||||
},
|
||||
paddingTop () {
|
||||
if (this.overrideTopPadding) {
|
||||
return this.overrideTopPadding;
|
||||
}
|
||||
|
||||
let val = '28px';
|
||||
|
||||
if (!this.avatarOnly) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
:member="user",
|
||||
:avatarOnly="true",
|
||||
:withBackground="true",
|
||||
:overrideAvatarGear="memberOverrideAvatarGear(item)"
|
||||
:overrideAvatarGear="memberOverrideAvatarGear(item)",
|
||||
:spritesMargin='"0px auto auto -1px"',
|
||||
)
|
||||
|
||||
h4.title {{ itemText }}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import setProps from 'lodash/set';
|
|||
import axios from 'axios';
|
||||
|
||||
import { togglePinnedItem as togglePinnedItemOp } from 'common/script/ops/pinnedGearUtils';
|
||||
import changeClassOp from 'common/script/ops/changeClass';
|
||||
import disableClassesOp from 'common/script/ops/disableClasses';
|
||||
|
||||
|
||||
export function fetch (store, forceLoad = false) { // eslint-disable-line no-shadow
|
||||
return loadAsyncResource({
|
||||
|
|
@ -58,6 +61,21 @@ export async function deleteWebhook (store, payload) {
|
|||
return response.data.data;
|
||||
}
|
||||
|
||||
export async function changeClass (store, params) {
|
||||
const user = store.state.user.data;
|
||||
|
||||
changeClassOp(user, params);
|
||||
let response = await axios.post(`/api/v3/user/change-class?class=${params.query.class}`);
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
export async function disableClasses (store) {
|
||||
const user = store.state.user.data;
|
||||
|
||||
disableClassesOp(user);
|
||||
let response = await axios.post('/api/v3/user/disable-classes');
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
export function togglePinnedItem (store, params) {
|
||||
const user = store.state.user.data;
|
||||
|
|
|
|||
|
|
@ -291,6 +291,11 @@
|
|||
"haveHatchablePet": "You have a <%= potion %> hatching potion and <%= egg %> egg to hatch this pet! <b>Click</b> the paw print to hatch.",
|
||||
"welcomeBack": "Welcome back!",
|
||||
"checkOffYesterDailies": "Check off any Dailies you did yesterday:",
|
||||
"wizardText": "Mages learn swiftly, gaining Experience and Levels faster than other classes. They also get a great deal of Mana for using special abilities. Play a Mage if you enjoy the tactical game aspects of Habitica, or if you are strongly motivated by leveling up and unlocking advanced features!",
|
||||
"chooseClass": "Choose your Class",
|
||||
"chooseClassLearnMarkdown": "[Learn more about Habitica's class system](http://habitica.wikia.com/wiki/Class_System)",
|
||||
"selectClass": "Select <%= heroClass %>",
|
||||
"wizard": "Mage",
|
||||
"introTour": "Here we are! I’ve filled out some Tasks for you based on your interests, so you can get started right away. Click a Task to edit or add new Tasks to fit your routine!",
|
||||
"partyInformationPlaceholder": "Write a message to your Party members here!",
|
||||
"selectPartyMember": "Select a Party Member",
|
||||
|
|
|
|||
Loading…
Reference in a new issue