Merge develop to release (#9115)

* Some random quick (#9111)

* Switch group button directions

* Allowed admins to export challenges

* Added scoping to some stable styles

* Fixed challenge cloning

* Tasks tags (#9112)

* Added auto apply and exit

* Add challenge tag editing

* Fixed lint

* Skill fixes (#9113)

* Added local storage setting for spell drawer

* Added new spell styles

* Fixed typo

* Reset local creds if access is denied (#9114)
This commit is contained in:
Keith Holliday
2017-09-30 23:54:12 -05:00
committed by GitHub
parent fd8120c80d
commit 8eb7c67f12
11 changed files with 137 additions and 64 deletions

View File

@@ -144,6 +144,16 @@ export default {
return response; return response;
}, (error) => { }, (error) => {
if (error.response.status >= 400) { if (error.response.status >= 400) {
// Check for conditions to reset the user auth
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
if (invalidUserMessage.indexOf(error.response.data.message) !== -1) {
localStorage.removeItem('habit-mobile-settings');
localStorage.removeItem('hello');
this.$store.state.isUserLoggedIn = false;
window.location.href = '/static/home';
return Promise.reject(error);
}
// Don't show errors from getting user details. These users have delete their account, // Don't show errors from getting user details. These users have delete their account,
// but their chat message still exists. // but their chat message still exists.
let configExists = Boolean(error.response) && Boolean(error.response.config); let configExists = Boolean(error.response) && Boolean(error.response.config);

View File

@@ -68,7 +68,7 @@
button.btn.btn-secondary(v-once, @click='edit()') {{$t('editChallenge')}} button.btn.btn-secondary(v-once, @click='edit()') {{$t('editChallenge')}}
div(v-if='isLeader') div(v-if='isLeader')
button.btn.btn-danger(v-once, @click='closeChallenge()') {{$t('endChallenge')}} button.btn.btn-danger(v-once, @click='closeChallenge()') {{$t('endChallenge')}}
div(v-if='isLeader') div(v-if='isLeader || isAdmin')
button.btn.btn-secondary(v-once, @click='exportChallengeCsv()') {{$t('exportChallengeCsv')}} button.btn.btn-secondary(v-once, @click='exportChallengeCsv()') {{$t('exportChallengeCsv')}}
div(v-if='isLeader') div(v-if='isLeader')
button.btn.btn-secondary(v-once, @click='cloneChallenge()') {{$t('clone')}} button.btn.btn-secondary(v-once, @click='cloneChallenge()') {{$t('clone')}}
@@ -246,6 +246,9 @@ export default {
if (!this.challenge.leader) return false; if (!this.challenge.leader) return false;
return this.user._id === this.challenge.leader._id; return this.user._id === this.challenge.leader._id;
}, },
isAdmin () {
return Boolean(this.user.contributor.admin);
},
canJoin () { canJoin () {
return !this.isMember; return !this.isMember;
}, },
@@ -403,6 +406,7 @@ export default {
cloneChallenge () { cloneChallenge () {
this.cloning = true; this.cloning = true;
this.$store.state.challengeOptions.tasksToClone = this.tasksByType; this.$store.state.challengeOptions.tasksToClone = this.tasksByType;
this.$store.state.challengeOptions.workingChallenge = Object.assign({}, this.$store.state.challengeOptions.workingChallenge, this.challenge);
this.$root.$emit('show::modal', 'challenge-modal'); this.$root.$emit('show::modal', 'challenge-modal');
}, },
}, },

View File

@@ -31,11 +31,13 @@
.row.new-message-row .row.new-message-row
textarea(:placeholder="!isParty ? $t('chatPlaceholder') : $t('partyChatPlaceholder')", v-model='newMessage', @keydown='updateCarretPosition') textarea(:placeholder="!isParty ? $t('chatPlaceholder') : $t('partyChatPlaceholder')", v-model='newMessage', @keydown='updateCarretPosition')
autocomplete(:text='newMessage', v-on:select="selectedAutocomplete", :coords='coords', :chat='group.chat') autocomplete(:text='newMessage', v-on:select="selectedAutocomplete", :coords='coords', :chat='group.chat')
button.btn.btn-secondary.send-chat.float-left(v-once, @click='sendMessage()') {{ $t('send') }}
.row .row
.col-6 .col-6
button.btn.btn-secondary.float-left.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }} button.btn.btn-secondary.float-left.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }}
button.btn.btn-secondary.float-left(v-once, @click='reverseChat()') {{ $t('reverseChat') }} button.btn.btn-secondary.float-left(v-once, @click='reverseChat()') {{ $t('reverseChat') }}
.col-6
button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }}
.row.community-guidelines(v-if='!communityGuidelinesAccepted') .row.community-guidelines(v-if='!communityGuidelinesAccepted')
div.col-8(v-once, v-html="$t('communityGuidelinesIntro')") div.col-8(v-once, v-html="$t('communityGuidelinesIntro')")
@@ -301,13 +303,6 @@
.new-message-row { .new-message-row {
position: relative; position: relative;
} }
.send-chat {
z-index: 10;
position: absolute;
right: 1em;
bottom: 1em;
}
} }
.toggle-up .svg-icon, .toggle-down .svg-icon { .toggle-up .svg-icon, .toggle-down .svg-icon {

View File

@@ -15,10 +15,10 @@
.row .row
.col-6 .col-6
button.btn.btn-secondary.send-chat.float-left(v-once, @click='sendMessage()') {{ $t('send') }} button.btn.btn-secondary.float-left.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }}
button.btn.btn-secondary.float-left(v-once, @click='reverseChat()') {{ $t('reverseChat') }}
.col-6 .col-6
button.btn.btn-secondary.float-right.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }} button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }}
button.btn.btn-secondary.float-right(v-once, @click='reverseChat()') {{ $t('reverseChat') }}
.row.community-guidelines(v-if='!communityGuidelinesAccepted') .row.community-guidelines(v-if='!communityGuidelinesAccepted')
div.col-8(v-once, v-html="$t('communityGuidelinesIntro')") div.col-8(v-once, v-html="$t('communityGuidelinesIntro')")

