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;
}, (error) => {
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,
// but their chat message still exists.
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')}}
div(v-if='isLeader')
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')}}
div(v-if='isLeader')
button.btn.btn-secondary(v-once, @click='cloneChallenge()') {{$t('clone')}}
@@ -246,6 +246,9 @@ export default {
if (!this.challenge.leader) return false;
return this.user._id === this.challenge.leader._id;
},
isAdmin () {
return Boolean(this.user.contributor.admin);
},
canJoin () {
return !this.isMember;
},
@@ -403,6 +406,7 @@ export default {
cloneChallenge () {
this.cloning = true;
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');
},
},

View File

@@ -31,11 +31,13 @@
.row.new-message-row
textarea(:placeholder="!isParty ? $t('chatPlaceholder') : $t('partyChatPlaceholder')", v-model='newMessage', @keydown='updateCarretPosition')
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
.col-6
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
button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }}
.row.community-guidelines(v-if='!communityGuidelinesAccepted')
div.col-8(v-once, v-html="$t('communityGuidelinesIntro')")
@@ -301,13 +303,6 @@
.new-message-row {
position: relative;
}
.send-chat {
z-index: 10;
position: absolute;
right: 1em;
bottom: 1em;
}
}
.toggle-up .svg-icon, .toggle-down .svg-icon {

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,7 @@
.col-4.offset-4
.input-group
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(
v-for="tagsType in tagsByType",
v-if="tagsType.tags.length > 0 || tagsType.key === 'tags'",
@@ -19,17 +19,17 @@
)
.tags-header
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
.row(:class="{'no-gutters': !editingTags}")
template(v-if="editingTags && tagsType.key === 'tags'")
.col-6(v-for="(tag, tagIndex) in tagsSnap")
template(v-if="editingTags && tagsType.key !== 'groups'")
.col-6(v-for="(tag, tagIndex) in tagsSnap[tagsType.key]")
.inline-edit-input-group.tag-edit-item.input-group
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")
.col-6
input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event)", v-model="newTag")
.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, tagsType.key)", v-model="newTag")
template(v-else)
.col-6(v-for="(tag, tagIndex) in tagsType.tags")
label.custom-control.custom-checkbox
@@ -50,7 +50,6 @@
.float-left
a.btn-filters-danger(@click="resetFilters()", v-once) {{ $t('resetFilters') }}
.float-right
a.mr-3.btn-filters-primary(@click="applyFilters()", v-once) {{ $t('applyFilters') }}
a.btn-filters-secondary(@click="closeFilterPanel()", v-once) {{ $t('cancel') }}
span.input-group-btn
button.btn.btn-secondary.filter-button(
@@ -322,7 +321,10 @@ export default {
}),
selectedTags: [],
temporarilySelectedTags: [],
tagsSnap: null, // tags snapshot when being edited
tagsSnap: {
tags: [],
challenges: [],
}, // tags snapshot when being edited
editingTags: false,
newTag: null,
editingTask: null,
@@ -368,26 +370,37 @@ export default {
},
methods: {
...mapActions({setUser: 'user:set'}),
checkMouseOver: throttle(function throttleSearch () {
this.closeFilterPanel();
}, 250),
editTags () {
// 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;
},
addTag () {
this.tagsSnap.push({id: uuid.v4(), name: this.newTag});
addTag (eventObj, key) {
this.tagsSnap[key].push({id: uuid.v4(), name: this.newTag});
this.newTag = null;
},
removeTag (index) {
this.tagsSnap.splice(index, 1);
removeTag (index, key) {
this.$delete(this.tagsSnap[key], index);
},
saveTags () {
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();
},
cancelTagsEditing () {
this.editingTags = false;
this.tagsSnap = null;
this.tagsSnap = {
tags: [],
challenges: [],
};
this.newTag = null;
},
editTask (task) {
@@ -430,7 +443,6 @@ export default {
applyFilters () {
const temporarilySelectedTags = this.temporarilySelectedTags;
this.selectedTags = temporarilySelectedTags.slice();
this.closeFilterPanel();
},
toggleTag (tag) {
const temporarilySelectedTags = this.temporarilySelectedTags;
@@ -440,6 +452,8 @@ export default {
} else {
temporarilySelectedTags.splice(tagI, 1);
}
this.applyFilters();
},
isTagSelected (tag) {
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') {
// Keep challenge and group tags
const oldTags = user.tags.filter(t => {
return t.group || t.challenge;
return t.group;
});
user.tags = changes[key].concat(oldTags);

View File

@@ -22,12 +22,15 @@ let AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
if (AUTH_SETTINGS) {
AUTH_SETTINGS = JSON.parse(AUTH_SETTINGS);
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-user-timezoneOffset'] = browserTimezoneOffset;
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-key'] = AUTH_SETTINGS.auth.apiToken;
isUserLoggedIn = true;
axios.defaults.headers.common['x-user-timezoneOffset'] = browserTimezoneOffset;
isUserLoggedIn = true;
}
}
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
isAmazonReady: false, // Whether the Amazon Payments lib can be used
user: asyncResourceFactory(),
credentials: AUTH_SETTINGS ? {
credentials: isUserLoggedIn ? {
API_ID: AUTH_SETTINGS.auth.apiId,
API_TOKEN: AUTH_SETTINGS.auth.apiToken,
} : {},

View File

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