Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/modal-notifications

# Conflicts:
#	website/client/components/notifications.vue
This commit is contained in:
negue
2018-08-18 14:25:20 +02:00
496 changed files with 26873 additions and 26458 deletions
+1
View File
@@ -39,6 +39,7 @@ dist-client
test/client/unit/coverage
test/client/e2e/reports
test/client-old/spec/mocks/translations.js
yarn.lock
# Elastic Beanstalk Files
.elasticbeanstalk/*
+4
View File
@@ -2,9 +2,13 @@ version: "3"
services:
client:
environment:
- NODE_ENV=development
volumes:
- '.:/usr/src/habitrpg'
server:
environment:
- NODE_ENV=development
volumes:
- '.:/usr/src/habitrpg'
+1 -1
View File
@@ -17,5 +17,5 @@ function setUpServer () {
setUpServer();
// Replace this with your migration
const processUsers = require('./tasks/habits-one-history-entry-per-day-challenges.js');
const processUsers = require('./users/takeThis.js');
processUsers();
+123
View File
@@ -0,0 +1,123 @@
let migrationName = '20180731_naming-day.js'; // Update when running in future years
let authorName = 'Sabe'; // in case script author needs to know when their ...
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/*
* Award Naming Day ladder items to participants in this month's Naming Day festivities
*/
import monk from 'monk';
import nconf from 'nconf';
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
migration: {$ne: migrationName},
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
'items.gear.owned',
'items.mounts',
'items.pets',
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
let progressCount = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
let set = {};
let push;
const inc = {
'items.food.Cake_Base': 1,
'items.food.Cake_CottonCandyBlue': 1,
'items.food.Cake_CottonCandyPink': 1,
'items.food.Cake_Desert': 1,
'items.food.Cake_Golden': 1,
'items.food.Cake_Red': 1,
'items.food.Cake_Shade': 1,
'items.food.Cake_Skeleton': 1,
'items.food.Cake_White': 1,
'items.food.Cake_Zombie': 1,
'achievements.habiticaDays': 1,
};
if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.head_special_namingDay2017 !== 'undefined') {
set = {migration: migrationName, 'items.gear.owned.body_special_namingDay2018': false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_namingDay2018', _id: monk.id()}};
} else if (user && user.items && user.items.pets && typeof user.items.pets['Gryphon-RoyalPurple'] !== 'undefined') {
set = {migration: migrationName, 'items.gear.owned.head_special_namingDay2017': false};
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_namingDay2017', _id: monk.id()}};
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Gryphon-RoyalPurple'] !== 'undefined') {
set = {migration: migrationName, 'items.pets.Gryphon-RoyalPurple': 5};
} else {
set = {migration: migrationName, 'items.mounts.Gryphon-RoyalPurple': true};
}
if (push) {
dbUsers.update({_id: user._id}, {$set: set, $push: push, $inc: inc});
} else {
dbUsers.update({_id: user._id}, {$set: set, $inc: inc});
}
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`);
}
function displayData () {
console.warn(`\n${ count } users processed\n`);
return exiting(0);
}
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
}
}
process.exit(code);
}
module.exports = processUsers;
+2 -2
View File
@@ -1,4 +1,4 @@
let migrationName = '20180702_takeThis.js'; // Update per month
let migrationName = '20180801_takeThis.js'; // Update per month
let authorName = 'Sabe'; // in case script author needs to know when their ...
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
@@ -15,7 +15,7 @@ function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
migration: {$ne: migrationName},
challenges: {$in: ['f0481f95-1dde-4ae7-a876-d19502a45d61']}, // Update per month
challenges: {$in: ['081f8912-3526-47d5-984f-f71bbeec77fc']}, // Update per month
};
if (lastId) {
+5808 -7034
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.54.0",
"version": "4.57.1",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -26,7 +26,7 @@
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"bcrypt": "^2.0.0",
"bcrypt": "^3.0.0",
"body-parser": "^1.18.3",
"bootstrap": "^4.1.1",
"bootstrap-vue": "^2.0.0-rc.9",
@@ -35,7 +35,7 @@
"coupon-code": "^0.4.5",
"cross-env": "^5.1.5",
"css-loader": "^0.28.11",
"csv-stringify": "^2.1.0",
"csv-stringify": "^3.0.0",
"cwait": "^1.1.1",
"domain-middleware": "~0.1.0",
"express": "^4.16.3",
@@ -43,7 +43,7 @@
"express-validator": "^5.2.0",
"extract-text-webpack-plugin": "^3.0.2",
"glob": "^7.1.2",
"got": "^8.3.1",
"got": "^9.0.0",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.1",
"gulp-imagemin": "^4.1.0",
@@ -95,7 +95,7 @@
"url-loader": "^1.0.0",
"useragent": "^2.1.9",
"uuid": "^3.0.1",
"validator": "^9.4.1",
"validator": "^10.5.0",
"vinyl-buffer": "^1.0.1",
"vue": "^2.5.16",
"vue-loader": "^14.2.2",
@@ -163,19 +163,19 @@
"expect.js": "^0.3.1",
"http-proxy-middleware": "^0.18.0",
"istanbul": "^1.1.0-alpha.1",
"karma": "^2.0.2",
"karma": "^3.0.0",
"karma-babel-preprocessor": "^7.0.0",
"karma-chai-plugins": "^0.9.0",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage": "^1.1.2",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.5",
"karma-sinon-chai": "^1.3.4",
"karma-sinon-chai": "^2.0.0",
"karma-sinon-stub-promise": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^3.0.0",
"lcov-result-merger": "^2.0.0",
"lcov-result-merger": "^3.0.0",
"mocha": "^5.1.1",
"monk": "^6.0.6",
"nightwatch": "^0.9.21",
+152 -77
View File
@@ -65,6 +65,12 @@ describe('cron', () => {
expect(analytics.track.callCount).to.equal(1);
});
it('calls analytics when user is sleeping', () => {
user.preferences.sleep = true;
cron({user, tasksByType, daysMissed, analytics});
expect(analytics.track.callCount).to.equal(1);
});
describe('end of the month perks', () => {
beforeEach(() => {
user.purchased.plan.customerId = 'subscribedId';
@@ -655,76 +661,6 @@ describe('cron', () => {
});
});
describe('user is sleeping', () => {
beforeEach(() => {
user.preferences.sleep = true;
});
it('calls analytics', () => {
cron({user, tasksByType, daysMissed, analytics});
expect(analytics.track.callCount).to.equal(1);
});
it('clears user buffs', () => {
user.stats.buffs = {
str: 1,
int: 1,
per: 1,
con: 1,
stealth: 1,
streaks: true,
};
cron({user, tasksByType, daysMissed, analytics});
expect(user.stats.buffs.str).to.equal(0);
expect(user.stats.buffs.int).to.equal(0);
expect(user.stats.buffs.per).to.equal(0);
expect(user.stats.buffs.con).to.equal(0);
expect(user.stats.buffs.stealth).to.equal(0);
expect(user.stats.buffs.streaks).to.be.false;
});
it('resets all dailies without damaging user', () => {
let daily = {
text: 'test daily',
type: 'daily',
frequency: 'daily',
everyX: 5,
startDate: new Date(),
};
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
tasksByType.dailys.push(task);
tasksByType.dailys[0].completed = true;
let healthBefore = user.stats.hp;
cron({user, tasksByType, daysMissed, analytics});
expect(tasksByType.dailys[0].completed).to.be.false;
expect(user.stats.hp).to.equal(healthBefore);
});
it('sets isDue for daily', () => {
let daily = {
text: 'test daily',
type: 'daily',
frequency: 'daily',
everyX: 5,
startDate: new Date(),
};
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
tasksByType.dailys.push(task);
tasksByType.dailys[0].completed = true;
cron({user, tasksByType, daysMissed, analytics});
expect(tasksByType.dailys[0].isDue).to.be.exist;
});
});
describe('todos', () => {
beforeEach(() => {
let todo = {
@@ -846,6 +782,15 @@ describe('cron', () => {
expect(tasksByType.dailys[0].isDue).to.be.false;
});
it('computes isDue when user is sleeping', () => {
user.preferences.sleep = true;
tasksByType.dailys[0].frequency = 'daily';
tasksByType.dailys[0].everyX = 5;
tasksByType.dailys[0].startDate = moment().toDate();
cron({user, tasksByType, daysMissed, analytics});
expect(tasksByType.dailys[0].isDue).to.exist;
});
it('computes nextDue', () => {
tasksByType.dailys[0].frequency = 'daily';
tasksByType.dailys[0].everyX = 5;
@@ -865,6 +810,13 @@ describe('cron', () => {
expect(tasksByType.dailys[0].completed).to.be.false;
});
it('should set tasks completed to false when user is sleeping', () => {
user.preferences.sleep = true;
tasksByType.dailys[0].completed = true;
cron({user, tasksByType, daysMissed, analytics});
expect(tasksByType.dailys[0].completed).to.be.false;
});
it('should reset task checklist for completed dailys', () => {
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
tasksByType.dailys[0].completed = true;
@@ -872,6 +824,14 @@ describe('cron', () => {
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
});
it('should reset task checklist for completed dailys when user is sleeping', () => {
user.preferences.sleep = true;
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
tasksByType.dailys[0].completed = true;
cron({user, tasksByType, daysMissed, analytics});
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
});
it('should reset task checklist for dailys with scheduled misses', () => {
daysMissed = 10;
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
@@ -884,12 +844,19 @@ describe('cron', () => {
daysMissed = 1;
let hpBefore = user.stats.hp;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
cron({user, tasksByType, daysMissed, analytics});
expect(user.stats.hp).to.be.lessThan(hpBefore);
});
it('should not do damage for missing a daily when user is sleeping', () => {
user.preferences.sleep = true;
daysMissed = 1;
let hpBefore = user.stats.hp;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
cron({user, tasksByType, daysMissed, analytics});
expect(user.stats.hp).to.equal(hpBefore);
});
it('should not do damage for missing a daily when CRON_SAFE_MODE is set', () => {
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
let cronOverride = requireAgain(pathToCronLib).cron;
@@ -930,7 +897,7 @@ describe('cron', () => {
expect(hpDifferenceOfPartiallyIncompleteDaily).to.be.lessThan(hpDifferenceOfFullyIncompleteDaily);
});
it('should decrement quest progress down for missing a daily', () => {
it('should decrement quest.progress.down for missing a daily', () => {
daysMissed = 1;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
@@ -939,6 +906,16 @@ describe('cron', () => {
expect(progress.down).to.equal(-1);
});
it('should not decrement quest.progress.down for missing a daily when user is sleeping', () => {
user.preferences.sleep = true;
daysMissed = 1;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
let progress = cron({user, tasksByType, daysMissed, analytics});
expect(progress.down).to.equal(0);
});
it('should do damage for only yesterday\'s dailies', () => {
daysMissed = 3;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
@@ -1017,7 +994,7 @@ describe('cron', () => {
expect(tasksByType.habits[0].counterDown).to.equal(0);
});
it('should reset habit counters even if user is resting in the Inn', () => {
it('should reset habit counters even if user is sleeping', () => {
user.preferences.sleep = true;
tasksByType.habits[0].counterUp = 1;
tasksByType.habits[0].counterDown = 1;
@@ -1278,7 +1255,23 @@ describe('cron', () => {
expect(user.achievements.perfect).to.equal(0);
});
it('increments user buffs if all (at least 1) due dailies were completed', () => {
it('gives perfect day buff if all (at least 1) due dailies were completed', () => {
daysMissed = 1;
tasksByType.dailys[0].completed = true;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
let previousBuffs = user.stats.buffs.toObject();
cron({user, tasksByType, daysMissed, analytics});
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
});
it('gives perfect day buff if all (at least 1) due dailies were completed when user is sleeping', () => {
user.preferences.sleep = true;
daysMissed = 1;
tasksByType.dailys[0].completed = true;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
@@ -1317,6 +1310,31 @@ describe('cron', () => {
expect(user.stats.buffs.streaks).to.be.false;
});
it('clears buffs if user does not have a perfect day (no due dailys) when user is sleeping', () => {
user.preferences.sleep = true;
daysMissed = 1;
tasksByType.dailys[0].completed = true;
tasksByType.dailys[0].startDate = moment(new Date()).add({days: 1});
user.stats.buffs = {
str: 1,
int: 1,
per: 1,
con: 1,
stealth: 0,
streaks: true,
};
cron({user, tasksByType, daysMissed, analytics});
expect(user.stats.buffs.str).to.equal(0);
expect(user.stats.buffs.int).to.equal(0);
expect(user.stats.buffs.per).to.equal(0);
expect(user.stats.buffs.con).to.equal(0);
expect(user.stats.buffs.stealth).to.equal(0);
expect(user.stats.buffs.streaks).to.be.false;
});
it('clears buffs if user does not have a perfect day (at least one due daily not completed)', () => {
daysMissed = 1;
tasksByType.dailys[0].completed = false;
@@ -1341,7 +1359,50 @@ describe('cron', () => {
expect(user.stats.buffs.streaks).to.be.false;
});
it('still grants a perfect day when CRON_SAFE_MODE is set', () => {
it('clears buffs if user does not have a perfect day (at least one due daily not completed) when user is sleeping', () => {
user.preferences.sleep = true;
daysMissed = 1;
tasksByType.dailys[0].completed = false;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
user.stats.buffs = {
str: 1,
int: 1,
per: 1,
con: 1,
stealth: 0,
streaks: true,
};
cron({user, tasksByType, daysMissed, analytics});
expect(user.stats.buffs.str).to.equal(0);
expect(user.stats.buffs.int).to.equal(0);
expect(user.stats.buffs.per).to.equal(0);
expect(user.stats.buffs.con).to.equal(0);
expect(user.stats.buffs.stealth).to.equal(0);
expect(user.stats.buffs.streaks).to.be.false;
});
it('always grants a perfect day buff when CRON_SAFE_MODE is set', () => {
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
let cronOverride = requireAgain(pathToCronLib).cron;
daysMissed = 1;
tasksByType.dailys[0].completed = false;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
let previousBuffs = user.stats.buffs.toObject();
cronOverride({user, tasksByType, daysMissed, analytics});
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
});
it('always grants a perfect day buff when CRON_SAFE_MODE is set when user is sleeping', () => {
user.preferences.sleep = true;
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
let cronOverride = requireAgain(pathToCronLib).cron;
daysMissed = 1;
@@ -1373,6 +1434,20 @@ describe('cron', () => {
common.statsComputed.restore();
});
it('should not add mp to user when user is sleeping', () => {
const statsComputedRes = common.statsComputed(user);
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
user.preferences.sleep = true;
let mpBefore = user.stats.mp;
tasksByType.dailys[0].completed = true;
stubbedStatsComputed.returns(Object.assign(statsComputedRes, {maxMP: 100}));
cron({user, tasksByType, daysMissed, analytics});
expect(user.stats.mp).to.equal(mpBefore);
common.statsComputed.restore();
});
it('set user\'s mp to statsComputed.maxMP when user.stats.mp is greater', () => {
const statsComputedRes = common.statsComputed(user);
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
@@ -1568,7 +1643,7 @@ describe('cron', () => {
expect(user.loginIncentives).to.eql(1);
});
it('increments loginIncentives by 1 even if user has Dailies paused', () => {
it('increments loginIncentives by 1 even if user is sleeping', () => {
user.preferences.sleep = true;
cron({user, tasksByType, daysMissed, analytics});
expect(user.loginIncentives).to.eql(1);
+1 -1
View File
@@ -58,7 +58,7 @@ describe('slack', () => {
title: 'Flag in Some group - (private guild)',
title_link: undefined,
text: 'some text',
footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message.>/),
mrkdwn_in: [
'text',
],
@@ -304,14 +304,14 @@ describe('POST /challenges', () => {
expect(groupLeader.challenges.length).to.equal(0);
});
it('awards achievement if this is creator\'s first challenge', async () => {
it('does not award joinedChallenge achievement for creating a challenge', async () => {
await groupLeader.post('/challenges', {
group: group._id,
name: 'Test Challenge',
shortName: 'TC Label',
});
groupLeader = await groupLeader.sync();
expect(groupLeader.achievements.joinedChallenge).to.be.true;
expect(groupLeader.achievements.joinedChallenge).to.not.be.true;
});
it('sets summary to challenges name when not supplied', async () => {
@@ -3,15 +3,23 @@ import {
translate as t,
} from '../../../../helpers/api-integration/v3';
import { find } from 'lodash';
import moment from 'moment';
import nconf from 'nconf';
import { IncomingWebhook } from '@slack/client';
const BASE_URL = nconf.get('BASE_URL');
describe('POST /chat/:chatId/flag', () => {
let user, admin, anotherUser, group;
let user, admin, anotherUser, newUser, group;
const TEST_MESSAGE = 'Test Message';
const USER_AGE_FOR_FLAGGING = 3;
beforeEach(async () => {
user = await generateUser({balance: 1});
user = await generateUser({balance: 1, 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate()});
admin = await generateUser({balance: 1, 'contributor.admin': true});
anotherUser = await generateUser();
anotherUser = await generateUser({'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate()});
newUser = await generateUser({'auth.timestamps.created': moment().subtract(1, 'days').toDate()});
sandbox.stub(IncomingWebhook.prototype, 'send');
group = await user.post('/groups', {
name: 'Test Guild',
@@ -20,6 +28,10 @@ describe('POST /chat/:chatId/flag', () => {
});
});
afterEach(() => {
sandbox.restore();
});
it('Returns an error when chat message is not found', async () => {
await expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`))
.to.eventually.be.rejected.and.eql({
@@ -34,7 +46,7 @@ describe('POST /chat/:chatId/flag', () => {
await expect(user.post(`/groups/${group._id}/chat/${message.message.id}/flag`)).to.eventually.be.ok;
});
it('Flags a chat', async () => {
it('Flags a chat and sends normal message to moderator Slack when user is not new', async () => {
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
@@ -45,6 +57,62 @@ describe('POST /chat/:chatId/flag', () => {
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck.flags[user._id]).to.equal(true);
// Slack message to mods
const timestamp = `${moment(message.timestamp).utc().format('YYYY-MM-DD HH:mm')} UTC`;
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `${user.profile.name} (${user.id}; language: en) flagged a message`,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `${anotherUser.profile.name} - ${anotherUser.auth.local.email} - ${anotherUser._id}\n${timestamp}`,
title: 'Flag in Test Guild',
title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE,
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
mrkdwn_in: [
'text',
],
}],
});
/* eslint-ensable camelcase */
});
it('Does not increment message flag count and sends different message to moderator Slack when user is new', async () => {
let automatedComment = `The post's flag count has not been increased because the flagger's account is less than ${USER_AGE_FOR_FLAGGING} days old.`;
let { message } = await newUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
let flagResult = await newUser.post(`/groups/${group._id}/chat/${message.id}/flag`);
expect(flagResult.flags[newUser._id]).to.equal(true);
expect(flagResult.flagCount).to.equal(0);
let groupWithFlags = await admin.get(`/groups/${group._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck.flags[newUser._id]).to.equal(true);
// Slack message to mods
const timestamp = `${moment(message.timestamp).utc().format('YYYY-MM-DD HH:mm')} UTC`;
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a message`,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `${newUser.profile.name} - ${newUser.auth.local.email} - ${newUser._id}\n${timestamp}`,
title: 'Flag in Test Guild',
title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE,
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
mrkdwn_in: [
'text',
],
}],
});
/* eslint-ensable camelcase */
});
it('Flags a chat when the author\'s account was deleted', async () => {
@@ -117,7 +185,7 @@ describe('POST /chat/:chatId/flag', () => {
});
});
it('Returns an error when user tries to flag a message that is already flagged', async () => {
it('Returns an error when user tries to flag a message that they already flagged', async () => {
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
+11 -10
View File
@@ -1,3 +1,5 @@
import { IncomingWebhook } from '@slack/client';
import nconf from 'nconf';
import {
createAndPopulateGroup,
generateUser,
@@ -15,8 +17,6 @@ import { getMatchesByWordArray } from '../../../../../website/server/libs/string
import bannedWords from '../../../../../website/server/libs/bannedWords';
import guildsAllowingBannedWords from '../../../../../website/server/libs/guildsAllowingBannedWords';
import * as email from '../../../../../website/server/libs/email';
import { IncomingWebhook } from '@slack/client';
import nconf from 'nconf';
const BASE_URL = nconf.get('BASE_URL');
@@ -80,12 +80,14 @@ describe('POST /chat', () => {
});
});
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
let userWithChatRevoked = await member.update({'flags.chatRevoked': true});
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('chatPrivilegesRevoked'),
describe('mute user', () => {
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
const userWithChatRevoked = await member.update({'flags.chatRevoked': true});
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('chatPrivilegesRevoked'),
});
});
});
@@ -259,7 +261,6 @@ describe('POST /chat', () => {
title: 'Slur in Test Guild',
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
text: testSlurMessage,
// footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
mrkdwn_in: [
'text',
],
@@ -274,6 +275,7 @@ describe('POST /chat', () => {
message: t('chatPrivilegesRevoked'),
});
// @TODO: The next test should not depend on this. We should reset the user test in a beforeEach
// Restore chat privileges to continue testing
user.flags.chatRevoked = false;
await user.update({'flags.chatRevoked': false});
@@ -312,7 +314,6 @@ describe('POST /chat', () => {
title: 'Slur in Party - (private party)',
title_link: undefined,
text: testSlurMessage,
// footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
mrkdwn_in: [
'text',
],
@@ -4,23 +4,24 @@ import {
translate as t,
} from '../../../../helpers/api-integration/v3';
import config from '../../../../../config.json';
import moment from 'moment';
import { v4 as generateUUID } from 'uuid';
describe('POST /groups/:id/chat/:id/clearflags', () => {
const USER_AGE_FOR_FLAGGING = 3;
let groupWithChat, message, author, nonAdmin, admin;
before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
members: 1,
});
groupWithChat = group;
author = groupLeader;
nonAdmin = members[0];
nonAdmin = await generateUser({'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate()});
admin = await generateUser({'contributor.admin': true});
message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
@@ -69,9 +70,14 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
privateMessage = privateMessage.message;
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/flag`);
// first test that the flag was actually successful
let messages = await members[0].get(`/groups/${group._id}/chat`);
expect(messages[0].flagCount).to.eql(5);
await admin.post(`/groups/${group._id}/chat/${privateMessage.id}/clearflags`);
let messages = await members[0].get(`/groups/${group._id}/chat`);
messages = await members[0].get(`/groups/${group._id}/chat`);
expect(messages[0].flagCount).to.eql(0);
});
@@ -77,7 +77,7 @@ describe('GET /groups/:groupId/members', () => {
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background', 'tasks',
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
].sort());
expect(memberRes.stats.maxMP).to.exist;
@@ -98,7 +98,7 @@ describe('GET /groups/:groupId/members', () => {
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background', 'tasks',
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
].sort());
expect(memberRes.stats.maxMP).to.exist;
@@ -37,7 +37,7 @@ describe('GET /members/:memberId', () => {
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background', 'tasks',
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
].sort());
expect(memberRes.stats.maxMP).to.exist;
@@ -0,0 +1,39 @@
import {
createAndPopulateGroup,
} from '../../../../helpers/api-integration/v3';
describe('Prevent multiple notifications', () => {
let partyLeader, partyMembers, party;
before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
type: 'party',
privacy: 'private',
},
members: 4,
});
party = group;
partyLeader = groupLeader;
partyMembers = members;
});
it('does not add the same notification twice', async () => {
const multipleChatMessages = [];
for (let i = 0; i < 4; i++) {
for (let memberIndex = 0; memberIndex < partyMembers.length; memberIndex++) {
multipleChatMessages.push(
partyMembers[memberIndex].post(`/groups/${party._id}/chat`, { message: `Message ${i}_${memberIndex}`}),
);
}
}
await Promise.all(multipleChatMessages);
const userWithNotification = await partyLeader.get('/user');
expect(userWithNotification.notifications.length).to.be.eq(1);
});
});
@@ -58,6 +58,21 @@ describe('POST /user/class/cast/:spellId', () => {
});
});
it('returns an error if use Healing Light spell with full health', async () => {
await user.update({
'stats.class': 'healer',
'stats.lvl': 11,
'stats.hp': 50,
'stats.mp': 200,
});
await expect(user.post('/user/class/cast/heal'))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('messageHealthAlreadyMax'),
});
});
it('returns an error if spell.lvl > user.level', async () => {
await user.update({'stats.mp': 200, 'stats.class': 'wizard'});
await expect(user.post('/user/class/cast/earth'))
@@ -50,11 +50,24 @@ describe('POST /user/push-devices', () => {
});
it('adds a push device to the user', async () => {
let response = await user.post('/user/push-devices', {type, regId});
const response = await user.post('/user/push-devices', {type, regId});
await user.sync();
expect(response.message).to.equal(t('pushDeviceAdded'));
expect(response.data[0].type).to.equal(type);
expect(response.data[0].regId).to.equal(regId);
expect(user.pushDevices[0].type).to.equal(type);
expect(user.pushDevices[0].regId).to.equal(regId);
});
it('removes a push device to the user', async () => {
await user.post('/user/push-devices', {type, regId});
const response = await user.del(`/user/push-devices/${regId}`);
await user.sync();
expect(response.message).to.equal(t('pushDeviceRemoved'));
expect(response.data[0]).to.not.exist;
expect(user.pushDevices[0]).to.not.exist;
});
});
+38
View File
@@ -0,0 +1,38 @@
import {
generateUser,
} from '../../helpers/common.helper';
import spells from '../../../website/common/script/content/spells';
import {
NotAuthorized,
} from '../../../website/common/script/libs/errors';
import i18n from '../../../website/common/script/i18n';
// TODO complete the test suite...
describe('shared.ops.spells', () => {
let user;
beforeEach(() => {
user = generateUser();
});
it('returns an error when healer tries to cast Healing Light with full health', (done) => {
user.stats.class = 'healer';
user.stats.lvl = 11;
user.stats.hp = 50;
user.stats.mp = 200;
let spell = spells.healer.heal;
try {
spell.cast(user);
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageHealthAlreadyMax'));
expect(user.stats.hp).to.eql(50);
expect(user.stats.mp).to.eql(200);
done();
}
});
});
@@ -1,90 +1,42 @@
.promo_aquatic_glass_potions {
.promo_armoire_backgrounds_201808 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -853px 0px;
width: 141px;
height: 441px;
}
.promo_armoire_backgrounds_201807 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -995px 0px;
background-position: -817px 0px;
width: 141px;
height: 441px;
}
.promo_ios {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -477px 0px;
background-position: -441px 0px;
width: 375px;
height: 361px;
}
.promo_kangaroo {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -327px;
width: 420px;
height: 336px;
}
.promo_mystery_201807 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -995px -442px;
background-position: -817px -442px;
width: 114px;
height: 120px;
}
.promo_orcas {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -954px;
width: 219px;
height: 147px;
}
.promo_seafoam {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -853px -442px;
width: 141px;
height: 441px;
}
.promo_seaserpent {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 476px;
height: 364px;
}
.promo_seasonal_shop_summer {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -642px -552px;
width: 162px;
height: 138px;
}
.promo_splashy_skins {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -142px -365px;
width: 375px;
height: 186px;
}
.customize-option.promo_splashy_skins {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -167px -380px;
width: 60px;
height: 60px;
}
.promo_summer_splash_2018 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -365px;
width: 141px;
height: 588px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -995px -563px;
background-position: -817px -563px;
width: 96px;
height: 69px;
}
.promo_unconventional_armor {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -518px -365px;
background-position: -441px -362px;
width: 180px;
height: 180px;
}
.scene_pomodoro {
.scene_tavern {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -142px -552px;
width: 258px;
height: 258px;
}
.scene_todos {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -401px -552px;
width: 240px;
height: 195px;
background-position: 0px 0px;
width: 440px;
height: 326px;
}
@@ -354,13 +354,13 @@
}
.background_at_the_docks {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px 0px;
background-position: -710px -148px;
width: 141px;
height: 147px;
}
.background_aurora {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -148px;
background-position: -710px -296px;
width: 141px;
height: 147px;
}
@@ -372,19 +372,19 @@
}
.background_back_of_giant_beast {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -296px;
background-position: -710px -444px;
width: 141px;
height: 147px;
}
.background_bamboo_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -444px;
background-position: 0px -592px;
width: 141px;
height: 147px;
}
.background_beach {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -592px;
background-position: -142px -592px;
width: 141px;
height: 147px;
}
@@ -424,375 +424,387 @@
width: 140px;
height: 147px;
}
.background_bug_covered_log {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1128px -1036px;
width: 140px;
height: 147px;
}
.background_buried_treasure {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -284px -592px;
width: 141px;
height: 147px;
}
.background_champions_colosseum {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -148px;
width: 140px;
height: 147px;
}
.background_cherry_trees {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -296px;
width: 140px;
height: 147px;
}
.background_chessboard_land {
.background_bridge {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -426px -592px;
width: 141px;
height: 147px;
}
.background_clouds {
.background_bug_covered_log {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -592px;
background-position: -1277px 0px;
width: 140px;
height: 147px;
}
.background_coral_reef {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -740px;
width: 140px;
height: 147px;
}
.background_cornfields {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -888px;
width: 140px;
height: 147px;
}
.background_cozy_library {
.background_buried_treasure {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -568px -592px;
width: 141px;
height: 147px;
}
.background_crosscountry_ski_trail {
.background_champions_colosseum {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -296px;
width: 140px;
height: 147px;
}
.background_cherry_trees {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -444px;
width: 140px;
height: 147px;
}
.background_chessboard_land {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -592px;
width: 141px;
height: 147px;
}
.background_crystal_cave {
.background_clouds {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -141px -1184px;
background-position: -1277px -740px;
width: 140px;
height: 147px;
}
.background_dark_deep {
.background_coral_reef {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -888px;
width: 140px;
height: 147px;
}
.background_cornfields {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -1036px;
width: 140px;
height: 147px;
}
.background_cozy_library {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px 0px;
width: 141px;
height: 147px;
}
.background_deep_mine {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -423px -1184px;
width: 140px;
height: 147px;
}
.background_deep_sea {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -564px -1184px;
width: 140px;
height: 147px;
}
.background_desert_dunes {
.background_crosscountry_ski_trail {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -148px;
width: 141px;
height: 147px;
}
.background_dilatory_castle {
.background_crystal_cave {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -846px -1184px;
background-position: -282px -1184px;
width: 140px;
height: 147px;
}
.background_dilatory_city {
.background_dark_deep {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -296px;
width: 141px;
height: 147px;
}
.background_dilatory_ruins {
.background_deep_mine {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1128px -1184px;
background-position: -564px -1184px;
width: 140px;
height: 147px;
}
.background_distant_castle {
.background_deep_sea {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1269px -1184px;
background-position: -705px -1184px;
width: 140px;
height: 147px;
}
.background_drifting_raft {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px 0px;
width: 140px;
height: 147px;
}
.background_driving_a_coach {
.background_desert_dunes {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -444px;
width: 141px;
height: 147px;
}
.background_driving_a_sleigh {
.background_dilatory_castle {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -142px 0px;
width: 141px;
height: 147px;
}
.background_dusty_canyons {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -444px;
background-position: -987px -1184px;
width: 140px;
height: 147px;
}
.background_elegant_balcony {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -740px;
width: 141px;
height: 147px;
}
.background_fairy_ring {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -740px;
width: 140px;
height: 147px;
}
.background_fantastical_shoe_store {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -888px;
width: 140px;
height: 147px;
}
.background_farmhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -1036px;
width: 140px;
height: 147px;
}
.background_fiber_arts_room {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -142px -740px;
width: 141px;
height: 147px;
}
.background_floating_islands {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -1332px;
width: 140px;
height: 147px;
}
.background_floral_meadow {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -141px -1332px;
width: 140px;
height: 147px;
}
.background_flying_over_a_field_of_wildflowers {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -284px -740px;
width: 141px;
height: 147px;
}
.customize-option.background_flying_over_a_field_of_wildflowers {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -309px -755px;
width: 60px;
height: 60px;
}
.background_flying_over_an_ancient_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -426px -740px;
width: 141px;
height: 147px;
}
.background_flying_over_icy_steppes {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -568px -740px;
width: 141px;
height: 147px;
}
.background_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -705px -1332px;
width: 140px;
height: 147px;
}
.background_frigid_peak {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -846px -1332px;
width: 140px;
height: 147px;
}
.background_frozen_lake {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1332px;
width: 140px;
height: 147px;
}
.background_garden_shed {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -740px;
width: 141px;
height: 147px;
}
.background_gazebo {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1269px -1332px;
width: 140px;
height: 147px;
}
.background_giant_birdhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1410px -1332px;
width: 140px;
height: 147px;
}
.background_giant_florals {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -740px;
width: 141px;
height: 147px;
}
.background_giant_seashell {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px 0px;
width: 141px;
height: 147px;
}
.background_giant_wave {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -148px;
width: 141px;
height: 147px;
}
.background_gorgeous_greenhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -296px;
width: 141px;
height: 147px;
}
.background_grand_staircase {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -444px;
width: 141px;
height: 147px;
}
.background_graveyard {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -740px;
width: 140px;
height: 147px;
}
.background_green {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -888px;
width: 140px;
height: 147px;
}
.background_guardian_statues {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -592px;
width: 141px;
height: 147px;
}
.background_gumdrop_land {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -1184px;
width: 140px;
height: 147px;
}
.background_habit_city_streets {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -740px;
width: 141px;
height: 147px;
}
.background_harvest_feast {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -1480px;
width: 140px;
height: 147px;
}
.background_harvest_fields {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -888px;
width: 141px;
height: 147px;
}
.background_harvest_moon {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -142px -888px;
width: 141px;
height: 147px;
}
.background_haunted_house {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -423px -1480px;
width: 140px;
height: 147px;
}
.background_ice_cave {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -284px -888px;
width: 141px;
height: 147px;
}
.background_iceberg {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -705px -1480px;
width: 140px;
height: 147px;
}
.background_idyllic_cabin {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -846px -1480px;
width: 140px;
height: 147px;
}
.background_island_waterfalls {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1480px;
width: 140px;
height: 147px;
}
.background_kelp_forest {
.background_dilatory_city {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -592px;
width: 141px;
height: 147px;
}
.background_dilatory_ruins {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1269px -1184px;
width: 140px;
height: 147px;
}
.background_distant_castle {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px 0px;
width: 140px;
height: 147px;
}
.background_drifting_raft {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -148px;
width: 140px;
height: 147px;
}
.background_driving_a_coach {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -142px 0px;
width: 141px;
height: 147px;
}
.background_driving_a_sleigh {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -740px;
width: 141px;
height: 147px;
}
.background_dusty_canyons {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -592px;
width: 140px;
height: 147px;
}
.background_elegant_balcony {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -142px -740px;
width: 141px;
height: 147px;
}
.background_fairy_ring {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -888px;
width: 140px;
height: 147px;
}
.background_fantastical_shoe_store {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -1036px;
width: 140px;
height: 147px;
}
.background_farmhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -1184px;
width: 140px;
height: 147px;
}
.background_fiber_arts_room {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -284px -740px;
width: 141px;
height: 147px;
}
.background_floating_islands {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -141px -1332px;
width: 140px;
height: 147px;
}
.background_floral_meadow {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1332px;
width: 140px;
height: 147px;
}
.background_flying_over_a_field_of_wildflowers {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -426px -740px;
width: 141px;
height: 147px;
}
.customize-option.background_flying_over_a_field_of_wildflowers {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -451px -755px;
width: 60px;
height: 60px;
}
.background_flying_over_an_ancient_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -568px -740px;
width: 141px;
height: 147px;
}
.background_flying_over_icy_steppes {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px -740px;
width: 141px;
height: 147px;
}
.background_flying_over_rocky_canyon {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -852px -740px;
width: 141px;
height: 147px;
}
.background_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1332px;
width: 140px;
height: 147px;
}
.background_frigid_peak {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1128px -1332px;
width: 140px;
height: 147px;
}
.background_frozen_lake {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1269px -1332px;
width: 140px;
height: 147px;
}
.background_garden_shed {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px 0px;
width: 141px;
height: 147px;
}
.background_gazebo {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px 0px;
width: 140px;
height: 147px;
}
.background_giant_birdhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -148px;
width: 140px;
height: 147px;
}
.background_giant_florals {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -148px;
width: 141px;
height: 147px;
}
.background_giant_seashell {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -296px;
width: 141px;
height: 147px;
}
.background_giant_wave {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -444px;
width: 141px;
height: 147px;
}
.background_gorgeous_greenhouse {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -592px;
width: 141px;
height: 147px;
}
.background_grand_staircase {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -994px -740px;
width: 141px;
height: 147px;
}
.background_graveyard {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -1036px;
width: 140px;
height: 147px;
}
.background_green {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -1184px;
width: 140px;
height: 147px;
}
.background_guardian_statues {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -888px;
width: 141px;
height: 147px;
}
.background_gumdrop_land {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -1480px;
width: 140px;
height: 147px;
}
.background_habit_city_streets {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -142px -888px;
width: 141px;
height: 147px;
}
.background_harvest_feast {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1480px;
width: 140px;
height: 147px;
}
.background_harvest_fields {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -284px -888px;
width: 141px;
height: 147px;
}
.background_harvest_moon {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -426px -888px;
width: 141px;
height: 147px;
}
.background_haunted_house {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -705px -1480px;
width: 140px;
height: 147px;
}
.background_ice_cave {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -568px -888px;
width: 141px;
height: 147px;
}
.background_iceberg {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1480px;
width: 140px;
height: 147px;
}
.background_idyllic_cabin {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -296px;
width: 140px;
height: 147px;
}
.background_island_waterfalls {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -992px -888px;
width: 140px;
height: 147px;
}
.background_kelp_forest {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -710px 0px;
width: 141px;
height: 147px;
}
.background_lighthouse_shore {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -990px -888px;
background-position: -710px -888px;
width: 140px;
height: 147px;
}
.background_lilypad {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -849px -888px;
background-position: -846px -1480px;
width: 140px;
height: 147px;
}
.background_magic_beanstalk {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -708px -888px;
background-position: -564px -1480px;
width: 140px;
height: 147px;
}
@@ -816,13 +828,13 @@
}
.background_market {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1480px;
background-position: -1559px -888px;
width: 140px;
height: 147px;
}
.background_meandering_cave {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -141px -1480px;
background-position: -1559px -740px;
width: 140px;
height: 147px;
}
@@ -834,7 +846,7 @@
}
.background_midnight_clouds {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -1036px;
background-position: -1559px -444px;
width: 140px;
height: 147px;
}
@@ -846,31 +858,31 @@
}
.background_mist_shrouded_mountain {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -444px;
background-position: -1410px -1332px;
width: 140px;
height: 147px;
}
.background_mistiflying_circus {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -296px;
background-position: -846px -1332px;
width: 140px;
height: 147px;
}
.background_mountain_lake {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -148px;
background-position: -705px -1332px;
width: 140px;
height: 147px;
}
.background_mountain_pyramid {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px 0px;
background-position: -564px -1332px;
width: 140px;
height: 147px;
}
.background_night_dunes {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1128px -1332px;
background-position: -423px -1332px;
width: 140px;
height: 147px;
}
@@ -894,13 +906,13 @@
}
.background_orchard {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -1184px;
background-position: -1128px -1184px;
width: 140px;
height: 147px;
}
.background_pagodas {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -592px;
background-position: -846px -1184px;
width: 140px;
height: 147px;
}
@@ -918,13 +930,13 @@
}
.background_pumpkin_patch {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -987px -1184px;
background-position: 0px -1184px;
width: 140px;
height: 147px;
}
.background_purple {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -705px -1184px;
background-position: -1277px -592px;
width: 140px;
height: 147px;
}
@@ -948,13 +960,13 @@
}
.background_rainy_city {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -444px;
background-position: -141px -1036px;
width: 140px;
height: 147px;
}
.background_red {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px 0px;
background-position: 0px -1036px;
width: 140px;
height: 147px;
}
@@ -984,61 +996,61 @@
}
.background_seafarer_ship {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1136px -592px;
background-position: -423px -1480px;
width: 140px;
height: 147px;
}
.background_shimmering_ice_prism {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1136px -444px;
background-position: -141px -1480px;
width: 140px;
height: 147px;
}
.background_shimmery_bubbles {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -567px -888px;
background-position: -1559px -1332px;
width: 140px;
height: 147px;
}
.background_slimy_swamp {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -426px -888px;
background-position: -1559px -592px;
width: 140px;
height: 147px;
}
.background_snowman_army {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -564px -1480px;
background-position: -1559px -296px;
width: 140px;
height: 147px;
}
.background_snowy_pines {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -1332px;
background-position: 0px -1332px;
width: 140px;
height: 147px;
}
.background_snowy_sunrise {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1559px -592px;
background-position: -1418px -740px;
width: 140px;
height: 147px;
}
.background_south_pole {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -564px -1332px;
background-position: -1418px -444px;
width: 140px;
height: 147px;
}
.background_sparkling_snowflake {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -423px -1332px;
background-position: -423px -1184px;
width: 140px;
height: 147px;
}
.background_spider_web {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1332px;
background-position: -141px -1184px;
width: 140px;
height: 147px;
}
@@ -1050,61 +1062,49 @@
}
.background_spring_rain {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -148px;
background-position: -1128px -1036px;
width: 140px;
height: 147px;
}
.background_stable {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -282px -1184px;
background-position: -564px -1036px;
width: 140px;
height: 147px;
}
.background_stained_glass {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -1184px;
background-position: -1136px -888px;
width: 140px;
height: 147px;
}
.background_starry_skies {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1277px -1036px;
background-position: -1136px -592px;
width: 140px;
height: 147px;
}
.background_starry_winter_night {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -142px -592px;
background-position: -284px -592px;
width: 141px;
height: 147px;
}
.background_stoikalm_volcanoes {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -141px -1036px;
background-position: -851px -888px;
width: 140px;
height: 147px;
}
.background_stone_circle {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: 0px -1036px;
background-position: -1277px -148px;
width: 140px;
height: 147px;
}
.background_stormy_rooftops {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1136px -888px;
width: 140px;
height: 147px;
}
.background_stormy_ship {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -1418px -296px;
width: 140px;
height: 147px;
}
.background_strange_sewers {
background-image: url('~assets/images/sprites/spritesmith-main-0.png');
background-position: -564px -1036px;
background-position: -1136px -444px;
width: 140px;
height: 147px;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 553 KiB

After

Width:  |  Height:  |  Size: 549 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 432 KiB

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 112 KiB

+5 -5
View File
@@ -2,8 +2,8 @@
// possible values are: normal, fall, habitoween, thanksgiving, winter, nye, birthday, valentines, spring, summer
// more to be added on future seasons
$npc_market_flavor: 'summer';
$npc_quests_flavor: 'summer';
$npc_seasonal_flavor: 'summer';
$npc_timetravelers_flavor: 'summer';
$npc_tavern_flavor: 'summer';
$npc_market_flavor: 'normal';
$npc_quests_flavor: 'normal';
$npc_seasonal_flavor: 'normal';
$npc_timetravelers_flavor: 'normal';
$npc_tavern_flavor: 'normal';
@@ -164,30 +164,30 @@ export default {
classGear (heroClass) {
if (heroClass === 'rogue') {
return {
armor: 'armor_special_summer2018Rogue',
head: 'head_special_summer2018Rogue',
shield: 'shield_special_summer2018Rogue',
weapon: 'weapon_special_summer2018Rogue',
armor: 'armor_rogue_5',
head: 'head_rogue_5',
shield: 'shield_rogue_6',
weapon: 'weapon_rogue_6',
};
} else if (heroClass === 'wizard') {
return {
armor: 'armor_special_summer2018Mage',
head: 'head_special_summer2018Mage',
weapon: 'weapon_special_summer2018Mage',
armor: 'armor_wizard_5',
head: 'head_wizard_5',
weapon: 'weapon_wizard_6',
};
} else if (heroClass === 'healer') {
return {
armor: 'armor_special_summer2018Healer',
head: 'head_special_summer2018Healer',
shield: 'shield_special_summer2018Healer',
weapon: 'weapon_special_summer2018Healer',
armor: 'armor_healer_5',
head: 'head_healer_5',
shield: 'shield_healer_5',
weapon: 'weapon_healer_6',
};
} else {
return {
armor: 'armor_special_summer2018Warrior',
head: 'head_special_summer2018Warrior',
shield: 'shield_special_summer2018Warrior',
weapon: 'weapon_special_summer2018Warrior',
armor: 'armor_warrior_5',
head: 'head_warrior_5',
shield: 'shield_warrior_5',
weapon: 'weapon_warrior_6',
};
}
},
@@ -6,21 +6,21 @@ b-modal#login-incentives(:title="data.message", size='md', :hide-footer="true")
.row.reward-row
.col-12
avatar.avatar(:member='user', :avatarOnly='true', :withBackground='true')
.text-center.col-12
.text-center.col-12(v-if='nextReward')
.reward-wrap(v-if="!data.rewardText")
div(v-if="nextReward.rewardKey.length === 1", :class="nextReward.rewardKey[0]")
.reward(v-for="reward in nextReward.rewardKey", v-if="nextReward.rewardKey.length > 1", :class='reward')
.reward-wrap(v-if="data.rewardText")
div(v-if="data.rewardKey.length === 1", :class="data.rewardKey[0]")
.reward(v-for="reward in data.rewardKey", v-if="data.rewardKey.length > 1", :class='reward')
.col-12.text-center(v-if="data.nextRewardAt")
.col-12.text-center(v-if="data && data.nextRewardAt")
h4 {{ $t('countLeft', {count: data.nextRewardAt - user.loginIncentives}) }}
.row
.col-12.text-center(v-if='data.rewardText')
p {{ $t('earnedRewardForDevotion', {reward: data.rewardText}) }}
.col-12.text-center
p {{ $t('incentivesDescription') }}
.col-12.text-center(v-if="data.nextRewardAt")
.col-12.text-center(v-if="data && data.nextRewardAt")
h3 {{ $t('nextRewardUnlocksIn', {numberOfCheckinsLeft: data.nextRewardAt - user.loginIncentives}) }}
.modal-footer
.col-12.text-center
@@ -70,8 +70,9 @@ export default {
user: 'user.data',
}),
nextReward () {
let nextRewardKey = this.loginIncentives[this.user.loginIncentives].nextRewardAt;
let nextReward = this.loginIncentives[nextRewardKey];
if (!this.loginIncentives[this.user.loginIncentives]) return;
const nextRewardKey = this.loginIncentives[this.user.loginIncentives].nextRewardAt;
const nextReward = this.loginIncentives[nextRewardKey];
return nextReward;
},
},
+24 -15
View File
@@ -15,22 +15,22 @@
// Show avatar only if not currently affected by visual buff
template(v-if="showAvatar()")
span(:class="'chair_' + member.preferences.chair")
span(:class="getGearClass('back')")
span(:class="skinClass")
span(:class="member.preferences.size + '_shirt_' + member.preferences.shirt")
span.head_0
span(:class="member.preferences.size + '_' + getGearClass('armor')")
span(:class="getGearClass('back_collar')")
span(:class="['chair_' + member.preferences.chair, specialMountClass]")
span(:class="[getGearClass('back'), specialMountClass]")
span(:class="[skinClass, specialMountClass]")
span(:class="[member.preferences.size + '_shirt_' + member.preferences.shirt, specialMountClass]")
span(:class="['head_0', specialMountClass]")
span(:class="[member.preferences.size + '_' + getGearClass('armor'), specialMountClass]")
span(:class="[getGearClass('back_collar'), specialMountClass]")
template(v-for="type in ['bangs', 'base', 'mustache', 'beard']")
span(:class="'hair_' + type + '_' + member.preferences.hair[type] + '_' + member.preferences.hair.color")
span(:class="getGearClass('body')")
span(:class="getGearClass('eyewear')")
span(:class="getGearClass('head')")
span(:class="getGearClass('headAccessory')")
span(:class="'hair_flower_' + member.preferences.hair.flower")
span(v-if="!hideGear('shield')", :class="getGearClass('shield')")
span(v-if="!hideGear('weapon')", :class="getGearClass('weapon')")
span(:class="['hair_' + type + '_' + member.preferences.hair[type] + '_' + member.preferences.hair.color, specialMountClass]")
span(:class="[getGearClass('body'), specialMountClass]")
span(:class="[getGearClass('eyewear'), specialMountClass]")
span(:class="[getGearClass('head'), specialMountClass]")
span(:class="[getGearClass('headAccessory'), specialMountClass]")
span(:class="['hair_flower_' + member.preferences.hair.flower, specialMountClass]")
span(v-if="!hideGear('shield')", :class="[getGearClass('shield'), specialMountClass]")
span(v-if="!hideGear('weapon')", :class="[getGearClass('weapon'), specialMountClass]")
// Resting
span.zzz(v-if="member.preferences.sleep")
@@ -67,6 +67,10 @@
bottom: 0px;
left: 0px;
}
.offset-kangaroo {
margin-top: 24px;
}
</style>
<script>
@@ -172,6 +176,11 @@ export default {
costumeClass () {
return this.member.preferences.costume ? 'costume' : 'equipped';
},
specialMountClass () {
if (!this.avatarOnly && this.member.items.currentMount && this.member.items.currentMount.indexOf('Kangaroo') !== -1) {
return 'offset-kangaroo';
}
},
},
methods: {
getGearClass (gearType) {
@@ -344,14 +344,14 @@ export default {
this.tasksByType[task.type].splice(index, 1);
},
showMemberModal () {
// @TODO: Change these to options and add a custom event to members modal
this.$store.state.memberModalOptions.challengeId = this.challenge._id;
this.$store.state.memberModalOptions.groupId = 'challenge'; // @TODO: change these terrible settings
this.$store.state.memberModalOptions.group = this.group;
this.$store.state.memberModalOptions.memberCount = this.challenge.memberCount;
this.$store.state.memberModalOptions.viewingMembers = this.members;
this.$store.state.memberModalOptions.fetchMoreMembers = this.loadMembers;
this.$root.$emit('bv::show::modal', 'members-modal');
this.$root.$emit('habitica:show-member-modal', {
challengeId: this.challenge._id,
groupId: 'challenge', // @TODO: change these terrible settings
group: this.group,
memberCount: this.challenge.memberCount,
viewingMembers: this.members,
fetchMoreMembers: this.loadMembers,
});
},
async joinChallenge () {
this.user.challenges.push(this.searchId);
@@ -2,7 +2,7 @@
.container
.row
.col-12
copy-as-todo-modal(:group-name='groupName', :group-id='groupId')
copy-as-todo-modal(:group-type='groupType', :group-name='groupName', :group-id='groupId')
report-flag-modal
div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)')
// @TODO: is there a different way to do these conditionals? This creates an infinite loop
@@ -87,7 +87,7 @@ import reportFlagModal from './reportFlagModal';
import chatCard from './chatCard';
export default {
props: ['chat', 'groupId', 'groupName', 'inbox'],
props: ['chat', 'groupType', 'groupId', 'groupName', 'inbox'],
components: {
copyAsTodoModal,
reportFlagModal,
@@ -202,8 +202,17 @@ export default {
if (!profile._id) {
const result = await this.$store.dispatch('members:fetchMember', { memberId });
this.cachedProfileData[memberId] = result.data.data;
profile = result.data.data;
if (result.response && result.response.status === 404) {
return this.$store.dispatch('snackbars:add', {
title: 'Habitica',
text: this.$t('messageDeletedUser'),
type: 'error',
timeout: false,
});
} else {
this.cachedProfileData[memberId] = result.data.data;
profile = result.data.data;
}
}
// Open the modal only if the data is available
@@ -18,6 +18,7 @@ import notificationsMixin from 'client/mixins/notifications';
import Task from 'client/components/tasks/task';
import taskDefaults from 'common/script/libs/taskDefaults';
import { TAVERN_ID } from '../../../common/script/constants';
const baseUrl = 'https://habitica.com';
@@ -29,7 +30,7 @@ export default {
Task,
},
mixins: [notificationsMixin],
props: ['copyingMessage', 'groupName', 'groupId'],
props: ['copyingMessage', 'groupType', 'groupName', 'groupId'],
data () {
return {
isUser: true,
@@ -38,7 +39,7 @@ export default {
},
mounted () {
this.$root.$on('habitica::copy-as-todo', message => {
const notes = `${message.user || 'system message'}${message.user ? ' wrote' : ''} in [${this.groupName}](${baseUrl}/groups/guild/${this.groupId})`;
const notes = `${message.user || 'system message'}${message.user ? ' wrote' : ''} in [${this.groupName}](${this.groupPath()})`;
const newTask = {
text: message.text,
type: 'todo',
@@ -55,6 +56,15 @@ export default {
...mapActions({
createTask: 'tasks:create',
}),
groupPath () {
if (this.groupId === TAVERN_ID) {
return `${baseUrl}/groups/tavern`;
} else if (this.groupType === 'party') {
return `${baseUrl}/party`;
} else {
return `${baseUrl}/groups/guild/${this.groupId}`;
}
},
close () {
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
},
+3 -2
View File
@@ -222,7 +222,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
.col-12.customize-options
.option(@click='set({"preferences.chair": "none"})', :class='{active: user.preferences.chair === "none"}')
| None
.option(v-for='option in ["black", "blue", "green", "pink", "red", "yellow"]',
.option(v-for='option in chairKeys',
:class='{active: user.preferences.chair === option}')
.chair.sprite.customize-option(:class="`button_chair_${option}`", @click='set({"preferences.chair": option})')
#flowers.row(v-if='activeSubPage === "flower"')
@@ -856,7 +856,7 @@ import isPinned from 'common/script/libs/isPinned';
const skinsBySet = groupBy(appearance.skin, 'set.key');
const hairColorBySet = groupBy(appearance.hair.color, 'set.key');
let tasksByCategory = {
const tasksByCategory = {
work: [
{
type: 'habit',
@@ -1014,6 +1014,7 @@ export default {
baseHair5Keys: [1, 2],
baseHair6Keys: [1, 2, 3],
animalEarsKeys: ['bearEars', 'cactusEars', 'foxEars', 'lionEars', 'pandaEars', 'pigEars', 'tigerEars', 'wolfEars'],
chairKeys: ['black', 'blue', 'green', 'pink', 'red', 'yellow', 'handleless_black', 'handleless_blue', 'handleless_green', 'handleless_pink', 'handleless_red', 'handleless_yellow'],
icons: Object.freeze({
logoPurple,
bodyIcon,
+1 -1
View File
@@ -34,7 +34,7 @@
.row
.hr.col-12
chat-message(:chat.sync='group.chat', :group-id='group._id', :group-name='group.name')
chat-message(:chat.sync='group.chat', :group-type='group.type', :group-id='group._id', :group-name='group.name')
</template>
<script>
+11 -25
View File
@@ -3,6 +3,7 @@
group-form-modal(v-if='isParty')
start-quest-modal(:group='this.group')
quest-details-modal(:group='this.group')
participant-list-modal(:group='this.group')
group-gems-modal
.col-12.col-sm-8.standard-page
.row
@@ -126,7 +127,7 @@
.sidebar {
background-color: $gray-600;
padding-bottom: 2em;
}
.buttons-wrapper {
@@ -262,6 +263,7 @@ import * as Analytics from 'client/libs/analytics';
import membersModal from './membersModal';
import startQuestModal from './startQuestModal';
import questDetailsModal from './questDetailsModal';
import participantListModal from './participantListModal';
import groupFormModal from './groupFormModal';
import groupChallenges from '../challenges/groupChallenges';
import groupGemsModal from 'client/components/groups/groupGemsModal';
@@ -292,6 +294,7 @@ export default {
groupFormModal,
groupChallenges,
questDetailsModal,
participantListModal,
groupGemsModal,
questSidebarSection,
sidebarSection,
@@ -426,12 +429,13 @@ export default {
return this.$store.dispatch('members:getGroupMembers', payload);
},
showMemberModal () {
this.$store.state.memberModalOptions.groupId = this.group._id;
this.$store.state.memberModalOptions.group = this.group;
this.$store.state.memberModalOptions.memberCount = this.group.memberCount;
this.$store.state.memberModalOptions.viewingMembers = this.members;
this.$store.state.memberModalOptions.fetchMoreMembers = this.loadMembers;
this.$root.$emit('bv::show::modal', 'members-modal');
this.$root.$emit('habitica:show-member-modal', {
groupId: this.group._id,
group: this.group,
memberCount: this.group.memberCount,
viewingMembers: this.members,
fetchMoreMembers: this.loadMembers,
});
},
fetchRecentMessages () {
this.fetchGuild();
@@ -538,24 +542,6 @@ export default {
this.$store.state.upgradingGroup = this.group;
this.$router.push('/group-plans');
},
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('bv::show::modal', 'start-quest-modal');
return;
}
// $rootScope.$state.go('options.inventory.quests');
},
showGroupGems () {
this.$root.$emit('bv::show::modal', 'group-gems-modal');
},
@@ -53,7 +53,7 @@ div
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.removeIcon")
span.text {{$t('removeManager2')}}
b-dropdown-item(@click='viewProgress(member)')
b-dropdown-item(@click='viewProgress(member)', v-if='challengeId')
span.dropdown-icon-item
span.text {{ $t('viewProgress') }}
.row(v-if='isLoadMoreAvailable')
@@ -278,7 +278,20 @@ export default {
};
},
mounted () {
this.getMembers();
this.$root.$on('habitica:show-member-modal', (data) => {
// @TODO: Remove store
this.$store.state.memberModalOptions.challengeId = data.challengeId;
this.$store.state.memberModalOptions.groupId = data.groupId;
this.$store.state.memberModalOptions.group = data.group;
this.$store.state.memberModalOptions.memberCount = data.memberCount;
this.$store.state.memberModalOptions.viewingMembers = data.viewingMembers;
this.$store.state.memberModalOptions.fetchMoreMembers = data.fetchMoreMembers;
this.$root.$emit('bv::show::modal', 'members-modal');
this.getMembers();
});
},
destroyed () {
this.$root.$off('habitica:show-member-modal');
},
computed: {
...mapState({user: 'user.data'}),
@@ -363,6 +376,7 @@ export default {
});
this.invites = invites;
}
if (this.$store.state.memberModalOptions.viewingMembers.length > 0) {
this.members = this.$store.state.memberModalOptions.viewingMembers;
}
@@ -0,0 +1,93 @@
<template lang="pug">
b-modal#participant-list(size='md', :hide-footer='true')
.header-wrap(slot="modal-header")
.row
.col-6
h1(v-once) {{ $t('participantsTitle') }}
.col-6
button(type="button" aria-label="Close" class="close", @click='close()')
span(aria-hidden="true") ×
.row(v-for='member in participants')
.col-12.no-padding-left
member-details(:member='member')
.modal-footer
button.btn.btn-primary(@click='close()') {{ $t('close') }}
</template>
<style lang='scss'>
#participant-list {
.modal-header {
background-color: #edecee;
border-radius: 8px 8px 0 0;
box-shadow: 0 1px 2px 0 rgba(26, 24, 29, 0.24);
}
.modal-footer {
background-color: #edecee;
border-radius: 0 0 8px 8px;
}
.small-text, .character-name {
color: #878190;
}
.no-padding-left {
padding-left: 0;
}
.modal-body {
padding-left: 0;
padding-right: 0;
}
.member-details {
margin: 0;
}
}
</style>
<style lang='scss' scoped>
.header-wrap {
width: 100%;
}
h1 {
color: #4f2a93;
}
#participant-list_modal_body {
padding: 0;
max-height: 450px;
.member-details {
margin: 0;
}
}
</style>
<script>
import { mapGetters } from 'client/libs/store';
import MemberDetails from '../memberDetails';
export default {
props: ['group'],
components: {
MemberDetails,
},
computed: {
...mapGetters({
partyMembers: 'party:members',
}),
participants () {
let partyMembers = this.partyMembers || [];
return partyMembers.filter(member => this.group.quest.members[member._id] === true);
},
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'participant-list');
},
},
};
</script>
@@ -24,6 +24,9 @@ sidebar-section(:title="$t('questDetailsTitle')")
h3(v-once) {{ questData.text() }}
.quest-box
.collect-info(v-if='questData.collect')
.row
.col-12
a.float-right(@click="openParticipantList()") {{ $t('participantsTitle') }}
.row(v-for='(value, key) in questData.collect')
.col-2
div(:class="'quest_' + questData.key + '_' + key")
@@ -38,7 +41,7 @@ sidebar-section(:title="$t('questDetailsTitle')")
.col-6
h4.float-left(v-once) {{ questData.boss.name() }}
.col-6
span.float-right(v-once) {{ $t('participantsTitle') }}
a.float-right(@click="openParticipantList()") {{ $t('participantsTitle') }}
.row
.col-12
.grey-progress-bar
@@ -134,6 +137,12 @@ sidebar-section(:title="$t('questDetailsTitle')")
padding: .5em;
margin-bottom: 1em;
a {
font-family: 'Roboto Condensed', sans-serif;
font-weight: bold;
color: $gray-10;
}
svg: {
width: 100%;
height: 100%;
@@ -258,6 +267,9 @@ export default {
openQuestDetails () {
this.$root.$emit('bv::show::modal', 'quest-details');
},
openParticipantList () {
this.$root.$emit('bv::show::modal', 'participant-list');
},
async questAbort () {
if (!confirm(this.$t('sureAbort'))) return;
if (!confirm(this.$t('doubleSureAbort'))) return;
@@ -148,8 +148,13 @@ export default {
};
},
mounted () {
let questKeys = Object.keys(this.user.items.quests);
this.selectedQuest = questKeys[0];
const userQuests = this.user.items.quests;
for (const key in userQuests) {
if (userQuests[key] > 0) {
this.selectedQuest = key;
break;
}
}
this.$root.$on('selectQuest', this.selectQuest);
},
@@ -177,13 +182,14 @@ export default {
let groupId = this.group._id || this.user.party._id;
const key = this.selectedQuest;
const response = await this.$store.dispatch('guilds:inviteToQuest', {groupId, key});
const quest = response.data.data;
if (this.$store.state.party.data) this.$store.state.party.data.quest = quest;
this.loading = false;
try {
const response = await this.$store.dispatch('guilds:inviteToQuest', {groupId, key});
const quest = response.data.data;
if (this.$store.state.party.data) this.$store.state.party.data.quest = quest;
} finally {
this.loading = false;
}
this.$root.$emit('bv::hide::modal', 'start-quest-modal');
},
},
+4 -7
View File
@@ -58,7 +58,7 @@
.checkbox
label
input(type='checkbox', v-if='hero.flags', v-model='hero.flags.chatRevoked')
| Chat Privileges Revoked
strong Chat Privileges Revoked
.form-group
.checkbox
label
@@ -103,10 +103,10 @@
</style>
<script>
// import keys from 'lodash/keys';
import each from 'lodash/each';
import markdownDirective from 'client/directives/markdown';
import styleHelper from 'client/mixins/styleHelper';
import { mapState } from 'client/libs/store';
import quests from 'common/script/content/quests';
import { mountInfo, petInfo } from 'common/script/content/stable';
@@ -115,7 +115,7 @@ import gear from 'common/script/content/gear';
import notifications from 'client/mixins/notifications';
export default {
mixins: [notifications],
mixins: [notifications, styleHelper],
data () {
return {
heroes: [],
@@ -174,7 +174,7 @@ export default {
async loadHero (uuid, heroIndex) {
this.currentHeroIndex = heroIndex;
let hero = await this.$store.dispatch('hall:getHero', { uuid });
this.hero = Object.assign({}, this.hero, hero);
this.hero = Object.assign({}, hero);
if (!this.hero.flags) {
this.hero.flags = {
chatRevoked: false,
@@ -204,9 +204,6 @@ export default {
startingPage: 'profile',
});
},
userLevelStyle () {
// @TODO: implement
},
},
};
</script>
+5 -5
View File
@@ -176,11 +176,11 @@ export default {
}
},
showPartyMembers () {
// Set the party details for the members-modal component
this.$store.state.memberModalOptions.groupId = this.user.party._id;
this.$store.state.memberModalOptions.viewingMembers = this.partyMembers;
this.$store.state.memberModalOptions.group = this.user.party;
this.$root.$emit('bv::show::modal', 'members-modal');
this.$root.$emit('habitica:show-member-modal', {
groupId: this.user.party._id,
viewingMembers: this.partyMembers,
group: this.user.party,
});
},
setPartyMembersWidth ($event) {
if (this.currentWidth !== $event.width) {
@@ -27,8 +27,8 @@ menu-dropdown.item-notifications(:right="true", @toggled="handleOpenStatusChange
v-if="notificationsCount === 0"
)
.svg-icon(v-html="icons.success")
h2 You're all caught up!
p The notification fairies give you a raucous round of applause! Well done!
h2 {{ $t('noNotifications') }}
p {{ $t('noNotificationsText') }}
</template>
<style lang='scss' scoped>
@@ -0,0 +1,84 @@
<template lang="pug">
b-modal#hatching-modal(@change="resetHatchablePet($event)")
div.content(v-if="hatchablePet")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+hatchablePet.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+hatchablePet.eggKey")
h4.title {{ hatchablePet.name }}
div.text(v-html="$t('hatchDialogText', { potionName: hatchablePet.potionName, eggName: hatchablePet.eggName, petName: hatchablePet.name })")
span.svg-icon.icon-10(v-html="icons.close", slot="modal-header", @click="closeHatchPetDialog()")
div(slot="modal-footer")
button.btn.btn-primary(@click="hatchPet(hatchablePet)") {{ $t('hatch') }}
button.btn.btn-secondary.btn-flat(@click="closeHatchPetDialog()") {{ $t('cancel') }}
</template>
<style lang="scss">
@import '~client/assets/scss/modal.scss';
#hatching-modal {
@include centeredModal();
.modal-dialog {
width: 310px;
}
.content {
text-align: center;
margin: 9px;
}
.title {
margin-top: 24px;
font-size: 20px;
font-weight: bold;
line-height: 1.2;
text-align: center;
color: #4e4a57;
}
.text {
height: 60px;
font-size: 14px;
line-height: 1.43;
text-align: center;
color: #686274;
}
span.svg-icon.icon-10 {
position: absolute;
right: 10px;
top: 10px;
}
.modal-footer {
justify-content: center;
}
}
</style>
<script>
import svgClose from 'assets/svg/close.svg';
import petMixin from 'client/mixins/petMixin';
export default {
props: ['hatchablePet'],
mixins: [petMixin],
data () {
return {
icons: Object.freeze({
close: svgClose,
}),
};
},
methods: {
resetHatchablePet ($event) {
if (!$event) {
this.hatchablePet = null;
}
},
},
};
</script>
@@ -1,245 +1,205 @@
<template lang="pug">
// @TODO: breakdown to componentes and use some SOLID
.row.stable(v-mousePosition="30", @mouseMoved="mouseMoved($event)")
.standard-sidebar.d-none.d-sm-block
div
#npmMattStable.npc_matt
b-popover(
triggers="hover",
placement="right",
target="npmMattStable"
)
h4.popover-content-title(v-once) {{ $t('mattBoch') }}
.popover-content-text(v-once) {{ $t('mattBochText1') }}
.row.stable(v-mousePosition="30", @mouseMoved="mouseMoved($event)")
.standard-sidebar.d-none.d-sm-block
div
#npmMattStable.npc_matt
b-popover(
triggers="hover",
placement="right",
target="npmMattStable"
)
h4.popover-content-title(v-once) {{ $t('mattBoch') }}
.popover-content-text(v-once) {{ $t('mattBochText1') }}
.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
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",
:key="petGroup.key"
)
.custom-control.custom-checkbox
input.custom-control-input(
type="checkbox",
v-model="viewOptions[petGroup.key].selected",
:disabled="viewOptions[petGroup.key].animalCount == 0",
:id="petGroup.key",
)
label.custom-control-label(v-once, :for="petGroup.key") {{ petGroup.label }}
h3(v-once) {{ $t('mounts') }}
.form-group
.form-check(
v-for="mountGroup in mountGroups",
:key="mountGroup.key"
)
.custom-control.custom-checkbox
input.custom-control-input(
type="checkbox",
v-model="viewOptions[mountGroup.key].selected",
:disabled="viewOptions[mountGroup.key].animalCount == 0",
:id="mountGroup.key",
)
label.custom-control-label(v-once, :for="mountGroup.key") {{ mountGroup.label }}
div.form-group.clearfix
h3.float-left {{ $t('hideMissing') }}
toggle-switch.float-right(
:checked="hideMissing",
@change="updateHideMissing"
)
.standard-page
.clearfix
h1.float-left.mb-4.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.mb-3
| {{ $t('pets') }}
|
span.badge.badge-pill.badge-default {{countOwnedAnimals(petGroups[0], 'pet')}}
div(
v-for="(petGroup, index) in petGroups",
v-if="viewOptions[petGroup.key].selected",
:key="petGroup.key"
)
h4(v-if="viewOptions[petGroup.key].animalCount !== 0") {{ petGroup.label }}
.pet-row.d-flex(
v-for="(group, key, index) in pets(petGroup, hideMissing, selectedSortBy, searchTextThrottled)",
v-if='index === 0 || $_openedItemRows_isToggled(petGroup.key)')
.pet-group(
v-for='item in group'
v-drag.drop.food="item.key",
@itemDragOver="onDragOver($event, item)",
@itemDropped="onDrop($event, item)",
@itemDragLeave="onDragLeave()",
:class="{'last': item.isLastInRow}"
)
petItem(
:item="item",
:itemContentClass="getPetItemClass(item)",
:popoverPosition="'top'",
:progress="item.progress",
:emptyItem="!item.isOwned()",
:showPopover="currentDraggingFood == null",
:highlightBorder="highlightPet == item.key",
@click="petClicked(item)"
)
span(slot="popoverContent")
div.hatchablePopover(v-if="item.isHatchable()")
h4.popover-content-title {{ item.name }}
div.popover-content-text(v-html="$t('haveHatchablePet', { potion: item.potionName, egg: item.eggName })")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+item.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+item.eggKey")
div(v-else)
h4.popover-content-title {{ item.name }}
template(slot="itemBadge", slot-scope="context")
starBadge(:selected="item.key === currentPet", :show="item.isOwned()", @click="selectPet(item)")
.btn.btn-flat.btn-show-more(@click="setShowMore(petGroup.key)", v-if='petGroup.key !== "specialPets"')
| {{ $_openedItemRows_isToggled(petGroup.key) ? $t('showLess') : $t('showMore') }}
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 }}
.pet-row.d-flex(v-for="(group, key, index) in mounts(mountGroup, hideMissing, selectedSortBy, searchTextThrottled)"
v-if='index === 0 || $_openedItemRows_isToggled(mountGroup.key)')
.pet-group(v-for='item in group')
mountItem(
:item="item",
:itemContentClass="item.isOwned() ? ('Mount_Icon_' + item.key) : 'PixelPaw GreyedOut'",
:key="item.key",
:popoverPosition="'top'",
:emptyItem="!item.isOwned()",
:showPopover="true",
@click="selectMount(item)"
)
span(slot="popoverContent")
h4.popover-content-title {{ item.name }}
template(slot="itemBadge", slot-scope="context")
starBadge(
:selected="item.key === currentMount",
:show="item.isOwned()",
@click="selectMount(item)",
)
.btn.btn-flat.btn-show-more(@click="setShowMore(mountGroup.key)", v-if='mountGroup.key !== "specialMounts"')
| {{ $_openedItemRows_isToggled(mountGroup.key) ? $t('showLess') : $t('showMore') }}
drawer(
:title="$t('quickInventory')",
:errorMessage="(!hasDrawerTabItems(selectedDrawerTab)) ? ((selectedDrawerTab === 0) ? $t('noFoodAvailable') : $t('noSaddlesAvailable')) : 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 }}
#petLikeToEatStable.drawer-help-text(v-once)
| {{ $t('petLikeToEat') + ' ' }}
span.svg-icon.inline.icon-16(v-html="icons.information")
b-popover(
target="petLikeToEatStable"
placement="top"
)
.popover-content-text(v-html="$t('petLikeToEatText')", v-once)
drawer-slider(
:items="drawerTabs[selectedDrawerTab].items",
:scrollButtonsVisible="hasDrawerTabItems(selectedDrawerTab)",
slot="drawer-slider",
:itemWidth=94,
:itemMargin=24,
:itemType="selectedDrawerTab"
.form-check(
v-for="petGroup in petGroups",
:key="petGroup.key"
)
template(slot="item", slot-scope="context")
foodItem(
:item="context.item",
:itemCount="userItems.food[context.item.key]",
:active="currentDraggingFood == context.item",
@itemDragEnd="onDragEnd()",
@itemDragStart="onDragStart($event, context.item)",
@itemClick="onFoodClicked($event, context.item)"
.custom-control.custom-checkbox
input.custom-control-input(
type="checkbox",
v-model="viewOptions[petGroup.key].selected",
:disabled="viewOptions[petGroup.key].animalCount == 0",
:id="petGroup.key",
)
b-modal#welcome-modal(
:ok-only="true",
:ok-title="$t('gotIt')",
:visible="!hideDialog",
:hide-header="true",
@hide="hideFlag()"
)
div.content
div.npc_matt
h1.page-header(v-once) {{ $t('welcomeStable') }}
div.content-text(v-once) {{ $t('welcomeStableText') }}
b-modal#hatching-modal(
@change="resetHatchablePet($event)"
)
div.content(v-if="hatchablePet")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+hatchablePet.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+hatchablePet.eggKey")
h4.title {{ hatchablePet.name }}
div.text(v-html="$t('hatchDialogText', { potionName: hatchablePet.potionName, eggName: hatchablePet.eggName, petName: hatchablePet.name })")
label.custom-control-label(v-once, :for="petGroup.key") {{ petGroup.label }}
h3(v-once) {{ $t('mounts') }}
.form-group
.form-check(
v-for="mountGroup in mountGroups",
:key="mountGroup.key"
)
.custom-control.custom-checkbox
input.custom-control-input(
type="checkbox",
v-model="viewOptions[mountGroup.key].selected",
:disabled="viewOptions[mountGroup.key].animalCount == 0",
:id="mountGroup.key",
)
label.custom-control-label(v-once, :for="mountGroup.key") {{ mountGroup.label }}
span.svg-icon.icon-10(v-html="icons.close", slot="modal-header", @click="closeHatchPetDialog()")
div.form-group.clearfix
h3.float-left {{ $t('hideMissing') }}
toggle-switch.float-right(
:checked="hideMissing",
@change="updateHideMissing"
)
.standard-page
.clearfix
h1.float-left.mb-4.page-header(v-once) {{ $t('stable') }}
div(slot="modal-footer")
button.btn.btn-primary(@click="hatchPet(hatchablePet)") {{ $t('hatch') }}
button.btn.btn-secondary.btn-flat(@click="closeHatchPetDialog()") {{ $t('cancel') }}
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) }}
hatchedPetDialog(
:hideText="true",
)
h2.mb-3
| {{ $t('pets') }}
|
span.badge.badge-pill.badge-default {{countOwnedAnimals(petGroups[0], 'pet')}}
div.foodInfo(ref="dragginFoodInfo")
div(v-if="currentDraggingFood != null")
div.food-icon(:class="'Pet_Food_'+currentDraggingFood.key")
div.popover
div.popover-content {{ $t('dragThisFood', {foodName: currentDraggingFood.text() }) }}
div(v-for="(petGroup, index) in petGroups",
v-if="viewOptions[petGroup.key].selected",
:key="petGroup.key")
h4(v-if="viewOptions[petGroup.key].animalCount !== 0") {{ petGroup.label }}
div.foodInfo.mouse(ref="clickFoodInfo", v-if="foodClickMode")
div(v-if="currentDraggingFood != null")
div.food-icon(:class="'Pet_Food_'+currentDraggingFood.key")
div.popover
div.popover-content {{ $t('clickOnPetToFeed', {foodName: currentDraggingFood.text() }) }}
mount-raised-modal
.pet-row.d-flex(
v-for="(group, key, index) in pets(petGroup, hideMissing, selectedSortBy, searchTextThrottled)",
v-if='index === 0 || $_openedItemRows_isToggled(petGroup.key)')
.pet-group(
v-for='item in group'
v-drag.drop.food="item.key",
@itemDragOver="onDragOver($event, item)",
@itemDropped="onDrop($event, item)",
@itemDragLeave="onDragLeave()",
:class="{'last': item.isLastInRow}"
)
petItem(
:item="item",
:itemContentClass="getPetItemClass(item)",
:popoverPosition="'top'",
:progress="item.progress",
:emptyItem="!item.isOwned()",
:showPopover="currentDraggingFood == null",
:highlightBorder="highlightPet == item.key",
@click="petClicked(item)"
)
span(slot="popoverContent")
div.hatchablePopover(v-if="item.isHatchable()")
h4.popover-content-title {{ item.name }}
div.popover-content-text(v-html="$t('haveHatchablePet', { potion: item.potionName, egg: item.eggName })")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+item.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+item.eggKey")
div(v-else)
h4.popover-content-title {{ item.name }}
template(slot="itemBadge", slot-scope="context")
starBadge(:selected="item.key === currentPet", :show="item.isOwned()", @click="selectPet(item)")
.btn.btn-flat.btn-show-more(@click="setShowMore(petGroup.key)", v-if='petGroup.key !== "specialPets"')
| {{ $_openedItemRows_isToggled(petGroup.key) ? $t('showLess') : $t('showMore') }}
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 }}
.pet-row.d-flex(v-for="(group, key, index) in mounts(mountGroup, hideMissing, selectedSortBy, searchTextThrottled)"
v-if='index === 0 || $_openedItemRows_isToggled(mountGroup.key)')
.pet-group(v-for='item in group')
mountItem(
:item="item",
:itemContentClass="item.isOwned() ? ('Mount_Icon_' + item.key) : 'PixelPaw GreyedOut'",
:key="item.key",
:popoverPosition="'top'",
:emptyItem="!item.isOwned()",
:showPopover="true",
@click="selectMount(item)"
)
span(slot="popoverContent")
h4.popover-content-title {{ item.name }}
template(slot="itemBadge", slot-scope="context")
starBadge(
:selected="item.key === currentMount",
:show="item.isOwned()",
@click="selectMount(item)",
)
.btn.btn-flat.btn-show-more(@click="setShowMore(mountGroup.key)", v-if='mountGroup.key !== "specialMounts"')
| {{ $_openedItemRows_isToggled(mountGroup.key) ? $t('showLess') : $t('showMore') }}
drawer(:title="$t('quickInventory')",
:errorMessage="(!hasDrawerTabItems(selectedDrawerTab)) ? ((selectedDrawerTab === 0) ? $t('noFoodAvailable') : $t('noSaddlesAvailable')) : 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 }}
#petLikeToEatStable.drawer-help-text(v-once)
| {{ $t('petLikeToEat') + ' ' }}
span.svg-icon.inline.icon-16(v-html="icons.information")
b-popover(
target="petLikeToEatStable"
placement="top"
)
.popover-content-text(v-html="$t('petLikeToEatText')", v-once)
drawer-slider(
:items="drawerTabs[selectedDrawerTab].items",
:scrollButtonsVisible="hasDrawerTabItems(selectedDrawerTab)",
slot="drawer-slider",
:itemWidth=94,
:itemMargin=24,
:itemType="selectedDrawerTab"
)
template(slot="item", slot-scope="context")
foodItem(
:item="context.item",
:itemCount="userItems.food[context.item.key]",
:active="currentDraggingFood == context.item",
@itemDragEnd="onDragEnd()",
@itemDragStart="onDragStart($event, context.item)",
@itemClick="onFoodClicked($event, context.item)"
)
hatchedPetDialog(:hideText="true")
div.foodInfo(ref="dragginFoodInfo")
div(v-if="currentDraggingFood != null")
div.food-icon(:class="'Pet_Food_'+currentDraggingFood.key")
div.popover
div.popover-content {{ $t('dragThisFood', {foodName: currentDraggingFood.text() }) }}
div.foodInfo.mouse(ref="clickFoodInfo", v-if="foodClickMode")
div(v-if="currentDraggingFood != null")
div.food-icon(:class="'Pet_Food_'+currentDraggingFood.key")
div.popover
div.popover-content {{ $t('clickOnPetToFeed', {foodName: currentDraggingFood.text() }) }}
mount-raised-modal
welcome-modal
hatching-modal(:hatchablePet.sync='hatchablePet')
</template>
<style lang='scss' scoped>
@@ -328,79 +288,6 @@
height: 114px;
}
#welcome-modal {
@include centeredModal();
.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;
}
}
#hatching-modal {
@include centeredModal();
.modal-dialog {
width: 310px;
}
.content {
text-align: center;
margin: 9px;
}
.title {
margin-top: 24px;
font-family: Roboto;
font-size: 20px;
font-weight: bold;
font-stretch: condensed;
line-height: 1.2;
text-align: center;
color: #4e4a57;
}
.text {
height: 60px;
font-family: Roboto;
font-size: 14px;
line-height: 1.43;
text-align: center;
color: #686274;
}
span.svg-icon.icon-10 {
position: absolute;
right: 10px;
top: 10px;
}
.modal-footer {
justify-content: center;
}
}
.modal-backdrop.fade.show {
background-color: $purple-50;
opacity: 0.9;
@@ -476,7 +363,7 @@
</style>
<script>
import {mapState} from 'client/libs/store';
import { mapState } from 'client/libs/store';
import _each from 'lodash/each';
import _sortBy from 'lodash/sortBy';
@@ -492,6 +379,8 @@
import FoodItem from './foodItem';
import HatchedPetDialog from './hatchedPetDialog';
import MountRaisedModal from './mountRaisedModal';
import WelcomeModal from './welcomeModal';
import HatchingModal from './hatchingModal';
import Drawer from 'client/components/ui/drawer';
import toggleSwitch from 'client/components/ui/toggleSwitch';
import StarBadge from 'client/components/ui/starBadge';
@@ -505,10 +394,10 @@
import createAnimal from 'client/libs/createAnimal';
import svgInformation from 'assets/svg/information.svg';
import svgClose from 'assets/svg/close.svg';
import notifications from 'client/mixins/notifications';
import openedItemRowsMixin from 'client/mixins/openedItemRows';
import petMixin from 'client/mixins/petMixin';
// TODO Normalize special pets and mounts
// import Store from 'client/store';
@@ -518,7 +407,7 @@
let lastMouseMoveEvent = {};
export default {
mixins: [notifications, openedItemRowsMixin],
mixins: [notifications, openedItemRowsMixin, petMixin],
components: {
PetItem,
Item,
@@ -532,6 +421,8 @@
DrawerSlider,
HatchedPetDialog,
MountRaisedModal,
WelcomeModal,
HatchingModal,
},
directives: {
resize: ResizeDirective,
@@ -554,7 +445,6 @@
],
icons: Object.freeze({
information: svgInformation,
close: svgClose,
}),
highlightPet: '',
@@ -578,7 +468,6 @@
currentPet: 'user.data.items.currentPet',
currentMount: 'user.data.items.currentMount',
userItems: 'user.data.items',
hideDialog: 'user.data.flags.tutorial.common.mounts',
user: 'user.data',
}),
petGroups () {
@@ -871,12 +760,6 @@
selectMount (item) {
this.$store.dispatch('common:equip', {key: item.key, type: 'mount'});
},
hatchPet (pet) {
this.closeHatchPetDialog();
this.$store.dispatch('common:hatch', {egg: pet.eggKey, hatchingPotion: pet.potionKey});
this.text(this.$t('hatchedPet', {egg: pet.eggName, potion: pet.potionName}));
},
onDragStart (ev, food) {
this.currentDraggingFood = food;
@@ -952,14 +835,6 @@
});
}
},
closeHatchPetDialog () {
this.$root.$emit('bv::hide::modal', 'hatching-modal');
},
resetHatchablePet ($event) {
if (!$event) {
this.hatchablePet = null;
}
},
onFoodClicked ($event, food) {
if (this.currentDraggingFood === null || this.currentDraggingFood !== food) {
this.currentDraggingFood = food;
@@ -981,11 +856,6 @@
lastMouseMoveEvent = $event;
}
},
hideFlag () {
this.$store.dispatch('user:set', {
'flags.tutorial.common.mounts': true,
});
},
},
};
</script>
@@ -0,0 +1,63 @@
<template lang="pug">
b-modal#welcome-modal(:ok-only="true",
:ok-title="$t('gotIt')",
:visible="!hideDialog",
:hide-header="true",
@hide="hideFlag()")
div.content
div.npc_matt
h1.page-header(v-once) {{ $t('welcomeStable') }}
div.content-text(v-once) {{ $t('welcomeStableText') }}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/modal.scss';
#welcome-modal {
@include centeredModal();
.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-size: 14px;
font-weight: normal;
line-height: 1.43;
width: 400px;
}
.modal-footer {
justify-content: center;
}
}
</style>
<script>
import { mapState } from 'client/libs/store';
export default {
computed: {
...mapState({
hideDialog: 'user.data.flags.tutorial.common.mounts',
}),
},
methods: {
hideFlag () {
this.$store.dispatch('user:set', {
'flags.tutorial.common.mounts': true,
});
},
},
};
</script>
+8 -2
View File
@@ -89,6 +89,7 @@ import moment from 'moment';
import throttle from 'lodash/throttle';
import debounce from 'lodash/debounce';
import { toNextLevel } from '../../common/script/statHelpers';
import { shouldDo } from '../../common/script/cron';
import { mapState } from 'client/libs/store';
import notifications from 'client/mixins/notifications';
@@ -255,7 +256,12 @@ export default {
userExp (after, before) {
if (after === before) return;
if (this.user.stats.lvl === 0) return;
this.exp(after - before);
let exp = after - before;
if (exp < -50) { // recalculate exp if user level up
exp = toNextLevel(this.user.stats.lvl - 1) - before + after;
}
this.exp(exp);
},
userGp (after, before) {
if (after === before) return;
@@ -525,7 +531,7 @@ export default {
this.$root.$emit('bv::show::modal', 'won-challenge');
break;
case 'STREAK_ACHIEVEMENT':
this.text(this.user.achievements.streak, () => {
this.text(`${this.$t('streaks')}: ${this.user.achievements.streak}`, () => {
if (!this.user.preferences.suppressModals.streak) {
this.$root.$emit('bv::show::modal', 'streak');
}
@@ -15,8 +15,12 @@
<style scoped>
#AmazonPayButton {
margin: 0 auto;
width: 150px;
margin-bottom: 12px;
}
#AmazonPayButton, #AmazonPayWallet, #AmazonPayRecurring {
margin: 0 auto;
}
#AmazonPayRecurring {
@@ -83,6 +83,7 @@ export default {
removeTask () {
if (!confirm('Are you sure you want to delete this task?')) return;
this.destroyTask(this.brokenChallengeTask);
this.close();
},
close () {
this.$store.state.brokenChallengeTask = {};
+5 -3
View File
@@ -37,7 +37,7 @@
draggable.sortable-tasks(
ref="tasksList",
@update='taskSorted',
:options='{disabled: activeFilter.label === "scheduled"}',
:options='{disabled: activeFilter.label === "scheduled", scrollSensitivity: 64}',
class="sortable-tasks"
)
task(
@@ -362,7 +362,7 @@ export default {
type: this.type,
filterType: this.activeFilter.label,
}) :
this.filterByCompleted(this.taskListOverride, this.activeFilter.label);
this.filterByLabel(this.taskListOverride, this.activeFilter.label);
let taggedList = this.filterByTagList(filteredTaskList, this.selectedTags);
let searchedList = this.filterBySearchText(taggedList, this.searchText);
@@ -598,10 +598,12 @@ export default {
}
});
},
filterByCompleted (taskList, filter) {
filterByLabel (taskList, filter) {
if (!taskList) return [];
return taskList.filter(task => {
if (filter === 'complete2') return task.completed;
if (filter === 'due') return task.isDue;
if (filter === 'notDue') return !task.isDue;
return !task.completed;
});
},
+40 -26
View File
@@ -9,36 +9,50 @@ div(v-if='user.stats.lvl > 10')
.col-4.mana
.img(:class='`shop_${spell.key} shop-sprite item-img`')
drawer(
:title="$t('skillsTitle')",
v-if='user.stats.class && !user.preferences.disableClasses',
v-mousePosition="30",
@mouseMoved="mouseMoved($event)",
:openStatus='openStatus',
@toggled='drawerToggled'
)
div(slot="drawer-slider")
.container.spell-container
.row
.col-3(
@click='castStart(skill)',
v-for='(skill, key) in spells[user.stats.class]',
v-if='user.stats.lvl >= skill.lvl',
v-b-popover.hover.auto='skill.notes()')
.spell.col-12.row
.col-8.details
a(:class='{"disabled": spellDisabled(key)}')
div.img(:class='`shop_${skill.key} shop-sprite item-img`')
span.title {{skill.text()}}
.col-4.mana
.mana-text
.svg-icon(v-html="icons.mana")
div {{skill.mana}}
.drawer-wrapper.d-flex.justify-content-center
drawer(
:title="$t('skillsTitle')",
v-if='user.stats.class && !user.preferences.disableClasses',
v-mousePosition="30",
@mouseMoved="mouseMoved($event)",
:openStatus='openStatus',
@toggled='drawerToggled'
)
div(slot="drawer-slider")
.container.spell-container
.row
.col-12.col-md-3(
@click='castStart(skill)',
v-for='(skill, key) in spells[user.stats.class]',
v-if='user.stats.lvl >= skill.lvl',
v-b-popover.hover.auto='skill.notes()')
.spell.col-12.row
.col-8.details
a(:class='{"disabled": spellDisabled(key)}')
div.img(:class='`shop_${skill.key} shop-sprite item-img`')
span.title {{skill.text()}}
.col-4.mana
.mana-text
.svg-icon(v-html="icons.mana")
div {{skill.mana}}
</template>
<style lang="scss" scoped>
.drawer-wrapper {
width: 100vw;
position: fixed;
bottom: 0;
left: 0;
z-index: 19;
.drawer-container {
left: auto !important;
right: auto !important;
min-width: 60%;
}
}
.drawer-container {
left: calc((100% - 978px) / 2);
}
.drawer-slider {
+29 -9
View File
@@ -1,7 +1,7 @@
<template lang="pug">
form(v-if="task", @submit.stop.prevent="submit()")
b-modal#task-modal(size="sm", @hidden="onClose()", @show="handleOpen()", @shown="focusInput()")
.task-modal-header(slot="modal-header", :class="cssClass('bg')")
form(v-if="task", @submit.stop.prevent="submit()", @click="handleClick($event)")
b-modal#task-modal(v-bind:no-close-on-esc="showTagsSelect", v-bind:no-close-on-backdrop="showTagsSelect", size="sm", @hidden="onClose()", @show="handleOpen()", @shown="focusInput()")
.task-modal-header(slot="modal-header", :class="cssClass('bg')", @click="handleClick($event)")
.clearfix
h1.float-left {{ title }}
.float-right.d-flex.align-items-center
@@ -23,7 +23,7 @@
a(target="_blank", href="http://habitica.wikia.com/wiki/Markdown_Cheat_Sheet") {{ $t('markdownHelpLink') }}
textarea.form-control(v-model="task.notes", rows="3")
.task-modal-content
.task-modal-content(@click="handleClick($event)")
.option.mt-0(v-if="task.type === 'reward'")
.form-group
label(v-once) {{ $t('cost') }}
@@ -147,7 +147,7 @@
.category-label(v-for='tagName in truncatedSelectedTags', :title="tagName", v-markdown='tagName')
.tags-more(v-if='remainingSelectedTags.length > 0') +{{ $t('more', { count: remainingSelectedTags.length }) }}
.dropdown-toggle
tags-popup(v-if="showTagsSelect", :tags="user.tags", v-model="task.tags", @close='closeTagsPopup()')
tags-popup(ref="popup", v-if="showTagsSelect", :tags="user.tags", v-model="task.tags", @close='closeTagsPopup()')
.option(v-if="task.type === 'habit'")
.form-group
@@ -242,7 +242,7 @@
.svg-icon.d-inline-b(v-html="icons.destroy")
span {{ $t('deleteTask') }}
.task-modal-footer.d-flex.justify-content-center.align-items-center(slot="modal-footer")
.task-modal-footer.d-flex.justify-content-center.align-items-center(slot="modal-footer", @click="handleClick($event)")
.cancel-task-btn(v-once, @click="cancel()") {{ $t('cancel') }}
button.btn.btn-primary(type="submit", v-once) {{ $t('save') }}
</template>
@@ -714,6 +714,9 @@ export default {
},
};
},
mounted () {
this.showAdvancedOptions = !this.user.preferences.advancedCollapsed;
},
watch: {
task () {
this.syncTask();
@@ -803,6 +806,12 @@ export default {
return this.selectedTags.slice(this.maxTags);
},
},
created () {
document.addEventListener('keyup', this.handleEsc);
},
destroyed () {
document.removeEventListener('keyup', this.handleEsc);
},
methods: {
...mapActions({saveTask: 'tasks:save', destroyTask: 'tasks:destroy', createTask: 'tasks:create'}),
async syncTask () {
@@ -914,17 +923,18 @@ export default {
if (this.purpose === 'create') {
if (this.challengeId) {
this.$store.dispatch('tasks:createChallengeTasks', {
const response = await this.$store.dispatch('tasks:createChallengeTasks', {
challengeId: this.challengeId,
tasks: [this.task],
});
Object.assign(this.task, response);
this.$emit('taskCreated', this.task);
} else if (this.groupId) {
await this.$store.dispatch('tasks:createGroupTasks', {
const response = await this.$store.dispatch('tasks:createGroupTasks', {
groupId: this.groupId,
tasks: [this.task],
});
Object.assign(this.task, response);
let promises = this.assignedMembers.map(memberId => {
return this.$store.dispatch('tasks:assignTask', {
taskId: this.task._id,
@@ -988,6 +998,16 @@ export default {
focusInput () {
this.$refs.inputToFocus.focus();
},
handleEsc (e) {
if (e.keyCode === 27 && this.showTagsSelect) {
this.closeTagsPopup();
}
},
handleClick (e) {
if (this.$refs.popup && !this.$refs.popup.$el.contains(e.target)) {
this.closeTagsPopup();
}
},
},
};
</script>
+11 -2
View File
@@ -52,8 +52,14 @@
// @TODO: Implement new message header here when we fix the above
.new-message-row(v-if='selectedConversation.key && !user.flags.chatRevoked')
textarea(v-model='newMessage', @keyup.ctrl.enter='sendPrivateMessage()')
button.btn.btn-secondary(@click='sendPrivateMessage()') Send
textarea(
v-model='newMessage',
@keyup.ctrl.enter='sendPrivateMessage()',
maxlength='3000'
)
button.btn.btn-secondary(@click='sendPrivateMessage()') {{$t('send')}}
.row
span.ml-3 {{ currentLength }} / 3000
</template>
<style lang="scss" scoped>
@@ -322,6 +328,9 @@ export default {
return conversation.name.toLowerCase().indexOf(this.search.toLowerCase()) !== -1;
});
},
currentLength () {
return this.newMessage.length;
},
placeholderTexts () {
if (this.user.flags.chatRevoked) {
return {
+1 -1
View File
@@ -47,6 +47,6 @@ export function round (number, nDigits) {
}
export function getXPMessage (val) {
if (val < -50) return; // don't show when they level up (resetting their exp)
if (val < -50) return; // don't show when they multi-level up (resetting their exp)
return `${getSign(val)} ${round(val)}`;
}
+8 -16
View File
@@ -1,7 +1,9 @@
import times from 'lodash/times';
import Intro from 'intro.js/';
import introjs from 'intro.js';
import * as Analytics from 'client/libs/analytics';
let showingTour = false;
export default {
data () {
return {
@@ -13,6 +15,8 @@ export default {
},
watch: {
$route () {
if (showingTour) return;
showingTour = true;
this.routeChange();
},
},
@@ -168,13 +172,15 @@ export default {
});
// @TODO: Do we always need to initialize here?
let intro = Intro.introJs();
const intro = introjs();
intro.setOptions({
exitOnOverlayClick: false,
steps: opts.steps,
doneLabel: this.$t('letsgo'),
});
intro.start();
intro.oncomplete(() => {
showingTour = false;
this.markTourComplete(chapter);
});
},
@@ -187,19 +193,6 @@ export default {
return;
}
// if (true) { // -2 indicates complete
// if (chapter === 'intro') {
// // Manually show bunny scroll reward
// // let rewardData = {
// // reward: [Shared.content.quests.dustbunnies],
// // rewardKey: ['inventory_quest_scroll_dustbunnies'],
// // rewardText: Shared.content.quests.dustbunnies.text(),
// // message: this.$t('checkinEarned'),
// // nextRewardAt: 1,
// // };
// // @TODO: Notification.showLoginIncentive(this.user, rewardData, Social.loadWidgets);
// }
// Mark tour complete
ups[`flags.tour.${chapter}`] = -2; // @TODO: Move magic numbers to enum
@@ -211,7 +204,6 @@ export default {
eventValue: lastKnownStep,
complete: true,
});
// }
this.$store.dispatch('user:set', ups);
},
+13
View File
@@ -0,0 +1,13 @@
export default {
methods: {
closeHatchPetDialog () {
this.$root.$emit('bv::hide::modal', 'hatching-modal');
},
hatchPet (pet) {
this.closeHatchPetDialog();
this.$store.dispatch('common:hatch', {egg: pet.eggKey, hatchingPotion: pet.potionKey});
this.text(this.$t('hatchedPet', {egg: pet.eggName, potion: pet.potionName}));
},
},
};
+14 -1
View File
@@ -85,7 +85,20 @@ export default {
// the selected member doesn't have the flags property which sets `cardReceived`
if (spell.pinType !== 'card') {
spell.cast(this.user, target);
try {
spell.cast(this.user, target);
} catch (e) {
if (!e.request) {
this.$store.dispatch('snackbars:add', {
title: '',
text: e.message,
type: 'error',
});
return;
} else {
throw e;
}
}
}
let targetId = target ? target._id : null;
+6
View File
@@ -64,6 +64,12 @@ async function buyArmoire (store, params) {
if (item.type === 'gear') {
store.state.user.data.items.gear.owned[item.dropKey] = true;
}
if (item.type === 'food') {
if (!store.state.user.data.items.food[item.dropKey]) store.state.user.data.items.food[item.dropKey] = 0;
store.state.user.data.items.food[item.dropKey] += 1;
}
store.state.user.data.stats.gp -= armoire.value;
// @TODO: We might need to abstract notifications to library rather than mixin
+1 -1
View File
@@ -2,7 +2,7 @@
module.exports = {
invalidAttribute: '"<%= attr %>" is not a valid Stat.',
statsObjectRequired: '"stats" update is required',
statsObjectRequired: '"stats" object is required',
missingTypeParam: '"req.params.type" is required.',
missingKeyParam: '"req.params.key" is required.',

Some files were not shown because too many files have changed in this diff Show More