Onboarding guide and initial achievements refactoring (#11536)
* add achievements to user * add placeholder strings * add to achievements to common script * add onboarding achievements category * add notifications * more notifications * award achievements * wip notification panel * add achievements icons and copy * do not count onboarding tasks for the created task achievement * add notes * sprites, fixes and completion status and reward * add onboarding panel * add toggle * fix toggle size * fix tests * fix typo * add notification * start adding modal * fix remove button positionin, timeout, progress bar * modal + fixes * disable broken social links from level up modal * change toggle icon color on hover * add border bottom to onboarding guide panel * add collapse animation * expanded onboarding on first open * onboarding: flip toggle colors * onboarding: show progress bar all the time * onboarding: fix panel closing on click * onboarding modal: add close icon and fix padding * wip: add migration for existing users * fix titles in guide * fix achievements copy * do not award completed task achievement when direction is down * start implementing new achievements * start migrating client * remove social links from achievements modals * prevent skipping tutorial + fix achievement notification * sync fixes * start redesign achievement modal * misc fixes to achievements, polish generic achievement modal and hatched pet modal * add special badge for onboarding * fix badge condition * modals fixes * hatched pet modal: add close icon * fix badge typo * fix justin button * new scrolling behavior for dropdowns * fix strings capitalization * add common tests * add api unit tests * add date check * achievements modal polishing * typos * add toggle for achievements categories * typo * fix test * fix edit avatar modal cannot be closed * finish migration and correct launch date * fix migration * migration fixes * fix tests
107
migrations/users/20191125_onboarding_achievements.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Award Onboarding Achievements for existing users
|
||||
*/
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
const MIGRATION_NAME = '20191218_onboarding_achievements';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count += 1;
|
||||
|
||||
const set = {};
|
||||
|
||||
set.migration = MIGRATION_NAME;
|
||||
|
||||
const hasPet = Object.keys(user.items.pets).find(petKey => {
|
||||
const pet = user.items.pets[petKey];
|
||||
|
||||
if (pet >= 5) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (hasPet) {
|
||||
set['achievements.hatchedPet'] = true;
|
||||
}
|
||||
|
||||
const hasFedPet = Object.keys(user.items.pets).find(petKey => {
|
||||
const pet = user.items.pets[petKey];
|
||||
|
||||
if (pet > 5) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (hasFedPet) {
|
||||
set['achievements.fedPet'] = true;
|
||||
}
|
||||
|
||||
const hasGear = Object.keys(user.items.gear.owned).find(gearKey => {
|
||||
const gear = user.items.gear.owned[gearKey];
|
||||
|
||||
if (gear === true && gearKey.indexOf('_special_') === -1) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (hasGear) {
|
||||
set['achievements.purchasedEquipment'] = true;
|
||||
}
|
||||
|
||||
const hasTask = Object.keys(user.tasksOrder).find(tasksOrderType => {
|
||||
const order = user.tasksOrder[tasksOrderType];
|
||||
|
||||
if (order && order.length > 0) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (hasTask) {
|
||||
set['achievements.createdTask'] = true;
|
||||
}
|
||||
|
||||
const hasExperience = user.stats && user.stats.exp && user.stats.exp > 0;
|
||||
if (hasTask && hasExperience) {
|
||||
set['achievements.completedTask'] = true;
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
return User.update({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
module.exports = async function processUsers () { // eslint-disable-line import/no-commonjs
|
||||
const query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
stats: 1,
|
||||
items: 1,
|
||||
achievements: 1,
|
||||
tasksOrder: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(100)
|
||||
.sort({ _id: 1 })
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1],
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
@@ -11,6 +11,7 @@ import { model as User } from '../../../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../../../website/server/models/group';
|
||||
import {
|
||||
generateGroup,
|
||||
sleep,
|
||||
} from '../../../../../helpers/api-unit.helper';
|
||||
|
||||
describe('Purchasing a group plan for group', () => {
|
||||
@@ -307,6 +308,7 @@ describe('Purchasing a group plan for group', () => {
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
await sleep(0.5);
|
||||
|
||||
expect(sender.sendTxn).to.have.callCount(4);
|
||||
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);
|
||||
@@ -340,6 +342,7 @@ describe('Purchasing a group plan for group', () => {
|
||||
data.groupId = group._id;
|
||||
|
||||
await api.createSubscription(data);
|
||||
await sleep(0.5);
|
||||
|
||||
expect(sender.sendTxn).to.have.callCount(4);
|
||||
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
moveTask,
|
||||
} from '../../../../website/server/libs/taskManager';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import shared from '../../../../website/common/script';
|
||||
import {
|
||||
generateUser,
|
||||
generateGroup,
|
||||
@@ -58,6 +59,51 @@ describe('taskManager', () => {
|
||||
expect(newTask.createdAt).to.exist;
|
||||
});
|
||||
|
||||
describe('onboarding', () => {
|
||||
beforeEach(() => {
|
||||
user.addAchievement = sinon.spy();
|
||||
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shared.onboarding.checkOnboardingStatus.restore();
|
||||
});
|
||||
|
||||
it('adds the onboarding achievement to the user and checks the onboarding status', async () => {
|
||||
req.body = testHabit;
|
||||
res.t = i18n.t;
|
||||
user.flags.welcomed = true;
|
||||
|
||||
await createTasks(req, res, { user });
|
||||
|
||||
expect(user.addAchievement).to.be.calledOnce;
|
||||
expect(user.addAchievement).to.be.calledWith('createdTask');
|
||||
|
||||
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
|
||||
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
|
||||
});
|
||||
|
||||
it('does not add the onboarding achievement to the user if flags.welcomed is false', async () => {
|
||||
req.body = testHabit;
|
||||
res.t = i18n.t;
|
||||
user.flags.welcomed = false;
|
||||
|
||||
await createTasks(req, res, { user });
|
||||
|
||||
expect(user.addAchievement).to.not.be.called;
|
||||
});
|
||||
|
||||
it('does not add the onboarding achievement to the user if it\'s already been awarded', async () => {
|
||||
req.body = testHabit;
|
||||
res.t = i18n.t;
|
||||
user.achievements.createdTask = true;
|
||||
|
||||
await createTasks(req, res, { user });
|
||||
|
||||
expect(user.addAchievement).to.not.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
it('gets user tasks', async () => {
|
||||
req.body = testHabit;
|
||||
res.t = i18n.t;
|
||||
|
||||
@@ -1879,6 +1879,8 @@ describe('Group Model', () => {
|
||||
await questLeader.save();
|
||||
await party.finishQuest(quest);
|
||||
|
||||
await sleep(0.5);
|
||||
|
||||
const [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
|
||||
@@ -84,6 +84,103 @@ describe('User Model', () => {
|
||||
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
|
||||
});
|
||||
|
||||
context('achievements', () => {
|
||||
it('can add an achievement', () => {
|
||||
const user = new User();
|
||||
const originalUserToJSON = user.toJSON({ minimize: false });
|
||||
expect(originalUserToJSON.achievements.createdTask).to.not.eql(true);
|
||||
const notificationsN = originalUserToJSON.notifications.length;
|
||||
|
||||
user.addAchievement('createdTask');
|
||||
|
||||
const userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(notificationsN + 1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
|
||||
expect(userToJSON.notifications[0].data).to.eql({
|
||||
achievement: 'createdTask',
|
||||
});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
|
||||
expect(userToJSON.achievements.createdTask).to.eql(true);
|
||||
});
|
||||
|
||||
it('throws an error if the achievement is not valid', () => {
|
||||
const user = new User();
|
||||
expect(() => user.addAchievement('notAnAchievement')).to.throw;
|
||||
});
|
||||
|
||||
context('static push method', () => {
|
||||
it('throws an error if the achievement is not valid', async () => {
|
||||
const user = new User();
|
||||
await user.save();
|
||||
|
||||
await expect(User.addAchievementUpdate({ _id: user._id }, 'notAnAchievement'))
|
||||
.to.eventually.be.rejected;
|
||||
|
||||
expect(() => user.addAchievement('notAnAchievement')).to.throw;
|
||||
});
|
||||
|
||||
it('adds an achievement for a single member via static method', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
|
||||
const originalUserToJSON = user.toJSON({ minimize: false });
|
||||
expect(originalUserToJSON.achievements.createdTask).to.not.eql(true);
|
||||
const notificationsN = originalUserToJSON.notifications.length;
|
||||
|
||||
await User.addAchievementUpdate({ _id: user._id }, 'createdTask');
|
||||
|
||||
user = await User.findOne({ _id: user._id }).exec();
|
||||
|
||||
const userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(notificationsN + 1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
|
||||
expect(userToJSON.notifications[0].data).to.eql({
|
||||
achievement: 'createdTask',
|
||||
});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
|
||||
expect(userToJSON.achievements.createdTask).to.eql(true);
|
||||
});
|
||||
|
||||
it('adds an achievement for all given users via static method', async () => {
|
||||
let user = new User();
|
||||
const otherUser = new User();
|
||||
await Promise.all([user.save(), otherUser.save()]);
|
||||
|
||||
await User.addAchievementUpdate({ _id: { $in: [user._id, otherUser._id] } }, 'createdTask');
|
||||
|
||||
user = await User.findOne({ _id: user._id }).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
|
||||
expect(userToJSON.notifications[0].data).to.eql({
|
||||
achievement: 'createdTask',
|
||||
});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
|
||||
expect(userToJSON.achievements.createdTask).to.eql(true);
|
||||
|
||||
user = await User.findOne({ _id: otherUser._id }).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('ACHIEVEMENT');
|
||||
expect(userToJSON.notifications[0].data).to.eql({
|
||||
achievement: 'createdTask',
|
||||
});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
|
||||
expect(userToJSON.achievements.createdTask).to.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('notifications', () => {
|
||||
it('can add notifications without data', () => {
|
||||
const user = new User();
|
||||
|
||||
@@ -239,6 +239,47 @@ describe('achievements', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('unearned onboarding achievements', () => {
|
||||
const user = generateUser();
|
||||
const onboardingAchievs = shared
|
||||
.achievements.getAchievementsForProfile(user).onboarding.achievements;
|
||||
|
||||
it('created task achievement exists with no count', () => {
|
||||
const { createdTask } = onboardingAchievs;
|
||||
|
||||
expect(createdTask).to.exist;
|
||||
expect(createdTask.optionalCount).to.be.undefined;
|
||||
});
|
||||
|
||||
it('completed task achievement exists with no count', () => {
|
||||
const { completedTask } = onboardingAchievs;
|
||||
|
||||
expect(completedTask).to.exist;
|
||||
expect(completedTask.optionalCount).to.be.undefined;
|
||||
});
|
||||
|
||||
it('hatched pet achievement exists with no count', () => {
|
||||
const { hatchedPet } = onboardingAchievs;
|
||||
|
||||
expect(hatchedPet).to.exist;
|
||||
expect(hatchedPet.optionalCount).to.be.undefined;
|
||||
});
|
||||
|
||||
it('fed pet achievement exists with no count', () => {
|
||||
const { fedPet } = onboardingAchievs;
|
||||
|
||||
expect(fedPet).to.exist;
|
||||
expect(fedPet.optionalCount).to.be.undefined;
|
||||
});
|
||||
|
||||
it('purchased equipment achievement exists with no count', () => {
|
||||
const { purchasedEquipment } = onboardingAchievs;
|
||||
|
||||
expect(purchasedEquipment).to.exist;
|
||||
expect(purchasedEquipment.optionalCount).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('earned seasonal achievements', () => {
|
||||
const user = generateUser();
|
||||
const quests = ['dilatory', 'stressbeast', 'burnout', 'bewilder'];
|
||||
|
||||
100
test/common/libs/onboarding.test.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import moment from 'moment';
|
||||
import {
|
||||
hasActiveOnboarding,
|
||||
hasCompletedOnboarding,
|
||||
onOnboardingComplete,
|
||||
checkOnboardingStatus,
|
||||
} from '../../../website/common/script/libs/onboarding';
|
||||
import { generateUser } from '../../helpers/common.helper';
|
||||
|
||||
describe('onboarding', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user.addNotification = sinon.spy();
|
||||
// Make sure the onboarding is active
|
||||
user.auth.timestamps.created = moment('2019-12-20').toDate();
|
||||
});
|
||||
|
||||
describe('hasActiveOnboarding', () => {
|
||||
// The value of BEGIN DATE is available in common/script/libs/onboarding
|
||||
|
||||
it('returns true if the account is created after BEGIN_DATE', () => {
|
||||
expect(hasActiveOnboarding(user)).to.eql(true);
|
||||
});
|
||||
|
||||
it('returns false if the account is created before BEGIN_DATE', () => {
|
||||
user.auth.timestamps.created = moment('2019-12-01').toDate();
|
||||
expect(hasActiveOnboarding(user)).to.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasCompletedOnboarding', () => {
|
||||
it('returns false if no achievement has been awarded', () => {
|
||||
const result = hasCompletedOnboarding(user);
|
||||
expect(result).to.eql(false);
|
||||
});
|
||||
|
||||
it('returns false if not all achievements have been awarded', () => {
|
||||
user.achievements.completedTask = true;
|
||||
const result = hasCompletedOnboarding(user);
|
||||
expect(result).to.eql(false);
|
||||
});
|
||||
|
||||
it('returns true if all achievements have been awarded', () => {
|
||||
user.achievements.createdTask = true;
|
||||
user.achievements.completedTask = true;
|
||||
user.achievements.hatchedPet = true;
|
||||
user.achievements.fedPet = true;
|
||||
user.achievements.purchasedEquipment = true;
|
||||
|
||||
const result = hasCompletedOnboarding(user);
|
||||
expect(result).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onOnboardingComplete', () => {
|
||||
it('awards prizes', () => {
|
||||
const { gp } = user.stats;
|
||||
onOnboardingComplete(user);
|
||||
expect(user.stats.gp).to.eql(gp + 100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkOnboardingStatus', () => {
|
||||
it('does nothing if onboarding is not active', () => {
|
||||
const { gp } = user.stats;
|
||||
user.auth.timestamps.created = moment('2019-12-01').toDate();
|
||||
|
||||
checkOnboardingStatus(user);
|
||||
expect(user.addNotification).to.not.be.called;
|
||||
|
||||
expect(user.stats.gp).to.eql(gp);
|
||||
});
|
||||
|
||||
it('does nothing if onboarding is not complete', () => {
|
||||
const { gp } = user.stats;
|
||||
|
||||
checkOnboardingStatus(user);
|
||||
expect(user.addNotification).to.not.be.called;
|
||||
|
||||
expect(user.stats.gp).to.eql(gp);
|
||||
});
|
||||
|
||||
it('awards prize and add notification when onboarding is complete', () => {
|
||||
user.achievements.createdTask = true;
|
||||
user.achievements.completedTask = true;
|
||||
user.achievements.hatchedPet = true;
|
||||
user.achievements.fedPet = true;
|
||||
user.achievements.purchasedEquipment = true;
|
||||
const { gp } = user.stats;
|
||||
|
||||
checkOnboardingStatus(user);
|
||||
expect(user.addNotification).to.be.calledOnce;
|
||||
expect(user.addNotification).to.be.calledWith('ONBOARDING_COMPLETE');
|
||||
|
||||
expect(user.stats.gp).to.eql(gp + 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -41,7 +41,10 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
},
|
||||
});
|
||||
|
||||
user.addAchievement = sinon.spy();
|
||||
|
||||
sinon.stub(shared, 'randomVal');
|
||||
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
|
||||
sinon.stub(shared.fns, 'predictableRandom');
|
||||
sinon.stub(analytics, 'track');
|
||||
});
|
||||
@@ -49,6 +52,7 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
afterEach(() => {
|
||||
shared.randomVal.restore();
|
||||
shared.fns.predictableRandom.restore();
|
||||
shared.onboarding.checkOnboardingStatus.restore();
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
@@ -86,6 +90,27 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('adds the onboarding achievement to the user and checks the onboarding status', () => {
|
||||
user.stats.gp = 31;
|
||||
|
||||
buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
|
||||
|
||||
expect(user.addAchievement).to.be.calledOnce;
|
||||
expect(user.addAchievement).to.be.calledWith('purchasedEquipment');
|
||||
|
||||
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
|
||||
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
|
||||
});
|
||||
|
||||
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
|
||||
user.stats.gp = 31;
|
||||
user.achievements.purchasedEquipment = true;
|
||||
|
||||
buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
|
||||
|
||||
expect(user.addAchievement).to.not.be.called;
|
||||
});
|
||||
|
||||
it('deducts gold from user', () => {
|
||||
user.stats.gp = 31;
|
||||
|
||||
|
||||
@@ -10,12 +10,19 @@ import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import errorMessage from '../../../website/common/script/libs/errorMessage';
|
||||
import shared from '../../../website/common/script';
|
||||
|
||||
describe('shared.ops.feed', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user.addAchievement = sinon.spy();
|
||||
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shared.onboarding.checkOnboardingStatus.restore();
|
||||
});
|
||||
|
||||
context('failure conditions', () => {
|
||||
@@ -223,5 +230,28 @@ describe('shared.ops.feed', () => {
|
||||
expect(user.items.mounts['Wolf-Base']).to.equal(true);
|
||||
expect(user.items.currentPet).to.equal('');
|
||||
});
|
||||
|
||||
it('adds the onboarding achievement to the user and checks the onboarding status', () => {
|
||||
user.items.pets['Wolf-Base'] = 5;
|
||||
user.items.food.Meat = 2;
|
||||
|
||||
feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' } });
|
||||
|
||||
expect(user.addAchievement).to.be.calledOnce;
|
||||
expect(user.addAchievement).to.be.calledWith('fedPet');
|
||||
|
||||
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
|
||||
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
|
||||
});
|
||||
|
||||
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
|
||||
user.items.pets['Wolf-Base'] = 5;
|
||||
user.items.food.Meat = 2;
|
||||
user.achievements.fedPet = true;
|
||||
|
||||
feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' } });
|
||||
|
||||
expect(user.addAchievement).to.not.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,12 +9,19 @@ import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import errorMessage from '../../../website/common/script/libs/errorMessage';
|
||||
import shared from '../../../website/common/script';
|
||||
|
||||
describe('shared.ops.hatch', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user.addAchievement = sinon.spy();
|
||||
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shared.onboarding.checkOnboardingStatus.restore();
|
||||
});
|
||||
|
||||
context('Pet Hatching', () => {
|
||||
@@ -195,6 +202,30 @@ describe('shared.ops.hatch', () => {
|
||||
hatch(user, { params: { egg: 'Wolf', hatchingPotion: 'Spooky' } });
|
||||
expect(user.achievements.dustDevil).to.eql(true);
|
||||
});
|
||||
|
||||
it('adds the onboarding achievement to the user and checks the onboarding status', () => {
|
||||
user.items.eggs = { Wolf: 1 };
|
||||
user.items.hatchingPotions = { Base: 1 };
|
||||
user.items.pets = {};
|
||||
hatch(user, { params: { egg: 'Wolf', hatchingPotion: 'Base' } });
|
||||
|
||||
expect(user.addAchievement).to.be.calledOnce;
|
||||
expect(user.addAchievement).to.be.calledWith('hatchedPet');
|
||||
|
||||
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
|
||||
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(user);
|
||||
});
|
||||
|
||||
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
|
||||
user.items.eggs = { Wolf: 1 };
|
||||
user.items.hatchingPotions = { Base: 1 };
|
||||
user.items.pets = {};
|
||||
user.achievements.hatchedPet = true;
|
||||
|
||||
hatch(user, { params: { egg: 'Wolf', hatchingPotion: 'Base' } });
|
||||
|
||||
expect(user.addAchievement).to.not.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
NotAuthorized,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import crit from '../../../website/common/script/fns/crit';
|
||||
import shared from '../../../website/common/script';
|
||||
|
||||
const EPSILON = 0.0001; // negligible distance between datapoints
|
||||
|
||||
@@ -340,5 +341,49 @@ describe('shared.ops.scoreTask', () => {
|
||||
expectClosePoints(ref.beforeUser, ref.afterUser, freshTodo, todo);
|
||||
});
|
||||
});
|
||||
|
||||
context('onboarding', () => {
|
||||
beforeEach(() => {
|
||||
ref.afterUser.addAchievement = sinon.spy();
|
||||
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shared.onboarding.checkOnboardingStatus.restore();
|
||||
});
|
||||
|
||||
it('adds the achievement to the user and checks the onboarding status', () => {
|
||||
scoreTask({ user: ref.afterUser, task: todo, direction: 'up' });
|
||||
expect(ref.afterUser.addAchievement).to.be.calledOnce;
|
||||
expect(ref.afterUser.addAchievement).to.be.calledWith('completedTask');
|
||||
|
||||
expect(shared.onboarding.checkOnboardingStatus).to.be.calledOnce;
|
||||
expect(shared.onboarding.checkOnboardingStatus).to.be.calledWith(ref.afterUser);
|
||||
});
|
||||
|
||||
it('does not add the onboarding achievement to the user if it\'s already been awarded', () => {
|
||||
ref.afterUser.achievements.completedTask = true;
|
||||
scoreTask({ user: ref.afterUser, task: todo, direction: 'up' });
|
||||
|
||||
expect(ref.afterUser.addAchievement).to.not.be.called;
|
||||
});
|
||||
|
||||
it('does not add the onboarding achievement to the user if it\'s scored down', () => {
|
||||
scoreTask({ user: ref.afterUser, task: todo, direction: 'down' });
|
||||
|
||||
expect(ref.afterUser.addAchievement).to.not.be.called;
|
||||
});
|
||||
|
||||
it('does not add the onboarding achievement to the user if cron is running', () => {
|
||||
scoreTask({
|
||||
user: ref.afterUser,
|
||||
task: todo,
|
||||
direction: 'up',
|
||||
cron: true,
|
||||
});
|
||||
|
||||
expect(ref.afterUser.addAchievement).to.not.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,222 +1,246 @@
|
||||
.achievement-alien {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1323px -1628px;
|
||||
background-position: -1656px -1480px;
|
||||
width: 24px;
|
||||
height: 26px;
|
||||
}
|
||||
.achievement-alien2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1357px -1480px;
|
||||
background-position: -1460px -1480px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-allYourBase2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -901px -1480px;
|
||||
background-position: -759px -1480px;
|
||||
width: 64px;
|
||||
height: 56px;
|
||||
}
|
||||
.achievement-alpha2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1406px -1480px;
|
||||
background-position: -1509px -1480px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-aridAuthority2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -966px -1480px;
|
||||
background-position: -824px -1480px;
|
||||
width: 64px;
|
||||
height: 56px;
|
||||
}
|
||||
.achievement-armor2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1455px -1480px;
|
||||
background-position: -1558px -1480px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-backToBasics2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1161px -1480px;
|
||||
background-position: -1019px -1480px;
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
}
|
||||
.achievement-bewilder2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1504px -1480px;
|
||||
background-position: -1607px -1480px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-birthday2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1553px -1480px;
|
||||
background-position: -568px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-boot2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1602px -1480px;
|
||||
background-position: -617px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-bow2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1651px -1480px;
|
||||
background-position: -666px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-burnout2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -710px -1549px;
|
||||
background-position: -715px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-cactus2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -759px -1549px;
|
||||
background-position: -764px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-cake2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -808px -1549px;
|
||||
background-position: -813px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-cave2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -857px -1549px;
|
||||
background-position: -862px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-challenge2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -906px -1549px;
|
||||
background-position: -911px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-comment2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -955px -1549px;
|
||||
background-position: -960px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-completedTask2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1068px -1480px;
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
}
|
||||
.achievement-congrats2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1004px -1549px;
|
||||
background-position: -1009px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-costumeContest2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1053px -1549px;
|
||||
background-position: -1058px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-createdTask2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1117px -1480px;
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
}
|
||||
.achievement-dilatory2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1102px -1549px;
|
||||
background-position: -1107px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-dustDevil2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1210px -1480px;
|
||||
background-position: -1166px -1480px;
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
}
|
||||
.achievement-dysheartener2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1151px -1549px;
|
||||
background-position: -1156px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-fedPet2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1215px -1480px;
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
}
|
||||
.achievement-friends2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1200px -1549px;
|
||||
background-position: -1205px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-getwell2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1249px -1549px;
|
||||
background-position: -1254px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-goodluck2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1298px -1549px;
|
||||
background-position: -1303px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-greeting2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1347px -1549px;
|
||||
background-position: -1352px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-guild2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1396px -1549px;
|
||||
background-position: -1401px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-habitBirthday2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1445px -1549px;
|
||||
background-position: -1450px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-habiticaDay2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1494px -1549px;
|
||||
background-position: -1499px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-hatchedPet2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1264px -1480px;
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
}
|
||||
.achievement-heart2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1543px -1549px;
|
||||
background-position: -1548px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-justAddWater2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -779px -1480px;
|
||||
background-position: -637px -1480px;
|
||||
width: 60px;
|
||||
height: 64px;
|
||||
}
|
||||
.achievement-karaoke-2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1592px -1549px;
|
||||
background-position: -1597px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-karaoke {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1348px -1628px;
|
||||
background-position: -1323px -1628px;
|
||||
width: 24px;
|
||||
height: 26px;
|
||||
}
|
||||
.achievement-kickstarter20192x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -710px -1480px;
|
||||
background-position: -568px -1480px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.achievement-lostMasterclasser2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1641px -1549px;
|
||||
background-position: -1646px -1549px;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-mindOverMatter2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -840px -1480px;
|
||||
background-position: -698px -1480px;
|
||||
width: 60px;
|
||||
height: 64px;
|
||||
}
|
||||
.achievement-monsterMagus2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1259px -1480px;
|
||||
background-position: -1313px -1480px;
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
}
|
||||
@@ -252,7 +276,7 @@
|
||||
}
|
||||
.achievement-pearlyPro2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1031px -1480px;
|
||||
background-position: -889px -1480px;
|
||||
width: 64px;
|
||||
height: 56px;
|
||||
}
|
||||
@@ -264,7 +288,13 @@
|
||||
}
|
||||
.achievement-primedForPainting2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1308px -1480px;
|
||||
background-position: -1362px -1480px;
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
}
|
||||
.achievement-purchasedEquipment2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1411px -1480px;
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
}
|
||||
@@ -378,7 +408,7 @@
|
||||
}
|
||||
.achievement-undeadUndertaker2x {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -1096px -1480px;
|
||||
background-position: -954px -1480px;
|
||||
width: 64px;
|
||||
height: 56px;
|
||||
}
|
||||
@@ -1162,9 +1192,3 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_mountain_pyramid {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-0.png');
|
||||
background-position: -568px -1480px;
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
|
||||
@@ -1,72 +1,90 @@
|
||||
.weapon_warrior_3 {
|
||||
.weapon_warrior_0 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1802px -997px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_warrior_4 {
|
||||
.weapon_warrior_1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1893px -997px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_warrior_5 {
|
||||
.weapon_warrior_2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1802px -1088px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_warrior_6 {
|
||||
.weapon_warrior_3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1893px -1088px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_wizard_0 {
|
||||
.weapon_warrior_4 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1802px -1179px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_wizard_1 {
|
||||
.weapon_warrior_5 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1893px -1179px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_wizard_2 {
|
||||
.weapon_warrior_6 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1802px -1270px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_wizard_3 {
|
||||
.weapon_wizard_0 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1893px -1270px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_wizard_4 {
|
||||
.weapon_wizard_1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1802px -1361px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_wizard_5 {
|
||||
.weapon_wizard_2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1893px -1361px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_wizard_6 {
|
||||
.weapon_wizard_3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1802px -1452px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_wizard_4 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1893px -1452px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_wizard_5 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1802px -1543px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_wizard_6 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1893px -1543px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.Pet_Currency_Gem {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -1298px;
|
||||
background-position: -1696px -1433px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
@@ -84,19 +102,19 @@
|
||||
}
|
||||
.PixelPaw-Gold {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1688px -1436px;
|
||||
background-position: -660px -508px;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
}
|
||||
.PixelPaw {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1740px -1436px;
|
||||
background-position: -660px -560px;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
}
|
||||
.PixelPaw002 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -220px -203px;
|
||||
background-position: -880px -655px;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
}
|
||||
@@ -132,13 +150,13 @@
|
||||
}
|
||||
.ghost {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1893px -1452px;
|
||||
background-position: -1627px -422px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.inventory_present {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -746px;
|
||||
background-position: -1696px -881px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
@@ -168,187 +186,187 @@
|
||||
}
|
||||
.inventory_present_05 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -470px;
|
||||
background-position: -1718px -422px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_06 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -470px;
|
||||
background-position: -1718px -513px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_07 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -539px;
|
||||
background-position: -1718px -604px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_08 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -539px;
|
||||
background-position: -1627px -743px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_09 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -608px;
|
||||
background-position: -1696px -743px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_10 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -608px;
|
||||
background-position: -1627px -812px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_11 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -677px;
|
||||
background-position: -1696px -812px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_present_12 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -677px;
|
||||
background-position: -1627px -881px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_birthday {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -746px;
|
||||
background-position: -1627px -950px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_congrats {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -815px;
|
||||
background-position: -1696px -950px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_fortify {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -815px;
|
||||
background-position: -1627px -1019px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_getwell {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -884px;
|
||||
background-position: -1696px -1019px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_goodluck {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -884px;
|
||||
background-position: -1627px -1088px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_greeting {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -953px;
|
||||
background-position: -1696px -1088px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_nye {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -953px;
|
||||
background-position: -1627px -1157px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_opaquePotion {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -1022px;
|
||||
background-position: -1696px -1157px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_seafoam {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -1022px;
|
||||
background-position: -1627px -1226px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_shinySeed {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -1091px;
|
||||
background-position: -1696px -1226px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_snowball {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -1091px;
|
||||
background-position: -1627px -1295px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_spookySparkles {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -1160px;
|
||||
background-position: -1696px -1295px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_thankyou {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -1160px;
|
||||
background-position: -1627px -1364px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_trinket {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -1229px;
|
||||
background-position: -1696px -1364px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.inventory_special_valentine {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -1229px;
|
||||
background-position: -1627px -1433px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.knockout {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -422px;
|
||||
background-position: -1627px -695px;
|
||||
width: 120px;
|
||||
height: 47px;
|
||||
}
|
||||
.pet_key {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -1298px;
|
||||
background-position: -220px -203px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.rebirth_orb {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -1367px;
|
||||
background-position: -220px -272px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.seafoam_star {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1802px -1543px;
|
||||
background-position: -1627px -513px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.shop_armoire {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1696px -1367px;
|
||||
background-position: -220px -341px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.snowman {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1893px -1543px;
|
||||
background-position: -1627px -604px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.zzz {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -220px -255px;
|
||||
background-position: -660px -612px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.zzz_light {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1748px -422px;
|
||||
background-position: -1748px -695px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
@@ -474,7 +492,7 @@
|
||||
}
|
||||
.npc_bailey {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1627px -1436px;
|
||||
background-position: -660px -435px;
|
||||
width: 60px;
|
||||
height: 72px;
|
||||
}
|
||||
@@ -486,7 +504,7 @@
|
||||
}
|
||||
.npc_matt {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -208px -1529px;
|
||||
background-position: 0px -1529px;
|
||||
width: 195px;
|
||||
height: 138px;
|
||||
}
|
||||
@@ -504,13 +522,13 @@
|
||||
}
|
||||
.phobia_dysheartener {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -307px -220px;
|
||||
background-position: -527px -220px;
|
||||
width: 201px;
|
||||
height: 195px;
|
||||
}
|
||||
.quest_alligator {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -967px -660px;
|
||||
background-position: -1187px -880px;
|
||||
width: 201px;
|
||||
height: 213px;
|
||||
}
|
||||
@@ -528,19 +546,19 @@
|
||||
}
|
||||
.quest_atom1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1084px -1315px;
|
||||
background-position: -873px -1315px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
.quest_atom2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: 0px -1529px;
|
||||
background-position: -1375px -1315px;
|
||||
width: 207px;
|
||||
height: 138px;
|
||||
}
|
||||
.quest_atom3 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -433px -1315px;
|
||||
background-position: -222px -1315px;
|
||||
width: 216px;
|
||||
height: 180px;
|
||||
}
|
||||
@@ -564,7 +582,7 @@
|
||||
}
|
||||
.quest_beetle {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -747px -440px;
|
||||
background-position: -967px -660px;
|
||||
width: 204px;
|
||||
height: 201px;
|
||||
}
|
||||
@@ -576,7 +594,7 @@
|
||||
}
|
||||
.quest_bunny {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -222px -1315px;
|
||||
background-position: -307px -220px;
|
||||
width: 210px;
|
||||
height: 186px;
|
||||
}
|
||||
@@ -606,7 +624,7 @@
|
||||
}
|
||||
.quest_dilatoryDistress1 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1187px -880px;
|
||||
background-position: -1407px -1097px;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
}
|
||||
@@ -690,7 +708,7 @@
|
||||
}
|
||||
.quest_goldenknight2 {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1335px -1315px;
|
||||
background-position: -1124px -1315px;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
}
|
||||
@@ -702,7 +720,7 @@
|
||||
}
|
||||
.quest_gryphon {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -650px -1315px;
|
||||
background-position: -439px -1315px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
@@ -720,7 +738,7 @@
|
||||
}
|
||||
.quest_hedgehog {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -527px -220px;
|
||||
background-position: -747px -440px;
|
||||
width: 219px;
|
||||
height: 186px;
|
||||
}
|
||||
@@ -744,7 +762,7 @@
|
||||
}
|
||||
.quest_kraken {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -867px -1315px;
|
||||
background-position: -656px -1315px;
|
||||
width: 216px;
|
||||
height: 177px;
|
||||
}
|
||||
@@ -826,9 +844,3 @@
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
.quest_nudibranch {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-12.png');
|
||||
background-position: -1407px -1097px;
|
||||
width: 216px;
|
||||
height: 216px;
|
||||
}
|
||||
|
||||
@@ -1,132 +1,180 @@
|
||||
.Pet_Food_Candy_CottonCandyBlue {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -636px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Candy_CottonCandyPink {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -705px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Candy_Desert {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -774px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Candy_Golden {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -843px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Candy_Red {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -912px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Candy_Shade {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -981px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Candy_Skeleton {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1050px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Candy_White {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1119px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Candy_Zombie {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -954px -1550px;
|
||||
background-position: -1188px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Chocolate {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1023px -1550px;
|
||||
background-position: -1257px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_CottonCandyBlue {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1092px -1550px;
|
||||
background-position: -1326px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_CottonCandyPink {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1161px -1550px;
|
||||
background-position: -1395px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Fish {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1230px -1550px;
|
||||
background-position: -1464px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Honey {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1299px -1550px;
|
||||
background-position: -1533px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Meat {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1368px -1550px;
|
||||
background-position: -1602px -1550px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Milk {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1437px -1550px;
|
||||
background-position: 0px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Pie_Base {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1506px -1550px;
|
||||
background-position: -69px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Pie_CottonCandyBlue {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1575px -1550px;
|
||||
background-position: -138px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Pie_CottonCandyPink {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -1644px -1550px;
|
||||
background-position: -207px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Pie_Desert {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: 0px -1656px;
|
||||
background-position: -276px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Pie_Golden {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -69px -1656px;
|
||||
background-position: -345px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Pie_Red {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -138px -1656px;
|
||||
background-position: -414px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Pie_Shade {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -207px -1656px;
|
||||
background-position: -483px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Pie_Skeleton {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -276px -1656px;
|
||||
background-position: -552px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Pie_White {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -345px -1656px;
|
||||
background-position: -621px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Pie_Zombie {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -414px -1656px;
|
||||
background-position: -690px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Potatoe {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -483px -1656px;
|
||||
background-position: -759px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_RottenMeat {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -552px -1656px;
|
||||
background-position: -828px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Saddle {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -621px -1656px;
|
||||
background-position: -897px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_Food_Strawberry {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -690px -1656px;
|
||||
background-position: -966px -1656px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
@@ -1510,21 +1558,3 @@
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
}
|
||||
.Mount_Body_Egg-CottonCandyBlue {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -636px -1550px;
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
}
|
||||
.Mount_Body_Egg-CottonCandyPink {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -742px -1550px;
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
}
|
||||
.Mount_Body_Egg-Desert {
|
||||
background-image: url('~@/assets/images/sprites/spritesmith-main-14.png');
|
||||
background-position: -848px -1550px;
|
||||
width: 105px;
|
||||
height: 105px;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 476 KiB After Width: | Height: | Size: 475 KiB |
|
Before Width: | Height: | Size: 680 KiB After Width: | Height: | Size: 682 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 363 KiB After Width: | Height: | Size: 358 KiB |
|
Before Width: | Height: | Size: 318 KiB After Width: | Height: | Size: 328 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 177 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 127 KiB |
@@ -50,3 +50,18 @@
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
cursor: pointer;
|
||||
|
||||
& svg path {
|
||||
stroke: $gray-200;
|
||||
}
|
||||
|
||||
&:hover svg path {
|
||||
stroke: $gray-100;
|
||||
}
|
||||
}
|
||||
48
website/client/src/assets/svg/onboarding-guide-banner.svg
Normal file
@@ -0,0 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="48" viewBox="0 0 150 48">
|
||||
<defs>
|
||||
<filter id="a" width="225%" height="225%" x="-62.5%" y="-62.5%" filterUnits="objectBoundingBox">
|
||||
<feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"/>
|
||||
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="4"/>
|
||||
<feColorMatrix in="shadowBlurOuter1" result="shadowMatrixOuter1" values="0 0 0 0 0.101960784 0 0 0 0 0.0941176471 0 0 0 0 0.11372549 0 0 0 0.32 0"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g fill="#FFA624">
|
||||
<path d="M125.45 35.414l3.346-2.967-4.382-.896-2.967-3.347-.896 4.382-3.347 2.967 4.382.896 2.967 3.347z" opacity=".75"/>
|
||||
<path d="M136.43 30.407l3.325-.442-2.348-2.395-.442-3.325-2.395 2.348-3.325.442 2.348 2.395.442 3.325z" opacity=".5"/>
|
||||
<path d="M146.901 35.41l2.067.854-.557-2.165.853-2.067-2.165.557-2.067-.853.557 2.165-.853 2.067z" opacity=".25"/>
|
||||
<path d="M138.353 39.874l1.488-.093-.967-1.134-.093-1.488-1.134.967-1.488.093.967 1.134.093 1.488z" opacity=".35"/>
|
||||
</g>
|
||||
<g fill="#FFA624">
|
||||
<path d="M24.55 35.414l-3.346-2.967 4.382-.896 2.967-3.347.896 4.382 3.347 2.967-4.382.896-2.967 3.347z" opacity=".75"/>
|
||||
<path d="M13.57 30.407l-3.325-.442 2.348-2.395.442-3.325 2.395 2.348 3.325.442-2.348 2.395-.442 3.325z" opacity=".5"/>
|
||||
<path d="M3.099 35.41l-2.067.854.557-2.165-.853-2.067 2.165.557 2.067-.853-.557 2.165.853 2.067z" opacity=".25"/>
|
||||
<path d="M11.647 39.874l-1.488-.093.967-1.134.093-1.488 1.134.967 1.488.093-.967 1.134-.093 1.488z" opacity=".35"/>
|
||||
</g>
|
||||
<g opacity=".5" transform="translate(83 22)">
|
||||
<circle cx="12" cy="12" r="12" fill="#FFA623"/>
|
||||
<path fill="#FFF" d="M6.3 17.7c-3.1-3.1-3.1-8.2 0-11.3 3.1-3.1 8.2-3.1 11.3 0" opacity=".5"/>
|
||||
<path fill="#FFF" d="M17.7 6.3c3.1 3.1 3.1 8.2 0 11.3-3.1 3.1-8.2 3.1-11.3 0" opacity=".25"/>
|
||||
<path fill="#BF7D1A" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" opacity=".5"/>
|
||||
<path fill="#BF7D1A" d="M13 9v2h-2V9H9v6h2v-2h2v2h2V9z" opacity=".75"/>
|
||||
</g>
|
||||
<g opacity=".5" transform="translate(43 22)">
|
||||
<circle cx="12" cy="12" r="12" fill="#FFA623"/>
|
||||
<path fill="#FFF" d="M6.3 17.7c-3.1-3.1-3.1-8.2 0-11.3 3.1-3.1 8.2-3.1 11.3 0" opacity=".5"/>
|
||||
<path fill="#FFF" d="M17.7 6.3c3.1 3.1 3.1 8.2 0 11.3-3.1 3.1-8.2 3.1-11.3 0" opacity=".25"/>
|
||||
<path fill="#BF7D1A" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" opacity=".5"/>
|
||||
<path fill="#BF7D1A" d="M13 9v2h-2V9H9v6h2v-2h2v2h2V9z" opacity=".75"/>
|
||||
</g>
|
||||
<g transform="translate(59 6)">
|
||||
<circle cx="16" cy="16" r="16" fill="#FFA623"/>
|
||||
<path fill="#FFF" d="M8.4 23.6c-4.133-4.133-4.133-10.933 0-15.067 4.133-4.133 10.933-4.133 15.067 0" opacity=".5"/>
|
||||
<path fill="#FFF" d="M23.6 8.4c4.133 4.133 4.133 10.933 0 15.067-4.133 4.133-10.933 4.133-15.067 0" opacity=".25"/>
|
||||
<path fill="#BF7D1A" d="M16 2.667c-7.333 0-13.333 6-13.333 13.333s6 13.333 13.333 13.333 13.333-6 13.333-13.333S23.333 2.667 16 2.667zm0 24c-5.867 0-10.667-4.8-10.667-10.667S10.133 5.333 16 5.333 26.667 10.133 26.667 16 21.867 26.667 16 26.667z" opacity=".5"/>
|
||||
<path fill="#BF7D1A" d="M17.333 12v2.667h-2.666V12H12v8h2.667v-2.667h2.666V20H20v-8z" opacity=".75"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
3
website/client/src/assets/svg/star-badge.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="11" viewBox="0 0 12 11">
|
||||
<path fill="#FFF" fill-rule="evenodd" d="M6 8.76l-3.527 2.094.902-4.001L.294 4.146l4.084-.379L6 0l1.622 3.767 4.084.379-3.081 2.707.902 4.001z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 241 B |
@@ -1,104 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
class="modal-footer"
|
||||
style="margin-top:0"
|
||||
ng-init="loadWidgets()"
|
||||
>
|
||||
<div class="container-fluid share-buttons">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<a
|
||||
class="twitter-share-button share-button"
|
||||
:href="twitterLink"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
class="social-icon twitter svg-icon"
|
||||
v-html="icons.twitter"
|
||||
></div>
|
||||
{{ $t('tweet') }}
|
||||
</a>
|
||||
<a
|
||||
class="fb-share-button share-button"
|
||||
:href="facebookLink"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
class="social-icon facebook svg-icon"
|
||||
v-html="icons.facebook"
|
||||
></div>
|
||||
{{ $t('share') }}
|
||||
</a>
|
||||
<!-- @TODO: Still want this?
|
||||
.col-4a.tumblr-share-button(:data-href='socialLevelLink', data-notes='none')-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TODO disabled for now, see https://github.com/HabitRPG/habitica/issues/11283 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.share-buttons {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.share-button {
|
||||
display: inline-block;
|
||||
width: 77px;
|
||||
padding: .5em;
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.fb-share-button {
|
||||
background-color: #2995cd;
|
||||
}
|
||||
|
||||
.twitter-share-button {
|
||||
margin-right: .5em;
|
||||
background-color: #3bcad7;
|
||||
}
|
||||
|
||||
.social-icon {
|
||||
width: 16px;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.social-icon.facebook svg {
|
||||
width: 7.5px;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
|
||||
.social-icon.twitter {
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
.modal-footer {
|
||||
border-top: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// @TODO:
|
||||
import twitter from '@/assets/svg/twitter.svg';
|
||||
import facebook from '@/assets/svg/facebook.svg';
|
||||
|
||||
const BASE_URL = 'https://habitica.com';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
const tweet = this.$t('achievementShare');
|
||||
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
twitter,
|
||||
facebook,
|
||||
}),
|
||||
tweet,
|
||||
achievementLink: `${BASE_URL}`,
|
||||
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}&count=none`,
|
||||
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}`,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -3,14 +3,32 @@
|
||||
id="generic-achievement"
|
||||
:title="data.message"
|
||||
size="md"
|
||||
:hide-footer="true"
|
||||
:hide-header="true"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<div class="col-12">
|
||||
<achievement-avatar class="avatar" />
|
||||
<span
|
||||
class="close-icon svg-icon inline icon-10"
|
||||
@click="close()"
|
||||
v-html="icons.close"
|
||||
></span>
|
||||
<div class="content">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-header title"
|
||||
>
|
||||
{{ $t('earnedAchievement') }}
|
||||
</div>
|
||||
<div class="col-6 offset-3 text-center">
|
||||
<p v-html="data.modalText"></p>
|
||||
<div class="inner-content">
|
||||
<div class="achievement-background d-flex align-items-center">
|
||||
<div
|
||||
class="icon"
|
||||
:class="achievementClass"
|
||||
></div>
|
||||
</div>
|
||||
<h4
|
||||
class="title"
|
||||
v-html="$t(achievement.titleKey)"
|
||||
>
|
||||
</h4>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="close()"
|
||||
@@ -19,33 +37,86 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<achievement-footer />
|
||||
<div
|
||||
slot="modal-footer"
|
||||
class="clearfix"
|
||||
></div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.avatar {
|
||||
width: 140px;
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/modal.scss';
|
||||
|
||||
#generic-achievement {
|
||||
@include centeredModal();
|
||||
|
||||
.modal-dialog {
|
||||
width: 330px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding-top: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inner-content {
|
||||
margin: 24px auto auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.achievement-background {
|
||||
width: 112px;
|
||||
height: 112px;
|
||||
border-radius: 4px;
|
||||
background-color: $gray-700;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
margin-top: 16px !important;
|
||||
color: $purple-200 !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 24px !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin: 0 auto;
|
||||
margin-bottom: 1.5em;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import achievementFooter from './achievementFooter';
|
||||
import achievementAvatar from './achievementAvatar';
|
||||
|
||||
import { mapState } from '@/libs/store';
|
||||
import achievements from '@/../../common/script/content/achievements';
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
achievementFooter,
|
||||
achievementAvatar,
|
||||
},
|
||||
props: ['data'],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
close: svgClose,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
achievement () {
|
||||
return achievements[this.data.achievement];
|
||||
},
|
||||
achievementClass () {
|
||||
return `${this.achievement.icon}2x`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
|
||||
@@ -27,36 +27,6 @@
|
||||
label(style='display:inline-block') {{ $t('dontShowAgain') }}
|
||||
-->
|
||||
</div>
|
||||
<div class="container-fluid share-buttons">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<a
|
||||
class="twitter-share-button share-button"
|
||||
:href="twitterLink"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
class="social-icon twitter svg-icon"
|
||||
v-html="icons.twitter"
|
||||
></div>
|
||||
{{ $t('tweet') }}
|
||||
</a>
|
||||
<a
|
||||
class="fb-share-button share-button"
|
||||
:href="facebookLink"
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
class="social-icon facebook svg-icon"
|
||||
v-html="icons.facebook"
|
||||
></div>
|
||||
{{ $t('share') }}
|
||||
</a>
|
||||
</div>
|
||||
<!-- @TODO: Still want this? .col-4a.tumblr
|
||||
-share-button(:data-href='socialLevelLink', data-notes='none')-->
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
@@ -72,7 +42,6 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
|
||||
|
||||
.modal-body {
|
||||
padding-top: 1em;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
@@ -99,45 +68,6 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
|
||||
margin-top: 1em;
|
||||
min-height: 0px;
|
||||
}
|
||||
|
||||
.share-buttons {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.share-button {
|
||||
display: inline-block;
|
||||
width: 77px;
|
||||
padding: .5em;
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.fb-share-button {
|
||||
background-color: #2995cd;
|
||||
}
|
||||
|
||||
.twitter-share-button {
|
||||
margin-right: .5em;
|
||||
background-color: #3bcad7;
|
||||
}
|
||||
|
||||
.social-icon {
|
||||
width: 16px;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.social-icon.facebook svg {
|
||||
width: 7.5px;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
|
||||
.social-icon.twitter {
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -152,10 +82,6 @@ import Avatar from '../avatar';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants';
|
||||
import styleHelper from '@/mixins/styleHelper';
|
||||
import twitter from '@/assets/svg/twitter.svg';
|
||||
import facebook from '@/assets/svg/facebook.svg';
|
||||
|
||||
const BASE_URL = 'https://habitica.com';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -163,18 +89,9 @@ export default {
|
||||
},
|
||||
mixins: [styleHelper],
|
||||
data () {
|
||||
const tweet = this.$t('levelUpShare');
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
twitter,
|
||||
facebook,
|
||||
}),
|
||||
statsAllocationBoxIsOpen: true,
|
||||
maxHealth,
|
||||
tweet,
|
||||
socialLevelLink: `${BASE_URL}/social/level-up`,
|
||||
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}/social/level-up&count=none`,
|
||||
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}/social/level-up`,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -183,16 +100,10 @@ export default {
|
||||
return this.$store.getters['members:hasClass'](this.user) && !this.user.preferences.automaticAllocation;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.loadWidgets();
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'level-up');
|
||||
},
|
||||
loadWidgets () {
|
||||
// @TODO:
|
||||
},
|
||||
changeLevelupSuppress () {
|
||||
// @TODO: dispatch set({"preferences.suppressModals.levelUp":
|
||||
// user.preferences.suppressModals.levelUp?true: false})
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="onboarding-complete"
|
||||
size="sm"
|
||||
:hide-footer="true"
|
||||
:hide-header="true"
|
||||
>
|
||||
<div class="content text-center">
|
||||
<span
|
||||
class="close-icon svg-icon inline icon-10"
|
||||
@click="close()"
|
||||
v-html="icons.close"
|
||||
></span>
|
||||
<h2>{{ $t('congratulations') }}</h2>
|
||||
<img
|
||||
class="onboarding-complete-banner d-block"
|
||||
src="~@/assets/images/onboarding-complete-banner@2x.png"
|
||||
>
|
||||
<p
|
||||
class="onboarding-complete-text"
|
||||
v-html="$t('onboardingCompleteDesc')"
|
||||
></p>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="closeWithAction()"
|
||||
>
|
||||
{{ $t('viewAchievements') }}
|
||||
</button>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#onboarding-complete {
|
||||
.modal-content {
|
||||
min-width: 330px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding-top: 1em;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
h2 {
|
||||
color: $purple-200;
|
||||
margin-bottom: 24px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 8px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.onboarding-complete-banner {
|
||||
width: 282px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.onboarding-complete-text {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.onboarding-complete-text ::v-deep .gold-amount {
|
||||
color: $yellow-5;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
close: svgClose,
|
||||
}),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'onboarding-complete');
|
||||
},
|
||||
closeWithAction () {
|
||||
this.close();
|
||||
setTimeout(() => {
|
||||
this.$router.push({ name: 'achievements' });
|
||||
}, 200);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -6,7 +6,15 @@
|
||||
:hide-header="true"
|
||||
:hide-footer="true"
|
||||
:modal-class="{'page-2':modalPage > 1 && !editing}"
|
||||
:no-close-on-esc="!editing"
|
||||
:no-close-on-backdrop="!editing"
|
||||
>
|
||||
<span
|
||||
v-if="editing"
|
||||
class="close-icon svg-icon inline icon-10"
|
||||
@click="close()"
|
||||
v-html="icons.close"
|
||||
></span>
|
||||
<div
|
||||
v-if="modalPage === 1 && !editing"
|
||||
class="section row welcome-section"
|
||||
@@ -1099,6 +1107,7 @@ import gold from '@/assets/svg/gold.svg';
|
||||
import pin from '@/assets/svg/pin.svg';
|
||||
import arrowRight from '@/assets/svg/arrow_right.svg';
|
||||
import arrowLeft from '@/assets/svg/arrow_left.svg';
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
import isPinned from '@/../../common/script/libs/isPinned';
|
||||
import { avatarEditorUtilies } from '../mixins/avatarEditUtilities';
|
||||
|
||||
@@ -1138,6 +1147,7 @@ export default {
|
||||
gold,
|
||||
arrowRight,
|
||||
arrowLeft,
|
||||
close: svgClose,
|
||||
}),
|
||||
modalPage: 1,
|
||||
activeTopPage: 'body',
|
||||
@@ -1158,8 +1168,6 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
|
||||
|
||||
editing () {
|
||||
return this.$store.state.avatarEditorOptions.editingUser;
|
||||
},
|
||||
@@ -1228,6 +1236,9 @@ export default {
|
||||
this.$root.$on('buyModal::boughtItem', this.backgroundPurchased);
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'avatar-modal');
|
||||
},
|
||||
purchase (type, key) {
|
||||
this.$store.dispatch('shops:purchase', {
|
||||
type,
|
||||
@@ -1278,6 +1289,9 @@ export default {
|
||||
if (this.$route.path !== '/') {
|
||||
this.$router.push('/');
|
||||
}
|
||||
|
||||
// NOTE: it's important this flag is set AFTER the onboarding default tasks
|
||||
// have been created or it'll break the onboarding guide achievement for creating a task
|
||||
this.$store.dispatch('user:set', {
|
||||
'flags.welcomed': true,
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template functional>
|
||||
<span
|
||||
class="message-count"
|
||||
class="message-count d-flex align-items-center justify-content-center"
|
||||
:class="{'top-count': props.top === true, 'top-count-gray': props.gray === true}"
|
||||
> {{ props.count }} </span>
|
||||
v-html="props.badge || props.count"
|
||||
></span>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -18,6 +19,11 @@
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.message-count.top-count {
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
>
|
||||
<slot name="icon"></slot>
|
||||
</div>
|
||||
<div class="notification-content">
|
||||
<div
|
||||
class="notification-content"
|
||||
:class="{'has-text': hasText}"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
<div
|
||||
@@ -107,17 +110,19 @@
|
||||
line-height: 1.43;
|
||||
color: $gray-50;
|
||||
|
||||
max-width: calc(100% - 26px); // to make space for the close icon
|
||||
max-width: 100%;
|
||||
|
||||
&.has-text {
|
||||
padding-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-remove {
|
||||
// total distance from the notification top edge is 20 pixels
|
||||
margin-top: 7px;
|
||||
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-left: 12px;
|
||||
padding: 4px;
|
||||
right: 24px;
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
@@ -131,7 +136,25 @@ import closeIcon from '@/assets/svg/close.svg';
|
||||
import { mapActions, mapState } from '@/libs/store';
|
||||
|
||||
export default {
|
||||
props: ['notification', 'canRemove', 'hasIcon', 'readAfterClick'],
|
||||
props: {
|
||||
notification: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
canRemove: {
|
||||
type: Boolean,
|
||||
},
|
||||
hasIcon: {
|
||||
type: Boolean,
|
||||
},
|
||||
readAfterClick: {
|
||||
type: Boolean,
|
||||
},
|
||||
hasText: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<base-notification
|
||||
:can-remove="canRemove"
|
||||
:has-icon="false"
|
||||
:read-after-click="true"
|
||||
:notification="notification"
|
||||
:has-text="false"
|
||||
@click="action"
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
class="d-flex flex-column align-items-center onboarding-complete-content"
|
||||
>
|
||||
<img
|
||||
class="onboarding-complete-banner d-block"
|
||||
src="~@/assets/images/onboarding-complete-banner@2x.png"
|
||||
>
|
||||
<h3>{{ $t('congratulations') }}</h3>
|
||||
<p
|
||||
class="onboarding-complete-text"
|
||||
v-html="$t('onboardingCompleteDesc')"
|
||||
></p>
|
||||
<div class="notifications-buttons">
|
||||
<div
|
||||
class="btn btn-small btn-primary btn-block"
|
||||
>
|
||||
{{ $t('viewAchievements') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</base-notification>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.onboarding-complete-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.onboarding-complete-banner {
|
||||
width: 282px;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.onboarding-complete-text {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.onboarding-complete-text ::v-deep .gold-amount {
|
||||
color: $yellow-5;
|
||||
}
|
||||
|
||||
.notifications-buttons {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import BaseNotification from './base';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseNotification,
|
||||
},
|
||||
props: ['notification', 'canRemove'],
|
||||
methods: {
|
||||
action () {
|
||||
this.$router.push({ name: 'achievements' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -11,10 +11,11 @@
|
||||
:aria-label="$t('notifications')"
|
||||
>
|
||||
<message-count
|
||||
v-if="notificationsCount > 0"
|
||||
v-if="notificationsCount > 0 || hasSpecialBadge"
|
||||
:count="notificationsCount"
|
||||
:top="true"
|
||||
:gray="!hasUnseenNotifications"
|
||||
:gray="!hasUnseenNotifications && !hasSpecialBadge"
|
||||
:badge="hasSpecialBadge ? icons.starBadge : null"
|
||||
/>
|
||||
<div
|
||||
class="top-menu-icon svg-icon notifications"
|
||||
@@ -22,7 +23,10 @@
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="dropdown-content">
|
||||
<div
|
||||
v-if="openStatus === 1"
|
||||
slot="dropdown-content"
|
||||
>
|
||||
<div
|
||||
class="dropdown-item dropdown-separated
|
||||
d-flex justify-content-between dropdown-inactive align-items-center"
|
||||
@@ -41,6 +45,10 @@
|
||||
>{{ $t('dismissAll') }}</a>
|
||||
</div>
|
||||
<world-boss />
|
||||
<onboarding-guide
|
||||
v-if="showOnboardingGuide"
|
||||
:never-seen="hasSpecialBadge"
|
||||
/>
|
||||
<component
|
||||
:is="notification.type"
|
||||
v-for="notification in notifications"
|
||||
@@ -49,7 +57,7 @@
|
||||
:can-remove="!isActionable(notification)"
|
||||
/>
|
||||
<div
|
||||
v-if="notificationsCount === 0"
|
||||
v-if="notificationsCount === 0 && !showOnboardingGuide"
|
||||
class="dropdown-item dropdown-separated
|
||||
d-flex justify-content-center dropdown-inactive no-notifications flex-column"
|
||||
>
|
||||
@@ -106,10 +114,16 @@
|
||||
<script>
|
||||
import { mapState, mapActions } from '@/libs/store';
|
||||
import * as quests from '@/../../common/script/content/quests';
|
||||
import {
|
||||
hasCompletedOnboarding,
|
||||
hasActiveOnboarding,
|
||||
} from '@/../../common/script/libs/onboarding';
|
||||
import notificationsIcon from '@/assets/svg/notifications.svg';
|
||||
import MenuDropdown from '../ui/customMenuDropdown';
|
||||
import MessageCount from './messageCount';
|
||||
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
|
||||
import successImage from '@/assets/svg/success.svg';
|
||||
import starBadge from '@/assets/svg/star-badge.svg';
|
||||
|
||||
// Notifications
|
||||
import NEW_STUFF from './notifications/newStuff';
|
||||
@@ -132,6 +146,8 @@ import VERIFY_USERNAME from './notifications/verifyUsername';
|
||||
import ACHIEVEMENT_JUST_ADD_WATER from './notifications/justAddWater';
|
||||
import ACHIEVEMENT_LOST_MASTERCLASSER from './notifications/lostMasterclasser';
|
||||
import ACHIEVEMENT_MIND_OVER_MATTER from './notifications/mindOverMatter';
|
||||
import ONBOARDING_COMPLETE from './notifications/onboardingComplete';
|
||||
import OnboardingGuide from './onboardingGuide';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -158,13 +174,17 @@ export default {
|
||||
ACHIEVEMENT_MIND_OVER_MATTER,
|
||||
WorldBoss: WORLD_BOSS,
|
||||
VERIFY_USERNAME,
|
||||
OnboardingGuide,
|
||||
ONBOARDING_COMPLETE,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
notifications: notificationsIcon,
|
||||
success: successImage,
|
||||
starBadge,
|
||||
}),
|
||||
hasSpecialBadge: false,
|
||||
quests,
|
||||
openStatus: undefined,
|
||||
actionableNotifications: [
|
||||
@@ -177,11 +197,11 @@ export default {
|
||||
handledNotifications: [
|
||||
'NEW_STUFF', 'GROUP_TASK_NEEDS_WORK',
|
||||
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
|
||||
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED', 'GROUP_TASK_CLAIMED',
|
||||
'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
|
||||
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
|
||||
'GROUP_TASK_CLAIMED', 'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
|
||||
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
|
||||
'ACHIEVEMENT_JUST_ADD_WATER', 'ACHIEVEMENT_LOST_MASTERCLASSER', 'ACHIEVEMENT_MIND_OVER_MATTER',
|
||||
'VERIFY_USERNAME',
|
||||
'VERIFY_USERNAME', 'ONBOARDING_COMPLETE',
|
||||
],
|
||||
};
|
||||
},
|
||||
@@ -275,6 +295,20 @@ export default {
|
||||
hasClass () {
|
||||
return this.$store.getters['members:hasClass'](this.user);
|
||||
},
|
||||
showOnboardingGuide () {
|
||||
return hasActiveOnboarding(this.user) && !hasCompletedOnboarding(this.user);
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
const onboardingPanelState = getLocalSetting(CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE);
|
||||
if (
|
||||
onboardingPanelState !== CONSTANTS.onboardingPanelValues.PANEL_OPENED
|
||||
&& this.showOnboardingGuide
|
||||
) {
|
||||
// The first time the onboarding panel is opened a special
|
||||
// badge for notifications should be used
|
||||
this.hasSpecialBadge = true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
@@ -286,6 +320,18 @@ export default {
|
||||
|
||||
// Mark notifications as seen when the menu is opened
|
||||
if (openStatus) this.markAllAsSeen();
|
||||
|
||||
// Reset the special notification badge as soon as it's opened
|
||||
if (this.hasSpecialBadge) {
|
||||
setLocalSetting(
|
||||
CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE,
|
||||
CONSTANTS.onboardingPanelValues.PANEL_OPENED,
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
this.hasSpecialBadge = false;
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
markAllAsSeen () {
|
||||
const idsToSee = this.notifications.map(notification => {
|
||||
@@ -318,5 +364,6 @@ export default {
|
||||
return this.actionableNotifications.indexOf(notification.type) !== -1;
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
259
website/client/src/components/header/onboardingGuide.vue
Normal file
@@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<div
|
||||
class="onboarding-guide-panel d-flex align-items-center flex-column p-4 dropdown-separated"
|
||||
@click.stop.prevent="action"
|
||||
>
|
||||
<div
|
||||
class="svg-icon onboarding-toggle"
|
||||
:class="{'onboarding-toggle-open': open}"
|
||||
@click="openPanel"
|
||||
v-html="icons.down"
|
||||
></div>
|
||||
<div
|
||||
class="svg-icon onboarding-guide-banner"
|
||||
v-html="icons.onboardingGuideBanner"
|
||||
></div>
|
||||
<h3 class="getting-started">
|
||||
{{ $t('gettingStarted') }}
|
||||
</h3>
|
||||
<span
|
||||
class="getting-started-desc"
|
||||
v-html="$t('gettingStartedDesc')"
|
||||
></span>
|
||||
<div
|
||||
class="onboarding-progress-box d-flex flex-row justify-content-between small-text mb-2"
|
||||
>
|
||||
<strong>Your Progress</strong>
|
||||
<span :class="{'has-progress': progress > 0}">{{ progressText }}</span>
|
||||
</div>
|
||||
<div class="onboarding-progress-bar mb-3">
|
||||
<div
|
||||
class="onboarding-progress-bar-fill"
|
||||
:style="{width: `${progress}%`}"
|
||||
></div>
|
||||
</div>
|
||||
<b-collapse
|
||||
id="onboardingPanelCollapse"
|
||||
v-model="open"
|
||||
>
|
||||
<div
|
||||
v-for="(achievement, key) in onboardingAchievements"
|
||||
:key="key"
|
||||
:class="{
|
||||
'achievement-earned': achievement.earned
|
||||
}"
|
||||
class="achievement-box d-flex flex-row"
|
||||
>
|
||||
<div class="achievement-icon-wrapper">
|
||||
<div :class="`achievement-icon ${getAchievementIcon(achievement)}`"></div>
|
||||
</div>
|
||||
<div class="achievement-info d-flex flex-column">
|
||||
<strong class="achievement-title">{{ achievement.title }}</strong>
|
||||
<span class="small-text achievement-desc">{{ getAchievementText(key) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.onboarding-guide-panel {
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.onboarding-toggle {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 75px;
|
||||
right: 24px;
|
||||
width: 16px;
|
||||
& ::v-deep svg path {
|
||||
stroke: $gray-200;
|
||||
}
|
||||
|
||||
&:hover ::v-deep svg path {
|
||||
stroke: $gray-100;
|
||||
}
|
||||
|
||||
&-open {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-guide-banner {
|
||||
margin-top: -8px;
|
||||
margin-bottom: 12px;
|
||||
width: 154px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.getting-started {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.getting-started-desc {
|
||||
font-size: 14px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.getting-started-desc ::v-deep .gold-amount {
|
||||
color: $yellow-5;
|
||||
}
|
||||
|
||||
.onboarding-progress-box {
|
||||
width: 100%;
|
||||
font-style: normal;
|
||||
|
||||
strong {
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.has-progress {
|
||||
color: $yellow-5;
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-progress-bar {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background-color: $gray-600;
|
||||
|
||||
.onboarding-progress-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
background: $yellow-5;
|
||||
}
|
||||
}
|
||||
|
||||
.achievement-box {
|
||||
padding-top: 11px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px dashed $gray-500;
|
||||
width: 100%;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0px;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.achievement-earned {
|
||||
.achievement-icon-wrapper {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
color: $gray-200;
|
||||
|
||||
.achievement-title {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
.achievement-icon-wrapper {
|
||||
opacity: 0.5;
|
||||
width: 40px;
|
||||
|
||||
.achievement-icon {
|
||||
margin-left: -12px;
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.achievement-info {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.achievement-title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.achievement-desc {
|
||||
font-style: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import achievs from '@/../../common/script/libs/achievements';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import onboardingGuideBanner from '@/assets/svg/onboarding-guide-banner.svg';
|
||||
import downIcon from '@/assets/svg/down.svg';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
neverSeen: { // whether it's ever been seen by the user
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
onboardingGuideBanner,
|
||||
down: downIcon,
|
||||
}),
|
||||
open: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
onboardingAchievements () {
|
||||
return achievs.getAchievementsForProfile(this.user).onboarding.achievements;
|
||||
},
|
||||
progress () {
|
||||
const keys = Object.keys(this.onboardingAchievements);
|
||||
let nEarned = 0;
|
||||
|
||||
keys.forEach(key => {
|
||||
if (this.onboardingAchievements[key].earned) nEarned += 1;
|
||||
});
|
||||
|
||||
return (nEarned / keys.length) * 100;
|
||||
},
|
||||
progressText () {
|
||||
if (this.progress === 0) {
|
||||
return this.$t('letsGetStarted');
|
||||
}
|
||||
|
||||
return this.$t('onboardingProgress', { percentage: this.progress });
|
||||
},
|
||||
},
|
||||
created () {
|
||||
// null means it's never been automatically toggled
|
||||
if (this.neverSeen === true) {
|
||||
// The first time the panel should be automatically opened
|
||||
this.open = true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAchievementIcon (achievement) {
|
||||
if (achievement.earned) {
|
||||
return `${achievement.icon}2x`;
|
||||
}
|
||||
|
||||
return 'achievement-unearned2x';
|
||||
},
|
||||
getAchievementText (key) {
|
||||
let stringKey = 'achievement';
|
||||
stringKey += key.charAt(0).toUpperCase() + key.slice(1);
|
||||
stringKey += 'ModalText';
|
||||
|
||||
return this.$t(stringKey);
|
||||
},
|
||||
openPanel (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.open = !this.open;
|
||||
},
|
||||
action () {
|
||||
// Do nothing, used to prevent closure on click.
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -3,6 +3,11 @@
|
||||
id="hatchedPet-modal"
|
||||
:hide-header="true"
|
||||
>
|
||||
<span
|
||||
class="close-icon svg-icon inline icon-10"
|
||||
@click="close()"
|
||||
v-html="icons.close"
|
||||
></span>
|
||||
<div
|
||||
v-if="pet != null"
|
||||
class="content"
|
||||
@@ -14,7 +19,7 @@
|
||||
{{ $t('hatchedPetGeneric') }}
|
||||
</div>
|
||||
<div class="inner-content">
|
||||
<div class="pet-background">
|
||||
<div class="pet-background d-flex align-items-center">
|
||||
<div :class="pet.class"></div>
|
||||
</div>
|
||||
<h4 class="title">
|
||||
@@ -51,6 +56,10 @@
|
||||
width: 330px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -75,12 +84,25 @@
|
||||
|
||||
.dialog-header {
|
||||
color: $purple-200;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-bottom: 24px;
|
||||
min-height: 0;
|
||||
|
||||
&.markdown {
|
||||
p {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
import svgClose from '@/assets/svg/close.svg';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
@@ -94,6 +116,9 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
pet: null,
|
||||
icons: Object.freeze({
|
||||
close: svgClose,
|
||||
}),
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
|
||||
@@ -26,10 +26,14 @@
|
||||
<quest-completed />
|
||||
<quest-invitation />
|
||||
<verify-username />
|
||||
<generic-achievement :data="notificationData" />
|
||||
<generic-achievement
|
||||
v-if="notificationData && notificationData.achievement"
|
||||
:data="notificationData"
|
||||
/>
|
||||
<just-add-water />
|
||||
<lost-masterclasser />
|
||||
<mind-over-matter />
|
||||
<onboarding-complete />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -92,6 +96,10 @@
|
||||
background-color: #4f2a93 !important;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12) !important;
|
||||
}
|
||||
|
||||
.introjs-skipbutton.btn-primary, .introjs-donebutton.btn-primary {
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -99,9 +107,11 @@ import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
import throttle from 'lodash/throttle';
|
||||
import debounce from 'lodash/debounce';
|
||||
import Vue from 'vue';
|
||||
|
||||
import { toNextLevel } from '@/../../common/script/statHelpers';
|
||||
import { shouldDo } from '@/../../common/script/cron';
|
||||
import { onOnboardingComplete } from '@/../../common/script/libs/onboarding';
|
||||
import { mapState } from '@/libs/store';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import guide from '@/mixins/guide';
|
||||
@@ -132,6 +142,7 @@ import justAddWater from './achievements/justAddWater';
|
||||
import lostMasterclasser from './achievements/lostMasterclasser';
|
||||
import mindOverMatter from './achievements/mindOverMatter';
|
||||
import loginIncentives from './achievements/login-incentives';
|
||||
import onboardingComplete from './achievements/onboardingComplete';
|
||||
import verifyUsername from './settings/verifyUsername';
|
||||
|
||||
const NOTIFICATIONS = {
|
||||
@@ -164,21 +175,33 @@ const NOTIFICATIONS = {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementAllYourBase')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
achievement: 'allYourBase', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_BACK_TO_BASICS: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementBackToBasics')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
achievement: 'backToBasics', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_DUST_DEVIL: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementDustDevil')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
achievement: 'dustDevil', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_ARID_AUTHORITY: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementAridAuthority')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
achievement: 'aridAuthority', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_PARTY_UP: {
|
||||
achievement: true,
|
||||
@@ -187,6 +210,7 @@ const NOTIFICATIONS = {
|
||||
data: {
|
||||
message: $t => $t('achievement'),
|
||||
modalText: $t => $t('achievementPartyUp'),
|
||||
achievement: 'partyUp', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_PARTY_ON: {
|
||||
@@ -196,6 +220,7 @@ const NOTIFICATIONS = {
|
||||
data: {
|
||||
message: $t => $t('achievement'),
|
||||
modalText: $t => $t('achievementPartyOn'),
|
||||
achievement: 'partyOn', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_BEAST_MASTER: {
|
||||
@@ -205,6 +230,7 @@ const NOTIFICATIONS = {
|
||||
data: {
|
||||
message: $t => $t('achievement'),
|
||||
modalText: $t => $t('beastAchievement'),
|
||||
achievement: 'beastMaster', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_MOUNT_MASTER: {
|
||||
@@ -214,6 +240,7 @@ const NOTIFICATIONS = {
|
||||
data: {
|
||||
message: $t => $t('achievement'),
|
||||
modalText: $t => $t('mountAchievement'),
|
||||
achievement: 'mountMaster', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_TRIAD_BINGO: {
|
||||
@@ -223,27 +250,49 @@ const NOTIFICATIONS = {
|
||||
data: {
|
||||
message: $t => $t('achievement'),
|
||||
modalText: $t => $t('triadBingoAchievement'),
|
||||
achievement: 'triadBingo', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_MONSTER_MAGUS: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementMonsterMagus')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
achievement: 'monsterMagus', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_UNDEAD_UNDERTAKER: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementUndeadUndertaker')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
achievement: 'undeadUndertaker', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT: { // data filled in handleUserNotifications
|
||||
achievement: true,
|
||||
modalId: 'generic-achievement',
|
||||
label: null, // data filled in handleUserNotifications
|
||||
data: {
|
||||
message: $t => $t('achievement'),
|
||||
modalText: null, // data filled in handleUserNotifications
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_PRIMED_FOR_PAINTING: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementPrimedForPainting')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
achievement: 'primedForPainting', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_PEARLY_PRO: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementPearlyPro')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
achievement: 'pearlyPro', // defined manually until the server sends all the necessary data
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -276,6 +325,7 @@ export default {
|
||||
lostMasterclasser,
|
||||
mindOverMatter,
|
||||
justAddWater,
|
||||
onboardingComplete,
|
||||
},
|
||||
mixins: [notifications, guide],
|
||||
data () {
|
||||
@@ -302,7 +352,7 @@ export default {
|
||||
'GENERIC_ACHIEVEMENT', 'ACHIEVEMENT_PARTY_UP', 'ACHIEVEMENT_PARTY_ON', 'ACHIEVEMENT_BEAST_MASTER',
|
||||
'ACHIEVEMENT_MOUNT_MASTER', 'ACHIEVEMENT_TRIAD_BINGO', 'ACHIEVEMENT_DUST_DEVIL', 'ACHIEVEMENT_ARID_AUTHORITY',
|
||||
'ACHIEVEMENT_MONSTER_MAGUS', 'ACHIEVEMENT_UNDEAD_UNDERTAKER', 'ACHIEVEMENT_PRIMED_FOR_PAINTING',
|
||||
'ACHIEVEMENT_PEARLY_PRO', 'GENERIC_ACHIEVEMENT',
|
||||
'ACHIEVEMENT_PEARLY_PRO', 'ACHIEVEMENT', 'ONBOARDING_COMPLETE',
|
||||
].forEach(type => {
|
||||
handledNotifications[type] = true;
|
||||
});
|
||||
@@ -478,7 +528,7 @@ export default {
|
||||
this.text(config.label(this.$t), () => {
|
||||
this.notificationData = data;
|
||||
this.$root.$emit('bv::show::modal', config.modalId);
|
||||
}, false);
|
||||
}, true, 10000);
|
||||
}
|
||||
},
|
||||
debounceCheckUserAchievements: debounce(function debounceCheck () {
|
||||
@@ -713,6 +763,18 @@ export default {
|
||||
case 'GENERIC_ACHIEVEMENT':
|
||||
this.showNotificationWithModal(notification);
|
||||
break;
|
||||
case 'ACHIEVEMENT': { // generic achievement
|
||||
const { achievement } = notification.data;
|
||||
const upperCaseAchievement = achievement.charAt(0).toUpperCase() + achievement.slice(1);
|
||||
const achievementTitleKey = `achievement${upperCaseAchievement}`;
|
||||
NOTIFICATIONS.ACHIEVEMENT.label = $t => `${$t('achievement')}: ${$t(achievementTitleKey)}`;
|
||||
NOTIFICATIONS.ACHIEVEMENT.data.modalText = $t => $t(achievementTitleKey);
|
||||
this.showNotificationWithModal(notification);
|
||||
|
||||
// Set the achievement as it's not defined in the user schema
|
||||
Vue.set(this.user.achievements, achievement, true);
|
||||
break;
|
||||
}
|
||||
case 'CRON':
|
||||
if (notification.data) {
|
||||
if (notification.data.hp) this.hp(notification.data.hp, 'hp');
|
||||
@@ -742,6 +804,20 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'login-incentives');
|
||||
}
|
||||
break;
|
||||
case 'ONBOARDING_COMPLETE':
|
||||
// Award rewards
|
||||
onOnboardingComplete(this.user);
|
||||
|
||||
// If the user cronned in the last 3 minutes
|
||||
// Don't show too many modals on app load
|
||||
// Use notification panel
|
||||
if (moment().diff(this.user.lastCron, 'minutes') < 3) {
|
||||
markAsRead = false;
|
||||
} else {
|
||||
// Otherwise use the modal
|
||||
this.$root.$emit('bv::show::modal', 'onboarding-complete');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (markAsRead) notificationsToRead.push(notification.id);
|
||||
|
||||
@@ -50,8 +50,6 @@ A simplified dropdown component that doesn't rely on buttons as toggles like bo
|
||||
.dropdown-menu {
|
||||
cursor: auto;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
max-height: calc(100vh - 100px);
|
||||
overflow: auto;
|
||||
left: inherit;
|
||||
right: 0px !important;
|
||||
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
<template>
|
||||
<div class="standard-page container">
|
||||
<div
|
||||
v-for="(category, key) in achievements"
|
||||
:key="key"
|
||||
class="row"
|
||||
>
|
||||
<h2 class="col-12">
|
||||
{{ $t(key+'Achievs') }}
|
||||
</h2>
|
||||
<div
|
||||
v-for="(achievment, achievmentKey) in category.achievements"
|
||||
:key="achievmentKey"
|
||||
class="col-3 text-center"
|
||||
>
|
||||
<div
|
||||
class="achievement-container"
|
||||
:data-popover-html="achievment.title + achievment.text"
|
||||
popover-placement="achievPopoverPlacement"
|
||||
popover-append-to-body="achievAppendToBody"
|
||||
>
|
||||
<div
|
||||
popover-trigger="mouseenter"
|
||||
:data-popover-html="achievment.title + achievment.text"
|
||||
popover-placement="achievPopoverPlacement"
|
||||
popover-append-to-body="achievAppendToBody"
|
||||
>
|
||||
<div
|
||||
v-if="achievment.earned"
|
||||
class="achievement"
|
||||
:class="achievment.icon + '2x'"
|
||||
>
|
||||
<div
|
||||
v-if="achievment.optionalCount"
|
||||
class="counter badge badge-info stack-count"
|
||||
>
|
||||
{{ achievment.optionalCount }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!achievment.earned"
|
||||
class="achievement achievement-unearned2x"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h2>Challeges Won</h2>
|
||||
<div
|
||||
v-for="chal in user.achievements.challenges"
|
||||
:key="chal"
|
||||
>
|
||||
<span>{{ chal }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h2>Quests Completed</h2>
|
||||
<div
|
||||
v-for="(value, key) in user.achievements.quests"
|
||||
:key="key"
|
||||
>
|
||||
<span>{{ content.quests[k].text() }}</span>
|
||||
<span>{{ value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
h2 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.achievement-container {
|
||||
margin-bottom: 1em;
|
||||
padding: 2em;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.achievement {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.counter.badge {
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #ff944c;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import achievementsLib from '@/../../common/script/libs/achievements';
|
||||
import Content from '@/../../common/script/content';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
achievements: {},
|
||||
content: Content,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
mounted () {
|
||||
this.achievements = achievementsLib.getAchievementsForProfile(this.user);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -300,15 +300,17 @@
|
||||
<div
|
||||
v-for="(category, key) in achievements"
|
||||
:key="key"
|
||||
class="row"
|
||||
class="row category-row"
|
||||
>
|
||||
<h2 class="col-12 text-center">
|
||||
<h3 class="col-12 text-center mb-3">
|
||||
{{ $t(key+'Achievs') }}
|
||||
</h2>
|
||||
</h3>
|
||||
<div class="col-12">
|
||||
<div class="row achievements-row justify-content-center">
|
||||
<div
|
||||
v-for="(achievement, achievKey) in category.achievements"
|
||||
v-for="(achievement, achievKey) in achievementsCategory(key, category)"
|
||||
:key="achievKey"
|
||||
class="col-12 col-md-3 text-center"
|
||||
class="achievement-wrapper col text-center"
|
||||
>
|
||||
<div
|
||||
:id="achievKey + '-achievement'"
|
||||
@@ -335,7 +337,7 @@
|
||||
>
|
||||
<div
|
||||
v-if="achievement.optionalCount"
|
||||
class="counter badge badge-info stack-count"
|
||||
class="counter badge badge-pill stack-count"
|
||||
>
|
||||
{{ achievement.optionalCount }}
|
||||
</div>
|
||||
@@ -346,6 +348,18 @@
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="achievementsCategories[key].number > 5"
|
||||
class="btn btn-flat btn-show-more"
|
||||
@click="toggleAchievementsCategory(key)"
|
||||
>
|
||||
{{ achievementsCategories[key].open ?
|
||||
$t('hideAchievements', {category: $t(key+'Achievs')}) :
|
||||
$t('showAllAchievements', {category: $t(key+'Achievs')})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="col-12">
|
||||
<div class="row">
|
||||
@@ -353,31 +367,38 @@
|
||||
v-if="user.achievements.challenges"
|
||||
class="col-12 col-md-6"
|
||||
>
|
||||
<div class="achievement-icon achievement-karaoke"></div>
|
||||
<h2 class="text-center">
|
||||
<div class="achievement-icon achievement-karaoke-2x"></div>
|
||||
<h3 class="text-center mt-2 mb-4">
|
||||
{{ $t('challengesWon') }}
|
||||
</h2>
|
||||
</h3>
|
||||
<div
|
||||
v-for="chal in user.achievements.challenges"
|
||||
:key="chal"
|
||||
class="achievement-list-item"
|
||||
>
|
||||
<span v-markdown="chal"></span>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="user.achievements.quests"
|
||||
class="col-12 col-md-6"
|
||||
>
|
||||
<div class="achievement-icon achievement-alien"></div>
|
||||
<h2 class="text-center">
|
||||
<div class="achievement-icon achievement-alien2x"></div>
|
||||
<h3 class="text-center mt-2 mb-4">
|
||||
{{ $t('questsCompleted') }}
|
||||
</h2>
|
||||
</h3>
|
||||
<div
|
||||
v-for="(value, key) in user.achievements.quests"
|
||||
:key="key"
|
||||
class="achievement-list-item d-flex justify-content-between"
|
||||
>
|
||||
<span>{{ content.quests[key].text() }} ({{ value }})</span>
|
||||
<span>{{ content.quests[key].text() }}</span>
|
||||
<span
|
||||
v-if="value > 1"
|
||||
class="badge badge-pill stack-count"
|
||||
>
|
||||
{{ value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -519,37 +540,71 @@
|
||||
}
|
||||
|
||||
#achievements {
|
||||
.category-row {
|
||||
margin-bottom: 34px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.achievements-row {
|
||||
max-width: 590px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.achievement-wrapper {
|
||||
width: 94px;
|
||||
max-width: 94px;
|
||||
margin-right: 12px;
|
||||
margin-left: 12px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.box {
|
||||
margin: 0 auto;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
padding-top: 1.2em;
|
||||
background: #fff;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-bottom: 48px;
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.box.achievement-unearned {
|
||||
background-color: #edecee;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 1em;
|
||||
background-color: $gray-600;
|
||||
}
|
||||
|
||||
.counter.badge {
|
||||
position: absolute;
|
||||
top: .5em;
|
||||
right: 3em;
|
||||
color: #fff;
|
||||
background-color: #ff944c;
|
||||
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
border-radius: 2em;
|
||||
padding: .5em;
|
||||
top: -0.8em;
|
||||
right: -0.5em;
|
||||
color: $white;
|
||||
background-color: $orange-100;
|
||||
max-height: 24px;
|
||||
}
|
||||
|
||||
.achievement-icon {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.achievement-list-item {
|
||||
padding-top: 11px;
|
||||
padding-bottom: 12px;
|
||||
border-top: 1px solid $gray-500;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 1px solid $gray-500;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin-right: 8px;
|
||||
background: $gray-600;
|
||||
color: $gray-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.achievement {
|
||||
@@ -699,6 +754,7 @@ export default {
|
||||
},
|
||||
selectedPage: 'profile',
|
||||
achievements: {},
|
||||
achievementsCategories: {}, // number, open
|
||||
content: Content,
|
||||
user: undefined,
|
||||
};
|
||||
@@ -783,6 +839,16 @@ export default {
|
||||
// @TODO: this common code should handle the above
|
||||
this.achievements = achievementsLib.getAchievementsForProfile(user);
|
||||
|
||||
const achievementsCategories = {};
|
||||
Object.keys(this.achievements).forEach(category => {
|
||||
achievementsCategories[category] = {
|
||||
open: false,
|
||||
number: Object.keys(this.achievements[category].achievements).length,
|
||||
};
|
||||
});
|
||||
|
||||
this.achievementsCategories = achievementsCategories;
|
||||
|
||||
// @TODO For some reason markdown doesn't seem to be handling numbers or maybe undefined?
|
||||
user.profile.blurb = user.profile.blurb ? `${user.profile.blurb}` : '';
|
||||
|
||||
@@ -906,6 +972,27 @@ export default {
|
||||
showAllocation () {
|
||||
return this.user._id === this.userLoggedIn._id && this.hasClass;
|
||||
},
|
||||
achievementsCategory (categoryKey, category) {
|
||||
const achievementsKeys = Object.keys(category.achievements);
|
||||
|
||||
if (this.achievementsCategories[categoryKey].open === true) {
|
||||
return category.achievements;
|
||||
}
|
||||
|
||||
const fiveAchievements = achievementsKeys.slice(0, 5);
|
||||
|
||||
const categoryAchievements = {};
|
||||
|
||||
fiveAchievements.forEach(key => {
|
||||
categoryAchievements[key] = category.achievements[key];
|
||||
});
|
||||
|
||||
return categoryAchievements;
|
||||
},
|
||||
toggleAchievementsCategory (categoryKey) {
|
||||
const status = this.achievementsCategories[categoryKey].open;
|
||||
this.achievementsCategories[categoryKey].open = !status;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -5,6 +5,7 @@ const CONSTANTS = {
|
||||
EQUIPMENT_DRAWER_STATE: 'equipment-drawer-state',
|
||||
CURRENT_EQUIPMENT_DRAWER_TAB: 'current-equipment-drawer-tab',
|
||||
STABLE_SORT_STATE: 'stable-sort-state',
|
||||
ONBOARDING_PANEL_STATE: 'onboarding-panel-state',
|
||||
},
|
||||
drawerStateValues: {
|
||||
DRAWER_CLOSED: 'drawer-closed',
|
||||
@@ -17,6 +18,9 @@ const CONSTANTS = {
|
||||
savedAppStateValues: {
|
||||
SAVED_APP_STATE: 'saved-app-state',
|
||||
},
|
||||
onboardingPanelValues: {
|
||||
PANEL_OPENED: 'onboarding-panel-opened',
|
||||
},
|
||||
};
|
||||
|
||||
function setLocalSetting (key, value) {
|
||||
|
||||
@@ -63,9 +63,9 @@ export default {
|
||||
streak (val, onClick) {
|
||||
this.notify(`${val}`, 'streak', null, null, onClick, typeof onClick === 'undefined');
|
||||
},
|
||||
text (val, onClick, timeout) {
|
||||
text (val, onClick, timeout, delay) {
|
||||
if (!val) return;
|
||||
this.notify(val, 'info', null, null, onClick, timeout);
|
||||
this.notify(val, 'info', null, null, onClick, timeout, delay);
|
||||
},
|
||||
sign (number) {
|
||||
return getSign(number);
|
||||
@@ -73,7 +73,7 @@ export default {
|
||||
round (number, nDigits) {
|
||||
return round(number, nDigits);
|
||||
},
|
||||
notify (html, type, icon, sign, onClick, timeout = true) {
|
||||
notify (html, type, icon, sign, onClick, timeout = true, delay) {
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: '',
|
||||
text: html,
|
||||
@@ -82,6 +82,7 @@ export default {
|
||||
sign,
|
||||
onClick,
|
||||
timeout,
|
||||
delay,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -30,6 +30,7 @@ describe('shops actions', () => {
|
||||
preferences: {
|
||||
autoEquip: true,
|
||||
},
|
||||
achievements: {},
|
||||
};
|
||||
|
||||
store.state.user.data = user;
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
"onwards": "Onwards!",
|
||||
"levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!",
|
||||
"reachedLevel": "You Reached Level <%= level %>",
|
||||
"gettingStartedDesc": "Let’s create a task, complete it, then check out your rewards. You’ll earn <strong>5 achievements</strong> and <strong class=\"gold-amount\">100 gold</strong> once you’re done!",
|
||||
"onboardingProgress": "<%= percentage %>% progress",
|
||||
"letsGetStarted": "Let's get started!",
|
||||
"viewAchievements": "View Achievements",
|
||||
"earnedAchievement": "You earned an achievement!",
|
||||
"onboardingCompleteDesc": "You earned <strong>5 achievements</strong> and <strong class=\"gold-amount\">100</strong> gold for completing the list.",
|
||||
"showAllAchievements": "Show All <%= category %>",
|
||||
"hideAchievements": "Hide <%= category %>",
|
||||
"achievementLostMasterclasser": "Quest Completionist: Masterclasser Series",
|
||||
"achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!",
|
||||
"achievementLostMasterclasserModalText": "You completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!",
|
||||
@@ -35,6 +43,21 @@
|
||||
"achievementUndeadUndertaker": "Undead Undertaker",
|
||||
"achievementUndeadUndertakerText": "Has tamed all Zombie Mounts.",
|
||||
"achievementUndeadUndertakerModalText": "You tamed all the Zombie Mounts!",
|
||||
"achievementCreatedTask": "Create a Task",
|
||||
"achievementCreatedTaskText": "Created their first task.",
|
||||
"achievementCreatedTaskModalText": "Add a task for something you would like to accomplish this week",
|
||||
"achievementCompletedTask": "Complete a Task",
|
||||
"achievementCompletedTaskText": "Completed their first task.",
|
||||
"achievementCompletedTaskModalText": "Check off any of your tasks to earn rewards",
|
||||
"achievementHatchedPet": "Hatch a Pet",
|
||||
"achievementHatchedPetText": "Hatched their first pet.",
|
||||
"achievementHatchedPetModalText": "Head over to your inventory and try combining a hatching potion and an egg",
|
||||
"achievementFedPet": "Feed a Pet",
|
||||
"achievementFedPetText": "Fed their first pet.",
|
||||
"achievementFedPetModalText": "There are many different types of food, but pets can be picky",
|
||||
"achievementPurchasedEquipment": "Purchase Equipment",
|
||||
"achievementPurchasedEquipmentText": "Purchased their first piece of equipment.",
|
||||
"achievementPurchasedEquipmentModalText": "Equipment is a way to customize your avatar and improve your stats",
|
||||
"achievementPrimedForPainting": "Primed for Painting",
|
||||
"achievementPrimedForPaintingText": "Has collected all White Pets.",
|
||||
"achievementPrimedForPaintingModalText": "You collected all the White Pets!",
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
"noPermissionEditChallenge": "You don't have permissions to edit this challenge",
|
||||
"noPermissionDeleteChallenge": "You don't have permissions to delete this challenge",
|
||||
"noPermissionCloseChallenge": "You don't have permissions to close this challenge",
|
||||
"congratulations": "Congratulations!",
|
||||
"hurray": "Hurray!",
|
||||
"noChallengeOwner": "no owner",
|
||||
"noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account.",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"basicAchievs": "Basic Achievements",
|
||||
"seasonalAchievs": "Seasonal Achievements",
|
||||
"specialAchievs": "Special Achievements",
|
||||
"onboardingAchievs": "Onboarding Achievements",
|
||||
"modalAchievement": "Achievement!",
|
||||
"special": "Special",
|
||||
"site": "Site",
|
||||
@@ -79,6 +80,7 @@
|
||||
"saveAndClose": "Save & Close",
|
||||
"saveAndConfirm": "Save & Confirm",
|
||||
"cancel": "Cancel",
|
||||
"congratulations": "Congratulations!",
|
||||
"ok": "OK",
|
||||
"add": "Add",
|
||||
"undo": "Undo",
|
||||
|
||||
@@ -180,6 +180,35 @@ const basicAchievs = {
|
||||
};
|
||||
Object.assign(achievementsData, basicAchievs);
|
||||
|
||||
const onboardingAchievs = {
|
||||
createdTask: {
|
||||
icon: 'achievement-createdTask',
|
||||
titleKey: 'achievementCreatedTask',
|
||||
textKey: 'achievementCreatedTaskText',
|
||||
},
|
||||
completedTask: {
|
||||
icon: 'achievement-completedTask',
|
||||
titleKey: 'achievementCompletedTask',
|
||||
textKey: 'achievementCompletedTaskText',
|
||||
},
|
||||
hatchedPet: {
|
||||
icon: 'achievement-hatchedPet',
|
||||
titleKey: 'achievementHatchedPet',
|
||||
textKey: 'achievementHatchedPetText',
|
||||
},
|
||||
fedPet: {
|
||||
icon: 'achievement-fedPet',
|
||||
titleKey: 'achievementFedPet',
|
||||
textKey: 'achievementFedPetText',
|
||||
},
|
||||
purchasedEquipment: {
|
||||
icon: 'achievement-purchasedEquipment',
|
||||
titleKey: 'achievementPurchasedEquipment',
|
||||
textKey: 'achievementPurchasedEquipmentText',
|
||||
},
|
||||
};
|
||||
Object.assign(achievementsData, onboardingAchievs);
|
||||
|
||||
const specialAchievs = {
|
||||
contributor: {
|
||||
icon: 'achievement-boot',
|
||||
|
||||
@@ -1,82 +1,29 @@
|
||||
// When using a common module from the website or the server NEVER import the module directly
|
||||
// but access it through `api` (the main common) module,
|
||||
// otherwise you would require the non transpiled version of the file in production.
|
||||
import content from './content/index';
|
||||
|
||||
import * as errors from './libs/errors';
|
||||
import i18n from './i18n';
|
||||
|
||||
import commonErrors from './errors/commonErrorMessages';
|
||||
import apiErrors from './errors/apiErrorMessages';
|
||||
|
||||
// TODO under api.libs.cron?
|
||||
import { shouldDo, daysSince, DAY_MAPPING } from './cron';
|
||||
|
||||
import {
|
||||
MAX_HEALTH,
|
||||
MAX_LEVEL,
|
||||
MAX_STAT_POINTS,
|
||||
MAX_INCENTIVES,
|
||||
TAVERN_ID,
|
||||
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
|
||||
MAX_SUMMARY_SIZE_FOR_GUILDS,
|
||||
MAX_SUMMARY_SIZE_FOR_CHALLENGES,
|
||||
MIN_SHORTNAME_SIZE_FOR_CHALLENGES,
|
||||
SUPPORTED_SOCIAL_NETWORKS,
|
||||
GUILDS_PER_PAGE,
|
||||
PARTY_LIMIT_MEMBERS,
|
||||
CHAT_FLAG_LIMIT_FOR_HIDING,
|
||||
CHAT_FLAG_FROM_MOD,
|
||||
CHAT_FLAG_FROM_SHADOW_MUTE,
|
||||
CHAT_FLAG_LIMIT_FOR_HIDING,
|
||||
GUILDS_PER_PAGE,
|
||||
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
|
||||
MAX_HEALTH,
|
||||
MAX_INCENTIVES,
|
||||
MAX_LEVEL,
|
||||
MAX_STAT_POINTS,
|
||||
MAX_SUMMARY_SIZE_FOR_CHALLENGES,
|
||||
MAX_SUMMARY_SIZE_FOR_GUILDS,
|
||||
MIN_SHORTNAME_SIZE_FOR_CHALLENGES,
|
||||
PARTY_LIMIT_MEMBERS,
|
||||
SUPPORTED_SOCIAL_NETWORKS,
|
||||
TAVERN_ID,
|
||||
} from './constants';
|
||||
|
||||
// TODO under api.libs.statHelpers?
|
||||
import * as statHelpers from './statHelpers';
|
||||
|
||||
import splitWhitespace from './libs/splitWhitespace';
|
||||
|
||||
import refPush from './libs/refPush';
|
||||
|
||||
import planGemLimits from './libs/planGemLimits';
|
||||
|
||||
import preenTodos from './libs/preenTodos';
|
||||
|
||||
import updateStore from './libs/updateStore';
|
||||
|
||||
import inAppRewards from './libs/inAppRewards';
|
||||
|
||||
import setDebuffPotionItems from './libs/setDebuffPotionItems';
|
||||
|
||||
import getDebuffPotionItems from './libs/getDebuffPotionItems';
|
||||
|
||||
import uuid from './libs/uuid';
|
||||
|
||||
import taskDefaults from './libs/taskDefaults';
|
||||
|
||||
import percent from './libs/percent';
|
||||
|
||||
import gold from './libs/gold';
|
||||
|
||||
import silver from './libs/silver';
|
||||
|
||||
import noTags from './libs/noTags';
|
||||
|
||||
import appliedTags from './libs/appliedTags';
|
||||
|
||||
import pickDeep from './libs/pickDeep';
|
||||
|
||||
import content from './content/index';
|
||||
import * as count from './count';
|
||||
|
||||
import statsComputed from './libs/statsComputed';
|
||||
|
||||
import shops from './libs/shops';
|
||||
|
||||
import achievements from './libs/achievements';
|
||||
|
||||
import randomVal from './libs/randomVal';
|
||||
|
||||
import hasClass from './libs/hasClass';
|
||||
|
||||
// TODO under api.libs.cron?
|
||||
import { daysSince, DAY_MAPPING, shouldDo } from './cron';
|
||||
import apiErrors from './errors/apiErrorMessages';
|
||||
import commonErrors from './errors/commonErrorMessages';
|
||||
import autoAllocate from './fns/autoAllocate';
|
||||
import crit from './fns/crit';
|
||||
import handleTwoHanded from './fns/handleTwoHanded';
|
||||
@@ -85,33 +32,59 @@ import randomDrop from './fns/randomDrop';
|
||||
import resetGear from './fns/resetGear';
|
||||
import ultimateGear from './fns/ultimateGear';
|
||||
import updateStats from './fns/updateStats';
|
||||
|
||||
import scoreTask from './ops/scoreTask';
|
||||
import sleep from './ops/sleep';
|
||||
import allocateNow from './ops/stats/allocateNow';
|
||||
import allocate from './ops/stats/allocate';
|
||||
import allocateBulk from './ops/stats/allocateBulk';
|
||||
import i18n from './i18n';
|
||||
import achievements from './libs/achievements';
|
||||
import appliedTags from './libs/appliedTags';
|
||||
import * as errors from './libs/errors';
|
||||
import getDebuffPotionItems from './libs/getDebuffPotionItems';
|
||||
import gold from './libs/gold';
|
||||
import hasClass from './libs/hasClass';
|
||||
import inAppRewards from './libs/inAppRewards';
|
||||
import noTags from './libs/noTags';
|
||||
import * as onboarding from './libs/onboarding';
|
||||
import percent from './libs/percent';
|
||||
import pickDeep from './libs/pickDeep';
|
||||
import planGemLimits from './libs/planGemLimits';
|
||||
import preenTodos from './libs/preenTodos';
|
||||
import randomVal from './libs/randomVal';
|
||||
import refPush from './libs/refPush';
|
||||
import setDebuffPotionItems from './libs/setDebuffPotionItems';
|
||||
import shops from './libs/shops';
|
||||
import silver from './libs/silver';
|
||||
import splitWhitespace from './libs/splitWhitespace';
|
||||
import statsComputed from './libs/statsComputed';
|
||||
import taskDefaults from './libs/taskDefaults';
|
||||
import updateStore from './libs/updateStore';
|
||||
import uuid from './libs/uuid';
|
||||
import blockUser from './ops/blockUser';
|
||||
import buy from './ops/buy/buy';
|
||||
import hatch from './ops/hatch';
|
||||
import feed from './ops/feed';
|
||||
import equip from './ops/equip';
|
||||
import changeClass from './ops/changeClass';
|
||||
import disableClasses from './ops/disableClasses';
|
||||
import readCard from './ops/readCard';
|
||||
import equip from './ops/equip';
|
||||
import feed from './ops/feed';
|
||||
import hatch from './ops/hatch';
|
||||
import markPmsRead from './ops/markPMSRead';
|
||||
import openMysteryItem from './ops/openMysteryItem';
|
||||
import releasePets from './ops/releasePets';
|
||||
import * as pinnedGearUtils from './ops/pinnedGearUtils';
|
||||
import readCard from './ops/readCard';
|
||||
import rebirth from './ops/rebirth';
|
||||
import releaseBoth from './ops/releaseBoth';
|
||||
import releaseMounts from './ops/releaseMounts';
|
||||
import updateTask from './ops/updateTask';
|
||||
import sell from './ops/sell';
|
||||
import unlock from './ops/unlock';
|
||||
import revive from './ops/revive';
|
||||
import rebirth from './ops/rebirth';
|
||||
import blockUser from './ops/blockUser';
|
||||
import releasePets from './ops/releasePets';
|
||||
import reroll from './ops/reroll';
|
||||
import reset from './ops/reset';
|
||||
import markPmsRead from './ops/markPMSRead';
|
||||
import * as pinnedGearUtils from './ops/pinnedGearUtils';
|
||||
import revive from './ops/revive';
|
||||
import scoreTask from './ops/scoreTask';
|
||||
import sell from './ops/sell';
|
||||
import sleep from './ops/sleep';
|
||||
import allocate from './ops/stats/allocate';
|
||||
import allocateBulk from './ops/stats/allocateBulk';
|
||||
import allocateNow from './ops/stats/allocateNow';
|
||||
import unlock from './ops/unlock';
|
||||
import updateTask from './ops/updateTask';
|
||||
// TODO under api.libs.statHelpers?
|
||||
import * as statHelpers from './statHelpers';
|
||||
|
||||
|
||||
const api = {};
|
||||
api.content = content;
|
||||
@@ -162,10 +135,10 @@ api.shops = shops;
|
||||
api.achievements = achievements;
|
||||
api.randomVal = randomVal;
|
||||
api.hasClass = hasClass;
|
||||
api.onboarding = onboarding;
|
||||
api.setDebuffPotionItems = setDebuffPotionItems;
|
||||
api.getDebuffPotionItems = getDebuffPotionItems;
|
||||
|
||||
|
||||
api.fns = {
|
||||
autoAllocate,
|
||||
crit,
|
||||
|
||||
@@ -241,6 +241,18 @@ function _getBasicAchievements (user, language) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function _getOnboardingAchievements (user, language) {
|
||||
const result = {};
|
||||
|
||||
_addSimple(result, user, { path: 'createdTask', language });
|
||||
_addSimple(result, user, { path: 'completedTask', language });
|
||||
_addSimple(result, user, { path: 'hatchedPet', language });
|
||||
_addSimple(result, user, { path: 'fedPet', language });
|
||||
_addSimple(result, user, { path: 'purchasedEquipment', language });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function _getSeasonalAchievements (user, language) {
|
||||
const result = {};
|
||||
|
||||
@@ -321,6 +333,10 @@ achievs.getAchievementsForProfile = function getAchievementsForProfile (user, la
|
||||
label: 'Basic',
|
||||
achievements: _getBasicAchievements(user, language),
|
||||
},
|
||||
onboarding: {
|
||||
label: 'Onboarding',
|
||||
achievements: _getOnboardingAchievements(user, language),
|
||||
},
|
||||
seasonal: {
|
||||
label: 'Seasonal',
|
||||
achievements: _getSeasonalAchievements(user, language),
|
||||
|
||||
31
website/common/script/libs/onboarding.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import moment from 'moment';
|
||||
|
||||
const BEGIN_DATE = moment('2019-12-18');
|
||||
|
||||
// Only users that signed up after the BEGIN DATE should see the onboarding
|
||||
export function hasActiveOnboarding (user) {
|
||||
return BEGIN_DATE.isBefore(user.auth.timestamps.created);
|
||||
}
|
||||
|
||||
export function hasCompletedOnboarding (user) {
|
||||
return (
|
||||
user.achievements.createdTask === true
|
||||
&& user.achievements.completedTask === true
|
||||
&& user.achievements.hatchedPet === true
|
||||
&& user.achievements.fedPet === true
|
||||
&& user.achievements.purchasedEquipment === true
|
||||
);
|
||||
}
|
||||
|
||||
export function onOnboardingComplete (user) {
|
||||
// Award gold
|
||||
user.stats.gp += 100;
|
||||
}
|
||||
|
||||
// Add notification and awards (server)
|
||||
export function checkOnboardingStatus (user) {
|
||||
if (hasActiveOnboarding(user) && hasCompletedOnboarding(user) && user.addNotification) {
|
||||
user.addNotification('ONBOARDING_COMPLETE');
|
||||
onOnboardingComplete(user);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import get from 'lodash/get';
|
||||
import pick from 'lodash/pick';
|
||||
import content from '../../content/index';
|
||||
import splitWhitespace from '../../libs/splitWhitespace';
|
||||
import { checkOnboardingStatus } from '../../libs/onboarding';
|
||||
import {
|
||||
BadRequest,
|
||||
NotAuthorized,
|
||||
@@ -67,6 +68,11 @@ export class BuyMarketGearOperation extends AbstractGoldItemOperation { // eslin
|
||||
message = handleTwoHanded(user, item, undefined, req);
|
||||
}
|
||||
|
||||
if (!user.achievements.purchasedEquipment && user.addAchievement) {
|
||||
user.addAchievement('purchasedEquipment');
|
||||
checkOnboardingStatus(user);
|
||||
}
|
||||
|
||||
removePinnedGearAddPossibleNewOnes(user, `gear.flat.${item.key}`, item.key);
|
||||
|
||||
if (item.last) ultimateGear(user);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
NotFound,
|
||||
} from '../libs/errors';
|
||||
import errorMessage from '../libs/errorMessage';
|
||||
import { checkOnboardingStatus } from '../libs/onboarding';
|
||||
|
||||
function evolve (user, pet, req) {
|
||||
user.items.pets[pet.key] = -1;
|
||||
@@ -88,6 +89,11 @@ export default function feed (user, req = {}) {
|
||||
if (userPets[pet.key] >= 50 && !user.items.mounts[pet.key]) {
|
||||
message = evolve(user, pet, req);
|
||||
}
|
||||
|
||||
if (!user.achievements.fedPet && user.addAchievement) {
|
||||
user.addAchievement('fedPet');
|
||||
checkOnboardingStatus(user);
|
||||
}
|
||||
}
|
||||
|
||||
user.items.food[food.key] -= 1;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
NotFound,
|
||||
} from '../libs/errors';
|
||||
import errorMessage from '../libs/errorMessage';
|
||||
import { checkOnboardingStatus } from '../libs/onboarding';
|
||||
|
||||
export default function hatch (user, req = {}) {
|
||||
const egg = get(req, 'params.egg');
|
||||
@@ -49,6 +50,11 @@ export default function hatch (user, req = {}) {
|
||||
user.markModified('items.hatchingPotions');
|
||||
}
|
||||
|
||||
if (!user.achievements.hatchedPet && user.addAchievement) {
|
||||
user.addAchievement('hatchedPet');
|
||||
checkOnboardingStatus(user);
|
||||
}
|
||||
|
||||
forEach(content.animalColorAchievements, achievement => {
|
||||
if (!user.achievements[achievement.petAchievement]) {
|
||||
const petIndex = findIndex(
|
||||
|
||||
@@ -9,6 +9,7 @@ import i18n from '../i18n';
|
||||
import updateStats from '../fns/updateStats';
|
||||
import crit from '../fns/crit';
|
||||
import statsComputed from '../libs/statsComputed';
|
||||
import { checkOnboardingStatus } from '../libs/onboarding';
|
||||
|
||||
const MAX_TASK_VALUE = 21.27;
|
||||
const MIN_TASK_VALUE = -47.27;
|
||||
@@ -343,5 +344,11 @@ export default function scoreTask (options = {}, req = {}) {
|
||||
|
||||
req.yesterDailyScored = task.yesterDailyScored;
|
||||
updateStats(user, stats, req);
|
||||
|
||||
if (!user.achievements.completedTask && cron === false && direction === 'up' && user.addAchievement) {
|
||||
user.addAchievement('completedTask');
|
||||
checkOnboardingStatus(user);
|
||||
}
|
||||
|
||||
return [delta];
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 971 B |
|
After Width: | Height: | Size: 955 B |