Merged in develop

This commit is contained in:
Keith Holliday
2017-06-27 22:23:13 -06:00
897 changed files with 50434 additions and 41464 deletions
+92 -83
View File
@@ -1,121 +1,130 @@
<template lang="pug">
#app-header.row
avatar#header-avatar(:user="user")
div
span.character-name {{user.profile.name}}
span.character-level Lvl {{user.stats.lvl}}
.progress-container.d-flex
img.icon(src="~assets/header/png/health@3x.png")
.progress
.progress-bar.bg-danger(:style="{width: `${percent(user.stats.hp, MAX_HEALTH)}%`}")
span {{user.stats.hp | round}} / {{MAX_HEALTH}}
.progress-container.d-flex
img.icon(src="~assets/header/png/experience@3x.png")
.progress
.progress-bar.bg-warning(:style="{width: `${percent(user.stats.exp, toNextLevel)}%`}")
span {{user.stats.exp | round}} / {{toNextLevel}}
.progress-container.d-flex(ng-if="user.flags.classSelected && !user.preferences.disableClasses")
img.icon(src="~assets/header/png/magic@3x.png")
.progress
.progress-bar(:style="{width: `${percent(user.stats.mp, maxMP)}%`}")
span {{user.stats.mp | round}} / {{maxMP}}
member-details(:member="user", @click="$router.push({name: 'avatar'})")
.view-party
// TODO button should open the party members modal
router-link.btn.btn-primary(:active-class="''", :to="{name: 'party'}") {{ $t('viewParty') }}
.party-members.d-flex(v-if="partyMembers && partyMembers.length > 1")
member-details(
v-for="(member, $index) in partyMembers",
:key="member._id",
v-if="member._id !== user._id && $index < 10",
:member="member",
condensed=true,
@onHover="expandMember(member._id)",
:expanded="member._id === expandedMember",
)
.no-party.d-flex.justify-content-center.text-center(v-else)
.align-self-center(v-once)
h3 {{ $t('battleWithFriends') }}
span.small-text(v-html="$t('inviteFriendsParty')")
br
// TODO link to party creation or party page if partying solo
router-link.btn.btn-primary(:active-class="''", :to="{name: 'party'}") {{ $t('startAParty') }}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
// TODO move to colors.scss if used in other places
$header-dark-background: #271B3D;
$header-text-color: #D5C8FF;
/* TODO refactor: only partially ported from SemanticUI; */
#app-header {
padding-left: 14px;
margin-top: 56px;
background: $purple-50;
height: 192px;
color: $header-text-color;
height: 204px;
color: $header-color;
flex-wrap: nowrap;
position: relative;
}
.character-name {
display: block;
font-size: 16px;
margin-top: 32px;
line-height: 1.5;
color: $white;
font-weight: bold;
.no-party, .party-members {
flex-grow: 1;
}
.character-level {
display: block;
font-size: 12px;
margin-top: 4px;
margin-bottom: 20px;
line-height: 1;
.party-members {
}
#header-avatar {
margin-top: 24px;
margin-right: 1rem;
.view-party {
position: absolute;
z-index: 10;
right: 0;
padding-right: 40px;
height: 100%;
background-image: linear-gradient(to right, rgba($purple-50, 0), $purple-50);
.btn {
margin-top: 75%;
}
}
.progress-container {
margin-bottom: 12px;
}
.no-party {
.small-text {
color: $header-color;
flex-wrap: nowrap;
}
.progress-container > span {
font-size: 12px;
margin-left: 10px;
line-height: 1em;
}
.no-party, .party-members {
flex-grow: 1;
}
.progress-container > .icon {
width: 12px;
height: 12px;
margin-right: 10px;
}
.party-members {
overflow-x: auto;
}
.progress-container > .progress {
width: 203px;
margin: 0px;
border-radius: 0px;
height: 12px;
background-color: $header-dark-background;
}
.no-party {
.small-text {
color: $header-color;
}
.progress-container > .progress > .progress-bar {
border-radius: 0px;
height: 12px;
min-width: 0px;
h3 {
color: $white;
margin-bottom: 4px;
}
button {
margin-top: 16px;
}
}
}
</style>
<script>
import Avatar from './avatar';
import { mapState } from 'client/libs/store';
import { toNextLevel } from '../../common/script/statHelpers';
import statsComputed from '../../common/script/libs/statsComputed';
import percent from '../../common/script/libs/percent';
import { mapGetters, mapActions } from 'client/libs/store';
import MemberDetails from './memberDetails';
import createPartyModal from './guilds/createPartyModal';
export default {
components: {
Avatar,
MemberDetails,
createPartyModal,
},
methods: {
percent,
data () {
return {
expandedMember: null,
};
},
computed: {
...mapState({
user: 'user.data',
MAX_HEALTH: 'constants.MAX_HEALTH',
...mapGetters({
user: 'user:data',
partyMembers: 'party:members',
}),
maxMP () {
return statsComputed(this.user).maxMP;
},
methods: {
...mapActions({
getPartyMembers: 'party:getMembers',
}),
expandMember (memberId) {
if (this.expandedMember === memberId) {
this.expandedMember = null;
} else {
this.expandedMember = memberId;
}
},
toNextLevel () { // Exp to next level
return toNextLevel(this.user.stats.lvl);
launchPartyModal () {
this.$root.$emit('show::modal', 'create-party-modal');
},
},
created () {
this.getPartyMembers();
},
};
</script>
</script>
+165 -100
View File
@@ -1,8 +1,7 @@
<template lang="pug">
nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
.navbar-header
// TODO srcset / svg images
img(src="~assets/header/png/logo@3x.png")
.logo.svg-icon(v-html="icons.logo")
.collapse.navbar-collapse
ul.navbar-nav.mr-auto
router-link.nav-item(tag="li", :to="{name: 'tasks'}", exact)
@@ -13,20 +12,18 @@ nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
router-link.dropdown-item(:to="{name: 'items'}", exact) {{ $t('items') }}
router-link.dropdown-item(:to="{name: 'equipment'}") {{ $t('equipment') }}
router-link.dropdown-item(:to="{name: 'stable'}") {{ $t('stable') }}
router-link.nav-item(tag="li", :to="{name: 'market'}", exact)
a.nav-link(v-once) {{ $t('market') }}
router-link.nav-item(tag="li", :to="{name: 'shops'}", exact)
a.nav-link(v-once) {{ $t('shops') }}
router-link.nav-item.dropdown(:to="{name: 'party'}")
a.nav-link(v-once) {{ $t('party') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'tavern'}", :class="{'active': $route.path.startsWith('/guilds')}")
a.nav-link(v-once) {{ $t('guilds') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
router-link.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
router-link.dropdown-item(:to="{name: 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'tavern'}", :class="{'active': $route.path.startsWith('/social')}")
a.nav-link(v-once) {{ $t('social') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'inbox'}") {{ $t('inbox') }}
router-link.dropdown-item(:to="{name: 'challenges'}") {{ $t('challenges') }}
router-link.dropdown-item(:to="{name: 'party'}") {{ $t('party') }}
router-link.nav-item(tag="li", :to="{name: 'challenges'}", exact)
a.nav-link(v-once) {{ $t('challenges') }}
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}")
a.nav-link(v-once) {{ $t('help') }}
.dropdown-menu
@@ -34,129 +31,197 @@ nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
router-link.dropdown-item(to="/help/report-bug") {{ $t('reportBug') }}
router-link.dropdown-item(to="/help/request-feature") {{ $t('requestAF') }}
.item-with-icon
img(src="~assets/header/png/gem@3x.png")
span {{userGems}}
.svg-icon(v-html="icons.gem")
span {{userGems | roundBigNumber}}
.item-with-icon
img(src="~assets/header/png/gold@3x.png")
span {{user.stats.gp | floor}}
.item-with-icon
img(src="~assets/header/png/notifications@3x.png")
router-link.dropdown.item-with-icon(:to="{name: 'avatar'}")
// TODO icons should be white when active
img(src="~assets/header/png/user@3x.png")
.svg-icon(v-html="icons.gold")
span {{user.stats.gp | roundBigNumber}}
.item-with-icon.item-notifications
.svg-icon(v-html="icons.notifications")
router-link.dropdown.item-with-icon.item-user(:to="{name: 'avatar'}")
.svg-icon(v-html="icons.user")
.dropdown-menu.dropdown-menu-right.user-dropdown
router-link.dropdown-item(:to="{name: 'avatar'}") {{ $t('editAvatar') }}
// .dropdown-divider
router-link.dropdown-item.edit-avatar(:to="{name: 'avatar'}")
h3 {{ user.profile.name }}
span.small-text {{ $t('editAvatar') }}
router-link.dropdown-item(:to="{name: 'inbox'}") {{ $t('inbox') }}
router-link.dropdown-item(:to="{name: 'stats'}") {{ $t('stats') }}
router-link.dropdown-item(:to="{name: 'achievements'}") {{ $t('achievements') }}
// .dropdown-divider
router-link.dropdown-item(:to="{name: 'settings'}") {{ $t('settings') }}
// .dropdown-divider
router-link.dropdown-item(to="/logout") {{ $t('logout') }}
a.nav-link.dropdown-item(to="/", @click.prevent='logout()') {{ $t('logout') }}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/utils.scss';
nav.navbar {
background: $purple-100 url(~assets/header/png/bits.png) right no-repeat;
padding: 0 1.5rem;
height: 56px;
}
.navbar-header {
margin-right: 3rem;
img {
height: 28px;
}
}
.nav-item {
.nav-link {
font-size: 16px;
color: $white;
font-weight: bold;
line-height: 1.5;
padding: 1rem 1.5rem;
transition: none;
nav.navbar {
background: $purple-100 url(~assets/svg/for-css/bits.svg) right no-repeat;
padding-left: 25px;
padding-right: 12.5px;
height: 56px;
box-shadow: 0 1px 2px 0 rgba($black, 0.24);
}
&:hover {
.navbar-header {
margin-right: 48px;
.logo {
width: 128px;
height: 28px;
}
}
.nav-item {
.nav-link {
font-size: 16px;
color: $white;
background: $purple-300;
}
}
&.active,&:hover {
.nav-link {
box-shadow: 0px -4px 0px $purple-300 inset;
}
}
}
// Make the dropdown menu open on hover
.dropdown:hover .dropdown-menu {
display: block;
margin-top: 0; // remove the gap so it doesn't close
}
.dropdown-menu:not(.user-dropdown) {
background: $purple-300;
border-radius: 0px;
border: none;
box-shadow: none;
padding: 0px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
.dropdown-item {
font-size: 16px;
box-shadow: none;
color: $white;
border: none;
&.active {
background: $purple-300;
font-weight: bold;
line-height: 1.5;
padding: 16px 24px;
transition: none;
}
&:hover {
background: $purple-200;
.nav-link {
color: $white;
background: $purple-200;
}
}
&:last-child {
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
&.active:not(:hover) {
.nav-link {
box-shadow: 0px -4px 0px $purple-300 inset;
}
}
}
}
.item-with-icon {
color: $white;
font-weight: bold;
padding: 0.75rem 0;
padding-left: 1rem;
img {
vertical-align: middle;
width: 32px;
height: 32px;
margin-right: 0.5rem;
// Make the dropdown menu open on hover
.dropdown:hover .dropdown-menu {
display: block;
margin-top: 0; // remove the gap so it doesn't close
}
.dropdown + .dropdown {
margin-left: 0px;
}
.dropdown-menu:not(.user-dropdown) {
background: $purple-200;
border-radius: 0px;
border: none;
box-shadow: none;
padding: 0px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
.dropdown-item {
font-size: 16px;
box-shadow: none;
color: $white;
border: none;
line-height: 1.5;
&.active {
background: $purple-300;
}
&:hover {
background: $purple-300;
&:last-child {
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
}
}
}
}
.item-with-icon {
color: $white;
font-size: 16px;
font-weight: normal;
padding-top: 16px;
padding-left: 16px;
.svg-icon {
vertical-align: middle;
width: 24px;
height: 24px;
margin-right: 8px;
float: left;
}
}
.item-notifications, .item-user {
padding-right: 12.5px;
padding-left: 12.5px;
color: $header-color;
transition: none;
&:hover {
color: $white;
}
.svg-icon {
margin-right: 0px;
color: inherit;
}
}
.item-notifications {
margin-left: 33.5px;
}
.item-user .edit-avatar {
h3 {
color: $gray-10;
margin-bottom: 0px;
}
.small-text {
color: $gray-200;
font-style: normal;
display: block;
}
padding-top: 16px;
padding-bottom: 16px;
}
}
</style>
<script>
import { mapState, mapGetters } from 'client/libs/store';
import gemIcon from 'assets/svg/gem.svg';
import goldIcon from 'assets/svg/gold.svg';
import notificationsIcon from 'assets/svg/notifications.svg';
import userIcon from 'assets/svg/user.svg';
import logo from 'assets/svg/logo.svg';
export default {
data () {
return {
icons: Object.freeze({
gem: gemIcon,
gold: goldIcon,
notifications: notificationsIcon,
user: userIcon,
logo,
}),
};
},
computed: {
...mapGetters({
userGems: 'user:gems',
}),
...mapState({user: 'user.data'}),
},
methods: {
logout () {
localStorage.removeItem('habit-mobile-settings');
this.$router.go('/');
},
},
};
</script>
@@ -0,0 +1,230 @@
<template lang="pug">
.form-wrapper
#top-background
.seamless_stars_varied_opacity_repeat
#login-form
.text-center
div
.svg-icon.gryphon(v-html="icons.gryphon")
div
.svg-icon.habitica-logo(v-html="icons.habiticaIcon")
.form-group.row.text-center
.col-6
.btn.btn-secondary.social-button(@click='socialAuth("facebook")', v-once)
.svg-icon.social-icon(v-html="icons.facebookIcon")
| {{this.registering ? $t('signUpWithSocial', {social: 'Facebook'}) : $t('loginWithSocial', {social: 'Facebook'})}}
.col-6
.btn.btn-secondary.social-button(@click='socialAuth("google")', v-once)
.svg-icon.social-icon(v-html="icons.googleIcon")
| {{this.registering ? $t('signUpWithSocial', {social: 'Google'}) : $t('loginWithSocial', {social: 'Google'})}}
.form-group
label(for='usernameInput', v-once) {{$t('username')}}
input#usernameInput.form-control(type='text', :placeholder='$t("usernamePlaceholder")', v-model='username')
.form-group(v-if='registering')
label(for='emailInput', v-once) {{$t('email')}}
input#emailInput.form-control(type='email', :placeholder='$t("emailPlaceholder")', v-model='email')
.form-group
label(for='passwordInput', v-once) {{$t('password')}}
input#passwordInput.form-control(type='password', :placeholder='$t("passwordPlaceholder")', v-model='password')
.form-group(v-if='registering')
label(for='confirmPasswordInput', v-once) {{$t('confirmPassword')}}
input#confirmPasswordInput.form-control(type='password', :placeholder='$t("confirmPasswordPlaceholder")', v-model='passwordConfirm')
small.form-text(v-once) {{$t('termsAndAgreement')}}
.text-center
.btn.btn-info(@click='register()', v-if='registering', v-once) {{$t('joinHabitica')}}
.btn.btn-info(@click='login()', v-if='!registering', v-once) {{$t('login')}}
#bottom-background
.seamless_mountains_demo_repeat
.midground_foreground_extended2
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.form-wrapper {
background-color: $purple-200;
}
#login-form {
margin: 0 auto;
width: 40em;
padding-top: 5em;
padding-bottom: 22.5em;
position: relative;
z-index: 1;
.gryphon {
width: 63.2px;
height: 69.4px;
}
.habitica-logo {
width: 144px;
height: 31px;
margin-top: 2em;
margin-bottom: 2em;
}
label {
color: $white;
font-weight: bold;
}
input {
margin-bottom: 2em;
border-radius: 2px;
background-color: #432874;
border-color: transparent;
height: 50px;
color: $purple-400;
::-webkit-input-placeholder { /* Chrome/Opera/Safari */
color: $purple-400;
}
::-moz-placeholder { /* Firefox 19+ */
color: $purple-400;
}
:-ms-input-placeholder { /* IE 10+ */
color: $purple-400;
}
:-moz-placeholder { /* Firefox 18- */
color: $purple-400;
}
}
.form-text {
font-size: 14px;
color: $white;
}
.social-button {
width: 100%;
text-align: left;
}
.social-icon {
margin-right: 1em;
width: 13px;
}
}
#top-background {
.seamless_stars_varied_opacity_repeat {
background-image: url('~assets/images/auth/seamless_stars_varied_opacity.png');
background-repeat: repeat-x;
position: absolute;
height: 500px;
width: 1600px;
}
}
#bottom-background {
position: relative;
.seamless_mountains_demo_repeat {
background-image: url('~assets/images/auth/seamless_mountains_demo.png');
background-repeat: repeat-x;
width: 1600px;
height: 500px;
position: absolute;
z-index: 0;
bottom: 0;
}
.midground_foreground_extended2 {
background-image: url('~assets/images/auth/midground_foreground_extended2.png');
position: relative;
width: 1500px;
height: 150px;
}
}
</style>
<script>
import hello from 'hellojs';
import gryphon from 'assets/svg/gryphon.svg';
import habiticaIcon from 'assets/svg/habitica-logo.svg';
import facebookIcon from 'assets/svg/facebook.svg';
import googleIcon from 'assets/svg/google.svg';
export default {
data () {
let data = {
username: '',
email: '',
password: '',
passwordConfirm: '',
registering: true,
};
data.icons = Object.freeze({
gryphon,
habiticaIcon,
facebookIcon,
googleIcon,
});
return data;
},
mounted () {
if (this.$route.path.startsWith('/login')) {
this.registering = false;
}
hello.init({
facebook: '',
// windows: WINDOWS_CLIENT_ID,
google: '',
});
},
methods: {
async register () {
if (this.password !== this.passwordConfirm) {
alert('Passwords must match');
return;
}
// @TODO: implement langauge and invite accepting
// var url = ApiUrl.get() + "/api/v3/user/auth/local/register";
// if (location.search && location.search.indexOf('Invite=') !== -1) { // matches groupInvite and partyInvite
// url += location.search;
// }
//
// if($rootScope.selectedLanguage) {
// var toAppend = url.indexOf('?') !== -1 ? '&' : '?';
// url = url + toAppend + 'lang=' + $rootScope.selectedLanguage.code;
// }
await this.$store.dispatch('auth:register', {
username: this.username,
email: this.email,
password: this.password,
passwordConfirm: this.passwordConfirm,
});
this.$router.go('/tasks');
},
async login () {
await this.$store.dispatch('auth:login', {
username: this.username,
// email: this.email,
password: this.password,
});
this.$router.go('/tasks');
},
async socialAuth (network) {
let auth = await hello(network).login({scope: 'email'});
await this.$store.dispatch('auth:socialAuth', {
auth,
});
this.$router.go('/tasks');
},
},
};
</script>
+79 -34
View File
@@ -2,51 +2,57 @@
.avatar(:style="{width, height, paddingTop}", :class="backgroundClass")
.character-sprites
template(v-if="!avatarOnly" v-once)
// Mount
span(v-if="user.items.currentMount", :class="'Mount_Body_' + user.items.currentMount")
// Mount Body
span(v-if="member.items.currentMount", :class="'Mount_Body_' + member.items.currentMount")
// Buffs that cause visual changes to avatar: Snowman, Ghost, Flower, etc
template(v-for="(klass, item) in visualBuffs")
span(v-if="user.stats.buffs[item]", :class="klass")
span(v-if="member.stats.buffs[item]", :class="klass")
// Show flower ALL THE TIME!!!
// See https://github.com/HabitRPG/habitrpg/issues/7133
span(:class="'hair_flower_' + user.preferences.hair.flower")
// See https://github.com/HabitRPG/habitica/issues/7133
span(:class="'hair_flower_' + member.preferences.hair.flower")
// Show avatar only if not currently affected by visual buff
template(v-if!="!user.stats.buffs.snowball && !user.stats.buffs.spookySparkles && !user.stats.buffs.shinySeed && !user.stats.buffs.seafoam")
span(:class="'chair_' + user.preferences.chair")
span(:class="user.items.gear[costumeClass].back")
template(v-if!="!member.stats.buffs.snowball && !member.stats.buffs.spookySparkles && !member.stats.buffs.shinySeed && !member.stats.buffs.seafoam")
span(:class="'chair_' + member.preferences.chair")
span(:class="member.items.gear[costumeClass].back")
span(:class="skinClass")
span(:class="user.preferences.size + '_shirt_' + user.preferences.shirt")
span(:class="user.preferences.size + '_' + user.items.gear[costumeClass].armor")
span(:class="user.items.gear[costumeClass].back_collar")
span(:class="user.items.gear[costumeClass].body")
span(:class="member.preferences.size + '_shirt_' + member.preferences.shirt")
span(:class="member.preferences.size + '_' + member.items.gear[costumeClass].armor")
span(:class="member.items.gear[costumeClass].back_collar")
span(:class="member.items.gear[costumeClass].body")
span.head_0
template(v-for="type in ['base', 'bangs', 'mustache', 'beard']")
span(:class="'hair_' + type + '_' + user.preferences.hair[type] + '_' + user.preferences.hair.color")
span(:class="user.items.gear[costumeClass].eyewear")
span(:class="user.items.gear[costumeClass].head")
span(:class="user.items.gear[costumeClass].headAccessory")
span(:class="'hair_flower_' + user.preferences.hair.flower")
span(:class="user.items.gear[costumeClass].shield")
span(:class="user.items.gear[costumeClass].weapon")
span(:class="'hair_' + type + '_' + member.preferences.hair[type] + '_' + member.preferences.hair.color")
span(:class="member.items.gear[costumeClass].eyewear")
span(:class="member.items.gear[costumeClass].head")
span(:class="member.items.gear[costumeClass].headAccessory")
span(:class="'hair_flower_' + member.preferences.hair.flower")
span(:class="member.items.gear[costumeClass].shield")
span(:class="member.items.gear[costumeClass].weapon")
// Resting
span.zzz(v-if="user.preferences.sleep")
span.zzz(v-if="member.preferences.sleep")
template(v-if="!avatarOnly" v-once)
// Mount Head
span(v-if="user.items.currentMount", :class="'Mount_Head_' + user.items.currentMount")
span(v-if="member.items.currentMount", :class="'Mount_Head_' + member.items.currentMount")
// Pet
span.current-pet(v-if="user.items.currentPet", :class="'Pet-' + user.items.currentPet")
span.current-pet(v-if="member.items.currentPet", :class="'Pet-' + member.items.currentPet")
.class-badge.d-flex.justify-content-center(v-if="hasClass")
.align-self-center.svg-icon(v-html="icons[member.stats.class]")
</template>
<style scoped>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.avatar {
width: 140px;
height: 147px;
image-rendering: pixelated;
position: relative;
cursor: pointer;
}
.character-sprites {
@@ -60,15 +66,38 @@
}
.current-pet {
bottom: 21px;
left: 20px;
bottom: 0px;
left: 0px;
}
.class-badge {
$badge-size: 32px;
position: absolute;
left: calc(50% - (16px));
bottom: -($badge-size / 2);
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;
}
}
</style>
<script>
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 {
props: {
user: {
member: {
type: Object,
required: true,
},
@@ -85,22 +114,38 @@ export default {
default: 147,
},
},
data () {
return {
icons: Object.freeze({
warrior: warriorIcon,
rogue: rogueIcon,
healer: healerIcon,
wizard: wizardIcon,
}),
};
},
computed: {
hasClass () {
return this.$store.getters['members:hasClass'](this.member);
},
isBuffed () {
return this.$store.getters['members:isBuffed'](this.member);
},
paddingTop () {
let val = '28px';
if (!this.avatarOnly) {
if (this.user.items.currentPet) val = '24.5px';
if (this.user.items.currentMount) val = '0px';
if (this.member.items.currentPet) val = '24.5px';
if (this.member.items.currentMount) val = '0px';
}
return val;
},
backgroundClass () {
let background = this.user.preferences.background;
let background = this.member.preferences.background;
if (background && !this.avatarOnly) {
return `background_${this.user.preferences.background}`;
return `background_${this.member.preferences.background}`;
}
return '';
@@ -109,17 +154,17 @@ export default {
return {
snowball: 'snowman',
spookySparkles: 'ghost',
shinySeed: `avatar_floral_${this.user.stats.class}`,
shinySeed: `avatar_floral_${this.member.stats.class}`,
seafoam: 'seafoam_star',
};
},
skinClass () {
let baseClass = `skin_${this.user.preferences.skin}`;
let baseClass = `skin_${this.member.preferences.skin}`;
return `${baseClass}${this.user.preferences.sleep ? '_sleep' : ''}`;
return `${baseClass}${this.member.preferences.sleep ? '_sleep' : ''}`;
},
costumeClass () {
return this.user.preferences.costume ? 'costume' : 'equipped';
return this.member.preferences.costume ? 'costume' : 'equipped';
},
},
};
@@ -0,0 +1,121 @@
<template lang="pug">
b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
.header-wrap(slot="modal-header")
h2 Image Here
.row
.col-12.text-center
h2(v-once) {{$t('playInPartyTitle')}}
p(v-once) {{$t('playInPartyDescription')}}
.row.grey-row
.col-6.text-center
img
h3(v-once) {{$t('startYourOwnPartyTitle')}}
p(v-once) {{$t('startYourOwnPartyDescription')}}
button.btn.btn-primary(v-once) {{$t('createParty')}}
.col-6
div.text-center
img
h3(v-once) {{$t('wantToJoinPartyTitle')}}
p(v-once) {{$t('wantToJoinPartyDescription')}}
button.btn.btn-primary(v-once, @click='shareUserIdShown = !shareUserIdShown') {{$t('shartUserId')}}
.share-userid-options(v-if="shareUserIdShown")
.option-item(v-once)
.svg-icon(v-html="icons.copy")
| {{$t('copy')}}
.option-item(v-once)
.svg-icon(v-html="icons.greyBadge")
| {{$t('lookingForGroup')}}
.option-item(v-once)
.svg-icon(v-html="icons.qrCode")
| {{$t('qrCode')}}
.option-item(v-once)
.svg-icon.facebook(v-html="icons.facebook")
| {{$t('facebook')}}
.option-item(v-once)
.svg-icon(v-html="icons.twitter")
| {{$t('twitter')}}
</template>
<style lang='scss'>
@import '~client/assets/scss/colors.scss';
.modal-body {
padding-bottom: 0;
padding-top: 0;
}
.grey-row {
background-color: $gray-700;
padding: 2em;
border-radius: 0px 0px 2px 2px;
}
.share-userid-options {
background-color: $white;
border-radius: 2px;
width: 180px;
position: absolute;
top: -8em;
left: 4.8em;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
.option-item {
padding: 1em;
.svg-icon {
margin-right: .5em;
}
.facebook svg {
width: 15px;
height: 15px;
}
}
.option-item:hover {
background-color: $header-color;
color: $purple-200;
cursor: pointer;
}
}
</style>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
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';
export default {
components: {
bModal,
},
data () {
return {
icons: Object.freeze({
copy: copyIcon,
greyBadge: greyBadgeIcon,
qrCode: qrCodeIcon,
facebook: facebookIcon,
twitter: twitterIcon,
}),
shareUserIdShown: false,
};
},
methods: {
createParty () {
// group.loadingParty = true;
//
// if (!group.name) group.name = env.t('possessiveParty', {name: User.user.profile.name});
// Groups.Group.create(group)
// .then(function(response) {
// Analytics.updateUser({'partyID': $scope.group ._id, 'partySize': 1});
// $rootScope.hardRedirect('/#/options/groups/party');
// });
},
},
};
</script>
@@ -8,7 +8,7 @@
.float-right
span.dropdown-label {{ $t('sortBy') }}
b-dropdown(:text="$t('sort')", right=true)
b-dropdown-item(v-for='sortOption in sortOptions', @click='sort(sortOption.value)') {{sortOption.text}}
b-dropdown-item(v-for='sortOption in sortOptions', :key="sortOption.value", @click='sort(sortOption.value)') {{sortOption.text}}
.col-md-12
public-guild-item(v-for="guild in filteredGuilds", :key='guild._id', :guild="guild", :display-leave='true')
mugen-scroll(
@@ -1,10 +1,10 @@
<template lang="pug">
b-modal#guild-form(:title="title", :hide-footer="true")
b-modal#guild-form(:title="title", :hide-footer="true", size='lg')
form(@submit.stop.prevent="submit")
.form-group
label
strong(v-once) {{$t('name')}}*
b-form-input(type="text", placeholder="$t('newGuildPlaceHolder')", v-model="newGuild.name")
b-form-input(type="text", :placeholder="$t('newGuildPlaceHolder')", v-model="newGuild.name")
.form-group(v-if='newGuild.id')
label
@@ -20,7 +20,7 @@
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('onlyLeaderCreatesChallenges') }}
b-tooltip.icon(:content="$t('privateDescription')")
img(src='~assets/guilds/information.svg')
.svg-icon(v-html='icons.information')
br
label.custom-control.custom-checkbox
@@ -29,15 +29,15 @@
span.custom-control-description(v-once) {{ $t('guildLeaderCantBeMessaged') }}
br
label.custom-control.custom-checkbox
label.custom-control.custom-checkbox(v-if='!creatingParty')
input.custom-control-input(type="checkbox", v-model="newGuild.privateGuild")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('privateGuild') }}
b-tooltip.icon(:content="$t('privateDescription')")
img(src='~assets/guilds/information.svg')
.svg-icon(v-html='icons.information')
br
label.custom-control.custom-checkbox
label.custom-control.custom-checkbox(v-if='!creatingParty')
input.custom-control-input(type="checkbox", v-model="newGuild.allowGuildInvationsFromNonMembers")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('allowGuildInvationsFromNonMembers') }}
@@ -46,14 +46,18 @@
label
strong(v-once) {{$t('description')}}*
div.description-count {{charactersRemaining}} {{ $t('charactersRemaining') }}
b-form-input(type="text", textarea :placeholder="$t('guildDescriptionPlaceHolder')", v-model="newGuild.description")
b-form-input(type="text", textarea :placeholder="creatingParty ? $t('partyDescriptionPlaceHolder') : $t('guildDescriptionPlaceHolder')", v-model="newGuild.description")
.form-group(v-if='newGuild.id')
.form-group(v-if='newGuild.id && !creatingParty')
label
strong(v-once) {{$t('guildInformation')}}*
b-form-input(type="text", textarea :placeholder="$t('guildInformationPlaceHolder')", v-model="newGuild.guildInformation")
b-form-input(type="text", textarea, :placeholder="$t('guildInformationPlaceHolder')", v-model="newGuild.guildInformation")
.form-group(style='position: relative;')
.form-group(v-if='creatingParty && !newGuild.id')
span
toggleSwitch(:label="$t('inviteMembersNow')", v-model='inviteMembers')
.form-group(style='position: relative;', v-if='!creatingParty')
label
strong(v-once) {{$t('categories')}}*
div.category-wrap(@click.prevent="toggleCategorySelect")
@@ -70,22 +74,35 @@
span.custom-control-description(v-once) {{ $t(group.label) }}
button.btn.btn-primary(@click.prevent="toggleCategorySelect") {{$t('close')}}
.form-group(v-if='inviteMembers && !newGuild.id')
label
strong(v-once) Invite via Email or User ID
p Invite users via a valid email or 36-digit User ID. If an email isnt registered yet, well invite them to join.
div
div(v-for='(member, index) in membersToInvite')
input(type='text', v-model='member.value')
button(@click.prevent='removeMemberToInvite(index)') Remove
div
input(type='text', placeholder='Email address or User ID', v-model='newMemberToInvite.value')
button(@click.prevent='addMemberToInvite()') Add
.form-group.text-center
div.item-with-icon
img(src="~assets/guilds/green-gem.svg")
div.item-with-icon(v-if='!creatingParty')
.svg-icon(v-html="icons.gem")
span.count 4
button.btn.btn-primary.btn-md(v-if='!newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ $t('createGuild') }}
button.btn.btn-primary.btn-md(v-if='newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ $t('updateGuild') }}
.gem-description(v-once) {{ $t('guildGemCostInfo') }}
button.btn.btn-primary.btn-md(v-if='!newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ creatingParty ? $t('createParty') : $t('createGuild') }}
button.btn.btn-primary.btn-md(v-if='newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ creatingParty ? $t('updateParty') : $t('updateGuild') }}
.gem-description(v-once, v-if='!creatingParty') {{ $t('guildGemCostInfo') }}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
textarea {
height: 150px;
}
.description-count, .gem-description {
font-size: 12px;
line-height: 1.33;
@@ -101,31 +118,6 @@
margin-top: 1em;
}
.category-box {
padding: 1em;
max-width: 400px;
position: absolute;
top: -480px;
padding: 2em;
border-radius: 2px;
background-color: $white;
box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1);
}
.category-label {
min-width: 100px;
border-radius: 100px;
background-color: $gray-600;
padding: .5em;
display: inline-block;
margin-right: .5em;
font-size: 12px;
font-weight: 500;
line-height: 1.33;
text-align: center;
color: $gray-300;
}
.item-with-icon {
display: inline-block;
@@ -146,21 +138,6 @@
margin-top: 1em;
}
.category-select {
border-radius: 2px;
background-color: $white;
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
padding: 1em;
}
.category-select:hover {
cursor: pointer;
}
.category-wrap {
margin-top: .5em;
}
.icon {
margin-left: .5em;
display: inline-block;
@@ -175,6 +152,10 @@ import bFormCheckbox from 'bootstrap-vue/lib/components/form-checkbox';
import bFormSelect from 'bootstrap-vue/lib/components/form-select';
import bTooltip from 'bootstrap-vue/lib/components/tooltip';
import toggleSwitch from 'client/components/ui/toggleSwitch';
import gemIcon from 'assets/svg/gem.svg';
import informationIcon from 'assets/svg/information.svg';
export default {
components: {
bModal,
@@ -183,6 +164,7 @@ export default {
bFormCheckbox,
bFormSelect,
bTooltip,
toggleSwitch,
},
data () {
let data = {
@@ -248,6 +230,13 @@ export default {
],
showCategorySelect: false,
members: ['one', 'two'],
creatingParty: true,
inviteMembers: false,
newMemberToInvite: {
value: '',
type: '',
},
membersToInvite: [],
};
let hashedCategories = {};
@@ -256,6 +245,11 @@ export default {
});
data.categoriesHashByKey = hashedCategories;
data.icons = Object.freeze({
gem: gemIcon,
information: informationIcon,
});
return data;
},
mounted () {
@@ -274,11 +268,23 @@ export default {
return 500 - this.newGuild.description.length;
},
title () {
if (this.creatingParty) return this.$t('createParty');
if (!this.newGuild.id) return this.$t('createGuild');
return this.$t('updateGuild');
},
},
methods: {
addMemberToInvite () {
// @TODO: determine type
this.membersToInvite.push(this.newMemberToInvite);
this.newMemberToInvite = {
value: '',
type: '',
};
},
removeMemberToInvite (index) {
this.membersToInvite.splice(index, 1);
},
toggleCategorySelect () {
this.showCategorySelect = !this.showCategorySelect;
},
@@ -303,7 +309,7 @@ export default {
}
// @TODO: Add proper notifications
if (!confirm(this.$t('confirmGuild'))) return;
if (!this.newGuild.id && !confirm(this.$t('confirmGuild'))) return;
if (!this.newGuild.privateGuild) {
this.newGuild.privacy = 'public';
@@ -334,6 +340,8 @@ export default {
privateGuild: true,
allowGuildInvationsFromNonMembers: true,
};
this.$root.$emit('hide::modal', 'guild-form');
},
},
};
+452 -89
View File
@@ -1,30 +1,26 @@
<template lang="pug">
// TODO this is necessary until we have a way to wait for data to be loaded from the server
.row(v-if="guild")
.row(v-if="group")
group-form-modal
.clearfix.col-8
.row
.col-6.title-details
h1 {{group.name}}
strong.float-left(v-once) {{$t('groupLeader')}}
span.float-left(v-once, v-if='group.leader.profile') : {{group.leader.profile.name}}
.col-6
.float-left
h2 {{guild.name}}
strong.float-left(v-once) {{$t('groupLeader')}}
span.float-left : {{guild.leader.profile.name}}
.col-6
.float-right
.row.icon-row
.col-6
img.icon.shield(src="~assets/guilds/gold-guild-badge.svg")
span.number {{guild.memberCount}}
div(v-once) {{ $t('guildMembers') }}
.col-6
.item-with-icon
img.icon.gem(src="~assets/header/png/gem@3x.png")
span.number {{guild.memberCount}}
div(v-once) {{ $t('guildBank') }}
.row.icon-row
.col-4(v-bind:class="{ 'offset-8': isParty }")
members-modal(:group='group', v-if='isMember')
.col-6(v-if='!isParty')
.item-with-icon
.svg-icon.gem(v-html="icons.gem")
span.number {{group.memberCount}}
div(v-once) {{ $t('guildBank') }}
.row.chat-row
.col-12
h3(v-once) {{ $t('chat') }}
textarea(placeholder="$('chatPlaceHolder')")
textarea(:placeholder="$t('chatPlaceHolder')")
button.btn.btn-secondary.send-chat.float-right(v-once) {{ $t('send') }}
.hr
@@ -32,9 +28,9 @@
.row
.col-md-2
img.icon(src="~assets/chat/like.svg")
.svg-icon(v-html="icons.like")
.col-md-10
.card(v-for="msg in guild.chat", :key="msg.id")
.card(v-for="msg in group.chat", :key="msg.id")
.card-block
h3.leader Character name
span 2 hours ago
@@ -44,66 +40,160 @@
.text {{msg.text}}
hr
span.action(v-once)
img.icon(src="~assets/chat/like.svg")
.svg-icon(v-html="icons.like")
| {{$t('like')}}
span.action(v-once)
img.icon(src="~assets/chat/copy.svg")
.svg-icon(v-html="icons.copy")
| {{$t('copyAsTodo')}}
span.action(v-once)
img.icon(src="~assets/chat/report.svg")
.svg-icon(v-html="icons.report")
| {{$t('report')}}
span.action(v-once)
img.icon(src="~assets/chat/delete.svg")
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
span.action.float-right
img.icon(src="~assets/chat/liked.svg")
.svg-icon(v-html="icons.liked")
| +3
.col-md-4.sidebar
.guild-background.row
.col-6
p Image here
p(v-if='!isParty') Image here
.col-6
members-modal(:group='guild')
br
button.btn.btn-primary(v-once) {{$t('joinGuild')}}
br
button.btn.float-left(:class="[isMember ? 'btn-danger' : 'btn-success']") {{ isMember ? $t('leave') : $t('join') }}
br
button.btn.btn-primary(v-once) {{$t('inviteToGuild')}}
br
button.btn.btn-primary(v-once) {{$t('messageGuildLeader')}}
br
button.btn.btn-primary(v-once) {{$t('donateGems')}}
br
button.btn.btn-primary(b-btn, @click="updateGuild", v-once) {{ $t('updateGuild') }}
div
h3(v-once) {{ $t('description') }}
p(v-once) {{ guild.description }}
p Life hacks are tricks, shortcuts, or methods that help increase productivity, efficiency, health, and so on. Generally, they get you to a better state of life. Life hacking is the process of utilizing and implementing these secrets. And, in this guild, we want to help everyone discover these improved ways of doing things.
div
h3(v-once) {{$t('guildInformation')}}
h4 Welcome
p Below are some resources that some members might find useful. Consider checking them out before posting any questions, as they just might help answer some of them! Feel free to share your life hacks in the guild chat, or ask any questions that you might have. Please peruse at your leisure, and remember: this guild is meant to help guide you in the right direction. Only you will know what works best for you.
div
h3 Challenges
.card
h4 Challenge
.row
.col-8
p Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla scelerisque ultrices libero.
.col-4
.row
.col-md-12
span Tag
span 100
.button-container
button.btn.btn-success(class='btn-success', v-if='isLeader') {{ $t('upgrade') }}
.button-container
button.btn.btn-primary(b-btn, @click="updateGuild", v-once, v-if='isLeader') {{ $t('edit') }}
.button-container
button.btn.btn-success(class='btn-success', v-if='!isMember') {{ $t('join') }}
.button-container
button.btn.btn-primary(v-once) {{$t('invite')}}
.button-container
button.btn.btn-primary(v-once, v-if='!isLeader') {{$t('messageGuildLeader')}}
.button-container
button.btn.btn-primary(v-once, v-if='isMember && !isParty') {{$t('donateGems')}}
.section-header
.row
.col-10
h3(v-once) {{ $t('questDetailsTitle') }}
.col-2
.toggle-up(@click="sections.quest = !sections.quest", v-if="sections.quest")
.svg-icon(v-html="icons.upIcon")
.toggle-down(@click="sections.quest = !sections.quest", v-if="!sections.quest")
.svg-icon(v-html="icons.downIcon")
.section(v-if="sections.quest")
.row.no-quest-section(v-if='isParty && !onPendingQuest && !onActiveQuest')
.col-12.text-center
.svg-icon(v-html="icons.questIcon")
h4(v-once) {{ $t('yourNotOnQuest') }}
p(v-once) {{ $t('questDescription') }}
button.btn.btn-secondary(v-once, @click="openStartQuestModal()") {{ $t('startAQuest') }}
owned-quests-modal(:group='this.group')
.row.quest-active-section(v-if='isParty && onPendingQuest && !onActiveQuest')
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")
h3(v-once) {{ questData.text() }}
.quest-box.svg-icon(v-html="icons.questBackground")
.collect-info(v-if='questData.collect')
.row(v-for='(value, key) in questData.collect')
.col-2
div(:class="'quest_' + questData.key + '_' + key")
.col-10
strong {{value.text()}}
.collect-progress-bar
strong {{group.quest.progress.collect[key]}} / {{value.count}}
.boss-info(v-if='questData.boss')
.row
.col-6
h4.float-left(v-once) {{ questData.boss.name() }}
.col-6
span.float-right(v-once) {{ $t('participants') }}
.row
.col-12
.boss-health-bar
.row.boss-details
.col-6
span.float-left
| {{group.quest.progress.hp}} / {{questData.boss.hp}}
.col-6
span.float-right 30 pending damage
button.btn.btn-secondary(v-once, @click="questAbort()") {{ $t('abort') }}
.section-header
.row
.col-10
h3(v-once) {{ $t('description') }}
.col-2
.toggle-up(@click="sections.description = !sections.description", v-if="sections.description")
.svg-icon(v-html="icons.upIcon")
.toggle-down(@click="sections.description = !sections.description", v-if="!sections.description")
.svg-icon(v-html="icons.downIcon")
.section(v-if="sections.description")
p(v-once) {{ group.description }}
p Life hacks are tricks, shortcuts, or methods that help increase productivity, efficiency, health, and so on. Generally, they get you to a better state of life. Life hacking is the process of utilizing and implementing these secrets. And, in this guild, we want to help everyone discover these improved ways of doing things.
.section-header
.row
.col-10
h3(v-once) {{ $t('guildInformation') }}
.col-2
.toggle-up(@click="sections.information = !sections.information", v-if="sections.information")
.svg-icon(v-html="icons.upIcon")
.toggle-down(@click="sections.information = !sections.information", v-if="!sections.information")
.svg-icon(v-html="icons.downIcon")
.section(v-if="sections.information")
h4 Welcome
p Below are some resources that some members might find useful. Consider checking them out before posting any questions, as they just might help answer some of them! Feel free to share your life hacks in the guild chat, or ask any questions that you might have. Please peruse at your leisure, and remember: this guild is meant to help guide you in the right direction. Only you will know what works best for you.
.section-header.challenge
.row
.col-10.information-header
h3(v-once)
| {{ $t('challenges') }}
b-tooltip.icon.tooltip-wrapper(:content="$t('privateDescription')")
.svg-icon(v-html='icons.information')
.col-2
.toggle-up(@click="sections.challenges = !sections.challenges", v-if="sections.challenges")
.svg-icon(v-html="icons.upIcon")
.toggle-down(@click="sections.challenges = !sections.challenges", v-if="!sections.challenges")
.svg-icon(v-html="icons.downIcon")
.section(v-if="sections.challenges")
.row.no-quest-section(v-if='!hasChallenges')
.col-12.text-center
.svg-icon(v-html="icons.challengeIcon")
h4(v-once) {{ $t('haveNoChallenges') }}
p(v-once) {{ $t('challengeDescription') }}
button.btn.btn-secondary(v-once) {{ $t('createChallenge') }}
div.text-center
button.btn.btn-primary(class='btn-danger', v-if='isMember') {{ $t('leave') }}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.button-container {
margin-bottom: 1em;
button {
width: 100%;
}
}
.item-with-icon {
border-radius: 2px;
background-color: #ffffff;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
padding: 1em;
}
.sidebar {
background-color: $gray-600;
padding-top: 2em;
}
.card {
@@ -123,13 +213,11 @@
.guild-background {
background-image: linear-gradient(to bottom, rgba($gray-600, 0), $gray-600);
height: 300px;
}
textarea {
height: 150px;
width: 100%;
border-radius: 2px;
background-color: $white;
border: solid 1px $gray-400;
font-size: 16px;
@@ -139,15 +227,18 @@
padding: .5em;
}
.icon.shield, .icon.gem {
.svg-icon.shield, .svg-icon.gem {
width: 40px;
margin-right: 1em;
}
.title-details {
padding-top: 1em;
padding-left: 1em;
}
.icon-row {
width: 200px;
margin-top: 3em;
margin-right: 3em;
margin-top: 1em;
.number {
font-size: 22px;
@@ -199,39 +290,190 @@
span.action .icon {
margin-right: .3em;
}
.no-quest-section {
padding: 2em;
color: $gray-300;
h4 {
color: $gray-300;
}
p {
margin-bottom: 2em;
}
.svg-icon {
height: 30px;
width: 30px;
margin-bottom: 2em;
}
}
.information-header {
h3, .tooltip-wrapper {
display: inline-block;
}
.tooltip-wrapper {
margin-left: 2.2em;
}
}
.quest-active-section {
.quest-box {
height: 100px;
width: 100%;
svg: {
width: 100%;
height: 100%;
}
}
.boss-info, .collect-info {
position: relative;
top: -89px;
left: 15px;
width: 32em;
text-align: left;
}
}
.section-header {
border-top: 1px solid #e1e0e3;
margin-top: 1em;
padding-top: 1em;
}
.section-header.challenge {
border-bottom: 1px solid #e1e0e3;
margin-bottom: 1em;
padding-bottom: 1em;
}
.toggle-up, .toggle-down {
cursor: pointer;
}
.quest-boss {
margin: 0 auto;
}
.boss-health-bar {
width: 80%;
background-color: red;
height: 15px;
margin-bottom: .5em;
}
.collect-progress-bar {
background-color: #24cc8f;
height: 15px;
width: 80%;
}
</style>
<script>
import groupUtilities from 'client/mixins/groupsUtilities';
import { mapState } from 'client/libs/store';
import membersModal from './membersModal';
import ownedQuestsModal from './ownedQuestsModal';
import { TAVERN_ID } from 'common/script/constants';
import quests from 'common/script/content/quests';
import percent from 'common/script/libs/percent';
import groupFormModal from './groupFormModal';
import bCollapse from 'bootstrap-vue/lib/components/collapse';
import bCard from 'bootstrap-vue/lib/components/card';
import bToggle from 'bootstrap-vue/lib/directives/toggle';
import bTooltip from 'bootstrap-vue/lib/components/tooltip';
import deleteIcon from 'assets/svg/delete.svg';
import copyIcon from 'assets/svg/copy.svg';
import likeIcon from 'assets/svg/like.svg';
import likedIcon from 'assets/svg/liked.svg';
import reportIcon from 'assets/svg/report.svg';
import gemIcon from 'assets/svg/gem.svg';
import questIcon from 'assets/svg/quest.svg';
import challengeIcon from 'assets/svg/challenge.svg';
import informationIcon from 'assets/svg/information.svg';
import questBackground from 'assets/svg/quest-background-border.svg';
import upIcon from 'assets/svg/up.svg';
import downIcon from 'assets/svg/down.svg';
export default {
mixins: [groupUtilities],
props: ['guildId'],
components: {
membersModal,
ownedQuestsModal,
bCollapse,
bCard,
bTooltip,
groupFormModal,
},
directives: {
bToggle,
},
data () {
return {
guild: null,
group: null,
icons: Object.freeze({
like: likeIcon,
copy: copyIcon,
report: reportIcon,
delete: deleteIcon,
gem: gemIcon,
liked: likedIcon,
questIcon,
challengeIcon,
information: informationIcon,
questBackground,
upIcon,
downIcon,
}),
questData: {},
selectedQuest: {},
sections: {
quest: true,
description: true,
information: true,
challenges: true,
},
};
},
computed: {
...mapState({user: 'user.data'}),
isParty () {
return this.$route.path.startsWith('/party');
},
onPendingQuest () {
return Boolean(this.group.quest.key) && !this.group.quest.active;
},
onActiveQuest () {
return this.group.quest.active;
},
isLeader () {
return this.user._id === this.group.leader._id;
},
isMember () {
return this.isMemberOfGroup(this.user, this.guild);
return this.isMemberOfGroup(this.user, this.group);
},
canEditQuest () {
let isQuestLeader = this.group.quest && this.group.quest.leader === this.user._id;
return isQuestLeader;
},
isMemberOfPendingQuest () {
let userid = this.user._id;
let group = this.guild;
let group = this.group;
if (!group.quest || !group.quest.members) return false;
if (group.quest.active) return false; // quest is started, not pending
return userid in group.quest.members && group.quest.members[userid] !== false;
},
isMemberOfRunningQuest () {
let userid = this.user._id;
let group = this.guild;
let group = this.group;
if (!group.quest || !group.quest.members) return false;
if (!group.quest.active) return false; // quest is pending, not started
return group.quest.members[userid];
@@ -251,8 +493,28 @@ export default {
let userIsManager = Boolean(group.managers[userId]);
return leader || userIsManager;
},
hasChallenges () {
if (!this.group.challenges) return false;
return this.group.challenges.length === 0;
},
bossHpPercent () {
return percent(this.group.quest.progress.hp, this.questData.boss.hp);
},
},
created () {
if (this.isParty) {
this.groupId = 'party';
// @TODO: Set up from old client. Decide what we need and what we don't
// Check Desktop notifs
// Mark Chat seen
// Load members
// Load invites
// Load challenges
// Load group tasks for group plan
// Load approvals for group plan
} else if (this.$route.path.startsWith('/guilds/tavern')) {
this.groupId = TAVERN_ID;
}
this.fetchGuild();
},
watch: {
@@ -261,35 +523,28 @@ export default {
},
methods: {
updateGuild () {
this.$store.state.editingGroup = this.guild;
this.$store.state.editingGroup = this.group;
this.$root.$emit('show::modal', 'guild-form');
},
async fetchGuild () {
this.guild = await this.$store.dispatch('guilds:getGroup', {groupId: this.guildId});
this.guild.chat = [
{
text: '@CharacterName Vestibulum ultricies, lorem non bibendum consequat, nisl lacus semper nulla, hendrerit dignissim ipsum erat eu odio. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla at aliquet urna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla non est ut nisl interdum tincidunt in eu dui. Proin condimentum a.',
},
];
},
editGroup () {
// @TODO: Open up model
},
save () {
let newLeader = this.group._newLeader && this.group._newLeader._id;
if (newLeader) {
this.group.leader = newLeader;
let group = await this.$store.dispatch('guilds:getGroup', {groupId: this.groupId});
if (this.isParty) {
this.$store.party = group;
this.group = this.$store.party;
this.checkForAchievements();
this.questData = quests.quests[this.group.quest.key];
return;
}
// Groups.Group.update(group);
this.group = group;
},
deleteAllMessages () {
if (confirm(this.$t('confirmDeleteAllMessages'))) {
// User.clearPMs();
}
},
openStartQuestModal () {
this.$root.$emit('show::modal', 'owned-quests-modal');
},
// inviteOrStartParty (group) {
// Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'});
@@ -312,6 +567,114 @@ export default {
// },
// });
// },
checkForAchievements () {
// Checks if user's party has reached 2 players for the first time.
if (!this.user.achievements.partyUp && this.group.memberCount >= 2) {
// @TODO
// User.set({'achievements.partyUp':true});
// Achievement.displayAchievement('partyUp');
}
// Checks if user's party has reached 4 players for the first time.
if (!this.user.achievements.partyOn && this.group.memberCount >= 4) {
// @TODO
// User.set({'achievements.partyOn':true});
// Achievement.displayAchievement('partyOn');
}
},
// @TODO: This should be moved to notifications component
async join () {
if (this.group.cancelledPlan && !confirm(this.$t('aboutToJoinCancelledGroupPlan'))) {
return;
}
await this.$store.dispatch('guilds:join', {groupId: this.group._id});
// @TODO: Implement
// User.sync();
// Analytics.updateUser({'partyID': party.id});
// $rootScope.hardRedirect('/#/options/groups/party');
},
clickLeave () {
// Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Leave Party'});
// @TODO: Get challenges and ask to keep or remove
let keep = true;
this.leave(keep);
},
async leave (keepTasks) {
let keepChallenges = 'remain-in-challenges';
await this.$store.dispatch('guilds:leave', {
groupId: this.group._id,
keep: keepTasks,
keepChallenges,
});
// @TODO: Implement
// Analytics.updateUser({'partySize':null,'partyID':null});
// User.sync().then(function () {
// $rootScope.hardRedirect('/#/options/groups/party');
// });
},
// @TODO: Move to notificatin component
async leaveOldPartyAndJoinNewParty () {
let newPartyName = 'where does this come from';
if (!confirm(`Are you sure you want to delete your party and join${newPartyName}?`)) return;
let keepChallenges = 'remain-in-challenges';
await this.$store.dispatch('guilds:leave', {
groupId: this.group._id,
keep: false,
keepChallenges,
});
await this.$store.dispatch('guilds:join', {groupId: this.group._id});
},
// @TODO: Move to notificatin component
async reject () {
await this.$store.dispatch('guilds:rejectInvite', {groupId: this.group._id});
// User.sync();
},
clickStartQuest () {
// Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Start a Quest'});
let hasQuests = find(this.user.items.quests, (quest) => {
return quest > 0;
});
if (hasQuests) {
this.$root.$emit('show::modal', 'owned-quests-modal');
return;
}
// $rootScope.$state.go('options.inventory.quests');
},
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;
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/abort'});
this.group.quest = quest;
},
async questLeave () {
if (!confirm(this.$t('sureLeave'))) return;
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/leave'});
this.group.quest = quest;
},
async questAccept () {
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'});
this.group.quest = quest;
},
},
};
</script>
+182 -53
View File
@@ -1,14 +1,46 @@
<template lang="pug">
div
button.btn.btn-primary(b-btn, @click="$root.$emit('show::modal','members-modal')") {{ $t('viewMembers') }}
.item-with-icon(@click="$root.$emit('show::modal','members-modal')")
.svg-icon.shield(v-html="icons.goldGuildBadgeIcon")
span.number {{group.memberCount}}
div(v-once) {{ $t('members') }}
b-modal#members-modal(:title="$t('createGuild')")
ul(v-for='member in members', :key='member')
li(@click='clickMember') {{member}}
button(@click='removeMember(member)', v-once) {{$t('remove')}}
button(@click='quickReply(member)', v-once) {{$t('message')}}
button(@click='addManager(member)', v-once) {{$t('addManager')}}
button(@click='removeManager(member)', v-once) {{$t('addManager')}}
b-modal#members-modal(:title="$t('createGuild')", size='lg')
.header-wrap(slot="modal-header")
.row
.col-6
h1(v-once) {{$t('members')}}
.col-6
button(type="button" aria-label="Close" class="close")
span(aria-hidden="true") ×
.row
.form-group.col-6
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')
.col-4.offset-2
span.dropdown-label {{ $t('sortBy') }}
b-dropdown(:text="$t('sort')", right=true)
b-dropdown-item(v-for='sortOption in sortOptions', @click='sort(sortOption.value)', :key='sortOption.value') {{sortOption.text}}
.row(v-for='member in members', :key='member', )
.col-8.offset-1
member-details(:member='member')
.col-3.actions
b-dropdown(:text="$t('sort')", right=true)
b-dropdown-item(@click='sort(option.value)')
.svg-icon(v-html="icons.removeIcon")
| {{$t('removeMember')}}
b-dropdown-item(@click='sort(option.value)')
.svg-icon(v-html="icons.messageIcon")
| {{$t('sendMessage')}}
b-dropdown-item(@click='sort(option.value)')
.svg-icon(v-html="icons.starIcon")
| {{$t('promoteToLeader')}}
b-dropdown-item(@click='sort(option.value)')
.svg-icon(v-html="icons.starIcon")
| {{$t('addManager')}}
b-dropdown-item(@click='sort(option.value)')
.svg-icon(v-html="icons.removeIcon")
| {{$t('removeManager2')}}
.row-fluid.gradient
b-modal#remove-member(:title="$t('confirmRemoveMember')")
button(@click='confirmRemoveMember(member)', v-once) {{$t('remove')}}
@@ -17,25 +49,134 @@ div
button(@click='confirmRemoveMember(member)', v-once) {{$t('remove')}}
</template>
<style lang='scss' scoped>
header {
background-color: #edecee;
border-radius: 4px 4px 0 0;
}
.header-wrap {
width: 100%;
}
h1 {
color: #4f2a93;
}
.actions {
padding-top: 5em;
.action-icon {
margin-right: 1em;
}
}
#members-modal_modal_body {
padding: 0;
.col-8 {
margin-left: 0;
}
.member-details {
margin: 0;
}
.member-stats {
width: 382px;
height: 147px;
}
.gradient {
background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0), #ffffff);
height: 200px;
width: 100%;
position: absolute;
bottom: 0px;
}
}
.item-with-icon {
border-radius: 2px;
background-color: #ffffff;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
padding: 1em;
text-align: center;
}
.svg-icon.shield, .svg-icon.gem {
width: 40px;
margin-right: 1em;
}
.number {
font-size: 22px;
font-weight: bold;
}
</style>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
import MemberDetails from '../memberDetails';
import removeIcon from 'assets/members/remove.svg';
import messageIcon from 'assets/members/message.svg';
import starIcon from 'assets/members/star.svg';
import goldGuildBadgeIcon from 'assets/svg/gold-guild-badge.svg';
export default {
props: ['group'],
components: {
bModal,
bDropdown,
bDropdownItem,
MemberDetails,
},
created () {
this.getMembers();
},
data () {
return {
members: ['one', 'two'],
members: [],
memberToRemove: '',
sortOptions: [
{
value: 'tier',
text: this.$t('tier'),
},
{
value: 'name',
text: this.$t('name'),
},
{
value: 'level',
text: this.$t('level'),
},
{
value: 'class',
text: this.$t('class'),
},
],
searchTerm: '',
icons: Object.freeze({
removeIcon,
messageIcon,
starIcon,
goldGuildBadgeIcon,
}),
};
},
methods: {
getMembers () {
// We should get members here via store if they are not loaded
async getMembers () {
let members = await this.$store.dispatch('members:getGroupMembers', {
groupId: this.group._id,
includeAllPublicFields: true,
});
this.members = members;
},
clickMember (uid, forceShow) {
async clickMember (uid, forceShow) {
let user = this.$store.state.user.data;
if (user._id === uid && !forceShow) {
@@ -48,59 +189,47 @@ export default {
return;
}
// $root.$emit('show::modal','members-modal')
// We need the member information up top here, but then we pass it down to the modal controller
// down below. Better way of handling this?
// Members.selectMember(uid)
// .then(function () {
// $rootScope.openModal('member', {controller: 'MemberModalCtrl', windowClass: 'profile-modal', size: 'lg'});
// });
await this.$store.dispatch('members:selectMember', {
memberId: uid,
});
this.$root.$emit('show::modal', 'members-modal');
},
removeMember (member) {
async removeMember (member) {
this.memberToRemove = member;
this.$root.$emit('show::modal', 'remove-member');
},
confirmRemoveMember (confirmation) {
async confirmRemoveMember (confirmation) {
if (!confirmation) {
this.memberToRemove = '';
return;
}
// Groups.Group.removeMember(
// $scope.removeMemberData.group._id,
// $scope.removeMemberData.member._id,
// $scope.removeMemberData.message
// ).then(function (response) {
// if($scope.removeMemberData.isMember){
// _.pull($scope.removeMemberData.group.members, $scope.removeMemberData.member);
// }else{
// _.pull($scope.removeMemberData.group.invites, $scope.removeMemberData.member);
// }
//
// $scope.removeMemberData = undefined;
// });
await this.$store.dispatch('members:removeMember', {
memberId: this.memberToRemove._id,
groupId: this.group._id,
message: this.removeMessage,
});
this.memberToRemove = '';
this.removeMessage = '';
},
quickReply (uid) {
async quickReply (uid) {
this.memberToReply = uid;
this.$root.$emit('show::modal', 'private-message');
// Members.selectMember(uid)
// .then(function (response) {
// $rootScope.openModal('private-message', {controller: 'MemberModalCtrl'});
// });
await this.$store.dispatch('members:selectMember', {
memberId: uid,
});
this.$root.$emit('show::modal', 'private-message'); // MemberModalCtrl
},
addManager () {
// Groups.Group.addManager(this.group._id, this.group._newManager)
// .then(function (response) {
// this.group._newManager = '';
// this.group.managers = response.data.data.managers;
// });
async addManager (memberId) {
await this.$store.dispatch('group:addManager', {
memberId,
});
},
removeManager (memberId) {
this.memberToReply = memberId;
// Groups.Group.removeManager(this.group._id, memberId)
// .then(function (response) {
// this.group._newManager = '';
// this.group.managers = response.data.data.managers;
// });
async removeManager (memberId) {
await this.$store.dispatch('group:removeManager', {
memberId,
});
},
},
};
+24 -19
View File
@@ -4,7 +4,7 @@
.col-10.no-guilds.standard-page(v-if='filteredGuilds.length === 0')
.no-guilds-wrapper
img(src='~assets/guilds/grey-badge.svg')
.svg-icon(v-html='icons.greyBadge')
h2 {{$t('noGuildsTitle')}}
p {{$t('noGuildsParagraph1')}}
p {{$t('noGuildsParagraph2')}}
@@ -17,33 +17,33 @@
.float-right
span.dropdown-label {{ $t('sortBy') }}
b-dropdown(:text="$t('sort')", right=true)
b-dropdown-item(v-for='sortOption in sortOptions', @click='sort(sortOption.value)') {{sortOption.text}}
b-dropdown-item(v-for='sortOption in sortOptions', :key="sortOption.value", @click='sort(sortOption.value)') {{sortOption.text}}
.row
.col-md-12
public-guild-item(v-for="guild in filteredGuilds", :key='guild._id', :guild="guild", :display-gem-bank='true')
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.sort-select {
margin: 2em;
@import '~client/assets/scss/colors.scss';
.sort-select {
margin: 2em;
}
.no-guilds {
text-align: center;
color: $gray-200;
margin-top: 15em;
p {
font-size: 14px;
line-height: 1.43;
}
.no-guilds {
text-align: center;
color: $gray-200;
margin-top: 15em;
p {
font-size: 14px;
line-height: 1.43;
}
.no-guilds-wrapper {
width: 400px;
margin: 0 auto;
}
.no-guilds-wrapper {
width: 400px;
margin: 0 auto;
}
}
</style>
<script>
@@ -58,11 +58,16 @@ import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
import PublicGuildItem from './publicGuildItem';
import Sidebar from './sidebar';
import greyBadgeIcon from 'assets/svg/grey-badge.svg';
export default {
mixins: [groupUtilities],
components: { PublicGuildItem, MugenScroll, Sidebar, bFormSelect, bDropdown, bDropdownItem },
data () {
return {
icons: Object.freeze({
greyBadge: greyBadgeIcon,
}),
loading: false,
search: '',
filters: {},
@@ -0,0 +1,54 @@
<template lang="pug">
div
b-modal#owned-quests-modal(title="Which quest do you want to start?", size='md', hide-footer=true)
.row.content
.quest(v-for='(value, key, index) in user.items.quests', :class="'inventory_quest_scroll_' + key", @click='selectQuest(key)')
button.btn.btn-primary(@click='confirm()') Confirm
start-quest-modal(:group='group', :selectedQuest='selectedQuest')
</template>
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
.content {
padding: 2em;
.quest {
margin-right: 1em;
margin-bottom: 1em;
display: inline-block;
}
}
</style>
<script>
import { mapState } from 'client/libs/store';
import bModal from 'bootstrap-vue/lib/components/modal';
import startQuestModal from './startQuestModal';
export default {
props: ['group'],
components: {
bModal,
startQuestModal,
},
data () {
return {
selectedQuest: {},
};
},
computed: {
...mapState({user: 'user.data'}),
},
methods: {
selectQuest (quest) {
this.selectedQuest = quest;
},
confirm () {
this.$root.$emit('show::modal', 'start-quest-modal');
this.$root.$emit('hide::modal', 'owned-quests-modal');
},
},
};
</script>
@@ -3,7 +3,7 @@
.card-block
.row
.col-md-2
img.icon.shield(src="~assets/guilds/gold-guild-badge.svg")
.svg-icon.shield(v-html="icons.goldGuildBadge")
.member-count {{guild.memberCount}}
.col-md-10
.row
@@ -15,7 +15,7 @@
button.btn.btn-danger(v-if='isMember && displayLeave' @click='leave()', v-once) {{ $t('leave') }}
button.btn.btn-success(v-if='!isMember' @click='join()', v-once) {{ $t('join') }}
div.item-with-icon(v-if='displayGemBank')
img(src="~assets/guilds/green-gem.svg")
.svg-icon(v-html="icons.gem")
span.count {{ guild.balance }}
div.guild-bank(v-if='displayGemBank', v-once) {{$t('guildBank')}}
.row
@@ -26,89 +26,70 @@
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/colors.scss';
.card {
height: 260px;
border-radius: 4px;
background-color: $white;
box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1);
margin-bottom: 1rem;
.card {
height: 260px;
border-radius: 4px;
background-color: $white;
box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1);
margin-bottom: 1rem;
.category-label {
min-width: 100px;
border-radius: 100px;
background-color: $gray-600;
padding: .5em;
display: inline-block;
margin-right: .5em;
font-size: 12px;
font-weight: 500;
line-height: 1.33;
text-align: center;
color: $gray-300;
.recommend-text {
font-size: 12px;
font-style: italic;
line-height: 2;
color: $gray-300;
}
.cta-container {
margin: 0 auto;
margin-top: 4em;
}
.item-with-icon {
.svg-icon {
height: 37px;
}
.recommend-text {
font-size: 12px;
font-style: italic;
line-height: 2;
color: $gray-300;
}
.cta-container {
margin: 0 auto;
margin-top: 4em;
}
.shield {
width: 70px;
height: 76px;
margin: auto;
margin: 4em auto;
display: block;
background-size: cover;
width: 100%;
height: 100px;
}
.item-with-icon {
img {
height: 37px;
}
.count {
font-size: 20px;
height: 37px;
width: 37px;
margin-left: .2em;
}
}
.guild-bank {
font-size: 12px;
line-height: 1.33;
color: $gray-300;
}
.member-count {
position: relative;
top: -3.6em;
left: -.1em;
font-size: 28px;
font-weight: bold;
font-family: 'Roboto Condensed';
line-height: 1.2;
text-align: center;
color: #b36213;
.count {
font-size: 20px;
height: 37px;
width: 37px;
margin-left: .2em;
}
}
.shield {
width: 70px;
}
.guild-bank {
font-size: 12px;
line-height: 1.33;
color: $gray-300;
}
.member-count {
position: relative;
top: -3.6em;
left: -.1em;
font-size: 28px;
font-weight: bold;
font-family: 'Roboto Condensed';
line-height: 1.2;
text-align: center;
color: #b36213;
}
}
</style>
<script>
import { mapState } from 'client/libs/store';
import groupUtilities from 'client/mixins/groupsUtilities';
import findIndex from 'lodash/findIndex';
import gemIcon from 'assets/svg/gem.svg';
import goldGuildBadgeIcon from 'assets/svg/gold-guild-badge.svg';
export default {
mixins: [groupUtilities],
@@ -119,6 +100,14 @@ export default {
return this.isMemberOfGroup(this.user, this.guild);
},
},
data () {
return {
icons: Object.freeze({
gem: gemIcon,
goldGuildBadge: goldGuildBadgeIcon,
}),
};
},
methods: {
async join () {
// @TODO: This needs to be in the notifications where users will now accept invites
@@ -0,0 +1,156 @@
<template lang="pug">
b-modal#start-quest-modal(title="Empty", size='md', hide-footer=true, v-if='questData')
.quest-image(:class="'quest_' + questData.key")
h2 {{questData.text()}}
//- span by: Keith Holliday @TODO: Add author
p {{questData.notes()}}
div.quest-details
div(v-if=' questData.collect')
Strong {{$t('collect')}}: &nbsp;
span(v-for="(value, key, index) in questData.collect")
| {{$t('collectionItems', { number: questData.collect[key].count, items: questData.collect[key].text() })}}
div
Strong {{$t('collect')}}: &nbsp;
span
.svg-icon(v-html="icons.difficultyStarIcon")
div
button.btn.btn-primary(@click='questInit()') {{$t('inviteToPartyOrQuest')}}
div
p {{$t('inviteInformation')}}
.side-panel
h4.text-center {{$t('rewards')}}
.box
.svg-icon.rewards-icon(v-html="icons.starIcon")
strong {{questData.drop.exp}} {{$t('experience')}}
.box
.svg-icon.rewards-icon(v-html="icons.goldIcon")
strong {{questData.drop.gp}} {{$t('gold')}}
h4.text-center(v-if='questData.drop.items') {{$t('questOwnerRewards')}}
.box(v-for='item in questData.drop.items')
.rewards-icon(v-if='item.type === "quest"', :class="'quest_' + item.key")
.drop-rewards-icon(v-if='item.type === "gear"', :class="'shop_' + item.key")
strong.quest-reward-text {{item.text()}}
</template>
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
header {
background-color: $white !important;
border: none !important;
h5 {
text-indent: -99999px;
}
}
.quest-image {
margin: 0 auto;
margin-bottom: 1em;
}
.quest-details {
margin: 0 auto;
text-align: left;
width: 180px;
}
.btn-primary {
margin: 1em 0;
}
.side-panel {
background: #edecee;
position: absolute;
height: 460px;
width: 320px;
top: -1.8em;
left: 35em;
z-index: -1;
padding-top: 1em;
border-radius: 4px;
.drop-rewards-icon {
width: 35px;
height: 35px;
float: left;
}
.rewards-icon {
float: left;
width: 30px;
height: 30px;
svg {
width: 30px;
height: 30px;
}
}
.quest-reward-text {
font-size: 12px;
}
.box {
width: 220px;
height: 64px;
border-radius: 2px;
background-color: #ffffff;
margin: 0 auto;
margin-bottom: 1em;
padding: 1em;
}
}
</style>
<script>
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';
export default {
props: ['group', 'selectedQuest'],
components: {
bModal,
},
data () {
return {
icons: Object.freeze({
copy: copyIcon,
greyBadge: greyBadgeIcon,
qrCode: qrCodeIcon,
facebook: facebookIcon,
twitter: twitterIcon,
starIcon,
goldIcon,
difficultyStarIcon,
}),
shareUserIdShown: false,
};
},
computed: {
questData () {
return quests.quests[this.selectedQuest];
},
},
methods: {
async questInit () {
let key = this.selectedQuest;
// Analytics.updateUser({'partyID': party._id, 'partySize': party.memberCount});
let response = await this.$store.dispatch('guilds:inviteToQuest', {groupId: this.group._id, key});
let quest = response.data.data;
this.$store.party.quest = quest;
this.$root.$emit('hide::modal', 'start-quest-modal');
},
},
};
</script>
+11 -6
View File
@@ -2,8 +2,7 @@
.drawer-container
.drawer-title(@click="open = !open")
| {{title}}
img.drawer-toggle-icon(src="~assets/drawer/minimize.svg", v-if="open")
img.drawer-toggle-icon.closed(src="~assets/drawer/expand.svg", v-else)
.drawer-toggle-icon.svg-icon(v-html="open ? icons.minimize : icons.expand", :class="{ closed: !open }")
transition(name="slide-up", @afterLeave="adjustPagePadding", @afterEnter="adjustPagePadding")
.drawer-content(v-show="open")
slot(name="drawer-header")
@@ -36,10 +35,10 @@
.drawer-toggle-icon {
float: right;
margin: 10px;
margin-right: 16px;
&.closed {
margin-top: 5px;
margin-top: 3px;
}
}
@@ -92,8 +91,7 @@
}
.drawer-slider {
padding: 12px 0 0 24px;
margin-left: -24px;
padding: 12px 0 0 0;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
@@ -128,6 +126,9 @@
</style>
<script>
import expandIcon from 'assets/svg/expand.svg';
import minimizeIcon from 'assets/svg/minimize.svg';
export default {
props: {
title: {
@@ -141,6 +142,10 @@ export default {
data () {
return {
open: true,
icons: Object.freeze({
expand: expandIcon,
minimize: minimizeIcon,
}),
};
},
methods: {
@@ -112,7 +112,7 @@
a.btn.btn-show-more(
v-if="items[group.key].length > itemsPerLine",
@click="viewOptions[group.key].open = !viewOptions[group.key].open"
) {{ viewOptions[group.key].open ? $t('showLessGearItems', { type: group.label }) : $t('showAllGearItems', { type: group.label, items: items[group.key].length }) }}
) {{ viewOptions[group.key].open ? $t('showLessItems', { type: group.label }) : $t('showAllItems', { type: group.label, items: items[group.key].length }) }}
</template>
<style lang="scss" scoped>
+16 -7
View File
@@ -16,13 +16,16 @@ b-popover(
.item-wrapper
.item
slot(name="itemBadge", :item="item")
span.item-content(:class="itemContentClass")
span.item-content(
:class="itemContentClass",
:draggable="draggable",
@dragstart="onDrag"
)
span.item-label(v-if="label") {{ label }}
</template>
<script>
import bPopover from 'bootstrap-vue/lib/components/popover';
import { mapState } from 'client/libs/store';
export default {
components: {
@@ -46,16 +49,22 @@ export default {
type: String,
default: 'bottom',
},
},
computed: {
...mapState({
ATTRIBUTES: 'constants.ATTRIBUTES',
}),
draggable: {
type: Boolean,
default: false,
},
},
methods: {
click () {
this.$emit('click', this.item);
},
onDrag (ev) {
if (this.draggable) {
this.$emit('onDrag', ev);
} else {
ev.preventDefault();
}
},
},
};
</script>
@@ -53,7 +53,7 @@
a.btn.btn-show-more(
v-if="items[group.key].length > itemsPerLine",
@click="group.open = !group.open"
) {{ group.open ? $t('showLessGearItems', { type: $t(group.key) }) : $t('showAllGearItems', { type: $t(group.key), items: items[group.key].length }) }}
) {{ group.open ? $t('showLessItems', { type: $t(group.key) }) : $t('showAllItems', { type: $t(group.key), items: items[group.key].length }) }}
</template>
@@ -1,131 +0,0 @@
<template lang="pug">
.row
.col-2.standard-sidebar
.form-group
input.form-control(type="text", :placeholder="$t('search')")
.form
h3(v-once) {{ $t('filter') }}
.form-group
.form-check
label.form-check-label(v-once)
input.form-check-input(type="checkbox")
strong {{ $t('pets') }}
.form-check.nested-field
label.form-check-label(v-once)
input.form-check-input(type="checkbox")
span {{ $t('hatchingPotions') }}
.form-check.nested-field
label.form-check-label(v-once)
input.form-check-input(type="checkbox")
span {{ $t('quest') }}
.form-check.nested-field
label.form-check-label(v-once)
input.form-check-input(type="checkbox")
span {{ $t('special') }}
.form-group
.form-check
label.form-check-label(v-once)
input.form-check-input(type="checkbox")
strong {{ $t('mounts') }}
.form-check.nested-field
label.form-check-label(v-once)
input.form-check-input(type="checkbox")
span {{ $t('hatchingPotions') }}
.form-check.nested-field
label.form-check-label(v-once)
input.form-check-input(type="checkbox")
span {{ $t('quest') }}
.form-check.nested-field
label.form-check-label(v-once)
input.form-check-input(type="checkbox")
span {{ $t('special') }}
.col-10.standard-page
h4 Pets
.inventory-item-container(v-for="pet in dropPets")
.PixelPaw
.btn.btn-secondary.d-block(@click="open.dropPets = !open.dropPets") {{ open.dropPets ? 'Close' : 'Open' }}
h2 Magic Potions Pets
.inventory-item-container(v-for="pet in magicPets")
.PixelPaw
.btn.btn-secondary.d-block(@click="open.magicPets = !open.magicPets") {{ open.magicPets ? 'Close' : 'Open' }}
h2 Quest Pets
.inventory-item-container(v-for="pet in questPets")
.PixelPaw
.btn.btn-secondary.d-block(@click="open.questPets = !open.questPets") {{ open.questPets ? 'Close' : 'Open' }}
h2 Rare Pets
.inventory-item-container(v-for="pet in rarePets")
.PixelPaw
.btn.btn-secondary.d-block(@click="open.rarePets = !open.rarePets") {{ open.rarePets ? 'Close' : 'Open' }}
h2 Mounts
h2 Quest Mounts
h2 Rare Mounts
</template>
<style lang="scss">
.inventory-item-container {
padding: 20px;
border: 1px solid;
display: inline-block;
}
</style>
<script>
import { mapState } from 'client/libs/store';
import each from 'lodash/each';
// TODO Normalize special pets and mounts
// import Store from 'client/store';
// import deepFreeze from 'client/libs/deepFreeze';
// const specialMounts =
export default {
data () {
return {
open: {
dropPets: false,
magicPets: false,
questPets: false,
rarePets: false,
},
};
},
computed: {
...mapState(['content']),
dropPets () {
return this.listAnimals('pet', this.content.dropEggs, this.content.dropHatchingPotions, this.open.dropPets);
},
magicPets () {
return this.listAnimals('pet', this.content.dropEggs, this.content.premiumHatchingPotions, this.open.magicPets);
},
questPets () {
return this.listAnimals('pet', this.content.questEggs, this.content.dropHatchingPotions, this.open.questPets);
},
rarePets () {
return this.listAnimals('pet', this.content.dropEggs, this.content.dropHatchingPotions, this.open.rarePets);
},
},
methods: {
listAnimals (type, eggSource, potionSource, isOpen = false) {
let animals = [];
let iteration = 0;
each(eggSource, (egg) => {
if (iteration === 1 && !isOpen) return false;
iteration++;
each(potionSource, (potion) => {
let animalKey = `${egg.key}-${potion.key}`;
animals.push(this.content[`${type}Info`][animalKey].text());
});
});
return animals;
},
},
};
</script>
@@ -0,0 +1,33 @@
<template lang="pug">
span.badge.badge-pill.badge-item.badge-count(
v-if="show",
) {{ count }}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.badge-count {
right: -9px;
color: $white;
background: $orange-100;
padding: 4.5px 6px;
min-width: 24px;
height: 24px;
box-shadow: 0 1px 1px 0 rgba($black, 0.12);
}
</style>
<script>
export default {
props: {
show: {
type: Boolean,
},
count: {
type: Number,
},
},
};
</script>
@@ -0,0 +1,174 @@
<template lang="pug">
div.slider-root(
v-bind:class="{'scrollButtonsVisible': scrollButtonsVisible}",
)
div.slider-button-area.left-button(
v-if="scrollButtonsVisible",
@mousedown.left="lastPage"
)
a.slider-button
.svg-icon(v-html="icons.previous")
div.slider-button-area.right-button(
v-if="scrollButtonsVisible",
@mousedown.left="nextPage"
)
a.slider-button
.svg-icon(v-html="icons.next")
// 120 = width of the left/right buttons
div.sliding-content(v-resize="500", @resized="currentWidth = $event.width - 120")
.items.items-one-line
template(v-for="item in pages[currentPage]")
div.vertical-divider(v-if="item.ofNextPage")
slot(
name="item",
:item="item",
)
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
$buttonAreaWidth: 60;
.slider-root {
position: relative;
}
.slider-button {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #ffffff;
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
position: absolute;
top: calc((100% - 40px) / 2);
.svg-icon {
color: #a5a1ac;
margin: auto 0;
position: absolute;
top: calc((100% - 12px) / 2);
width: 8px;
height: 16px;
right: 16px;
}
}
.scrollButtonsVisible {
.sliding-content {
overflow: hidden;
}
}
.slider-button-area {
width: $buttonAreaWidth+px;
height: 100%;
position: absolute;
z-index: 2;
&.left-button {
left: 0;
}
&.right-button {
right: 0;
}
}
.sliding-content .items {
padding-top: 10px;
margin-left: $buttonAreaWidth+ px;
& > div:last-of-type {
margin-right: $buttonAreaWidth + 20px;
}
}
.vertical-divider {
height: 92px;
width: 1px;
background: #34313a;
margin-bottom: 8px;
}
</style>
<script>
import previous from 'assets/svg/previous.svg';
import next from 'assets/svg/next.svg';
import ResizeDirective from 'client/directives/resize.directive';
import _chunk from 'lodash/chunk';
export default {
directives: {
resize: ResizeDirective,
},
data () {
return {
icons: Object.freeze({
previous,
next,
}),
currentWidth: 0,
currentPage: 0,
};
},
computed: {
pages () {
return _chunk(this.items, this.itemsPerPage() - 1).map((content, index, array) => {
let resultData = [...content];
if (array[index + 1]) {
resultData.push({
...array[index + 1][0],
ofNextPage: true,
});
}
return resultData;
});
},
},
methods: {
lastPage () {
if (this.currentPage > 0) {
this.currentPage--;
} else {
this.currentPage = this.pages.length - 1;
}
},
nextPage () {
if (this.currentPage < this.pages.length - 1) {
this.currentPage++;
} else {
this.currentPage = 0;
}
},
itemsPerPage () {
return Math.floor(this.currentWidth / (this.itemWidth + this.itemMargin));
},
},
props: {
scrollButtonsVisible: {
type: Boolean,
default: true,
},
items: {
type: Array,
},
itemWidth: {
type: Number,
},
itemMargin: {
type: Number,
},
},
};
</script>
@@ -0,0 +1,48 @@
<template lang="pug">
b-popover(
:triggers="['hover']",
:placement="'top'",
)
span(slot="content")
h4.popover-content-title {{ item.text() }}
div.popover-content-text(v-html="item.notes()")
.item-wrapper
.item
countBadge(
:show="true",
:count="itemCount"
)
span.item-content(
:class="'Pet_Food_'+item.key",
v-drag.food="item.key"
)
</template>
<script>
import bPopover from 'bootstrap-vue/lib/components/popover';
import DragDropDirective from 'client/directives/dragdrop.directive';
import CountBadge from './countBadge';
export default {
components: {
bPopover,
CountBadge,
},
directives: {
drag: DragDropDirective,
},
props: {
item: {
type: Object,
},
itemCount: {
type: Number,
},
itemContentClass: {
type: String,
},
},
};
</script>
@@ -0,0 +1,721 @@
<template lang="pug">
.row.stable
.standard-sidebar
div
b-popover(
:triggers="['hover']",
:placement="'right'"
)
span(slot="content")
h4.popover-content-title(v-once) {{ $t('mattBoch') }}
.popover-content-text(v-once) {{ $t('mattBochText1') }}
div.npc_matt
.form-group
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
.form
h2(v-once) {{ $t('filter') }}
h3(v-once) {{ $t('pets') }}
.form-group
.form-check(
v-for="petGroup in petGroups",
v-if="viewOptions[petGroup.key].animalCount != 0",
:key="petGroup.key"
)
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", v-model="viewOptions[petGroup.key].selected")
span.custom-control-indicator
span.custom-control-description(v-once) {{ petGroup.label }}
h3(v-once) {{ $t('mounts') }}
.form-group
.form-check(
v-for="mountGroup in mountGroups",
v-if="viewOptions[mountGroup.key].animalCount != 0",
:key="mountGroup.key"
)
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", v-model="viewOptions[mountGroup.key].selected")
span.custom-control-indicator
span.custom-control-description(v-once) {{ mountGroup.label }}
div.form-group.clearfix
h3.float-left Hide Missing
toggle-switch.float-right.hideMissing(
:label="''",
:checked="hideMissing",
@change="updateHideMissing"
)
.standard-page(v-resize="500", @resized="availableContentWidth = $event.width - 48")
.clearfix
h1.float-left.mb-0.page-header(v-once) {{ $t('stable') }}
div.float-right
span.dropdown-label {{ $t('sortBy') }}
b-dropdown(:text="$t(selectedSortBy)", right=true)
b-dropdown-item(
v-for="sort in sortByItems",
@click="selectedSortBy = sort",
:active="selectedSortBy === sort",
:key="sort"
) {{ $t(sort) }}
h2
| {{ $t('pets') }}
|
span.badge.badge-pill.badge-default {{countOwnedAnimals(petGroups[0], 'pet')}}
div(
v-for="petGroup in petGroups",
v-if="viewOptions[petGroup.key].selected",
:key="petGroup.key"
)
h4(v-if="viewOptions[petGroup.key].animalCount != 0") {{ petGroup.label }}
div.items
div(
v-for="pet in pets(petGroup, viewOptions[petGroup.key].open, hideMissing, selectedSortBy, searchTextThrottled, availableContentWidth)",
:key="pet.key",
v-drag.drop.food="pet.key",
@dragover="onDragOver($event, pet)",
@dropped="onDrop($event, pet)",
)
petItem(
:item="pet",
:itemContentClass="getPetItemClass(pet)",
:popoverPosition="'top'",
:progress="pet.progress",
:emptyItem="!pet.isOwned()",
:showPopover="pet.isOwned() || pet.isHatchable()",
@hatchPet="hatchPet",
)
span(slot="popoverContent")
div(v-if="pet.isOwned()")
h4.popover-content-title {{ pet.name }}
div.hatchablePopover(v-else-if="pet.isHatchable()")
h4.popover-content-title {{ pet.name }}
div.popover-content-text(v-html="$t('haveHatchablePet', { potion: pet.potionName, egg: pet.eggName })")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+pet.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+pet.eggKey")
template(slot="itemBadge", scope="ctx")
starBadge(
:selected="ctx.item.key === currentPet",
:show="ctx.item.isOwned()",
@click="selectPet(ctx.item)",
)
.btn.btn-show-more(
@click="viewOptions[petGroup.key].open = !viewOptions[petGroup.key].open",
v-if="viewOptions[petGroup.key].animalCount != 0"
) {{ $t(viewOptions[petGroup.key].open ? 'showLessAnimals' : 'showAllAnimals', { color: petGroup.label, type: $t('pets')}) }}
h2
| {{ $t('mounts') }}
|
span.badge.badge-pill.badge-default {{countOwnedAnimals(mountGroups[0], 'mount')}}
div(
v-for="mountGroup in mountGroups",
v-if="viewOptions[mountGroup.key].selected",
:key="mountGroup.key"
)
h4(v-if="viewOptions[mountGroup.key].animalCount != 0") {{ mountGroup.label }}
div.items
item(
v-for="mount in mounts(mountGroup, viewOptions[mountGroup.key].open, hideMissing, selectedSortBy, searchTextThrottled, availableContentWidth)",
:item="mount",
:itemContentClass="mount.isOwned() ? ('Mount_Icon_' + mount.key) : 'PixelPaw greyedOut'",
:key="mount.key",
:popoverPosition="'top'"
)
span(slot="popoverContent")
h4.popover-content-title {{ mount.name }}
template(slot="itemBadge", scope="ctx")
starBadge(
:selected="ctx.item.key === currentMount",
:show="mount.isOwned()",
@click="selectMount(ctx.item)",
)
.btn.btn-show-more(
@click="viewOptions[mountGroup.key].open = !viewOptions[mountGroup.key].open",
v-if="viewOptions[mountGroup.key].animalCount != 0"
) {{ $t(viewOptions[mountGroup.key].open ? 'showLessAnimals' : 'showAllAnimals', { color: mountGroup.label, type: $t('mounts')}) }}
drawer(
:title="$t('quickInventory')",
:errorMessage="(!hasDrawerTabItems(selectedDrawerTab)) ? $t('noFoodAvailable') : null"
)
div(slot="drawer-header")
.drawer-tab-container
.drawer-tab.text-right
a.drawer-tab-text(
@click="selectedDrawerTab = 0",
:class="{'drawer-tab-text-active': selectedDrawerTab === 0}",
) {{ drawerTabs[0].label }}
.clearfix
.drawer-tab.float-left
a.drawer-tab-text(
@click="selectedDrawerTab = 1",
:class="{ 'drawer-tab-text-active': selectedDrawerTab === 1 }",
) {{ drawerTabs[1].label }}
b-popover(
:triggers="['hover']",
:placement="'top'"
)
span(slot="content")
.popover-content-text Test Popover
div.float-right What does my pet like to eat?
drawer-slider(
:items="drawerTabs[selectedDrawerTab].items",
:scrollButtonsVisible="hasDrawerTabItems(selectedDrawerTab)",
slot="drawer-slider",
:itemWidth=94,
:itemMargin=24,
)
template(slot="item", scope="ctx")
foodItem(
:item="ctx.item",
:itemCount="userItems.food[ctx.item.key]",
)
b-modal#welcome-modal(
:ok-only="true",
:ok-title="$t('gotIt')",
:visible="!hideDialog",
:hide-header="true"
)
div.content
div.npc_matt
h1.page-header(v-once) {{ $t('welcomeStable') }}
div.content-text(v-once) {{ $t('welcomeStableText') }}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.inventory-item-container {
padding: 20px;
border: 1px solid;
display: inline-block;
}
.stable .item .item-content.Pet {
position: absolute;
top: -28px;
}
.toggle-switch-container.hideMissing {
margin-top: 0;
}
.hatchablePopover {
width: 180px
}
.potionEggGroup {
margin: 0 auto;
}
.potionEggBackground {
display: inline-flex;
align-items: center;
width: 64px;
height: 64px;
border-radius: 2px;
background-color: #4e4a57;
&:first-child {
margin-right: 24px;
}
& div {
margin: 0 auto;
}
}
.GreyedOut {
opacity: 0.3;
}
.item.item-empty {
width: 94px;
height: 92px;
border-radius: 2px;
background-color: #edecee;
}
.npc_matt {
margin-bottom: 17px;
}
.stable {
.standard-page {
flex: 1;
}
.drawer-container {
// 3% padding + 252px sidebar width
left: calc(3% + 252px) !important;
}
}
.drawer-slider .items {
height: 114px;
}
div#welcome-modal {
display: flex;
justify-content: center;
flex-direction: column;
header, footer {
border: 0;
}
.npc_matt {
margin: 0 auto 21px auto;
}
.content {
text-align: center;
// the modal already has 15px padding
margin-left: 33px;
margin-right: 33px;
margin-top: 25px;
}
.content-text {
font-family: 'Roboto', sans-serif;
font-size: 14px;
font-weight: normal;
line-height: 1.43;
width: 400px;
}
.modal-footer {
justify-content: center;
}
}
.modal-backdrop.fade.show {
background-color: $purple-50;
opacity: 0.9;
}
</style>
<script>
import {mapState} from 'client/libs/store';
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
import bPopover from 'bootstrap-vue/lib/components/popover';
import bModal from 'bootstrap-vue/lib/components/modal';
import _each from 'lodash/each';
import _sortBy from 'lodash/sortBy';
import _take from 'lodash/take';
import _filter from 'lodash/filter';
import _drop from 'lodash/drop';
import _flatMap from 'lodash/flatMap';
import _throttle from 'lodash/throttle';
import Item from '../item';
import PetItem from './petItem';
import FoodItem from './foodItem';
import Drawer from 'client/components/inventory/drawer';
import toggleSwitch from 'client/components/ui/toggleSwitch';
import StarBadge from 'client/components/inventory/starBadge';
import CountBadge from './countBadge';
import DrawerSlider from './drawerSlider';
import ResizeDirective from 'client/directives/resize.directive';
import DragDropDirective from 'client/directives/dragdrop.directive';
// TODO Normalize special pets and mounts
// import Store from 'client/store';
// import deepFreeze from 'client/libs/deepFreeze';
// const specialMounts =
export default {
components: {
PetItem,
Item,
FoodItem,
Drawer,
bDropdown,
bDropdownItem,
bPopover,
bModal,
toggleSwitch,
StarBadge,
CountBadge,
DrawerSlider,
},
directives: {
resize: ResizeDirective,
drag: DragDropDirective,
},
data () {
return {
viewOptions: {},
hideMissing: false,
searchText: null,
searchTextThrottled: '',
// sort has the translation-keys as values
selectedSortBy: 'standard',
sortByItems: [
'standard',
'AZ',
'sortByColor',
'sortByHatchable',
],
selectedDrawerTab: 0,
availableContentWidth: 0,
};
},
watch: {
searchText: _throttle(function throttleSearch () {
let search = this.searchText.toLowerCase();
this.searchTextThrottled = search;
}, 250),
},
computed: {
...mapState({
content: 'content',
currentPet: 'user.data.items.currentPet',
currentMount: 'user.data.items.currentMount',
userItems: 'user.data.items',
hideDialog: 'user.data.flags.tutorial.common.mounts',
}),
petGroups () {
let petGroups = [
{
label: this.$t('filterByStandard'),
key: 'standardPets',
petSource: {
eggs: this.content.dropEggs,
potions: this.content.dropHatchingPotions,
},
},
{
label: this.$t('filterByMagicPotion'),
key: 'magicPets',
petSource: {
eggs: this.content.dropEggs,
potions: this.content.premiumHatchingPotions,
},
},
{
label: this.$t('filterByQuest'),
key: 'questPets',
petSource: {
eggs: this.content.questEggs,
potions: this.content.dropHatchingPotions,
},
},
{
label: this.$t('special'),
key: 'specialPets',
petSource: {
special: this.content.specialPets,
},
alwaysHideMissing: true,
},
];
petGroups.map((petGroup) => {
this.$set(this.viewOptions, petGroup.key, {
selected: true,
open: false,
animalCount: 0,
});
});
return petGroups;
},
mountGroups () {
let mountGroups = [
{
label: this.$t('filterByStandard'),
key: 'standardMounts',
petSource: {
eggs: this.content.dropEggs,
potions: this.content.dropHatchingPotions,
},
},
{
label: this.$t('filterByMagicPotion'),
key: 'magicMounts',
petSource: {
eggs: this.content.dropEggs,
potions: this.content.premiumHatchingPotions,
},
},
{
label: this.$t('filterByQuest'),
key: 'questMounts',
petSource: {
eggs: this.content.questEggs,
potions: this.content.dropHatchingPotions,
},
},
{
label: this.$t('special'),
key: 'specialMounts',
petSource: {
special: this.content.specialMounts,
},
alwaysHideMissing: true,
},
];
mountGroups.map((mountGroup) => {
this.$set(this.viewOptions, mountGroup.key, {
selected: true,
open: false,
animalCount: 0,
});
});
return mountGroups;
},
drawerTabs () {
return [
{
label: this.$t('food'),
items: _filter(this.content.food, f => {
return f.key !== 'Saddle' && this.userItems.food[f.key];
}),
},
{
label: this.$t('special'),
items: _filter(this.content.food, f => {
return f.key === 'Saddle' && this.userItems.food[f.key];
}),
},
];
},
},
methods: {
getAnimalList (animalGroup, type) {
let key = animalGroup.key;
this.cachedAnimalList = this.cachedAnimalList || {};
if (this.cachedAnimalList[key]) {
return this.cachedAnimalList[key];
}
let animals = [];
let userItems = this.userItems;
switch (key) {
case 'specialPets':
case 'specialMounts': {
_each(animalGroup.petSource.special, (value, specialKey) => {
let eggKey = specialKey.split('-')[0];
let potionKey = specialKey.split('-')[1];
animals.push({
key: specialKey,
eggKey,
potionKey,
pet: this.content[`${type}Info`][specialKey].text(),
isOwned () {
return [`${type}s`][this.key] > 0;
},
isHatchable () {
return false;
},
});
});
break;
}
default: {
_each(animalGroup.petSource.eggs, (egg) => {
_each(animalGroup.petSource.potions, (potion) => {
let animalKey = `${egg.key}-${potion.key}`;
animals.push({
key: animalKey,
eggKey: egg.key,
eggName: egg.text(),
potionKey: potion.key,
potionName: potion.text(),
name: this.content[`${type}Info`][animalKey].text(),
isOwned () {
return userItems[`${type}s`][animalKey] > 0;
},
isHatchable () {
return userItems.eggs[egg.key] > 0 && userItems.hatchingPotions[potion.key] > 0;
},
});
});
});
}
}
this.cachedAnimalList[key] = animals;
return animals;
},
listAnimals (animalGroup, type, isOpen, hideMissing, sort, searchText, availableSpace) {
let animals = this.getAnimalList(animalGroup, type);
let isPetList = type === 'pet';
let withProgress = isPetList && animalGroup.key !== 'specialPets';
// 1. Filter
if (hideMissing || animalGroup.alwaysHideMissing) {
animals = _filter(animals, (a) => {
return a.isOwned();
});
}
if (searchText && searchText !== '') {
animals = _filter(animals, (a) => {
return a.name.toLowerCase().indexOf(searchText) !== -1;
});
}
// 2. Sort
switch (sort) {
case 'AZ':
animals = _sortBy(animals, ['pet']);
break;
case 'sortByColor':
animals = _sortBy(animals, ['potionName']);
break;
case 'sortByHatchable': {
if (isPetList) {
let sortFunc = (i) => {
return i.isHatchable() ? 0 : 1;
};
animals = _sortBy(animals, [sortFunc]);
}
break;
}
}
let animalRows = [];
let itemsPerRow = Math.floor(availableSpace / (94 + 24));
let rowsToShow = isOpen ? Math.ceil(animals.length / itemsPerRow) : 1;
for (let i = 0; i < rowsToShow; i++) {
let skipped = _drop(animals, i * itemsPerRow);
let row = _take(skipped, itemsPerRow);
let rowWithProgressData = withProgress ? _flatMap(row, (a) => {
let progress = this.userItems[`${type}s`][a.key];
return {
...a,
progress,
};
}) : row;
animalRows.push(...rowWithProgressData);
}
this.viewOptions[animalGroup.key].animalCount = animals.length;
return animalRows;
},
countOwnedAnimals (animalGroup, type) {
let animals = this.getAnimalList(animalGroup, type);
let countAll = animals.length;
let countOwned = _filter(animals, (a) => {
return a.isOwned();
});
return `${countOwned.length}/${countAll}`;
},
pets (animalGroup, showAll, hideMissing, sortBy, searchText, availableSpace) {
return this.listAnimals(animalGroup, 'pet', showAll, hideMissing, sortBy, searchText, availableSpace);
},
mounts (animalGroup, showAll, hideMissing, sortBy, searchText, availableSpace) {
return this.listAnimals(animalGroup, 'mount', showAll, hideMissing, sortBy, searchText, availableSpace);
},
getPetItemClass (pet) {
if (pet.isOwned()) {
return `Pet Pet-${pet.key}`;
}
if (pet.isHatchable()) {
return 'PixelPaw';
}
return 'GreyedOut PixelPaw';
},
hasDrawerTabItems (index) {
return this.drawerTabs && this.drawerTabs[index].items.length !== 0;
},
// Actions
updateHideMissing (newVal) {
this.hideMissing = newVal;
},
selectPet (item) {
this.$store.dispatch('common:equip', {key: item.key, type: 'pet'});
},
selectMount (item) {
this.$store.dispatch('common:equip', {key: item.key, type: 'mount'});
},
hatchPet (pet) {
this.$store.dispatch('common:hatch', {egg: pet.eggKey, hatchingPotion: pet.potionKey});
},
onDragOver (ev, pet) {
if (this.userItems.mounts[pet.key]) {
ev.dropable = false;
}
},
onDrop (ev, pet) {
this.$store.dispatch('common:feed', {pet: pet.key, food: ev.draggingKey});
},
},
};
</script>
@@ -0,0 +1,117 @@
<template lang="pug">
b-popover(
:triggers="[showPopover?'hover':'']",
:placement="popoverPosition",
)
span(slot="content")
slot(name="popoverContent", :item="item")
.item-wrapper
.item(
:class="{'item-empty': emptyItem}",
@mouseup="holdStop",
@mouseleave="holdStop",
@mousedown.left="holdStart"
)
slot(name="itemBadge", :item="item")
span.item-content(:class="itemContentClass")
span.pet-progress-background(v-if="progress > 0")
div.pet-progress-bar(v-bind:style="{width: 100 * progress/50 + '%' }")
span.pet-progress-background(v-if="holdProgress > 0")
div.pet-progress-bar.hold(v-bind:style="{width: 100 * holdProgress/5 + '%' }")
span.item-label(v-if="label") {{ label }}
</template>
<style lang="scss">
.pet-progress-background {
width: 62px;
height: 4px;
background-color: #e1e0e3;
position: absolute;
bottom: 4px;
left: calc((100% - 62px) / 2);
}
.pet-progress-bar {
height: 4px;
background-color: #24cc8f;
}
.pet-progress-bar.hold {
background-color: #54c3cc;
}
</style>
<script>
import bPopover from 'bootstrap-vue/lib/components/popover';
import {mapState} from 'client/libs/store';
export default {
components: {
bPopover,
},
props: {
item: {
type: Object,
},
itemContentClass: {
type: String,
},
label: {
type: String,
},
progress: {
type: Number,
default: -1,
},
emptyItem: {
type: Boolean,
default: false,
},
popoverPosition: {
type: String,
default: 'bottom',
},
showPopover: {
type: Boolean,
default: true,
},
},
data () {
return {
holdProgress: -1,
};
},
computed: {
...mapState({
ATTRIBUTES: 'constants.ATTRIBUTES',
}),
},
methods: {
holdStart () {
let pet = this.item;
if (pet.isOwned() || !pet.isHatchable()) {
return;
}
this.holdProgress = 1;
this.currentHoldingTimer = setInterval(() => {
if (this.holdProgress === 5) {
this.holdStop();
this.$emit('hatchPet', pet);
}
this.holdProgress += 1;
}, 1000);
},
holdStop () {
if (this.currentHoldingTimer) {
clearInterval(this.currentHoldingTimer);
this.holdProgress = -1;
}
},
},
};
</script>
+235
View File
@@ -0,0 +1,235 @@
<template lang="pug">
.d-flex.member-details(:class="{ condensed, expanded }")
avatar(:member="member",
@click.native="$emit('click')",
@mouseover.native="$emit('onHover')",
@mouseout.native="$emit('onHover')",
)
.member-stats
h3.character-name
| {{member.profile.name}}
.is-buffed(v-if="isBuffed")
.svg-icon(v-html="icons.buff")
span.small-text.character-level {{ characterLevel }}
.progress-container.d-flex
.svg-icon(v-html="icons.health")
.progress
.progress-bar.bg-health(:style="{width: `${percent(member.stats.hp, MAX_HEALTH)}%`}")
span.small-text {{member.stats.hp | round}} / {{MAX_HEALTH}}
.progress-container.d-flex
.svg-icon(v-html="icons.experience")
.progress
.progress-bar.bg-experience(:style="{width: `${percent(member.stats.exp, toNextLevel)}%`}")
span.small-text {{member.stats.exp | round}} / {{toNextLevel}}
.progress-container.d-flex(v-if="hasClass")
.svg-icon(v-html="icons.mana")
.progress
.progress-bar.bg-mana(:style="{width: `${percent(member.stats.mp, maxMP)}%`}")
span.small-text {{member.stats.mp | round}} / {{maxMP}}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.member-details {
white-space: nowrap;
margin-top: 24px;
margin-bottom: 24px;
transition: all 0.15s ease-out;
}
.member-stats {
padding-left: 16px;
padding-right: 24px;
opacity: 1;
transition: opacity 0.15s ease-out;
}
.member-details.condensed:not(.expanded) .member-stats {
opacity: 0;
position: absolute;
z-index: -1;
}
// Condensed version
.member-details.condensed.expanded {
background: $header-dark-background;
box-shadow: 0 0 0px 9px $header-dark-background;
position: relative;
margin-bottom: 33px;
z-index: 8;
.is-buffed {
background-color: $purple-50;
}
.member-stats {
background: $header-dark-background;
position: absolute;
right: 100%;
height: calc(100% + 18px);
margin-top: -9px;
padding-top: 9px;
padding-bottom: 24px;
padding-right: 16px;
padding-bottom: 14px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
z-index: 9;
}
.progress-container > .svg-icon {
width: 19px;
height: 19px;
margin-top: -2px;
}
.progress-container > .progress {
width: 152px;
border-radius: 0px;
height: 10px;
margin-top: 2px;
background: $purple-100;
}
.progress-container > .progress > .progress-bar {
border-radius: 0px;
height: 10px;
}
}
.small-text {
color: $header-color;
}
.character-name {
margin-bottom: 1px;
color: $white;
}
.character-level {
display: block;
font-style: normal;
margin-bottom: 16px;
}
.is-buffed {
width: 20px;
height: 20px;
background: $header-dark-background;
display: inline-block;
margin-left: 16px;
vertical-align: middle;
.svg-icon {
display: block;
width: 10px;
height: 12px;
margin: 0 auto;
}
}
#header-avatar {
margin-right: 16px;
}
.progress-container {
margin-bottom: 12px;
}
.progress-container > span {
color: $header-color;
margin-left: 16px;
font-style: normal;
}
.progress-container > .svg-icon {
width: 24px;
height: 24px;
margin-right: 8px;
margin-top: -4px;
}
.progress-container > .progress {
width: 303px;
margin: 0px;
border-radius: 2px;
height: 16px;
background-color: $header-dark-background;
}
.progress-container > .progress > .progress-bar {
border-radius: 2px;
height: 16px;
min-width: 0px;
}
</style>
<script>
import Avatar from './avatar';
import { mapState } from 'client/libs/store';
import { toNextLevel } from '../../common/script/statHelpers';
import statsComputed from '../../common/script/libs/statsComputed';
import percent from '../../common/script/libs/percent';
import buffIcon from 'assets/svg/buff.svg';
import healthIcon from 'assets/svg/health.svg';
import experienceIcon from 'assets/svg/experience.svg';
import manaIcon from 'assets/svg/mana.svg';
export default {
components: {
Avatar,
},
props: {
member: {
type: Object,
required: true,
},
condensed: {
type: Boolean,
default: false,
},
expanded: {
type: Boolean,
default: false,
},
},
data () {
return {
icons: Object.freeze({
buff: buffIcon,
health: healthIcon,
experience: experienceIcon,
mana: manaIcon,
}),
};
},
methods: {
percent,
},
computed: {
...mapState({
MAX_HEALTH: 'constants.MAX_HEALTH',
}),
maxMP () {
return statsComputed(this.member).maxMP;
},
toNextLevel () { // Exp to next level
return toNextLevel(this.member.stats.lvl);
},
characterLevel () {
return `${this.$t('level')} ${this.member.stats.lvl} ${
this.member.stats.class ? this.$t(this.member.stats.class) : ''
}`;
},
isBuffed () {
return this.$store.getters['members:isBuffed'](this.member);
},
hasClass () {
return this.$store.getters['members:hasClass'](this.member);
},
},
};
</script>
@@ -1,19 +0,0 @@
<template lang="pug">
.row
secondary-menu.col-12
router-link.nav-link(:to="{name: 'tavern'}", exact) {{ $t('tavern') }}
router-link.nav-link(:to="{name: 'inbox'}") {{ $t('inbox') }}
router-link.nav-link(:to="{name: 'guildsDiscovery'}") {{ $t('guilds') }}
.col-12
router-view
</template>
<script>
import SecondaryMenu from 'client/components/secondaryMenu';
export default {
components: {
SecondaryMenu,
},
};
</script>
+30
View File
@@ -0,0 +1,30 @@
<template lang="pug">
nav
a(href='/login') Login
a(href='/register') Register
</template>
<script>
export default {
methods: {
play () {
// @TODO:
// Auth play click
},
// passwordReset (email) {
// if(email == null || email.length == 0) {
// alert(window.env.t('invalidEmail'));
// } else {
// $http.post(ApiUrl.get() + '/api/v3/user/reset-password', {email:email})
// .success(function(){
// alert(window.env.t('newPassSent'));
// })
// .error(function(data){
// alert(data.err);
// });
// }
// },
},
};
</script>
-33
View File
@@ -1,33 +0,0 @@
<template lang="pug">
li
ul
li
strong {{task.text}}
li(v-if="task.type === 'habit'") up: {{task.up}}, down: {{task.down}}
li value: {{task.value}}
template(v-if="task.type === 'daily' || task.type === 'todo'")
li completed: {{task.completed}}
li
span checklist
ul
li(v-for="checklist in task.checklist") {{checklist.text}}
template(v-if="task.type === 'daily'")
li streak: {{task.streak}}
li repeat: {{task.repeat}}
li(v-if="task.type === 'todo'") due date: {{task.date}}
li attribute {{task.attribute}}
li difficulty {{task.priority}}
li tags {{getTagsFor(task)}}
</template>
<script>
import { mapState, mapGetters } from 'client/libs/store';
export default {
props: ['task'],
computed: {
...mapState({user: 'user.data'}),
...mapGetters({getTagsFor: 'tasks:getTagsFor'}),
},
};
</script>
+194
View File
@@ -0,0 +1,194 @@
<template lang="pug">
.tasks-column
.d-flex
h2.tasks-column-title(v-once) {{ $t(types[type].label) }}
.filters.d-flex.justify-content-end
.filter.small-text(
v-for="filter in types[type].filters",
:class="{active: activeFilter.label === filter.label}",
@click="activeFilter = filter",
) {{ $t(filter.label) }}
.tasks-list
task(v-for="task in tasks[`${type}s`]", :key="task.id", :task="task", v-if="activeFilter.filter(task)")
.bottom-gradient
.column-background(v-if="isUser === true", :class="{'initial-description': tasks[`${type}s`].length === 0}")
.svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once)
h3(v-once) {{$t('theseAreYourTasks', {taskType: `${type}s`})}}
.small-text {{$t(`${type}sDesc`)}}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.tasks-column {
flex-grow: 1;
}
.tasks-list {
border-radius: 4px;
background: $gray-600;
padding: 8px;
// not sure why but this is necessary or the last task will overflow the container
padding-bottom: 0.1px;
position: relative;
height: calc(100% - 64px);
}
.bottom-gradient {
position: absolute;
bottom: 0px;
left: -0px;
height: 42px;
background-image: linear-gradient(to bottom, rgba(52, 49, 58, 0), #34313a);
width: 100%;
}
.tasks-column-title {
margin-bottom: 8px;
}
.filters {
flex-grow: 1;
}
.filter {
font-weight: bold;
color: $gray-100;
font-style: normal;
padding: 8px;
cursor: pointer;
&:hover {
color: $purple-200;
}
&.active {
color: $purple-200;
border-bottom: 2px solid $purple-200;
padding-bottom: 6px;
}
}
.column-background {
position: absolute;
bottom: 32px;
z-index: 7;
&.initial-description {
top: 30%;
}
.svg-icon {
margin: 0 auto;
margin-bottom: 12px;
}
h3, .small-text {
color: $gray-300;
text-align: center;
}
h3 {
font-weight: normal;
margin-bottom: 4px;
}
.small-text {
font-style: normal;
padding-left: 24px;
padding-right: 24px;
}
}
.icon-habit {
width: 30px;
height: 20px;
}
.icon-daily {
width: 30px;
height: 20px;
}
.icon-todo {
width: 20px;
height: 20px;
}
.icon-reward {
width: 26px;
height: 20px;
}
</style>
<script>
import Task from './task';
import { mapState } from 'client/libs/store';
import { shouldDo } from 'common/script/cron';
import habitIcon from 'assets/svg/habit.svg';
import dailyIcon from 'assets/svg/daily.svg';
import todoIcon from 'assets/svg/todo.svg';
import rewardIcon from 'assets/svg/reward.svg';
export default {
components: {
Task,
},
props: ['type', 'isUser'],
data () {
const types = Object.freeze({
habit: {
label: 'habits',
filters: [
{label: 'all', filter: () => true, default: true},
{label: 'yellowred', filter: t => t.value < 1}, // weak
{label: 'greenblue', filter: t => t.value >= 1}, // strong
],
},
daily: {
label: 'dailies',
filters: [
{label: 'all', filter: () => true, default: true},
{label: 'due', filter: t => !t.completed && shouldDo(new Date(), t, this.userPreferences)},
{label: 'notDue', filter: t => t.completed || !shouldDo(new Date(), t, this.userPreferences)},
],
},
todo: {
label: 'todos',
filters: [
{label: 'remaining', filter: t => !t.completed, default: true}, // active
{label: 'scheduled', filter: t => !t.completed && t.date},
{label: 'complete2', filter: t => t.completed},
],
},
reward: {
label: 'rewards',
filters: [
{label: 'all', filter: () => true, default: true},
{label: 'custom', filter: () => true}, // all rewards made by the user
{label: 'wishlist', filter: () => false}, // not user tasks
],
},
});
const icons = Object.freeze({
habit: habitIcon,
daily: dailyIcon,
todo: todoIcon,
reward: rewardIcon,
});
return {
types,
activeFilter: types[this.type].filters.find(f => f.default === true),
icons,
};
},
computed: {
...mapState({
tasks: 'tasks.data',
userPreferences: 'user.data.preferences',
}),
},
};
</script>
+293
View File
@@ -0,0 +1,293 @@
<template lang="pug">
.task.d-flex
// Habits left side control
.left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.up")
.task-control.habit-control(:class="controlClass.up + '-control'")
.svg-icon.positive(v-html="icons.positive")
// Dailies and todos left side control
.left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'daily' || task.type === 'todo'", :class="controlClass")
.task-control.daily-todo-control(:class="controlClass + '-control'")
.svg-icon.check(v-html="icons.check", v-if="task.completed")
// Task title, description and icons
.task-content(:class="contentClass")
h3.task-title(:class="{ 'has-notes': task.notes }") {{task.text}}
.task-notes.small-text {{task.notes}}
.icons.small-text.d-flex.align-items-center
.d-flex.align-items-center(v-if="task.type === 'todo' && task.date", :class="{'due-overdue': isDueOverdue}")
.svg-icon.calendar(v-html="icons.calendar")
span {{dueIn}}
.icons-right.d-flex.justify-content-end
.d-flex.align-items-center(v-if="showStreak")
.svg-icon.streak(v-html="icons.streak")
span(v-if="task.type === 'daily'") {{task.streak}}
span(v-if="task.type === 'habit'")
span.m-0(v-if="task.up") +{{task.counterUp}}
span.m-0(v-if="task.up && task.down") &nbsp;|&nbsp;
span.m-0(v-if="task.down") -{{task.counterDown}}
.d-flex.align-items-center(v-if="task.challenge && task.challenge.id")
.svg-icon.challenge(v-html="icons.challenge")
b-popover.tags-popover.no-span-margin(
:triggers="['hover']",
:placement="'bottom'",
:popover-style="{'max-width': '1000px'}",
)
.d-flex.align-items-center(slot="content")
.tags-popover-title(v-once) {{ `${$t('tags')}:` }}
.tag-label(v-for="tag in getTagsFor(task)") {{tag}}
.d-flex.align-items-center(v-if="task.tags && task.tags.length > 0")
.svg-icon.tags(v-html="icons.tags")
// Habits right side control
.right-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.down")
.task-control.habit-control(:class="controlClass.down + '-control'")
.svg-icon.negative(v-html="icons.negative")
// Rewards right side control
.right-control.d-flex.align-items-center.justify-content-center.reward-control(v-if="task.type === 'reward'", :class="controlClass")
.svg-icon(v-html="icons.gold")
.small-text {{task.value}}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.task {
margin-bottom: 8px;
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
background: $white;
border-radius: 2px;
z-index: 9;
position: relative;
}
.task-title {
margin-bottom: 8px;
color: $gray-10;
font-weight: normal;
&.has-notes {
margin-bottom: 0px;
}
}
.task-notes {
color: $gray-100;
font-style: normal;
margin-bottom: 4px;
}
.task-content {
padding: 8px;
flex-grow: 1;
}
.icons {
color: $gray-300;
font-style: normal;
&-right {
flex-grow: 1;
}
}
.icons-right .svg-icon {
margin-left: 8px;
}
.icons span {
margin-left: 4px;
}
.no-span-margin span {
margin-left: 0px !important;
}
.svg-icon.streak {
width: 11.6px;
height: 7.1px;
}
.tags.svg-icon, .calendar.svg-icon {
width: 14px;
height: 14px;
}
.tags:hover {
color: $purple-500;
}
.due-overdue {
color: $red-50;
}
.calendar.svg-icon {
margin-right: 2px;
margin-top: -2px;
}
.challenge.svg-icon {
width: 14px;
height: 12px;
}
.check.svg-icon {
width: 12.3px;
height: 9.8px;
margin: 8px;
}
.left-control, .right-control {
width: 40px;
flex-shrink: 0;
}
.left-control {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
.right-control {
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
.task-control {
width: 28px;
height: 28px;
}
.habit-control {
border-radius: 100px;
color: $white;
.svg-icon {
width: 10px;
margin: 0 auto;
}
.positive {
margin-top: 9px;
}
.negative {
margin-top: 13px;
}
}
.daily-todo-control {
border-radius: 2px;
}
.reward-control {
flex-direction: column;
padding-top: 16px;
padding-bottom: 12px;
.svg-icon {
width: 24px;
height: 24px;
}
.small-text {
margin-top: 4px;
color: $yellow-10;
}
}
</style>
<style lang="scss"> // not working as scoped css
@import '~client/assets/scss/colors.scss';
.tags-popover {
// TODO fix padding, see https://github.com/bootstrap-vue/bootstrap-vue/issues/559#issuecomment-311150335
white-space: nowrap;
}
.tags-popover-title {
margin-right: 4px;
display: block;
float: left;
}
.tag-label {
display: block;
float: left;
margin-left: 4px;
border-radius: 100px;
background-color: $gray-50;
padding: 4px 10px;
color: $gray-300;
white-space: nowrap;
}
</style>
<script>
import { mapState, mapGetters } from 'client/libs/store';
import moment from 'moment';
import positiveIcon from 'assets/svg/positive.svg';
import negativeIcon from 'assets/svg/negative.svg';
import goldIcon from 'assets/svg/gold.svg';
import streakIcon from 'assets/svg/streak.svg';
import calendarIcon from 'assets/svg/calendar.svg';
import challengeIcon from 'assets/svg/challenge.svg';
import tagsIcon from 'assets/svg/tags.svg';
import checkIcon from 'assets/svg/check.svg';
import bPopover from 'bootstrap-vue/lib/components/popover';
export default {
components: {
bPopover,
},
props: ['task'],
data () {
return {
icons: Object.freeze({
positive: positiveIcon,
negative: negativeIcon,
gold: goldIcon,
streak: streakIcon,
calendar: calendarIcon,
challenge: challengeIcon,
tags: tagsIcon,
check: checkIcon,
}),
};
},
computed: {
...mapState({user: 'user.data'}),
...mapGetters({
getTagsFor: 'tasks:getTagsFor',
getTaskClasses: 'tasks:getTaskClasses',
}),
leftControl () {
const task = this.task;
if (task.type === 'reward') return false;
return true;
},
rightControl () {
const task = this.task;
if (task.type === 'reward') return true;
if (task.type === 'habit') return true;
return false;
},
controlClass () {
return this.getTaskClasses(this.task, 'control');
},
contentClass () {
return this.getTaskClasses(this.task, 'content');
},
showStreak () {
if (this.task.streak !== undefined) return true;
if (this.task.type === 'habit' && (this.task.up || this.task.down)) return true;
return false;
},
isDueOverdue () {
return moment().diff(this.task.date, 'days') >= 0;
},
dueIn () {
const dueIn = moment().to(this.task.date);
return this.$t('dueIn', {dueIn});
},
},
};
</script>
+56
View File
@@ -0,0 +1,56 @@
<template lang="pug">
.row.user-tasks-page
.col-12
.row.tasks-navigation
.col-4.offset-4
input.form-control.input-search(type="text", :placeholder="$t('search')")
.col-1.offset-3
button.btn.btn-success(v-once)
.svg-icon.positive(v-html="icons.positive")
| {{ $t('create') }}
.row.tasks-columns
task-column.col-3(v-for="column in columns", :type="column", :key="column", :isUser="true")
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.user-tasks-page {
padding-top: 31px;
}
.tasks-navigation {
margin-bottom: 40px;
}
.positive {
display: inline-block;
width: 10px;
color: $green-500;
margin-right: 8px;
padding-top: 6px;
}
.tasks-columns {
height: 100%;
}
</style>
<script>
import Column from './column';
import positiveIcon from 'assets/svg/positive.svg';
export default {
components: {
TaskColumn: Column,
},
data () {
return {
columns: ['habit', 'daily', 'todo', 'reward'],
icons: Object.freeze({
positive: positiveIcon,
}),
};
},
};
</script>
+90 -72
View File
@@ -4,8 +4,9 @@
.toggle-switch.float-left
input.toggle-switch-checkbox(
type='checkbox', :id="id",
@change="$emit('change', $event.target.checked)",
:checked="checked",
@change="handleChange",
:checked="isChecked",
:value="value",
)
label.toggle-switch-label(:for="id")
span.toggle-switch-inner
@@ -13,88 +14,88 @@
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/colors.scss';
.toggle-switch-container {
margin-top: 6px;
}
.toggle-switch-container {
margin-top: 6px;
}
.toggle-switch {
position: relative;
width: 40px;
user-select: none;
margin-left: 9px;
}
.toggle-switch {
position: relative;
width: 40px;
user-select: none;
margin-left: 9px;
}
.toggle-switch-description {
height: 20px;
border-bottom: 1px dashed $gray-200;
}
.toggle-switch-description {
height: 20px;
border-bottom: 1px dashed $gray-200;
}
.toggle-switch-checkbox {
display: none;
}
.toggle-switch-checkbox {
display: none;
}
.toggle-switch-label {
display: block;
overflow: hidden;
cursor: pointer;
border-radius: 100px;
margin-bottom: 0px;
margin-top: 3px;
}
.toggle-switch-label {
display: block;
overflow: hidden;
cursor: pointer;
border-radius: 100px;
margin-bottom: 0px;
margin-top: 3px;
}
.toggle-switch-inner {
display: block;
width: 200%;
margin-left: -100%;
transition: margin 0.3s ease-in 0s;
}
.toggle-switch-inner {
display: block;
width: 200%;
margin-left: -100%;
transition: margin 0.3s ease-in 0s;
}
.toggle-switch-inner:before, .toggle-switch-inner:after {
display: block;
float: left;
width: 50%;
height: 16px;
padding: 0;
}
.toggle-switch-inner:before, .toggle-switch-inner:after {
display: block;
float: left;
width: 50%;
height: 16px;
padding: 0;
}
.toggle-switch-inner:before {
content: "";
padding-left: 10px;
background-color: $purple-400;
}
.toggle-switch-inner:before {
content: "";
padding-left: 10px;
background-color: $purple-400;
}
.toggle-switch-inner:after {
content: "";
padding-right: 10px;
background-color: $gray-200;
text-align: right;
}
.toggle-switch-inner:after {
content: "";
padding-right: 10px;
background-color: $gray-200;
text-align: right;
}
.toggle-switch-switch {
box-shadow: 0 1px 2px 0 rgba($black, 0.32);
display: block;
width: 20px;
margin: -2px;
margin-top: 1px;
height: 20px;
background: $white;
position: absolute;
top: 0;
bottom: 0;
right: 22px;
border-radius: 100px;
transition: all 0.3s ease-in 0s;
}
.toggle-switch-switch {
box-shadow: 0 1px 2px 0 rgba($black, 0.32);
display: block;
width: 20px;
margin: -2px;
margin-top: 1px;
height: 20px;
background: $white;
position: absolute;
top: 0;
bottom: 0;
right: 22px;
border-radius: 100px;
transition: all 0.3s ease-in 0s;
}
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-inner {
margin-left: 0;
}
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-inner {
margin-left: 0;
}
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-switch {
right: 0px;
}
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-switch {
right: 0px;
}
</style>
<script>
@@ -105,7 +106,14 @@ export default {
id: Math.random(),
};
},
model: {
prop: 'checked',
event: 'change',
},
props: {
value: {
default: true,
},
label: {
type: String,
required: true,
@@ -115,5 +123,15 @@ export default {
default: false,
},
},
computed: {
isChecked () {
return this.checked === this.value;
},
},
methods: {
handleChange ({ target: { checked } }) {
this.$emit('change', checked ? this.value : this.uncheckedValue);
},
},
};
</script>
-163
View File
@@ -1,163 +0,0 @@
<template lang="pug">
.row
.col-12
.row
.col-3.p-4
h3 Input
input.form-control(type="text", placeholder="Placeholder")
.col-3.p-4
h3 Input Disabled
input.form-control(type="text", placeholder="Placeholder", disabled)
.col-3.p-4
h3 Input With Icon
input.form-control.input-search(type="text", placeholder="Placeholder")
.col-3.p-4
h3 Input With Icon Disabled
input.form-control.input-search(type="text", placeholder="Placeholder", disabled)
.col-3.p-4
h3 Input Valid
input.form-control.input-valid(type="text", placeholder="Placeholder")
.col-3.p-4
h3 Input Invalid
input.form-control.input-invalid(type="text", placeholder="Placeholder")
.row
.col-6.p-4
h3 Textarea
textarea.form-control(rows="5", cols="50")
.col-6.p-4
h3 Textarea Disabled
textarea.form-control(disabled, rows="10", cols="50")
.row
.col-2.p-4
toggleSwitch(label="Toggle Switch")
.row
.col-3.p-4
h3 Checkbox
label.custom-control.custom-checkbox
input.custom-control-input(type='checkbox')
span.custom-control-indicator
span.custom-control-description Check this custom checkbox
.col-3.p-4
h3 Checkbox Disabled Checked
label.custom-control.custom-checkbox
input.custom-control-input(type='checkbox', disabled, checked)
span.custom-control-indicator
span.custom-control-description Check this custom checkbox
.col-3.p-4
h3 Checkbox Disabled Not Checked
label.custom-control.custom-checkbox
input.custom-control-input(type='checkbox', disabled)
span.custom-control-indicator
span.custom-control-description Check this custom checkbox
.col-6.p-4
h3 Radio Button
form
label.custom-control.custom-radio
input#radio1.custom-control-input(name='radio', type='radio')
span.custom-control-indicator
span.custom-control-description Toggle this custom radio
label.custom-control.custom-radio
input#radio2.custom-control-input(name='radio', type='radio')
span.custom-control-indicator
span.custom-control-description Toggle this custom radio
.col-3.p-4
h3 Radio Button Disabled Checked
form
label.custom-control.custom-radio
input#radio3.custom-control-input(name='radio', type='radio', disabled, checked)
span.custom-control-indicator
span.custom-control-description Toggle this custom radio
.col-3.p-4
h3 Radio Button Disabled Not Checked
form
label.custom-control.custom-radio
input#radio3.custom-control-input(name='radio', type='radio', disabled)
span.custom-control-indicator
span.custom-control-description Toggle this custom radio
.row
.col-3.p-4
h3 Main Button
button.btn.btn-primary Button
.col-3.p-4
h3 Secondary Button
button.btn.btn-secondary Button
.col-3.p-4
h3 Green Button
button.btn.btn-success Button
.col-3.p-4
h3 Blue Button
button.btn.btn-info Button
.col-3.p-4
h3 Red Button
button.btn.btn-danger Button
.row
.col-3.p-4
h3 Main Button Disabled
button.btn.btn-primary(disabled=true) Button
.col-3.p-4
h3 Secondary Button Disabled
button.btn.btn-secondary(disabled=true) Button
.col-3.p-4
h3 Green Button Disabled
button.btn.btn-success(disabled=true) Button
.col-3.p-4
h3 Blue Button Disabled
button.btn.btn-info(disabled=true) Button
.col-3.p-4
h3 Red Button Disabled
button.btn.btn-danger(disabled=true) Button
.row
.col-6.p-4
h3 Dropdown Menu
b-dropdown(text="Menu", right=false)
b-dropdown-item(href="#") Menu item 1
b-dropdown-item(href="#") Menu item 2
b-dropdown-item(href="#") Menu item 3
b-dropdown-item(href="#") Menu item 4
.col-6.p-4
h3 Dropdown Menu Disabled
b-dropdown(text="Menu", disabled)
b-dropdown-item(href="#") Menu item 1
b-dropdown-item(href="#") Menu item 2
b-dropdown-item(href="#") Menu item 3
b-dropdown-item(href="#") Menu item 4
.row
.col-6.p-4
h1 Heading 1
h2 Heading 2
h3 Heading 3
h4 Heading 4
.col-6.p-4
p Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vehicula, purus sit amet sodales pharetra, ipsum ipsum mollis orci, id pharetra velit diam et dui. Sed placerat ipsum eget pharetra rutrum. Ut vitae rutrum lacus, eu imperdiet velit. Pellentesque eu velit cursus, scelerisque dui quis, dapibus magna. Vestibulum molestie sed sapien et ultricies. Nam porta ipsum leo, non congue magna vestibulum a. Etiam dictum felis sit amet augue varius tincidunt. Sed eget urna auctor, convallis felis in, pretium justo. Curabitur aliquet, ligula id tincidunt ullamcorper, orci lorem pharetra neque, in ornare arcu magna accumsan arcu. Maecenas dignissim lorem sed eros accumsan scelerisque.
p.small-text Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vehicula, purus sit amet sodales pharetra, ipsum ipsum mollis orci, id pharetra velit diam et dui.
.row
.col(v-for="taskType in tasksTypes")
h3 {{taskType}}s
ul
task(v-for="task in tasks", v-if="task.type === taskType", :key="task.id", :task="task")
</template>
<script>
import Task from './task';
import { mapState } from 'client/libs/store';
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
import toggleSwitch from 'client/components/ui/toggleSwitch';
export default {
components: {
Task,
bDropdown,
bDropdownItem,
toggleSwitch,
},
data () {
return {
tasksTypes: ['habit', 'daily', 'todo', 'reward'],
};
},
computed: {
...mapState({tasks: 'tasks.data'}),
},
};
</script>