New client party tutorial (#8968)

* Adjusted intro tutorial

* Added images to create party modal

* Fixed shared button styles

* Added method to start a party

* various style fixes
This commit is contained in:
Keith Holliday
2017-08-21 09:36:31 -06:00
committed by GitHub
parent 2a2192e196
commit 203d6d3ac2
14 changed files with 85 additions and 201 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -22,7 +22,7 @@
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') }}
a.btn.btn-primary(@click='openPartyModal()') {{ $t('startAParty') }}
</template>
<style lang="scss" scoped>

View File

@@ -5,7 +5,7 @@ div
.col-12.text-center
.svg-icon.challenge-icon(v-html="icons.challengeIcon")
h4(v-once) {{ $t('haveNoChallenges') }}
p(v-once) {{ $t('challengeDescription') }}
p(v-once) {{ $t('challengeDetails') }}
router-link.title(:to="{ name: 'challenge', params: { challengeId: challenge._id } }", v-for='challenge in challenges',:key='challenge._id')
.col-12.challenge-item
.row

View File

@@ -1,27 +1,27 @@
<template lang="pug">
b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
.header-wrap(slot="modal-header")
h2 Image Here
.row
.quest_screen
.row.heading
.col-12.text-center
h2(v-once) {{$t('playInPartyTitle')}}
p(v-once) {{$t('playInPartyDescription')}}
.row.grey-row
.col-6.text-center
img
.start-party
h3(v-once) {{$t('startYourOwnPartyTitle')}}
p(v-once) {{$t('startYourOwnPartyDescription')}}
button.btn.btn-primary(v-once, @click='createParty()') {{$t('createParty')}}
.col-6
div.text-center
img
.join-party
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')}}
| Copy User ID
.option-item(v-once)
.svg-icon(v-html="icons.greyBadge")
| {{$t('lookingForGroup')}}
@@ -30,23 +30,66 @@ b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
| {{$t('qrCode')}}
.option-item(v-once)
.svg-icon.facebook(v-html="icons.facebook")
| {{$t('facebook')}}
| Facebook
.option-item(v-once)
.svg-icon(v-html="icons.twitter")
| {{$t('twitter')}}
| Twitter
</template>
<style lang='scss'>
<style>
#create-party-modal .modal-dialog {
width: 684px;
}
#create-party-modal .modal-header {
padding: 0;
}
</style>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.heading {
margin-top: 1em;
margin-bottom: 1em;
}
.header-wrap {
padding: 0;
color: #4e4a57;
.quest_screen {
background-image: url('~client/assets/images/quest_screen.png');
background-size: cover;
width: 100%;
height: 246px;
margin-bottom: .5em;
border-radius: 2px 2px 0 0;
}
h2 {
color: #4f2a93;
}
}
.start-party {
background-image: url('~client/assets/images/basilist@3x.png');
background-size: cover;
width: 122px;
height: 69px;
margin: 0 auto;
margin-bottom: 1em;
}
.join-party {
background-image: url('~client/assets/images/party@3x.png');
background-size: cover;
width: 203px;
height: 66px;
margin: 0 auto;
margin-bottom: 1em;
}
.modal-body {
padding-bottom: 0;
padding-top: 0;
@@ -62,7 +105,7 @@ b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
.share-userid-options {
background-color: $white;
border-radius: 2px;
width: 180px;
width: 220px;
position: absolute;
top: -8em;
left: 4.8em;
@@ -73,6 +116,9 @@ b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
.svg-icon {
margin-right: .5em;
width: 20px;
display: inline-block;
vertical-align: bottom;
}
.facebook svg {

View File

@@ -131,7 +131,7 @@
.col-10.information-header
h3(v-once)
| {{ $t('challenges') }}
b-tooltip.icon.tooltip-wrapper(:content="$t('privateDescription')")
b-tooltip.icon.tooltip-wrapper(:content="isParty ? $t('challengeDetails') : $t('privateDescription')")
.svg-icon(v-html='icons.information')
.col-2
.toggle-up(@click="sections.challenges = !sections.challenges", v-if="sections.challenges")
@@ -167,9 +167,11 @@
text-align: center;
.svg-icon.shield, .svg-icon.gem {
width: 40px;
width: 28px;
margin: 0 auto;
display: inline-block;
vertical-align: bottom;
margin-right: 0.5em;
}
.number {

View File

@@ -3,12 +3,12 @@
form(@submit.stop.prevent="submit")
.form-group
label
strong(v-once) {{$t('name')}}*
strong(v-once) {{$t('name')}} *
b-form-input(type="text", :placeholder="$t('newGuildPlaceHolder')", v-model="workingGuild.name")
.form-group(v-if='workingGuild.id && members.length > 0')
label
strong(v-once) {{$t('guildLeader')}}*
strong(v-once) {{$t('leader')}} *
select.form-control(v-model="workingGuild.newLeader")
option(v-for='member in members', :value="member._id") {{ member.profile.name }}
@@ -30,7 +30,7 @@
span.custom-control-description(v-once) {{ $t('guildLeaderCantBeMessaged') }}
br
label.custom-control.custom-checkbox(v-if='!creatingParty')
label.custom-control.custom-checkbox(v-if='!isParty')
input.custom-control-input(type="checkbox", v-model="workingGuild.privateGuild")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('privateGuild') }}
@@ -45,20 +45,20 @@
.form-group
label
strong(v-once) {{$t('description')}}*
strong(v-once) {{$t('description')}} *
div.description-count {{charactersRemaining}} {{ $t('charactersRemaining') }}
textarea.form-control(:placeholder="creatingParty ? $t('partyDescriptionPlaceHolder') : $t('guildDescriptionPlaceHolder')", v-model="workingGuild.description")
textarea.form-control(:placeholder="isParty ? $t('partyDescriptionPlaceHolder') : $t('guildDescriptionPlaceHolder')", v-model="workingGuild.description")
.form-group(v-if='!creatingParty')
label
strong(v-once) {{$t('guildInformation')}}*
textarea.form-control(:placeholder="$t('guildInformationPlaceHolder')", v-model="workingGuild.guildInformation")
strong(v-once) {{$t('guildInformation')}} *
textarea.form-control(:placeholder="isParty ? $t('partyInformationPlaceHolder'): $t('guildInformationPlaceHolder')", v-model="workingGuild.guildInformation")
.form-group(v-if='creatingParty && !workingGuild.id')
span
toggleSwitch(:label="$t('inviteMembersNow')", v-model='inviteMembers')
.form-group(style='position: relative;', v-if='!creatingParty')
.form-group(style='position: relative;', v-if='!creatingParty && !isParty')
label
strong(v-once) {{$t('categories')}}*
div.category-wrap(@click.prevent="toggleCategorySelect")
@@ -89,12 +89,12 @@
button(@click.prevent='addMemberToInvite()') Add
.form-group.text-center
div.item-with-icon(v-if='!creatingParty && !workingGuild.id')
div.item-with-icon(v-if='!this.workingGuild.id')
.svg-icon(v-html="icons.gem")
span.count 4
button.btn.btn-primary.btn-md(v-if='!workingGuild.id', :disabled='!workingGuild.name || !workingGuild.description') {{ creatingParty ? $t('createParty') : $t('createGuild') }}
button.btn.btn-primary.btn-md(v-if='workingGuild.id', :disabled='!workingGuild.name || !workingGuild.description') {{ creatingParty ? $t('updateParty') : $t('updateGuild') }}
.gem-description(v-once, v-if='!creatingParty && !workingGuild.id') {{ $t('guildGemCostInfo') }}
button.btn.btn-primary.btn-md(v-if='workingGuild.id', :disabled='!workingGuild.name || !workingGuild.description') {{ isParty ? $t('updateParty') : $t('updateGuild') }}
.gem-description(v-once, v-if='!this.workingGuild.id') {{ $t('guildGemCostInfo') }}
</template>
<style lang="scss" scoped>
@@ -297,11 +297,15 @@ export default {
title () {
if (this.creatingParty) return this.$t('createParty');
if (!this.workingGuild.id) return this.$t('createGuild');
if (this.isParty) return this.$t('updateParty');
return this.$t('updateGuild');
},
creatingParty () {
return this.$store.state.groupFormOptions.createParty;
},
isParty () {
return this.workingGuild.type === 'party';
},
},
methods: {
async getMembers () {

View File

@@ -1,5 +1,3 @@
// import each from 'lodash/each';
// import flattenDeep from 'lodash/flattenDeep';
import times from 'lodash/times';
import Intro from 'intro.js/';
@@ -18,68 +16,15 @@ export default {
},
},
methods: {
load () {
// @TODO: this should be called after app is loaded
// Init and show the welcome tour (only after user is pulled from server & wrapped).
if (window.env.IS_MOBILE) return; // Don't show tour immediately on mobile devices
// let alreadyShown = (before, after) => {
// return Boolean(!before && after === true);
// };
// $rootScope.$watch('user.flags.dropsEnabled', _.flow(alreadyShown, function(already) { //FIXME requires lodash@~3.2.0
},
initTour () {
if (this.loaded) return;
this.chapters = {
intro: [
[
{
// state: 'options.profile.avatar',
element: '.member-details',
intro: this.$t('tourAvatar'),
// position: 'top',
// proceed: this.$t('tourAvatarProceed'),
// backdrop: false,
// orphan: true,
// gold: 4,
// experience: 29,
},
{
// state: 'tasks',
element: '.todo',
intro: this.$t('tourToDosBrief'),
position: 'left',
// proceed: this.$t('tourOkay'),
// gold: 4,
// experience: 29,
},
{
// state: 'tasks',
element: '.daily',
intro: this.$t('tourDailiesBrief'),
position: 'right',
// proceed: this.$t('tourDailiesProceed'),
// gold: 4,
// experience: 29,
},
{
// state: 'tasks',
element: '.habit',
intro: this.$t('tourHabitsBrief'),
position: 'right',
// proceed: this.$t('tourHabitsProceed'),
// gold: 4,
// experience: 29,
},
{
// state: 'tasks',
element: '.reward',
intro: this.user.flags.armoireEnabled ? this.$t('tourRewardsArmoire') : this.$t('tourRewardsBrief'),
position: 'left',
// proceed: this.$t('tourRewardsProceed'),
// gold: 4,
// experience: 29,
// final: true,
element: '.tasks-columns',
intro: this.$t('introTour'),
scrollTo: 'tooltip',
},
],
],
@@ -125,126 +70,41 @@ export default {
]],
guilds: [[
{
// orphan: true,
intro: this.$t('tourGuildsPage'),
// final: true,
// proceed: this.$t('tourNifty'),
// hideNavigation: true,
},
]],
challenges: [[
{
orphan: true,
intro: this.$t('tourChallengesPage'),
final: true,
proceed: this.$t('tourOkay'),
hideNavigation: true,
},
]],
market: [[
{
orphan: true,
intro: this.$t('tourMarketPage'),
final: true,
proceed: this.$t('tourAwesome'),
hideNavigation: true,
},
]],
hall: [[
{
orphan: true,
intro: this.$t('tourHallPage'),
final: true,
proceed: this.$t('tourSplendid'),
hideNavigation: true,
},
]],
pets: [[
{
orphan: true,
intro: this.$t('tourPetsPage'),
final: true,
proceed: this.$t('tourNifty'),
hideNavigation: true,
},
]],
mounts: [[
{
orphan: true,
intro: this.$t('tourMountsPage'),
final: true,
proceed: this.$t('tourOkay'),
hideNavigation: true,
},
]],
equipment: [[
{
orphan: true,
intro: this.$t('tourEquipmentPage'),
final: true,
proceed: this.$t('tourAwesome'),
hideNavigation: true,
},
]],
};
// let chapters = this.chapters;
// each(chapters, (chapter, k) => {
// flattenDeep(chapter).forEach((step, i) => {
// // @TODO: (env.worldDmg.guide ? 'npc_justin_broken' : 'npc_justin')
// step.content = `<div><div class='npc_justin float-left'></div>${step.content}</div>`;
// // @TODO: $(step.element).popover('destroy'); // destroy existing hover popovers so we can add our own
//
// step.onShow = () => {
// // @TODO: Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':false});
// // @TODO: Add Router if (!step.state || $state.is(step.state)) return;
// // @TODO: Add Router $state.go(step.state);
// // @TODO: Do we need this? return $timeout(() => {});
// };
// });
// });
// let tour = this.tour;
// each(chapters, (v, k) => {
// tour[k] = new Tour({
// name: k,
// backdrop: true,
// template: (i, step) => {
// let showFinish = step.final || k === 'classes';
// let showCounter = k === 'intro' && !step.final;
// // TODO: we can probably create a component for all this
//
// let counterSpan = '';
// if (showCounter) counterSpan = `<span style="float:right;">${i + 1} of ${flattenDeep(chapters[k]).length}</span>`;
//
// let prevButton = '';
// if (!step.hideNavigation) prevButton = '<button class="btn btn-sm btn-default" data-role="prev">&laquo; Previous</button>';
//
// let nextButton = '';
// let stepProceedText = 'Next';
// if (step.proceed) stepProceedText = step.proceed;
// if (!step.hideNavigation) nextButton = `<button class="btn btn-sm btn-primary" data-role="next">${stepProceedText} &raquo;</button>`;
// let stepFinishText = 'Finish Tour';
// if (step.proceed) stepFinishText = step.proceed;
// if (showFinish) nextButton = `<button class="btn btn-sm btn-primary" data-role="end" style="float:none;">${stepFinishText}</button>`;
//
// return `<div class="popover" role="tooltip"> \
// <div class="arrow"></div> \
// <h3 class="popover-title"></h3> \
// <div class="popover-content"></div> \
// <div class="popover-navigation"> \
// ${counterSpan} \
// <div class="btn-group"> \
// ${prevButton} \
// ${nextButton} \
// <button class="btn btn-sm btn-default" \
// data-role="pause-resume" data-pause-text="Pause" data-resume-text="Resume">Pause</button> \
// </div> \
// </div> \
// </div>`;
// },
// storage: false,
// });
// });
this.loaded = true;
},
routeChange () {
@@ -277,9 +137,6 @@ export default {
let curr = this.user.flags.tour[chapter];
if (page !== curr + 1 && !force) return;
// let chap = this.tour[chapter];
// if (!chap) return;
let opts = {}; // @TODO: chap._options;
opts.steps = [];
page += 1;
@@ -287,9 +144,6 @@ export default {
opts.steps = opts.steps.concat(this.chapters[chapter][p]);
});
// let end = opts.steps.length;
// opts.steps = opts.steps.concat(this.chapters[chapter][page]);
// chap._removeState('end');
// @TODO: Do we always need to initialize here?
let intro = Intro.introJs();
intro.setOptions({steps: opts.steps});
@@ -297,21 +151,8 @@ export default {
intro.oncomplete(() => {
this.markTourComplete(chapter);
});
// if (chap._inited) {
// chap.goTo(end);
// } else {
// chap.setCurrentStep(end);
// if (page > 0) {
// chap.init();
// chap.goTo(page);
// } else {
// chap.start();
// }
// }
},
markTourComplete (chapter) {
// @TODO: this is suppose to keep track of wher ethe left off. Do that later
let ups = {};
let lastKnownStep = this.user.flags.tour[chapter];
@@ -320,13 +161,6 @@ export default {
return;
}
// if (i > lastKnownStep) {
// if (step.gold) ups['stats.gp'] = this.user.stats.gp + step.gold;
// if (step.experience) ups['stats.exp'] = this.user.stats.exp + step.experience;
// ups[`flags.tour.${k}`] = i;
// }
// step.final
// if (true) { // -2 indicates complete
// if (chapter === 'intro') {
// // Manually show bunny scroll reward
@@ -346,11 +180,6 @@ export default {
// }
this.$store.dispatch('user:set', ups);
// User.set() doesn't include a check for level changes, so manually check here.
// @TODO:
// if (step.experience) {
// this.user.fns.updateStats(this.user.stats);
// }
},
},
};

View File

@@ -98,17 +98,17 @@
"yourNotOnQuest": "You're not on a quest",
"questDescription": "Quests allow players to focus on long-term, in-game goals with the members of their party.",
"haveNoChallenges": "You dont have any Challenges",
"challengeDescription": "Challenges are community events in which players compete and earn prizes by completing a group of related tasks.",
"challengeDetails": "Challenges are community events in which players compete and earn prizes by completing a group of related tasks.",
"createParty": "Create a Party",
"partyDescriptionPlaceHolder": "This is our partys description. It describes what we do in this party. If you want to learn more about what we do in this party, read the description. Party on.",
"inviteMembersNow": "Would you like to invite users now?",
"playInPartyTitle": "Play Habitica in a Party!",
"playInPartyDescription": "Take on amazing quests with friends or on your own. Battle monsters, create Challenges, and help yourself stay accountable through Parties.",
"startYourOwnPartyTitle": "Start your own Party",
"startYourOwnPartyDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam accumsan sagittis tellus tempor euismod. Sed imperdiet facilisis tortor in malesuada.",
"startYourOwnPartyDescription": "Battle monsters solo or invite as many of your friends as youd like!",
"shartUserId": "Share User ID",
"wantToJoinPartyTitle": "Want to join a Party?",
"wantToJoinPartyDescription": "Aenean non mattis eros, quis semper ipsum. Phasellus vulputate in nibh et suscipit. In hac habitasse platea dictumst.",
"wantToJoinPartyDescription": "Give your User ID to a friend who already has a Party, or head to the Party Wanted Guild to meet potential comrades!",
"copy": "Copy",
"inviteToPartyOrQuest": "Invite Party to Quest",
"inviteInformation": "Clicking “Invite” will send an invitation to your party members. When all members have accepted or denied, the Quest begins.",
@@ -286,6 +286,9 @@
"haveHatchablePet": "You have a <%= potion %> hatching potion and <%= egg %> egg to hatch this pet! <b>Click</b> the paw print to hatch.",
"welcomeBack": "Welcome back!",
"checkOffYesterDailies": "Check off any Dailies you did yesterday:",
"introTour": "Here we are! Ive filled out some Tasks for you based on your interests, so you can get started right away. Click a Task to edit or add new Tasks to fit your routine!",
"leader": "Leader",
"partyInformationPlaceHolder": "Write a message to your Party members here!",
"selectPartyMember": "Select a Party Member",
"errorNotInParty": "You are not in a Party"
}