View File

@@ -248,6 +248,22 @@
div.popover-content {{ $t('clickOnPetToFeed', {foodName: currentDraggingFood.text() }) }} div.popover-content {{ $t('clickOnPetToFeed', {foodName: currentDraggingFood.text() }) }}
</template> </template>
<style lang='scss' scoped>
.group {
height: 130px;
overflow: hidden;
}
.pet-row {
max-width: 100%;
flex-wrap: wrap;
.item {
margin-right: .5em;
}
}
</style>
<style lang="scss"> <style lang="scss">
@import '~client/assets/scss/colors.scss'; @import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/modal.scss'; @import '~client/assets/scss/modal.scss';
@@ -274,20 +290,6 @@
top: -16px !important; top: -16px !important;
} }
.group {
height: 130px;
overflow: hidden;
}
.pet-row {
max-width: 100%;
flex-wrap: wrap;
.item {
margin-right: .5em;
}
}
.hatchablePopover { .hatchablePopover {
width: 180px width: 180px
} }

View File

@@ -21,16 +21,13 @@ div(v-if='user.stats.lvl > 10')
@click='castStart(skill)', @click='castStart(skill)',
v-for='(skill, key) in spells[user.stats.class]', v-for='(skill, key) in spells[user.stats.class]',
v-if='user.stats.lvl >= skill.lvl', v-if='user.stats.lvl >= skill.lvl',
popover-trigger='mouseenter', v-b-popover.hover.auto='skill.notes()')
popover-placement='top',
:popover='skillNotes(skill)')
.spell.col-12.row .spell.col-12.row
.col-8.details .col-8.details
a(:class='{"disabled": spellDisabled(key)}') a(:class='{"disabled": spellDisabled(key)}')
p.title {{skill.text()}} div.img(:class='`shop_${skill.key} shop-sprite item-img`')
p.notes {{skill.notes()}} span.title {{skill.text()}}
.col-4.mana .col-4.mana
.img(:class='`shop_${skill.key} shop-sprite item-img`')
.mana-text .mana-text
.svg-icon(v-html="icons.mana") .svg-icon(v-html="icons.mana")
div {{skill.mana}} div {{skill.mana}}
@@ -52,23 +49,33 @@ div(v-if='user.stats.lvl > 10')
.spell:hover { .spell:hover {
cursor: pointer; cursor: pointer;
border: solid 2px #50b5e9;
} }
.spell { .spell {
background: #ffffff; background: #ffffff;
border: solid 2px transparent;
margin-bottom: 1em; margin-bottom: 1em;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12); box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
border-radius: 2px; border-radius: 1000px;
color: #4e4a57; color: #4e4a57;
padding-right: 0; padding-right: 0;
padding-left: 0; padding-left: 0;
overflow: hidden;
.details { .details {
text-align: left; text-align: left;
padding-top: .5em; padding-top: .5em;
p { .img {
margin-bottom: .5em; display: inline-block;
}
span {
display: inline-block;
width: 50%;
padding-bottom: .7em;
vertical-align: bottom;
} }
.notes { .notes {
@@ -83,6 +90,7 @@ div(v-if='user.stats.lvl > 10')
.mana-text { .mana-text {
margin-bottom: .2em; margin-bottom: .2em;
padding-top: 1.1em;
div { div {
display: inline-block; display: inline-block;
@@ -142,6 +150,7 @@ div(v-if='user.stats.lvl > 10')
<script> <script>
import axios from 'axios'; import axios from 'axios';
import isArray from 'lodash/isArray'; import isArray from 'lodash/isArray';
import bPopover from 'bootstrap-vue/lib/directives/popover';
import spells from '../../../common/script/content/spells'; import spells from '../../../common/script/content/spells';
@@ -152,6 +161,7 @@ import MouseMoveDirective from 'client/directives/mouseposition.directive';
import mana from 'assets/svg/mana.svg'; import mana from 'assets/svg/mana.svg';
import quests from 'common/script/content/quests'; import quests from 'common/script/content/quests';
import { CONSTANTS, setLocalSetting, getLocalSetting } from 'client/libs/userlocalManager';
export default { export default {
mixins: [notifications], mixins: [notifications],
@@ -160,6 +170,7 @@ export default {
}, },
directives: { directives: {
mousePosition: MouseMoveDirective, mousePosition: MouseMoveDirective,
bPopover,
}, },
data () { data () {
return { return {
@@ -183,6 +194,11 @@ export default {
if (keyEvent.keyCode !== 27) return; if (keyEvent.keyCode !== 27) return;
this.castCancel(); this.castCancel();
}); });
const spellDrawerState = getLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE);
if (spellDrawerState === CONSTANTS.valueConstants.SPELL_DRAWER_CLOSED) {
this.$store.state.spellOptions.spellDrawOpen = false;
}
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({user: 'user.data'}),
@@ -191,8 +207,15 @@ export default {
}, },
}, },
methods: { methods: {
drawerToggled (openChanged) { drawerToggled (newState) {
this.$store.state.spellOptions.spellDrawOpen = openChanged; this.$store.state.spellOptions.spellDrawOpen = newState;
if (newState) {
setLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE, CONSTANTS.valueConstants.SPELL_DRAWER_OPEN);
return;
}
setLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE, CONSTANTS.valueConstants.SPELL_DRAWER_CLOSED);
}, },
spellDisabled (skill) { spellDisabled (skill) {
if (skill === 'frost' && this.user.stats.buffs.streaks) { if (skill === 'frost' && this.user.stats.buffs.streaks) {

View File

@@ -11,7 +11,7 @@
.col-4.offset-4 .col-4.offset-4
.input-group .input-group
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText") input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
.filter-panel(v-if="isFilterPanelOpen") .filter-panel(v-if="isFilterPanelOpen", v-on:mouseleave="checkMouseOver")
.tags-category.d-flex( .tags-category.d-flex(
v-for="tagsType in tagsByType", v-for="tagsType in tagsByType",
v-if="tagsType.tags.length > 0 || tagsType.key === 'tags'", v-if="tagsType.tags.length > 0 || tagsType.key === 'tags'",
@@ -19,17 +19,17 @@
) )
.tags-header .tags-header
strong(v-once) {{ $t(tagsType.key) }} strong(v-once) {{ $t(tagsType.key) }}
a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }} a.d-block(v-if="tagsType.key !== 'groups' && !editingTags", @click="editTags(tagsType.key)") {{ $t('editTags2') }}
.tags-list.container .tags-list.container
.row(:class="{'no-gutters': !editingTags}") .row(:class="{'no-gutters': !editingTags}")
template(v-if="editingTags && tagsType.key === 'tags'") template(v-if="editingTags && tagsType.key !== 'groups'")
.col-6(v-for="(tag, tagIndex) in tagsSnap") .col-6(v-for="(tag, tagIndex) in tagsSnap[tagsType.key]")
.inline-edit-input-group.tag-edit-item.input-group .inline-edit-input-group.tag-edit-item.input-group
input.tag-edit-input.inline-edit-input.form-control(type="text", v-model="tag.name") input.tag-edit-input.inline-edit-input.form-control(type="text", v-model="tag.name")
span.input-group-btn(@click="removeTag(tagIndex)") span.input-group-btn(@click="removeTag(tagIndex, tagsType.key)")
.svg-icon.destroy-icon(v-html="icons.destroy") .svg-icon.destroy-icon(v-html="icons.destroy")
.col-6 .col-6(v-if="tagsType.key === 'tags'")
input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event)", v-model="newTag") input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event, tagsType.key)", v-model="newTag")
template(v-else) template(v-else)
.col-6(v-for="(tag, tagIndex) in tagsType.tags") .col-6(v-for="(tag, tagIndex) in tagsType.tags")
label.custom-control.custom-checkbox label.custom-control.custom-checkbox
@@ -50,7 +50,6 @@
.float-left .float-left
a.btn-filters-danger(@click="resetFilters()", v-once) {{ $t('resetFilters') }} a.btn-filters-danger(@click="resetFilters()", v-once) {{ $t('resetFilters') }}
.float-right .float-right
a.mr-3.btn-filters-primary(@click="applyFilters()", v-once) {{ $t('applyFilters') }}
a.btn-filters-secondary(@click="closeFilterPanel()", v-once) {{ $t('cancel') }} a.btn-filters-secondary(@click="closeFilterPanel()", v-once) {{ $t('cancel') }}
span.input-group-btn span.input-group-btn
button.btn.btn-secondary.filter-button( button.btn.btn-secondary.filter-button(
@@ -322,7 +321,10 @@ export default {
}), }),
selectedTags: [], selectedTags: [],
temporarilySelectedTags: [], temporarilySelectedTags: [],
tagsSnap: null, // tags snapshot when being edited tagsSnap: {
tags: [],
challenges: [],
}, // tags snapshot when being edited
editingTags: false, editingTags: false,
newTag: null, newTag: null,
editingTask: null, editingTask: null,
@@ -368,26 +370,37 @@ export default {
}, },
methods: { methods: {
...mapActions({setUser: 'user:set'}), ...mapActions({setUser: 'user:set'}),
checkMouseOver: throttle(function throttleSearch () {
this.closeFilterPanel();
}, 250),
editTags () { editTags () {
// clone the arrays being edited so that we can revert if needed // clone the arrays being edited so that we can revert if needed
this.tagsSnap = this.tagsByType.user.tags.slice(); this.tagsSnap.tags = this.tagsByType.user.tags.slice();
this.tagsSnap.challenges = this.tagsByType.challenges.tags.slice();
this.editingTags = true; this.editingTags = true;
}, },
addTag () { addTag (eventObj, key) {
this.tagsSnap.push({id: uuid.v4(), name: this.newTag}); this.tagsSnap[key].push({id: uuid.v4(), name: this.newTag});
this.newTag = null; this.newTag = null;
}, },
removeTag (index) { removeTag (index, key) {
this.tagsSnap.splice(index, 1); this.$delete(this.tagsSnap[key], index);
}, },
saveTags () { saveTags () {
if (this.newTag) this.addTag(); if (this.newTag) this.addTag();
this.setUser({tags: this.tagsSnap});
this.tagsByType.user.tags = this.tagsSnap.tags;
this.tagsByType.challenges.tags = this.tagsSnap.challenges;
this.setUser({tags: this.tagsSnap.tags.concat(this.tagsSnap.challenges)});
this.cancelTagsEditing(); this.cancelTagsEditing();
}, },
cancelTagsEditing () { cancelTagsEditing () {
this.editingTags = false; this.editingTags = false;
this.tagsSnap = null; this.tagsSnap = {
tags: [],
challenges: [],
};
this.newTag = null; this.newTag = null;
}, },
editTask (task) { editTask (task) {
@@ -430,7 +443,6 @@ export default {
applyFilters () { applyFilters () {
const temporarilySelectedTags = this.temporarilySelectedTags; const temporarilySelectedTags = this.temporarilySelectedTags;
this.selectedTags = temporarilySelectedTags.slice(); this.selectedTags = temporarilySelectedTags.slice();
this.closeFilterPanel();
}, },
toggleTag (tag) { toggleTag (tag) {
const temporarilySelectedTags = this.temporarilySelectedTags; const temporarilySelectedTags = this.temporarilySelectedTags;
@@ -440,6 +452,8 @@ export default {
} else { } else {
temporarilySelectedTags.splice(tagI, 1); temporarilySelectedTags.splice(tagI, 1);
} }
this.applyFilters();
}, },
isTagSelected (tag) { isTagSelected (tag) {
const tagId = tag.id; const tagId = tag.id;

View File

@@ -0,0 +1,23 @@
const CONSTANTS = {
keyConstants: {
SPELL_DRAWER_STATE: 'spell-drawer-state',
},
valueConstants: {
SPELL_DRAWER_CLOSED: 'spell-drawer-closed',
SPELL_DRAWER_OPEN: 'spell-drawer-open',
},
};
function setLocalSetting (key, value) {
localStorage.setItem(key, value);
}
function getLocalSetting (key) {
return localStorage.getItem(key);
}
export {
CONSTANTS,
getLocalSetting,
setLocalSetting,
};

View File

@@ -26,7 +26,7 @@ export async function set (store, changes) {
if (key === 'tags') { if (key === 'tags') {
// Keep challenge and group tags // Keep challenge and group tags
const oldTags = user.tags.filter(t => { const oldTags = user.tags.filter(t => {
return t.group || t.challenge; return t.group;
}); });
user.tags = changes[key].concat(oldTags); user.tags = changes[key].concat(oldTags);

View File

@@ -22,6 +22,8 @@ let AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
if (AUTH_SETTINGS) { if (AUTH_SETTINGS) {
AUTH_SETTINGS = JSON.parse(AUTH_SETTINGS); AUTH_SETTINGS = JSON.parse(AUTH_SETTINGS);
if (AUTH_SETTINGS.auth && AUTH_SETTINGS.auth.apiId && AUTH_SETTINGS.auth.apiToken) {
axios.defaults.headers.common['x-api-user'] = AUTH_SETTINGS.auth.apiId; axios.defaults.headers.common['x-api-user'] = AUTH_SETTINGS.auth.apiId;
axios.defaults.headers.common['x-api-key'] = AUTH_SETTINGS.auth.apiToken; axios.defaults.headers.common['x-api-key'] = AUTH_SETTINGS.auth.apiToken;
@@ -29,6 +31,7 @@ if (AUTH_SETTINGS) {
isUserLoggedIn = true; isUserLoggedIn = true;
} }
}
const i18nData = window && window['habitica-i18n']; const i18nData = window && window['habitica-i18n'];
@@ -57,7 +60,7 @@ export default function () {
isUserLoaded: false, // Means the user and the user's tasks are ready isUserLoaded: false, // Means the user and the user's tasks are ready
isAmazonReady: false, // Whether the Amazon Payments lib can be used isAmazonReady: false, // Whether the Amazon Payments lib can be used
user: asyncResourceFactory(), user: asyncResourceFactory(),
credentials: AUTH_SETTINGS ? { credentials: isUserLoggedIn ? {
API_ID: AUTH_SETTINGS.auth.apiId, API_ID: AUTH_SETTINGS.auth.apiId,
API_TOKEN: AUTH_SETTINGS.auth.apiToken, API_TOKEN: AUTH_SETTINGS.auth.apiToken,
} : {}, } : {},

View File

@@ -20,7 +20,6 @@ import {
} from '../../libs/email'; } from '../../libs/email';
import nconf from 'nconf'; import nconf from 'nconf';
import get from 'lodash/get'; import get from 'lodash/get';
import { model as Tag } from '../../models/tag';
const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL'); const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL');
const DELETE_CONFIRMATION = 'DELETE'; const DELETE_CONFIRMATION = 'DELETE';
@@ -305,7 +304,7 @@ api.updateUser = {
// Keep challenge and group tags // Keep challenge and group tags
user.tags.forEach(t => { user.tags.forEach(t => {
if (t.group || t.challenge) { if (t.group) {
oldTags.push(t); oldTags.push(t);
} else { } else {
removedTagsIds.push(t.id); removedTagsIds.push(t.id);
@@ -320,7 +319,7 @@ api.updateUser = {
removedTagsIds.splice(oldI, 1); removedTagsIds.splice(oldI, 1);
} }
user.tags.push(Tag.sanitize(t)); user.tags.push(t);
}); });
// Remove from all the tasks // Remove from all the tasks