Merge branch 'develop' into release
|
|
@ -13,6 +13,7 @@ if (process.env.NODE_ENV === 'production') {
|
|||
require('./gulp/gulp-newstuff');
|
||||
require('./gulp/gulp-build');
|
||||
require('./gulp/gulp-babelify');
|
||||
require('./gulp/gulp-bootstrap');
|
||||
} else {
|
||||
require('glob').sync('./gulp/gulp-*').forEach(require);
|
||||
require('gulp').task('default', ['test']);
|
||||
|
|
|
|||
|
|
@ -1,47 +1,47 @@
|
|||
import Bluebird from 'Bluebird';
|
||||
|
||||
import { model as Challenges } from '../../website/server/models/challenge';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
async function syncChallengeToMembers (challenges) {
|
||||
let challengSyncPromises = challenges.map(async function (challenge) {
|
||||
let users = await User.find({
|
||||
// _id: '',
|
||||
challenges: challenge._id,
|
||||
}).exec();
|
||||
|
||||
let promises = [];
|
||||
users.forEach(function (user) {
|
||||
promises.push(challenge.syncToUser(user));
|
||||
promises.push(challenge.save());
|
||||
promises.push(user.save());
|
||||
});
|
||||
|
||||
return Bluebird.all(promises);
|
||||
});
|
||||
|
||||
return await Bluebird.all(challengSyncPromises);
|
||||
}
|
||||
|
||||
async function syncChallenges (lastChallengeDate) {
|
||||
let query = {
|
||||
// _id: '',
|
||||
};
|
||||
|
||||
if (lastChallengeDate) {
|
||||
query.createdOn = { $lte: lastChallengeDate };
|
||||
}
|
||||
|
||||
let challengesFound = await Challenges.find(query)
|
||||
.limit(10)
|
||||
.sort('-createdAt')
|
||||
.exec();
|
||||
|
||||
let syncedChallenges = await syncChallengeToMembers(challengesFound)
|
||||
.catch(reason => console.error(reason));
|
||||
let lastChallenge = challengesFound[challengesFound.length - 1];
|
||||
if (lastChallenge) syncChallenges(lastChallenge.createdAt);
|
||||
return syncedChallenges;
|
||||
};
|
||||
|
||||
module.exports = syncChallenges;
|
||||
import Bluebird from 'Bluebird';
|
||||
|
||||
import { model as Challenges } from '../../website/server/models/challenge';
|
||||
import { model as User } from '../../website/server/models/user';
|
||||
|
||||
async function syncChallengeToMembers (challenges) {
|
||||
let challengSyncPromises = challenges.map(async function (challenge) {
|
||||
let users = await User.find({
|
||||
// _id: '',
|
||||
challenges: challenge._id,
|
||||
}).exec();
|
||||
|
||||
let promises = [];
|
||||
users.forEach(function (user) {
|
||||
promises.push(challenge.syncToUser(user));
|
||||
promises.push(challenge.save());
|
||||
promises.push(user.save());
|
||||
});
|
||||
|
||||
return Bluebird.all(promises);
|
||||
});
|
||||
|
||||
return await Bluebird.all(challengSyncPromises);
|
||||
}
|
||||
|
||||
async function syncChallenges (lastChallengeDate) {
|
||||
let query = {
|
||||
// _id: '',
|
||||
};
|
||||
|
||||
if (lastChallengeDate) {
|
||||
query.createdOn = { $lte: lastChallengeDate };
|
||||
}
|
||||
|
||||
let challengesFound = await Challenges.find(query)
|
||||
.limit(10)
|
||||
.sort('-createdAt')
|
||||
.exec();
|
||||
|
||||
let syncedChallenges = await syncChallengeToMembers(challengesFound)
|
||||
.catch(reason => console.error(reason));
|
||||
let lastChallenge = challengesFound[challengesFound.length - 1];
|
||||
if (lastChallenge) syncChallenges(lastChallenge.createdAt);
|
||||
return syncedChallenges;
|
||||
};
|
||||
|
||||
module.exports = syncChallenges;
|
||||
|
|
|
|||
|
|
@ -21,4 +21,4 @@ var processUsers = require('./groups/update-groups-with-group-plans');
|
|||
processUsers()
|
||||
.catch(function (err) {
|
||||
console.log(err)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
109
migrations/users/users-to-test.js
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
var migrationName = 'UserFromProdToTest';
|
||||
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||
var authorUuid = ''; //... own data is done
|
||||
|
||||
/*
|
||||
* This migraition will copy user data from prod to test
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var testConnectionSting = ''; // FOR TEST DATABASE
|
||||
var usersTest = monk(testConnectionSting).get('users', { castIds: false });
|
||||
var groupsTest = monk(testConnectionSting).get('groups', { castIds: false });
|
||||
var challengesTest = monk(testConnectionSting).get('challenges', { castIds: false });
|
||||
var tasksTest = monk(testConnectionSting).get('tasks', { castIds: false });
|
||||
|
||||
var monk2 = require('monk');
|
||||
var liveConnectString = ''; // FOR TEST DATABASE
|
||||
var userLive = monk2(liveConnectString).get('users', { castIds: false });
|
||||
var groupsLive = monk2(liveConnectString).get('groups', { castIds: false });
|
||||
var challengesLive = monk2(liveConnectString).get('challenges', { castIds: false });
|
||||
var tasksLive = monk2(liveConnectString).get('tasks', { castIds: false });
|
||||
|
||||
import uniq from 'lodash/uniq';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
// Variabls for updating
|
||||
let userIds = [
|
||||
'206039c6-24e4-4b9f-8a31-61cbb9aa3f66',
|
||||
];
|
||||
|
||||
let groupIds = [];
|
||||
let challengeIds = [];
|
||||
let tasksIds = [];
|
||||
|
||||
async function processUsers () {
|
||||
let userPromises = [];
|
||||
//{_id: {$in: userIds}}
|
||||
|
||||
return userLive.find({guilds: 'b0764d64-8276-45a1-afa5-5ca9a5c64ca0'})
|
||||
.each((user, {close, pause, resume}) => {
|
||||
if (user.guilds.length > 0) groupIds = groupIds.concat(user.guilds);
|
||||
if (user.party._id) groupIds.push(user.party._id);
|
||||
if (user.challenges.length > 0) challengeIds = challengeIds.concat(user.challenges);
|
||||
if (user.tasksOrder.rewards.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.rewards);
|
||||
if (user.tasksOrder.todos.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.todos);
|
||||
if (user.tasksOrder.dailys.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.dailys);
|
||||
if (user.tasksOrder.habits.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.habits);
|
||||
|
||||
let userPromise = usersTest.update({'_id': user._id}, user, {upsert:true});
|
||||
userPromises.push(userPromise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(userPromises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done User");
|
||||
});
|
||||
}
|
||||
|
||||
function processGroups () {
|
||||
let promises = [];
|
||||
let groupsToQuery = uniq(groupIds);
|
||||
return groupsLive.find({_id: {$in: groupsToQuery}})
|
||||
.each((group, {close, pause, resume}) => {
|
||||
let promise = groupsTest.update({_id: group._id}, group, {upsert:true});
|
||||
promises.push(promise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done Group");
|
||||
});
|
||||
}
|
||||
|
||||
function processChallenges () {
|
||||
let promises = [];
|
||||
let challengesToQuery = uniq(challengeIds);
|
||||
return challengesLive.find({_id: {$in: challengesToQuery}})
|
||||
.each((challenge, {close, pause, resume}) => {
|
||||
let promise = challengesTest.update({_id: challenge._id}, challenge, {upsert:true});
|
||||
promises.push(promise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done Challenge");
|
||||
});
|
||||
}
|
||||
|
||||
function processTasks () {
|
||||
let promises = [];
|
||||
let tasksToQuery = uniq(tasksIds);
|
||||
return tasksLive.find({_id: {$in: tasksToQuery}})
|
||||
.each((task, {close, pause, resume}) => {
|
||||
let promise = tasksTest.update({_id: task._id}, task, {upsert:true});
|
||||
promises.push(promise);
|
||||
}).then(() => {
|
||||
return Bluebird.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
console.log("Done Tasks");
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = async function prodToTest () {
|
||||
await processUsers();
|
||||
await processGroups();
|
||||
await processChallenges();
|
||||
await processTasks();
|
||||
};
|
||||
701
npm-shrinkwrap.json
generated
|
|
@ -4208,700 +4208,6 @@
|
|||
"from": "fs.realpath@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "1.1.1",
|
||||
"from": "fsevents@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.1.0",
|
||||
"from": "abbrev@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"from": "ansi-regex@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz"
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "2.2.1",
|
||||
"from": "ansi-styles@>=2.2.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.1.1",
|
||||
"from": "aproba@>=1.0.3 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"version": "1.1.2",
|
||||
"from": "are-we-there-yet@>=1.1.2 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.2.3",
|
||||
"from": "asn1@>=0.2.3 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "0.2.0",
|
||||
"from": "assert-plus@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"from": "asynckit@>=0.4.0 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"aws-sign2": {
|
||||
"version": "0.6.0",
|
||||
"from": "aws-sign2@>=0.6.0 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"aws4": {
|
||||
"version": "1.6.0",
|
||||
"from": "aws4@>=1.2.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "0.4.2",
|
||||
"from": "balanced-match@>=0.4.1 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz"
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.1",
|
||||
"from": "bcrypt-pbkdf@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"block-stream": {
|
||||
"version": "0.0.9",
|
||||
"from": "block-stream@*",
|
||||
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz"
|
||||
},
|
||||
"boom": {
|
||||
"version": "2.10.1",
|
||||
"from": "boom@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.6",
|
||||
"from": "brace-expansion@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz"
|
||||
},
|
||||
"buffer-shims": {
|
||||
"version": "1.0.0",
|
||||
"from": "buffer-shims@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz"
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.11.0",
|
||||
"from": "caseless@>=0.11.0 <0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"from": "chalk@>=1.1.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"from": "code-point-at@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz"
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.5",
|
||||
"from": "combined-stream@>=1.0.5 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz"
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.9.0",
|
||||
"from": "commander@>=2.9.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"from": "concat-map@0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"from": "console-control-strings@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz"
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"from": "core-util-is@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "2.0.5",
|
||||
"from": "cryptiles@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"from": "dashdash@>=1.12.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"from": "assert-plus@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.2.0",
|
||||
"from": "debug@>=2.2.0 <2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.4.1",
|
||||
"from": "deep-extend@>=0.4.0 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"from": "delayed-stream@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
||||
},
|
||||
"delegates": {
|
||||
"version": "1.0.0",
|
||||
"from": "delegates@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
"version": "0.1.1",
|
||||
"from": "ecc-jsbn@>=0.1.1 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"from": "escape-string-regexp@>=1.0.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.0",
|
||||
"from": "extend@>=3.0.0 <3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"extsprintf": {
|
||||
"version": "1.0.2",
|
||||
"from": "extsprintf@1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"from": "forever-agent@>=0.6.1 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.1.2",
|
||||
"from": "form-data@>=2.1.1 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"from": "fs.realpath@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||
},
|
||||
"fstream": {
|
||||
"version": "1.0.10",
|
||||
"from": "fstream@>=1.0.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz"
|
||||
},
|
||||
"fstream-ignore": {
|
||||
"version": "1.0.5",
|
||||
"from": "fstream-ignore@>=1.0.5 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.3",
|
||||
"from": "gauge@>=2.7.1 <2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "2.0.0",
|
||||
"from": "generate-function@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"generate-object-property": {
|
||||
"version": "1.2.0",
|
||||
"from": "generate-object-property@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"getpass": {
|
||||
"version": "0.1.6",
|
||||
"from": "getpass@>=0.1.1 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"from": "assert-plus@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.1",
|
||||
"from": "glob@>=7.0.5 <8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz"
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"from": "graceful-fs@>=4.1.2 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz"
|
||||
},
|
||||
"graceful-readlink": {
|
||||
"version": "1.0.1",
|
||||
"from": "graceful-readlink@>=1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "2.0.6",
|
||||
"from": "har-validator@>=2.0.6 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"has-ansi": {
|
||||
"version": "2.0.0",
|
||||
"from": "has-ansi@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"from": "has-unicode@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"hawk": {
|
||||
"version": "3.1.3",
|
||||
"from": "hawk@>=3.1.3 <3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"hoek": {
|
||||
"version": "2.16.3",
|
||||
"from": "hoek@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.1.1",
|
||||
"from": "http-signature@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"from": "inflight@>=1.0.4 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"from": "inherits@>=2.0.1 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.4",
|
||||
"from": "ini@>=1.3.0 <1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"from": "is-fullwidth-code-point@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz"
|
||||
},
|
||||
"is-my-json-valid": {
|
||||
"version": "2.15.0",
|
||||
"from": "is-my-json-valid@>=2.12.4 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"is-property": {
|
||||
"version": "1.0.2",
|
||||
"from": "is-property@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"from": "is-typedarray@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"from": "isarray@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"from": "isstream@>=0.1.2 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"jodid25519": {
|
||||
"version": "1.0.2",
|
||||
"from": "jodid25519@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"jsbn": {
|
||||
"version": "0.1.1",
|
||||
"from": "jsbn@>=0.1.0 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"json-schema": {
|
||||
"version": "0.2.3",
|
||||
"from": "json-schema@0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"from": "json-stringify-safe@>=5.0.1 <5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"jsonpointer": {
|
||||
"version": "4.0.1",
|
||||
"from": "jsonpointer@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.3.1",
|
||||
"from": "jsprim@>=1.2.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.26.0",
|
||||
"from": "mime-db@>=1.26.0 <1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz"
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.14",
|
||||
"from": "mime-types@>=2.1.7 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz"
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.3",
|
||||
"from": "minimatch@>=3.0.2 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz"
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"from": "minimist@0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"from": "mkdirp@>=0.5.1 <0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
|
||||
},
|
||||
"ms": {
|
||||
"version": "0.7.1",
|
||||
"from": "ms@0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.6.33",
|
||||
"from": "node-pre-gyp@>=0.6.29 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.6",
|
||||
"from": "nopt@>=3.0.6 <3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "4.0.2",
|
||||
"from": "npmlog@>=4.0.1 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"from": "number-is-nan@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz"
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.8.2",
|
||||
"from": "oauth-sign@>=0.8.1 <0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"from": "object-assign@>=4.1.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"from": "once@>=1.3.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"from": "path-is-absolute@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
|
||||
},
|
||||
"pinkie": {
|
||||
"version": "2.0.4",
|
||||
"from": "pinkie@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"pinkie-promise": {
|
||||
"version": "2.0.1",
|
||||
"from": "pinkie-promise@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"from": "process-nextick-args@>=1.0.6 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz"
|
||||
},
|
||||
"punycode": {
|
||||
"version": "1.4.1",
|
||||
"from": "punycode@>=1.4.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.3.1",
|
||||
"from": "qs@>=6.3.0 <6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.3.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.1.7",
|
||||
"from": "rc@>=1.1.6 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.1.7.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"from": "minimist@>=1.2.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.2.2",
|
||||
"from": "readable-stream@>=2.0.0 <3.0.0||>=1.1.13 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"request": {
|
||||
"version": "2.79.0",
|
||||
"from": "request@>=2.79.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.5.4",
|
||||
"from": "rimraf@>=2.5.4 <2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz"
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.3.0",
|
||||
"from": "semver@>=5.3.0 <5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"from": "set-blocking@>=2.0.0 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"from": "signal-exit@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"sntp": {
|
||||
"version": "1.0.9",
|
||||
"from": "sntp@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"sshpk": {
|
||||
"version": "1.10.2",
|
||||
"from": "sshpk@>=1.7.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.2.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"from": "assert-plus@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"from": "string_decoder@>=0.10.0 <0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"from": "string-width@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz"
|
||||
},
|
||||
"stringstream": {
|
||||
"version": "0.0.5",
|
||||
"from": "stringstream@>=0.0.4 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"from": "strip-ansi@>=3.0.1 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"from": "strip-json-comments@>=2.0.1 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "2.0.0",
|
||||
"from": "supports-color@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "2.2.1",
|
||||
"from": "tar@>=2.2.1 <2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz"
|
||||
},
|
||||
"tar-pack": {
|
||||
"version": "3.3.0",
|
||||
"from": "tar-pack@>=3.3.0 <3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.3.0.tgz",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"once": {
|
||||
"version": "1.3.3",
|
||||
"from": "once@>=1.3.3 <1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.1.5",
|
||||
"from": "readable-stream@>=2.1.4 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.3.2",
|
||||
"from": "tough-cookie@>=2.3.0 <2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.4.3",
|
||||
"from": "tunnel-agent@>=0.4.1 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"from": "tweetnacl@>=0.14.0 <0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"uid-number": {
|
||||
"version": "0.0.6",
|
||||
"from": "uid-number@>=0.0.6 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"from": "util-deprecate@>=1.0.1 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.0.1",
|
||||
"from": "uuid@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.3.6",
|
||||
"from": "verror@1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.0",
|
||||
"from": "wide-align@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz",
|
||||
"optional": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"from": "wrappy@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
"from": "xtend@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"fstream": {
|
||||
"version": "1.0.11",
|
||||
"from": "fstream@>=1.0.2 <2.0.0",
|
||||
|
|
@ -6128,6 +5434,11 @@
|
|||
"from": "he@>=1.1.0 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz"
|
||||
},
|
||||
"hellojs": {
|
||||
"version": "1.15.1",
|
||||
"from": "hellojs@>=1.15.1 <2.0.0",
|
||||
"resolved": "http://registry.npmjs.org/hellojs/-/hellojs-1.15.1.tgz"
|
||||
},
|
||||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"from": "hmac-drbg@>=1.0.0 <2.0.0",
|
||||
|
|
@ -7717,7 +7028,7 @@
|
|||
"lazy-debug-legacy": {
|
||||
"version": "0.0.1",
|
||||
"from": "lazy-debug-legacy@>=0.0.0 <0.1.0",
|
||||
"resolved": "http://registry.npmjs.org/lazy-debug-legacy/-/lazy-debug-legacy-0.0.1.tgz"
|
||||
"resolved": "https://registry.npmjs.org/lazy-debug-legacy/-/lazy-debug-legacy-0.0.1.tgz"
|
||||
},
|
||||
"lazy-req": {
|
||||
"version": "1.1.0",
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@
|
|||
"gulp-uglify": "^1.4.2",
|
||||
"gulp.spritesmith": "^4.1.0",
|
||||
"habitica-markdown": "^1.3.0",
|
||||
"hellojs": "^1.15.1",
|
||||
"html-webpack-plugin": "^2.8.1",
|
||||
"image-size": "~0.3.2",
|
||||
"in-app-purchase": "^1.1.6",
|
||||
|
|
|
|||
|
|
@ -79,6 +79,13 @@ describe('cron', () => {
|
|||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||
});
|
||||
|
||||
it('resets plan.gemsBought on a new month if user does not have purchased.plan.dateUpdated', () => {
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
user.purchased.plan.dateUpdated = undefined;
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||
});
|
||||
|
||||
it('does not reset plan.gemsBought within the month', () => {
|
||||
let clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').unix());
|
||||
user.purchased.plan.dateUpdated = moment().startOf('month').toDate();
|
||||
|
|
|
|||
|
|
@ -596,7 +596,7 @@ describe('Purchasing a group plan for group', () => {
|
|||
|
||||
let updatedUser = await User.findById(recipient._id).exec();
|
||||
|
||||
expect(updatedUser.purchased.plan.extraMonths).to.within(7, 8);
|
||||
expect(updatedUser.purchased.plan.extraMonths).to.within(7, 9);
|
||||
});
|
||||
|
||||
it('adds months to members with existing recurring subscription and ignores existing negative extraMonths', async () => {
|
||||
|
|
|
|||
|
|
@ -1,63 +1,63 @@
|
|||
describe('Group Tasks Meta Actions Controller', () => {
|
||||
let rootScope, scope, user, userSerivce;
|
||||
|
||||
beforeEach(() => {
|
||||
module(function($provide) {
|
||||
$provide.value('User', {});
|
||||
});
|
||||
|
||||
inject(($rootScope, $controller) => {
|
||||
rootScope = $rootScope;
|
||||
|
||||
user = specHelper.newUser();
|
||||
user._id = "unique-user-id";
|
||||
userSerivce = {user: user};
|
||||
|
||||
scope = $rootScope.$new();
|
||||
|
||||
scope.task = {
|
||||
group: {
|
||||
assignedUsers: [],
|
||||
approval: {
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
scope.task._edit = angular.copy(scope.task);
|
||||
|
||||
$controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleTaskRequiresApproval', function () {
|
||||
it('toggles task approval required field from false to true', function () {
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.true;
|
||||
});
|
||||
|
||||
it('toggles task approval required field from true to false', function () {
|
||||
scope.task._edit.group.approval.required = true;
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('assign events', function () {
|
||||
it('adds a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
rootScope.$broadcast('addedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.contain(testId);
|
||||
});
|
||||
|
||||
it('removes a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
scope.task.group.assignedUsers.push(testId);
|
||||
scope.task._edit.group.assignedUsers.push(testId);
|
||||
rootScope.$broadcast('removedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.not.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.not.contain(testId);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Group Tasks Meta Actions Controller', () => {
|
||||
let rootScope, scope, user, userSerivce;
|
||||
|
||||
beforeEach(() => {
|
||||
module(function($provide) {
|
||||
$provide.value('User', {});
|
||||
});
|
||||
|
||||
inject(($rootScope, $controller) => {
|
||||
rootScope = $rootScope;
|
||||
|
||||
user = specHelper.newUser();
|
||||
user._id = "unique-user-id";
|
||||
userSerivce = {user: user};
|
||||
|
||||
scope = $rootScope.$new();
|
||||
|
||||
scope.task = {
|
||||
group: {
|
||||
assignedUsers: [],
|
||||
approval: {
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
scope.task._edit = angular.copy(scope.task);
|
||||
|
||||
$controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleTaskRequiresApproval', function () {
|
||||
it('toggles task approval required field from false to true', function () {
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.true;
|
||||
});
|
||||
|
||||
it('toggles task approval required field from true to false', function () {
|
||||
scope.task._edit.group.approval.required = true;
|
||||
scope.toggleTaskRequiresApproval();
|
||||
expect(scope.task._edit.group.approval.required).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('assign events', function () {
|
||||
it('adds a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
rootScope.$broadcast('addedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.contain(testId);
|
||||
});
|
||||
|
||||
it('removes a group member to assigned users on "addedGroupMember" event ', () => {
|
||||
var testId = 'test-id';
|
||||
scope.task.group.assignedUsers.push(testId);
|
||||
scope.task._edit.group.assignedUsers.push(testId);
|
||||
rootScope.$broadcast('removedGroupMember', testId);
|
||||
expect(scope.task.group.assignedUsers).to.not.contain(testId);
|
||||
expect(scope.task._edit.group.assignedUsers).to.not.contain(testId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ describe('tasks actions', () => {
|
|||
});
|
||||
|
||||
describe('fetchUserTasks', () => {
|
||||
it('fetches user tasks', async () => {
|
||||
xit('fetches user tasks', async () => {
|
||||
expect(store.state.tasks.loadingStatus).to.equal('NOT_LOADED');
|
||||
const tasks = [{_id: 1}];
|
||||
sandbox.stub(axios, 'get').withArgs('/api/v3/tasks/user').returns(Promise.resolve({data: {data: tasks}}));
|
||||
|
|
@ -36,7 +36,7 @@ describe('tasks actions', () => {
|
|||
expect(store.state.tasks.loadingStatus).to.equal('LOADED');
|
||||
});
|
||||
|
||||
it('can reload tasks if forceLoad is true', async () => {
|
||||
xit('can reload tasks if forceLoad is true', async () => {
|
||||
store.state.tasks = {
|
||||
loadingStatus: 'LOADED',
|
||||
data: [{_id: 1}],
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import axios from 'axios';
|
||||
import generateStore from 'client/store';
|
||||
|
||||
describe('tasks actions', () => {
|
||||
describe('user actions', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -91,8 +91,8 @@
|
|||
height: 10.5em
|
||||
width: 100%
|
||||
|
||||
// Covers avatars, health bar at 1005-768. Fix:
|
||||
@media (max-width: 1005px) and (min-width: 768px)
|
||||
// Covers avatars, health bar at 1005-768. Fix:
|
||||
@media (max-width: 1005px) and (min-width: 768px)
|
||||
margin-top: 2.8em;
|
||||
|
||||
// this is a wrapper for avatars in the header
|
||||
|
|
|
|||
|
|
@ -1,43 +1,43 @@
|
|||
"use strict";
|
||||
|
||||
/*
|
||||
A controller to manage the Group Plans page
|
||||
*/
|
||||
|
||||
angular.module('habitrpg')
|
||||
.controller("GroupPlansCtrl", ['$scope', '$window', 'Groups', 'Payments',
|
||||
function($scope, $window, Groups, Payments) {
|
||||
$scope.PAGES = {
|
||||
BENEFITS: 'benefits',
|
||||
CREATE_GROUP: 'create-group',
|
||||
UPGRADE_GROUP: 'upgrade-group',
|
||||
};
|
||||
$scope.activePage = $scope.PAGES.BENEFITS;
|
||||
$scope.newGroup = {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
};
|
||||
$scope.PAYMENTS = {
|
||||
AMAZON: 'amazon',
|
||||
STRIPE: 'stripe',
|
||||
};
|
||||
|
||||
$scope.changePage = function (page) {
|
||||
$scope.activePage = page;
|
||||
$window.scrollTo(0, 0);
|
||||
};
|
||||
|
||||
$scope.newGroupIsReady = function () {
|
||||
return !!$scope.newGroup.name;
|
||||
};
|
||||
|
||||
$scope.createGroup = function () {
|
||||
$scope.changePage($scope.PAGES.UPGRADE_GROUP);
|
||||
};
|
||||
|
||||
$scope.upgradeGroup = function (paymentType) {
|
||||
var subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
|
||||
if (paymentType === $scope.PAYMENTS.STRIPE) Payments.showStripe({subscription: subscriptionKey, coupon: null, groupToCreate: $scope.newGroup});
|
||||
if (paymentType === $scope.PAYMENTS.AMAZON) Payments.amazonPayments.init({type: 'subscription', subscription: subscriptionKey, coupon: null, groupToCreate: $scope.newGroup});
|
||||
};
|
||||
}]);
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
A controller to manage the Group Plans page
|
||||
*/
|
||||
|
||||
angular.module('habitrpg')
|
||||
.controller("GroupPlansCtrl", ['$scope', '$window', 'Groups', 'Payments',
|
||||
function($scope, $window, Groups, Payments) {
|
||||
$scope.PAGES = {
|
||||
BENEFITS: 'benefits',
|
||||
CREATE_GROUP: 'create-group',
|
||||
UPGRADE_GROUP: 'upgrade-group',
|
||||
};
|
||||
$scope.activePage = $scope.PAGES.BENEFITS;
|
||||
$scope.newGroup = {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
};
|
||||
$scope.PAYMENTS = {
|
||||
AMAZON: 'amazon',
|
||||
STRIPE: 'stripe',
|
||||
};
|
||||
|
||||
$scope.changePage = function (page) {
|
||||
$scope.activePage = page;
|
||||
$window.scrollTo(0, 0);
|
||||
};
|
||||
|
||||
$scope.newGroupIsReady = function () {
|
||||
return !!$scope.newGroup.name;
|
||||
};
|
||||
|
||||
$scope.createGroup = function () {
|
||||
$scope.changePage($scope.PAGES.UPGRADE_GROUP);
|
||||
};
|
||||
|
||||
$scope.upgradeGroup = function (paymentType) {
|
||||
var subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
|
||||
if (paymentType === $scope.PAYMENTS.STRIPE) Payments.showStripe({subscription: subscriptionKey, coupon: null, groupToCreate: $scope.newGroup});
|
||||
if (paymentType === $scope.PAYMENTS.AMAZON) Payments.amazonPayments.init({type: 'subscription', subscription: subscriptionKey, coupon: null, groupToCreate: $scope.newGroup});
|
||||
};
|
||||
}]);
|
||||
|
|
|
|||
|
|
@ -326,11 +326,23 @@ function($rootScope, User, $http, Content) {
|
|||
paymentMethod = paymentMethod.toLowerCase();
|
||||
}
|
||||
|
||||
var cancelUrl = '/' + paymentMethod + '/subscribe/cancel?_id=' + User.user._id + '&apiToken=' + User.settings.auth.apiToken;
|
||||
var queryParams = {
|
||||
_id: User.user._id,
|
||||
apiToken: User.settings.auth.apiToken,
|
||||
noRedirect: true,
|
||||
};
|
||||
|
||||
if (group) {
|
||||
cancelUrl += '&groupId=' + group._id;
|
||||
queryParams.groupId = group._id;
|
||||
}
|
||||
window.location.href = cancelUrl;
|
||||
|
||||
var cancelUrl = '/' + paymentMethod + '/subscribe/cancel?' + $.param(queryParams);
|
||||
|
||||
$http.get(cancelUrl)
|
||||
.then(function (success) {
|
||||
alert(window.evn.t('paypalCanceled'));
|
||||
window.location.href = '/';
|
||||
});
|
||||
}
|
||||
|
||||
Payments.encodeGift = function(uuid, gift) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
#Running
|
||||
- Open a terminal and type `npm run client:dev`
|
||||
- Open a second terminal and type `npm start`
|
||||
|
||||
#Preparation Reading
|
||||
- Vue 2 (https://vuejs.org)
|
||||
|
||||
- Webpack (https://webpack.github.io/) is the build system and it includes plugins for code transformation, right now we have: BabelJS for ES6 transpilation, eslint for code style, less and postcss for css compilation. The code comes from https://github.com/vuejs-templates/webpack which is a Webpack template for Vue, with some small modifications to adapt it to our use case. Docs http://vuejs-templates.github.io/webpack/
|
||||
|
||||
- We’re using `.vue` files that make it possible to have HTML, JS and CSS for each component together in a single location. They’re implemented as a webpack plugin and the docs can be found here http://vue-loader.vuejs.org/en/
|
||||
|
||||
- SemanticUI is the UI framework http://semantic-ui.com/. So far I’ve only used the CSS part, it also has JS plugins but I’ve yet to use them. It supports theming so if it’s not too difficult we’ll want to customize the base theme with our own styles instead of writing CSS rules to override the original styling.
|
||||
|
||||
The code is in `/website/client`. We’re using something very similar to Vuex (equivalent of React’s Redux) for state management http://vuex.vuejs.org/en/index.html
|
||||
|
||||
The API is almost the same except that we don’t use mutations but only actions because it would make it difficult to work with common code
|
||||
|
||||
The project is developed directly in the `develop` branch as long as we’ll be able to avoid splitting it into a different branch.
|
||||
|
||||
So far most of the work has been on the template, so there’s no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it’s basically a Flux implementation: there’s a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
|
||||
#Running
|
||||
- Open a terminal and type `npm run client:dev`
|
||||
- Open a second terminal and type `npm start`
|
||||
|
||||
#Preparation Reading
|
||||
- Vue 2 (https://vuejs.org)
|
||||
|
||||
- Webpack (https://webpack.github.io/) is the build system and it includes plugins for code transformation, right now we have: BabelJS for ES6 transpilation, eslint for code style, less and postcss for css compilation. The code comes from https://github.com/vuejs-templates/webpack which is a Webpack template for Vue, with some small modifications to adapt it to our use case. Docs http://vuejs-templates.github.io/webpack/
|
||||
|
||||
- We’re using `.vue` files that make it possible to have HTML, JS and CSS for each component together in a single location. They’re implemented as a webpack plugin and the docs can be found here http://vue-loader.vuejs.org/en/
|
||||
|
||||
- SemanticUI is the UI framework http://semantic-ui.com/. So far I’ve only used the CSS part, it also has JS plugins but I’ve yet to use them. It supports theming so if it’s not too difficult we’ll want to customize the base theme with our own styles instead of writing CSS rules to override the original styling.
|
||||
|
||||
The code is in `/website/client`. We’re using something very similar to Vuex (equivalent of React’s Redux) for state management http://vuex.vuejs.org/en/index.html
|
||||
|
||||
The API is almost the same except that we don’t use mutations but only actions because it would make it difficult to work with common code
|
||||
|
||||
The project is developed directly in the `develop` branch as long as we’ll be able to avoid splitting it into a different branch.
|
||||
|
||||
So far most of the work has been on the template, so there’s no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it’s basically a Flux implementation: there’s a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
<!-- Entry point component for the entire app -->
|
||||
|
||||
<template lang="pug">
|
||||
#app
|
||||
app-menu
|
||||
.container-fluid
|
||||
app-header
|
||||
router-view
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
#loading-screen.h-100.w-100.d-flex.justify-content-center.align-items-center(v-if="!isUserLoaded")
|
||||
p Loading...
|
||||
template(v-else)
|
||||
app-menu
|
||||
.container-fluid
|
||||
app-header
|
||||
router-view
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppMenu from './components/appMenu';
|
||||
import AppHeader from './components/appHeader';
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
|
|
@ -18,9 +22,38 @@ export default {
|
|||
AppMenu,
|
||||
AppHeader,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isUserLoaded: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isUserLoggedIn']),
|
||||
isStaticPage () {
|
||||
return this.$route.meta.requiresLogin === false ? true : false;
|
||||
},
|
||||
},
|
||||
created () {
|
||||
// Setup listener for title
|
||||
this.$store.watch(state => state.title, (title) => {
|
||||
document.title = title;
|
||||
});
|
||||
|
||||
if (this.isUserLoggedIn && !this.isStaticPage) {
|
||||
// Load the user and the user tasks
|
||||
Promise.all([
|
||||
this.$store.dispatch('user:fetch'),
|
||||
this.$store.dispatch('tasks:fetchUserTasks'),
|
||||
]).then(() => {
|
||||
this.isUserLoaded = true;
|
||||
}).catch((err) => {
|
||||
console.error('Impossible to fetch user. Clean up localStorage and refresh.', err); // eslint-disable-line no-console
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style src="bootstrap/scss/bootstrap.scss" lang="scss"></style>
|
||||
<style src="assets/scss/index.scss" lang="scss"></style>
|
||||
<style src="assets/css/index.css"></style>
|
||||
<style src="assets/css/index.css"></style>
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
BIN
website/client/assets/images/auth/repeatinghill_leftslope.png
Normal file
|
After Width: | Height: | Size: 894 B |
BIN
website/client/assets/images/auth/repeatinghill_rightslope.png
Normal file
|
After Width: | Height: | Size: 882 B |
BIN
website/client/assets/images/auth/seamless_mountains_demo.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
39
website/client/assets/scss/categories.scss
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
.category-box {
|
||||
padding: 1em;
|
||||
max-width: 400px;
|
||||
position: absolute;
|
||||
top: -480px;
|
||||
padding: 2em;
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1);
|
||||
}
|
||||
|
||||
.category-label {
|
||||
min-width: 100px;
|
||||
border-radius: 100px;
|
||||
background-color: $gray-600;
|
||||
padding: .5em;
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
.category-select {
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.category-select:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.category-wrap {
|
||||
margin-top: .5em;
|
||||
}
|
||||
9
website/client/assets/scss/dragdrop.scss
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[draggable] {
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
/* (Optional) Apply a "closed-hand" cursor during drag operation. */
|
||||
[draggable]:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
.svg-icon {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
display: block;
|
||||
stroke-width: 0;
|
||||
stroke: currentColor;
|
||||
fill: currentColor;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.svg-icon * {
|
||||
transition: none !important;
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
* {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,4 +20,7 @@
|
|||
@import './popover';
|
||||
@import './item';
|
||||
@import './stats';
|
||||
@import './icon';
|
||||
@import './icon';
|
||||
@import './task';
|
||||
@import './categories';
|
||||
@import './dragdrop';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ html {
|
|||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
height: calc(100% - 56px); // 56px is the menu
|
||||
background: $gray-700;
|
||||
}
|
||||
|
||||
|
|
|
|||
114
website/client/assets/scss/task.scss
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
.task {
|
||||
// for editing rewards or when a task is created
|
||||
&-purple {
|
||||
background: $purple-300;
|
||||
}
|
||||
|
||||
&-worst {
|
||||
background: $maroon-100;
|
||||
&-control-habit {
|
||||
background: darken($maroon-100, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $maroon-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-worse {
|
||||
background: $red-100;
|
||||
&-control-habit {
|
||||
background: darken($red-100, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $red-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-bad {
|
||||
background: $orange-100;
|
||||
&-control-habit {
|
||||
background: darken($orange-100, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $orange-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-neutral {
|
||||
background: $yellow-50;
|
||||
&-control-habit {
|
||||
background: darken($yellow-50, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $yellow-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-good {
|
||||
background: $green-10;
|
||||
&-control-habit {
|
||||
background: darken($green-10, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $green-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-better {
|
||||
background: $blue-50;
|
||||
&-control-habit {
|
||||
background: darken($blue-50, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $blue-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-best {
|
||||
background: $teal-50;
|
||||
&-control-habit {
|
||||
background: darken($teal-50, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $teal-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-reward {
|
||||
background: rgba($yellow-500, 0.26);
|
||||
}
|
||||
|
||||
&-daily-todo-disabled {
|
||||
background: $gray-500;
|
||||
|
||||
&-control {
|
||||
background: $gray-400;
|
||||
color: $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
&-daily-todo-content-disabled {
|
||||
background: $gray-600;
|
||||
|
||||
* {
|
||||
color: $gray-300 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-habit-disabled {
|
||||
background: $gray-700;
|
||||
color: rgba(0, 0, 0, 0.12);
|
||||
|
||||
&-control {
|
||||
color: rgba(0, 0, 0, 0.12) !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
website/client/assets/svg/calendar.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
|
||||
<path fill-rule="evenodd" d="M2 12h10V6H2v6zM12 2V0h-2v2H4V0H2v2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 227 B |
3
website/client/assets/svg/challenge.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="12" viewBox="0 0 14 12">
|
||||
<path fill-rule="evenodd" d="M10 6.306L2.582 7.542A.5.5 0 0 1 2 7.05V2.591a.5.5 0 0 1 .582-.493L10 3.334v2.972zm2.329-4.612l-.024-.004c-.007-.002-.012-.007-.02-.009-.017-.005-.035.001-.052-.003L2.329.028A2 2 0 0 0 0 2v5.64a2 2 0 0 0 2.329 1.972l7.056-1.176-.525 2.1a1.175 1.175 0 0 0 2.28.57l.772-3.09.417-.07A2 2 0 0 0 14 5.971V3.667a2 2 0 0 0-1.671-1.973z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 456 B |
3
website/client/assets/svg/check.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="10" viewBox="0 0 13 10">
|
||||
<path fill-rule="evenodd" d="M4.662 9.832c-.312 0-.61-.123-.831-.344L0 5.657l1.662-1.662 2.934 2.934L10.534 0l1.785 1.529-6.764 7.893a1.182 1.182 0 0 1-.848.409l-.045.001"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 268 B |
3
website/client/assets/svg/daily.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="20" viewBox="0 0 24 20">
|
||||
<path fill="#C3C0C7" fill-rule="evenodd" d="M13 16h2v-2h-2v2zm-4 0h2v-2H9v2zm-4 0h2v-2H5v2zm12-4h2v-2h-2v2zm-4 0h2v-2h-2v2zm-4 0h2v-2H9v2zm13-4H2v8a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8zm2-2v10a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1V0h2v2h10V0h2v2h1a4 4 0 0 1 4 4zM5 12h2v-2H5v2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 382 B |
3
website/client/assets/svg/difficulty-star.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#FFB445" fill-rule="evenodd" d="M10.667 10.667L16 8l-5.333-2.667L8 0 5.333 5.333 0 8l5.333 2.667L8 16z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 213 B |
3
website/client/assets/svg/down.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="13" viewBox="0 0 20 13">
|
||||
<path fill="none" fill-rule="evenodd" stroke="#C3C0C7" stroke-width="4" d="M18 2l-8 8-8-8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 188 B |
3
website/client/assets/svg/facebook.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="16" viewBox="0 0 8 16">
|
||||
<path fill="#878190" fill-rule="evenodd" d="M7.145 8.006H4.903V16H1.58V8.006H0V5.182h1.58V3.354C1.58 2.045 2.202 0 4.933 0l2.461.01v2.742H5.608c-.291 0-.705.145-.705.77v1.66h2.533l-.291 2.824z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 289 B |
8
website/client/assets/svg/google.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#4285F4" d="M17.64 9.205c0-.639-.057-1.252-.164-1.841H9v3.481h4.844a4.14 4.14 0 0 1-1.796 2.716v2.259h2.908c1.702-1.567 2.684-3.875 2.684-6.615z"/>
|
||||
<path fill="#34A853" d="M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z"/>
|
||||
<path fill="#FBBC05" d="M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z"/>
|
||||
<path fill="#EA4335" d="M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 834 B |
3
website/client/assets/svg/gryphon.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="65" height="70" viewBox="0 0 65 70">
|
||||
<path fill="#FFF" fill-rule="evenodd" d="M62.749 65.754c1.728 2.297 1.853 3.623.136 3.623H51.783c-3.344 0-4.038.396-3.985-1.056.072-2.024.725-2.103 2.851-2.696 1.763-.493 1.13-3.63-.869-6.376-1.392-1.91-4.216-4.134-7.929-2.405-2.41 1.122-3.828 2.27-4.943 3.815-1.823 2.53-1.724 4.927 1.115 4.927 2.683 0 5.843-1.626 6.76.69 1.134 2.867.355 3.133-.2 3.133H26.79l.012.017c-9.532 0-19.054-.075-21.262-6.099-3.056-8.334 8.063-11.546 8.404-16.775.138-2.116-1.051-3.096-2.536-3.096H4.885V39.49H.921V27.6h3.964v3.964H8.85v3.964h3.965v5.506s3.447 1.71 3.33 5.478c-.186 5.982-10.304 9.296-8.45 15.567 1.695 5.738 14.502 4.364 14.053.804l-.222-2.028c-.063-2.84-.506-6.64.087-10.005.934-5.289 4.224-9.865 9.956-9.907 1.045-.008 1.929-.222 1.929-1.1 0-.927-1.68-.906-2.863-1.025-4.09-.417-9.18-1.787-13.627-5.445-2.634-2.167-6.736-8.999-4.054-8.498 1.656.31 3.116.437 4.4.433 1.346-.004 4.82-.382 4.82-1.203 0-1.138-2.01-.453-4.955-.741-3.521-.344-10.988-1.853-14.84-13.215C.932 5.89-.02-2.075 2.324.503 15.84 15.367 20.433 12.947 28.184 18.029c3.412 2.237 5.633 7.58 7.622 7.135 1.296-.292.502-1.483.696-4.262.173-2.484 1.354-3.022-1.683-3.773-3.538-.873-7.07-4.752-4.76-4.697 2.307.056 3.977.048 6-.828 2.024-.875 6.97-4.641 9.962-3.133 2.992 1.509 5.507-.656 7.531-.272 2.938.557 3.694 4.6 3.313 6.965-.24 1.5-.24 1.498-2.153 1.386-3.921-.23-4.92 2.893-2.162 4.615 4.197 2.621 5.98 4.588 7.121 10.395.555 2.817.105 3.474-.83 2.956-.933-.519-2.539-.778-.31 4.356 2.23 5.133-1.4 11.15-2.592 11.825-1.194.674-.986 1.452-.986 1.452-.023 12.387 0 13.315 2.078 13.538 3.15.34 4.49-1.565 5.718.067z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
3
website/client/assets/svg/habit.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="20" viewBox="0 0 30 20">
|
||||
<path fill="#C3C0C7" fill-rule="evenodd" d="M11 11H9v2H7v-2H5V9h2V7h2v2h2v2zm8 0h6V9h-6v2zm9 5c0 1.103-.897 2-2 2H16V2h10c1.103 0 2 .897 2 2v12zM4 18c-1.103 0-2-.897-2-2V4c0-1.103.897-2 2-2h10v16H4zM26 0H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h22a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 369 B |
7
website/client/assets/svg/habitica-logo.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="144" height="31" viewBox="0 0 144 31">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#FFF" d="M120.876 24.007a2.27 2.27 0 0 0-3.183.41 4.595 4.595 0 0 1-3.663 1.804 4.62 4.62 0 0 1-4.613-4.335c-.005-.35-.009-2.864-.009-3.19a4.627 4.627 0 0 1 4.622-4.622c1.28 0 2.47.51 3.353 1.44a2.269 2.269 0 0 0 3.29-3.125 9.2 9.2 0 0 0-6.643-2.853c-5.05 0-9.16 4.109-9.16 9.16 0 .03.002 3.175.014 3.406a9.158 9.158 0 0 0 9.146 8.657 9.1 9.1 0 0 0 7.257-3.57 2.27 2.27 0 0 0-.411-3.182M134.373 26.221a4.62 4.62 0 0 1-4.613-4.333c-.005-.353-.008-2.877-.008-3.193a4.627 4.627 0 0 1 4.621-4.622 4.627 4.627 0 0 1 4.622 4.622c0 .328-.003 2.84-.009 3.189a4.618 4.618 0 0 1-4.613 4.337m6.891-17.078a2.264 2.264 0 0 0-2.19 1.706 9.095 9.095 0 0 0-4.7-1.313c-5.051 0-9.16 4.109-9.16 9.16 0 .031.001 3.173.013 3.406a9.158 9.158 0 0 0 9.146 8.657 9.118 9.118 0 0 0 4.81-1.37 2.268 2.268 0 0 0 4.35-.899V11.412a2.27 2.27 0 0 0-2.269-2.269M30.546 26.221a4.62 4.62 0 0 1-4.613-4.335c-.006-.35-.01-2.863-.01-3.19a4.627 4.627 0 0 1 4.623-4.623 4.627 4.627 0 0 1 4.622 4.622c0 .328-.004 2.84-.01 3.189a4.618 4.618 0 0 1-4.612 4.337m6.89-17.078a2.264 2.264 0 0 0-2.19 1.706 9.095 9.095 0 0 0-4.7-1.313c-5.052 0-9.16 4.109-9.16 9.16 0 .031 0 3.174.013 3.406a9.158 9.158 0 0 0 9.147 8.657 9.118 9.118 0 0 0 4.809-1.37 2.268 2.268 0 0 0 4.35-.899V11.412a2.27 2.27 0 0 0-2.269-2.269M70.84 9.143a2.27 2.27 0 0 0-2.27 2.27V28.49a2.27 2.27 0 0 0 4.539 0V11.412a2.27 2.27 0 0 0-2.27-2.269M97.563 9.143a2.27 2.27 0 0 0-2.27 2.27V28.49a2.27 2.27 0 0 0 4.538 0V11.412a2.27 2.27 0 0 0-2.268-2.269M59.066 21.888a4.62 4.62 0 0 1-4.613 4.333 4.62 4.62 0 0 1-4.613-4.338c-.006-.35-.009-2.86-.009-3.187a4.627 4.627 0 0 1 4.622-4.622 4.627 4.627 0 0 1 4.622 4.622c0 .315-.004 2.84-.009 3.192M54.453 9.536a9.089 9.089 0 0 0-4.622 1.265V2.33a2.27 2.27 0 0 0-4.537 0V28.49a2.269 2.269 0 0 0 4.35.9 9.117 9.117 0 0 0 4.81 1.37 9.16 9.16 0 0 0 9.146-8.666c.011-.224.013-3.367.013-3.398 0-5.052-4.11-9.16-9.16-9.16M8.92 9.536a9.143 9.143 0 0 0-4.382 1.11V2.33A2.27 2.27 0 0 0 0 2.33v26.16a2.269 2.269 0 1 0 4.538 0V16.763c.173-.147.333-.314.46-.516a4.601 4.601 0 0 1 3.921-2.173 4.627 4.627 0 0 1 4.622 4.622c0 .415-.004 9.233-.01 9.738a2.27 2.27 0 0 0 4.535.172c.01-.225.012-9.814.012-9.91 0-5.052-4.108-9.16-9.159-9.16M88.95 9.143h-2.648V2.33a2.27 2.27 0 0 0-4.538 0v6.813h-2.647a2.27 2.27 0 0 0 0 4.538h2.647V28.49a2.27 2.27 0 0 0 4.538 0V13.681h2.647a2.27 2.27 0 0 0 0-4.538"/>
|
||||
<path fill="#FF6066" d="M73.025 2.33a2.27 2.27 0 1 1-4.538 0 2.27 2.27 0 0 1 4.538 0"/>
|
||||
<path fill="#4FB5E8" d="M99.748 2.33a2.27 2.27 0 1 1-4.539 0 2.27 2.27 0 0 1 4.539 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
3
website/client/assets/svg/message.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="12" viewBox="0 0 16 12">
|
||||
<path fill="#4F2A93" fill-rule="evenodd" d="M14 10H2V2l6 5 6-5v8zm0-10H2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 235 B |
3
website/client/assets/svg/negative.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="2" viewBox="0 0 10 2">
|
||||
<path fill-rule="evenodd" d="M0 0h10v2H0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 135 B |
3
website/client/assets/svg/next.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="20" viewBox="0 0 13 20">
|
||||
<path fill="none" fill-rule="evenodd" stroke="#A5A1AC" stroke-width="4" d="M2 2l8 8-8 8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 186 B |
3
website/client/assets/svg/positive.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<path fill-rule="evenodd" d="M6 4V0H4v4H0v2h4v4h2V6h4V4H6z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 157 B |
3
website/client/assets/svg/previous.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="20" viewBox="0 0 13 20">
|
||||
<path fill="none" fill-rule="evenodd" stroke="#A5A1AC" stroke-width="4" d="M11 2l-8 8 8 8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 188 B |
3
website/client/assets/svg/qrCode.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#878190" fill-rule="evenodd" d="M7 8h1V7H7v1zm4 5h2v-2h-2v2zm-8 0h2v-2H3v2zm8-8h2V3h-2v2zM3 5h2V3H3v2zm7 1h4V2h-4v4zm4 3h-1V8h-1V7h1v1h1v1zm-4 5h4v-4h-4v4zm-3 0v-1h1v-1H7v-1h1v-1H7V9H6V7h1V5h1V4h1v1H8v1h1v2h1V7h1v2H9V8H8v2h1v3H8v1H7zm-5 0h4v-4H2v4zm0-6h1V7h2v1H4v1H2V8zm0-2h4V2H2v4zm7-4v1H7V2h2zm5-2H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 477 B |
36
website/client/assets/svg/quest-background-border.svg
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="424" height="80" viewBox="0 0 424 80">
|
||||
<defs>
|
||||
<path id="a" d="M0 0h424v80H0z"/>
|
||||
<path id="b" d="M0 0h10v10H0z"/>
|
||||
<path id="c" d="M414 0h10v10h-10z"/>
|
||||
<path id="d" d="M0 70h10v10H0z"/>
|
||||
<path id="e" d="M414 70h10v10h-10z"/>
|
||||
</defs>
|
||||
<g fill="#F9F9F9" fill-rule="evenodd">
|
||||
<g>
|
||||
<use fill="#F9F9F9" xlink:href="#a"/>
|
||||
<path stroke="#E1E0E3" stroke-width="4" d="M2 2h420v76H2z"/>
|
||||
<path stroke="#C3C0C7" stroke-width="2" d="M1 1h422v78H1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<use fill="#EDECEE" xlink:href="#b"/>
|
||||
<path stroke="#E1E0E3" stroke-width="4" d="M2 2h6v6H2z"/>
|
||||
<path stroke="#C3C0C7" stroke-width="2" d="M1 1h8v8H1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<use fill="#EDECEE" xlink:href="#c"/>
|
||||
<path stroke="#E1E0E3" stroke-width="4" d="M416 2h6v6h-6z"/>
|
||||
<path stroke="#C3C0C7" stroke-width="2" d="M415 1h8v8h-8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<use fill="#EDECEE" xlink:href="#d"/>
|
||||
<path stroke="#E1E0E3" stroke-width="4" d="M2 72h6v6H2z"/>
|
||||
<path stroke="#C3C0C7" stroke-width="2" d="M1 71h8v8H1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<use fill="#EDECEE" xlink:href="#e"/>
|
||||
<path stroke="#E1E0E3" stroke-width="4" d="M416 72h6v6h-6z"/>
|
||||
<path stroke="#C3C0C7" stroke-width="2" d="M415 71h8v8h-8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
3
website/client/assets/svg/quest.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" viewBox="0 0 18 20">
|
||||
<path fill="#C3C0C7" fill-rule="evenodd" d="M3 16h4v-1H3v1zm0-4h9v-1H3v1zm0-2h9V9H3v1zm0-5h9V4H3v1zm0 3h9V7H3v1zm10 7v3H2V2h11v13zm2 3h1v-1h-1v1zm0-15h1V2h-1v1zm3 2V2a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-3h-3V5h3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 340 B |
3
website/client/assets/svg/remove.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#878190" fill-rule="evenodd" d="M8 14a5.96 5.96 0 0 1-3.327-1.011l8.316-8.316A5.96 5.96 0 0 1 14 8c0 3.309-2.691 6-6 6M8 2a5.96 5.96 0 0 1 3.327 1.011l-8.316 8.316A5.96 5.96 0 0 1 2 8c0-3.309 2.691-6 6-6m0-2a8 8 0 1 0 0 16A8 8 0 0 0 8 0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 347 B |
3
website/client/assets/svg/reward.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="20" viewBox="0 0 26 20">
|
||||
<path fill="#C3C0C7" fill-rule="evenodd" d="M24 10h-8V8h4a2 2 0 0 0 2-2V2c1.103 0 2 .897 2 2v6zm0 6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4h8v1a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-1h8v4zM2 4c0-1.103.897-2 2-2v4a2 2 0 0 0 2 2h4v2H2V4zm10 9h2V8h-2v5zm8-11v4H6V2h14zm2-2H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h18a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 421 B |
10
website/client/assets/svg/star.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#FFA623" d="M16 16l8-4-8-4-4-8-4 8-8 4 8 4 4 8z"/>
|
||||
<path fill="#FFF" d="M4.5 12l5-2.5L12 12zM12 19.5l-2.5-5L12 12zM19.5 12l-5 2.5L12 12zM12 4.5l2.5 5L12 12z" opacity=".25"/>
|
||||
<path fill="#BF7D1A" d="M19.5 12l-5-2.5L12 12z" opacity=".25"/>
|
||||
<path fill="#BF7D1A" d="M12 19.5l2.5-5L12 12z" opacity=".5"/>
|
||||
<path fill="#FFF" d="M4.5 12l5 2.5L12 12zM12 4.5l-2.5 5L12 12z" opacity=".5"/>
|
||||
<path fill="#FFF" d="M10.8 13.2L8.5 12l2.3-1.2L12 8.5l1.2 2.3 2.3 1.2-2.3 1.2-1.2 2.3z" opacity=".5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 682 B |
3
website/client/assets/svg/streak.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8">
|
||||
<path fill-rule="evenodd" d="M11.376 3.15L6.777.086A.5.5 0 0 0 6 .5v6.132a.5.5 0 0 0 .777.416l4.599-3.066a.5.5 0 0 0 0-.832M.777.085L6 3.567.777 7.049A.5.5 0 0 1 0 6.633V.5A.5.5 0 0 1 .777.085"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 289 B |
3
website/client/assets/svg/tags.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
|
||||
<path fill-rule="evenodd" d="M10 3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM2.004 6.994L7 2h5l-.004 5.006L7 12l.004-.004-5-5.002zM0 7c0 .55.22 1.05.59 1.41l5 5a1.996 1.996 0 0 0 2.83 0l4.99-4.99c.37-.37.59-.87.59-1.42V2c0-1.11-.89-2-2-2H7c-.55 0-1.05.22-1.41.58l-5 5C.23 5.94 0 6.44 0 7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |
3
website/client/assets/svg/todo.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<path fill="#C3C0C7" fill-rule="evenodd" d="M8.343 14.916c-.312 0-.61-.123-.831-.344l-3.831-3.831 1.662-1.662 2.934 2.934 5.938-6.929L16 6.613l-6.764 7.893a1.182 1.182 0 0 1-.848.409l-.045.001zM18 16c0 1.103-.897 2-2 2H4c-1.102 0-2-.897-2-2V4c0-1.103.898-2 2-2h12c1.103 0 2 .897 2 2v12zM16 0H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h12a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 457 B |
3
website/client/assets/svg/twitter.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="13" viewBox="0 0 16 13">
|
||||
<path fill="#878190" fill-rule="evenodd" d="M14.362 3.238c.007.141.01.281.01.424 0 4.338-3.302 9.34-9.34 9.34A9.284 9.284 0 0 1 0 11.527c.257.029.518.045.783.045a6.576 6.576 0 0 0 4.076-1.404 3.288 3.288 0 0 1-3.065-2.28 3.312 3.312 0 0 0 1.481-.056A3.288 3.288 0 0 1 .642 4.613v-.041c.444.246.949.393 1.488.41A3.28 3.28 0 0 1 .67 2.25c0-.602.162-1.166.444-1.651a9.315 9.315 0 0 0 6.766 3.43A3.28 3.28 0 0 1 11.078 0c.943 0 1.797.398 2.395 1.035a6.565 6.565 0 0 0 2.085-.797 3.289 3.289 0 0 1-1.443 1.816A6.543 6.543 0 0 0 16 1.539a6.665 6.665 0 0 1-1.638 1.699"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 660 B |
3
website/client/assets/svg/up.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="13" viewBox="0 0 20 13">
|
||||
<path fill="none" fill-rule="evenodd" stroke="#A5A1AC" stroke-width="4" d="M18 11l-8-8-8 8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 189 B |
|
|
@ -1,7 +1,7 @@
|
|||
<template lang="pug">
|
||||
#app-header.row
|
||||
member-details(:member="user", @click="$router.push({name: 'avatar'})")
|
||||
.view-party
|
||||
.view-party(v-if="user.party && user.party._id")
|
||||
// TODO button should open the party members modal
|
||||
router-link.btn.btn-primary(:active-class="''", :to="{name: 'party'}") {{ $t('viewParty') }}
|
||||
.party-members.d-flex(v-if="partyMembers && partyMembers.length > 1")
|
||||
|
|
@ -59,6 +59,7 @@
|
|||
.no-party {
|
||||
.small-text {
|
||||
color: $header-color;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
h3 {
|
||||
|
|
@ -66,7 +67,7 @@
|
|||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
button {
|
||||
.btn {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
|
@ -75,10 +76,12 @@
|
|||
<script>
|
||||
import { mapGetters, mapActions } from 'client/libs/store';
|
||||
import MemberDetails from './memberDetails';
|
||||
import createPartyModal from './guilds/createPartyModal';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MemberDetails,
|
||||
createPartyModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
@ -104,7 +107,7 @@ export default {
|
|||
},
|
||||
},
|
||||
created () {
|
||||
this.getPartyMembers();
|
||||
if (this.user.party && this.user.party._id) this.getPartyMembers();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
|
|||
router-link.dropdown-item(:to="{name: 'stable'}") {{ $t('stable') }}
|
||||
router-link.nav-item(tag="li", :to="{name: 'shops'}", exact)
|
||||
a.nav-link(v-once) {{ $t('shops') }}
|
||||
router-link.nav-item(tag="li", :to="{name: 'party'}", exact)
|
||||
router-link.nav-item(tag="li", :to="{name: 'party'}")
|
||||
a.nav-link(v-once) {{ $t('party') }}
|
||||
router-link.nav-item.dropdown(tag="li", :to="{name: 'tavern'}", :class="{'active': $route.path.startsWith('/guilds')}")
|
||||
a.nav-link(v-once) {{ $t('guilds') }}
|
||||
|
|
@ -41,154 +41,154 @@ nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
|
|||
router-link.dropdown.item-with-icon.item-user(:to="{name: 'avatar'}")
|
||||
.svg-icon(v-html="icons.user")
|
||||
.dropdown-menu.dropdown-menu-right.user-dropdown
|
||||
router-link.dropdown-item.edit-avatar(:to="{name: 'avatar'}")
|
||||
router-link.dropdown-item.edit-avatar(:to="{name: 'avatar'}")
|
||||
h3 {{ user.profile.name }}
|
||||
span.small-text {{ $t('editAvatar') }}
|
||||
router-link.dropdown-item(:to="{name: 'inbox'}") {{ $t('inbox') }}
|
||||
router-link.dropdown-item(:to="{name: 'stats'}") {{ $t('stats') }}
|
||||
router-link.dropdown-item(:to="{name: 'achievements'}") {{ $t('achievements') }}
|
||||
router-link.dropdown-item(:to="{name: 'settings'}") {{ $t('settings') }}
|
||||
router-link.dropdown-item(to="/logout") {{ $t('logout') }}
|
||||
a.nav-link.dropdown-item(to="/", @click.prevent='logout()') {{ $t('logout') }}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/utils.scss';
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/utils.scss';
|
||||
|
||||
nav.navbar {
|
||||
background: $purple-100 url(~assets/svg/for-css/bits.svg) right no-repeat;
|
||||
padding-left: 25px;
|
||||
padding-right: 12.5px;
|
||||
height: 56px;
|
||||
box-shadow: 0 1px 2px 0 rgba($black, 0.24);
|
||||
}
|
||||
|
||||
.navbar-header {
|
||||
margin-right: 48px;
|
||||
|
||||
.logo {
|
||||
width: 128px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
.nav-link {
|
||||
font-size: 16px;
|
||||
color: $white;
|
||||
font-weight: bold;
|
||||
line-height: 1.5;
|
||||
padding: 16px 24px;
|
||||
transition: none;
|
||||
nav.navbar {
|
||||
background: $purple-100 url(~assets/svg/for-css/bits.svg) right no-repeat;
|
||||
padding-left: 25px;
|
||||
padding-right: 12.5px;
|
||||
height: 56px;
|
||||
box-shadow: 0 1px 2px 0 rgba($black, 0.24);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.navbar-header {
|
||||
margin-right: 48px;
|
||||
|
||||
.logo {
|
||||
width: 128px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
.nav-link {
|
||||
font-size: 16px;
|
||||
color: $white;
|
||||
background: $purple-200;
|
||||
}
|
||||
}
|
||||
|
||||
&.active:not(:hover) {
|
||||
.nav-link {
|
||||
box-shadow: 0px -4px 0px $purple-300 inset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the dropdown menu open on hover
|
||||
.dropdown:hover .dropdown-menu {
|
||||
display: block;
|
||||
margin-top: 0; // remove the gap so it doesn't close
|
||||
}
|
||||
|
||||
.dropdown + .dropdown {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.dropdown-menu:not(.user-dropdown) {
|
||||
background: $purple-200;
|
||||
border-radius: 0px;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0px;
|
||||
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
|
||||
.dropdown-item {
|
||||
font-size: 16px;
|
||||
box-shadow: none;
|
||||
color: $white;
|
||||
border: none;
|
||||
line-height: 1.5;
|
||||
|
||||
&.active {
|
||||
background: $purple-300;
|
||||
font-weight: bold;
|
||||
line-height: 1.5;
|
||||
padding: 16px 24px;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $purple-300;
|
||||
.nav-link {
|
||||
color: $white;
|
||||
background: $purple-200;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
&.active:not(:hover) {
|
||||
.nav-link {
|
||||
box-shadow: 0px -4px 0px $purple-300 inset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-with-icon {
|
||||
color: $white;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
padding-top: 16px;
|
||||
padding-left: 16px;
|
||||
|
||||
.svg-icon {
|
||||
vertical-align: middle;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 8px;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.item-notifications, .item-user {
|
||||
padding-right: 12.5px;
|
||||
padding-left: 12.5px;
|
||||
color: $header-color;
|
||||
transition: none;
|
||||
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
margin-right: 0px;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.item-notifications {
|
||||
margin-left: 33.5px;
|
||||
}
|
||||
|
||||
.item-user .edit-avatar {
|
||||
h3 {
|
||||
color: $gray-10;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.small-text {
|
||||
color: $gray-200;
|
||||
font-style: normal;
|
||||
// Make the dropdown menu open on hover
|
||||
.dropdown:hover .dropdown-menu {
|
||||
display: block;
|
||||
margin-top: 0; // remove the gap so it doesn't close
|
||||
}
|
||||
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.dropdown + .dropdown {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.dropdown-menu:not(.user-dropdown) {
|
||||
background: $purple-200;
|
||||
border-radius: 0px;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0px;
|
||||
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
|
||||
.dropdown-item {
|
||||
font-size: 16px;
|
||||
box-shadow: none;
|
||||
color: $white;
|
||||
border: none;
|
||||
line-height: 1.5;
|
||||
|
||||
&.active {
|
||||
background: $purple-300;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $purple-300;
|
||||
|
||||
&:last-child {
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-with-icon {
|
||||
color: $white;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
padding-top: 16px;
|
||||
padding-left: 16px;
|
||||
|
||||
.svg-icon {
|
||||
vertical-align: middle;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 8px;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.item-notifications, .item-user {
|
||||
padding-right: 12.5px;
|
||||
padding-left: 12.5px;
|
||||
color: $header-color;
|
||||
transition: none;
|
||||
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
margin-right: 0px;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.item-notifications {
|
||||
margin-left: 33.5px;
|
||||
}
|
||||
|
||||
.item-user .edit-avatar {
|
||||
h3 {
|
||||
color: $gray-10;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.small-text {
|
||||
color: $gray-200;
|
||||
font-style: normal;
|
||||
display: block;
|
||||
}
|
||||
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
@ -217,5 +217,11 @@ export default {
|
|||
}),
|
||||
...mapState({user: 'user.data'}),
|
||||
},
|
||||
methods: {
|
||||
logout () {
|
||||
localStorage.removeItem('habit-mobile-settings');
|
||||
this.$router.go('/');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
230
website/client/components/auth/registerLogin.vue
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
<template lang="pug">
|
||||
.form-wrapper
|
||||
#top-background
|
||||
.seamless_stars_varied_opacity_repeat
|
||||
|
||||
#login-form
|
||||
.text-center
|
||||
div
|
||||
.svg-icon.gryphon(v-html="icons.gryphon")
|
||||
div
|
||||
.svg-icon.habitica-logo(v-html="icons.habiticaIcon")
|
||||
.form-group.row.text-center
|
||||
.col-6
|
||||
.btn.btn-secondary.social-button(@click='socialAuth("facebook")', v-once)
|
||||
.svg-icon.social-icon(v-html="icons.facebookIcon")
|
||||
| {{this.registering ? $t('signUpWithSocial', {social: 'Facebook'}) : $t('loginWithSocial', {social: 'Facebook'})}}
|
||||
.col-6
|
||||
.btn.btn-secondary.social-button(@click='socialAuth("google")', v-once)
|
||||
.svg-icon.social-icon(v-html="icons.googleIcon")
|
||||
| {{this.registering ? $t('signUpWithSocial', {social: 'Google'}) : $t('loginWithSocial', {social: 'Google'})}}
|
||||
.form-group
|
||||
label(for='usernameInput', v-once) {{$t('username')}}
|
||||
input#usernameInput.form-control(type='text', :placeholder='$t("usernamePlaceholder")', v-model='username')
|
||||
.form-group(v-if='registering')
|
||||
label(for='emailInput', v-once) {{$t('email')}}
|
||||
input#emailInput.form-control(type='email', :placeholder='$t("emailPlaceholder")', v-model='email')
|
||||
.form-group
|
||||
label(for='passwordInput', v-once) {{$t('password')}}
|
||||
input#passwordInput.form-control(type='password', :placeholder='$t("passwordPlaceholder")', v-model='password')
|
||||
.form-group(v-if='registering')
|
||||
label(for='confirmPasswordInput', v-once) {{$t('confirmPassword')}}
|
||||
input#confirmPasswordInput.form-control(type='password', :placeholder='$t("confirmPasswordPlaceholder")', v-model='passwordConfirm')
|
||||
small.form-text(v-once) {{$t('termsAndAgreement')}}
|
||||
.text-center
|
||||
.btn.btn-info(@click='register()', v-if='registering', v-once) {{$t('joinHabitica')}}
|
||||
.btn.btn-info(@click='login()', v-if='!registering', v-once) {{$t('login')}}
|
||||
|
||||
#bottom-background
|
||||
.seamless_mountains_demo_repeat
|
||||
.midground_foreground_extended2
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.form-wrapper {
|
||||
background-color: $purple-200;
|
||||
}
|
||||
|
||||
#login-form {
|
||||
margin: 0 auto;
|
||||
width: 40em;
|
||||
padding-top: 5em;
|
||||
padding-bottom: 22.5em;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.gryphon {
|
||||
width: 63.2px;
|
||||
height: 69.4px;
|
||||
}
|
||||
|
||||
.habitica-logo {
|
||||
width: 144px;
|
||||
height: 31px;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
label {
|
||||
color: $white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-bottom: 2em;
|
||||
border-radius: 2px;
|
||||
background-color: #432874;
|
||||
border-color: transparent;
|
||||
height: 50px;
|
||||
color: $purple-400;
|
||||
|
||||
::-webkit-input-placeholder { /* Chrome/Opera/Safari */
|
||||
color: $purple-400;
|
||||
}
|
||||
::-moz-placeholder { /* Firefox 19+ */
|
||||
color: $purple-400;
|
||||
}
|
||||
:-ms-input-placeholder { /* IE 10+ */
|
||||
color: $purple-400;
|
||||
}
|
||||
:-moz-placeholder { /* Firefox 18- */
|
||||
color: $purple-400;
|
||||
}
|
||||
}
|
||||
|
||||
.form-text {
|
||||
font-size: 14px;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.social-button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.social-icon {
|
||||
margin-right: 1em;
|
||||
width: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
#top-background {
|
||||
.seamless_stars_varied_opacity_repeat {
|
||||
background-image: url('~assets/images/auth/seamless_stars_varied_opacity.png');
|
||||
background-repeat: repeat-x;
|
||||
position: absolute;
|
||||
height: 500px;
|
||||
width: 1600px;
|
||||
}
|
||||
}
|
||||
|
||||
#bottom-background {
|
||||
position: relative;
|
||||
|
||||
.seamless_mountains_demo_repeat {
|
||||
background-image: url('~assets/images/auth/seamless_mountains_demo.png');
|
||||
background-repeat: repeat-x;
|
||||
width: 1600px;
|
||||
height: 500px;
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.midground_foreground_extended2 {
|
||||
background-image: url('~assets/images/auth/midground_foreground_extended2.png');
|
||||
position: relative;
|
||||
width: 1500px;
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import hello from 'hellojs';
|
||||
|
||||
import gryphon from 'assets/svg/gryphon.svg';
|
||||
import habiticaIcon from 'assets/svg/habitica-logo.svg';
|
||||
import facebookIcon from 'assets/svg/facebook.svg';
|
||||
import googleIcon from 'assets/svg/google.svg';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
let data = {
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
passwordConfirm: '',
|
||||
registering: true,
|
||||
};
|
||||
|
||||
data.icons = Object.freeze({
|
||||
gryphon,
|
||||
habiticaIcon,
|
||||
facebookIcon,
|
||||
googleIcon,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
mounted () {
|
||||
if (this.$route.path.startsWith('/login')) {
|
||||
this.registering = false;
|
||||
}
|
||||
|
||||
hello.init({
|
||||
facebook: '',
|
||||
// windows: WINDOWS_CLIENT_ID,
|
||||
google: '',
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
async register () {
|
||||
if (this.password !== this.passwordConfirm) {
|
||||
alert('Passwords must match');
|
||||
return;
|
||||
}
|
||||
|
||||
// @TODO: implement langauge and invite accepting
|
||||
// var url = ApiUrl.get() + "/api/v3/user/auth/local/register";
|
||||
// if (location.search && location.search.indexOf('Invite=') !== -1) { // matches groupInvite and partyInvite
|
||||
// url += location.search;
|
||||
// }
|
||||
//
|
||||
// if($rootScope.selectedLanguage) {
|
||||
// var toAppend = url.indexOf('?') !== -1 ? '&' : '?';
|
||||
// url = url + toAppend + 'lang=' + $rootScope.selectedLanguage.code;
|
||||
// }
|
||||
|
||||
await this.$store.dispatch('auth:register', {
|
||||
username: this.username,
|
||||
email: this.email,
|
||||
password: this.password,
|
||||
passwordConfirm: this.passwordConfirm,
|
||||
});
|
||||
|
||||
this.$router.go('/tasks');
|
||||
},
|
||||
async login () {
|
||||
await this.$store.dispatch('auth:login', {
|
||||
username: this.username,
|
||||
// email: this.email,
|
||||
password: this.password,
|
||||
});
|
||||
|
||||
this.$router.go('/tasks');
|
||||
},
|
||||
async socialAuth (network) {
|
||||
let auth = await hello(network).login({scope: 'email'});
|
||||
|
||||
await this.$store.dispatch('auth:socialAuth', {
|
||||
auth,
|
||||
});
|
||||
|
||||
this.$router.go('/tasks');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
121
website/client/components/guilds/createPartyModal.vue
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<template lang="pug">
|
||||
b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
|
||||
.header-wrap(slot="modal-header")
|
||||
h2 Image Here
|
||||
.row
|
||||
.col-12.text-center
|
||||
h2(v-once) {{$t('playInPartyTitle')}}
|
||||
p(v-once) {{$t('playInPartyDescription')}}
|
||||
.row.grey-row
|
||||
.col-6.text-center
|
||||
img
|
||||
h3(v-once) {{$t('startYourOwnPartyTitle')}}
|
||||
p(v-once) {{$t('startYourOwnPartyDescription')}}
|
||||
button.btn.btn-primary(v-once) {{$t('createParty')}}
|
||||
.col-6
|
||||
div.text-center
|
||||
img
|
||||
h3(v-once) {{$t('wantToJoinPartyTitle')}}
|
||||
p(v-once) {{$t('wantToJoinPartyDescription')}}
|
||||
button.btn.btn-primary(v-once, @click='shareUserIdShown = !shareUserIdShown') {{$t('shartUserId')}}
|
||||
.share-userid-options(v-if="shareUserIdShown")
|
||||
.option-item(v-once)
|
||||
.svg-icon(v-html="icons.copy")
|
||||
| {{$t('copy')}}
|
||||
.option-item(v-once)
|
||||
.svg-icon(v-html="icons.greyBadge")
|
||||
| {{$t('lookingForGroup')}}
|
||||
.option-item(v-once)
|
||||
.svg-icon(v-html="icons.qrCode")
|
||||
| {{$t('qrCode')}}
|
||||
.option-item(v-once)
|
||||
.svg-icon.facebook(v-html="icons.facebook")
|
||||
| {{$t('facebook')}}
|
||||
.option-item(v-once)
|
||||
.svg-icon(v-html="icons.twitter")
|
||||
| {{$t('twitter')}}
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.modal-body {
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.grey-row {
|
||||
background-color: $gray-700;
|
||||
padding: 2em;
|
||||
border-radius: 0px 0px 2px 2px;
|
||||
}
|
||||
|
||||
.share-userid-options {
|
||||
background-color: $white;
|
||||
border-radius: 2px;
|
||||
width: 180px;
|
||||
position: absolute;
|
||||
top: -8em;
|
||||
left: 4.8em;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
|
||||
.option-item {
|
||||
padding: 1em;
|
||||
|
||||
.svg-icon {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.facebook svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.option-item:hover {
|
||||
background-color: $header-color;
|
||||
color: $purple-200;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
|
||||
import copyIcon from 'assets/svg/copy.svg';
|
||||
import greyBadgeIcon from 'assets/svg/grey-badge.svg';
|
||||
import qrCodeIcon from 'assets/svg/qrCode.svg';
|
||||
import facebookIcon from 'assets/svg/facebook.svg';
|
||||
import twitterIcon from 'assets/svg/twitter.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
bModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
copy: copyIcon,
|
||||
greyBadge: greyBadgeIcon,
|
||||
qrCode: qrCodeIcon,
|
||||
facebook: facebookIcon,
|
||||
twitter: twitterIcon,
|
||||
}),
|
||||
shareUserIdShown: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
createParty () {
|
||||
// group.loadingParty = true;
|
||||
//
|
||||
// if (!group.name) group.name = env.t('possessiveParty', {name: User.user.profile.name});
|
||||
// Groups.Group.create(group)
|
||||
// .then(function(response) {
|
||||
// Analytics.updateUser({'partyID': $scope.group ._id, 'partySize': 1});
|
||||
// $rootScope.hardRedirect('/#/options/groups/party');
|
||||
// });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
<template lang="pug">
|
||||
b-modal#guild-form(:title="title", :hide-footer="true")
|
||||
b-modal#guild-form(:title="title", :hide-footer="true", size='lg')
|
||||
form(@submit.stop.prevent="submit")
|
||||
.form-group
|
||||
label
|
||||
strong(v-once) {{$t('name')}}*
|
||||
b-form-input(type="text", placeholder="$t('newGuildPlaceHolder')", v-model="newGuild.name")
|
||||
b-form-input(type="text", :placeholder="$t('newGuildPlaceHolder')", v-model="newGuild.name")
|
||||
|
||||
.form-group(v-if='newGuild.id')
|
||||
label
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
span.custom-control-description(v-once) {{ $t('guildLeaderCantBeMessaged') }}
|
||||
|
||||
br
|
||||
label.custom-control.custom-checkbox
|
||||
label.custom-control.custom-checkbox(v-if='!creatingParty')
|
||||
input.custom-control-input(type="checkbox", v-model="newGuild.privateGuild")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ $t('privateGuild') }}
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
.svg-icon(v-html='icons.information')
|
||||
|
||||
br
|
||||
label.custom-control.custom-checkbox
|
||||
label.custom-control.custom-checkbox(v-if='!creatingParty')
|
||||
input.custom-control-input(type="checkbox", v-model="newGuild.allowGuildInvationsFromNonMembers")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ $t('allowGuildInvationsFromNonMembers') }}
|
||||
|
|
@ -46,14 +46,18 @@
|
|||
label
|
||||
strong(v-once) {{$t('description')}}*
|
||||
div.description-count {{charactersRemaining}} {{ $t('charactersRemaining') }}
|
||||
b-form-input(type="text", textarea :placeholder="$t('guildDescriptionPlaceHolder')", v-model="newGuild.description")
|
||||
b-form-input(type="text", textarea :placeholder="creatingParty ? $t('partyDescriptionPlaceHolder') : $t('guildDescriptionPlaceHolder')", v-model="newGuild.description")
|
||||
|
||||
.form-group(v-if='newGuild.id')
|
||||
.form-group(v-if='newGuild.id && !creatingParty')
|
||||
label
|
||||
strong(v-once) {{$t('guildInformation')}}*
|
||||
b-form-input(type="text", textarea :placeholder="$t('guildInformationPlaceHolder')", v-model="newGuild.guildInformation")
|
||||
b-form-input(type="text", textarea, :placeholder="$t('guildInformationPlaceHolder')", v-model="newGuild.guildInformation")
|
||||
|
||||
.form-group(style='position: relative;')
|
||||
.form-group(v-if='creatingParty && !newGuild.id')
|
||||
span
|
||||
toggleSwitch(:label="$t('inviteMembersNow')", v-model='inviteMembers')
|
||||
|
||||
.form-group(style='position: relative;', v-if='!creatingParty')
|
||||
label
|
||||
strong(v-once) {{$t('categories')}}*
|
||||
div.category-wrap(@click.prevent="toggleCategorySelect")
|
||||
|
|
@ -70,13 +74,26 @@
|
|||
span.custom-control-description(v-once) {{ $t(group.label) }}
|
||||
button.btn.btn-primary(@click.prevent="toggleCategorySelect") {{$t('close')}}
|
||||
|
||||
.form-group(v-if='inviteMembers && !newGuild.id')
|
||||
label
|
||||
strong(v-once) Invite via Email or User ID
|
||||
p Invite users via a valid email or 36-digit User ID. If an email isn’t registered yet, we’ll invite them to join.
|
||||
|
||||
div
|
||||
div(v-for='(member, index) in membersToInvite')
|
||||
input(type='text', v-model='member.value')
|
||||
button(@click.prevent='removeMemberToInvite(index)') Remove
|
||||
div
|
||||
input(type='text', placeholder='Email address or User ID', v-model='newMemberToInvite.value')
|
||||
button(@click.prevent='addMemberToInvite()') Add
|
||||
|
||||
.form-group.text-center
|
||||
div.item-with-icon
|
||||
div.item-with-icon(v-if='!creatingParty')
|
||||
.svg-icon(v-html="icons.gem")
|
||||
span.count 4
|
||||
button.btn.btn-primary.btn-md(v-if='!newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ $t('createGuild') }}
|
||||
button.btn.btn-primary.btn-md(v-if='newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ $t('updateGuild') }}
|
||||
.gem-description(v-once) {{ $t('guildGemCostInfo') }}
|
||||
button.btn.btn-primary.btn-md(v-if='!newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ creatingParty ? $t('createParty') : $t('createGuild') }}
|
||||
button.btn.btn-primary.btn-md(v-if='newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ creatingParty ? $t('updateParty') : $t('updateGuild') }}
|
||||
.gem-description(v-once, v-if='!creatingParty') {{ $t('guildGemCostInfo') }}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -101,31 +118,6 @@
|
|||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.category-box {
|
||||
padding: 1em;
|
||||
max-width: 400px;
|
||||
position: absolute;
|
||||
top: -480px;
|
||||
padding: 2em;
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1);
|
||||
}
|
||||
|
||||
.category-label {
|
||||
min-width: 100px;
|
||||
border-radius: 100px;
|
||||
background-color: $gray-600;
|
||||
padding: .5em;
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
.item-with-icon {
|
||||
display: inline-block;
|
||||
|
||||
|
|
@ -146,21 +138,6 @@
|
|||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.category-select {
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.category-select:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.category-wrap {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: .5em;
|
||||
display: inline-block;
|
||||
|
|
@ -175,6 +152,7 @@ import bFormCheckbox from 'bootstrap-vue/lib/components/form-checkbox';
|
|||
import bFormSelect from 'bootstrap-vue/lib/components/form-select';
|
||||
import bTooltip from 'bootstrap-vue/lib/components/tooltip';
|
||||
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
import gemIcon from 'assets/svg/gem.svg';
|
||||
import informationIcon from 'assets/svg/information.svg';
|
||||
|
||||
|
|
@ -186,6 +164,7 @@ export default {
|
|||
bFormCheckbox,
|
||||
bFormSelect,
|
||||
bTooltip,
|
||||
toggleSwitch,
|
||||
},
|
||||
data () {
|
||||
let data = {
|
||||
|
|
@ -251,6 +230,13 @@ export default {
|
|||
],
|
||||
showCategorySelect: false,
|
||||
members: ['one', 'two'],
|
||||
creatingParty: true,
|
||||
inviteMembers: false,
|
||||
newMemberToInvite: {
|
||||
value: '',
|
||||
type: '',
|
||||
},
|
||||
membersToInvite: [],
|
||||
};
|
||||
|
||||
let hashedCategories = {};
|
||||
|
|
@ -282,11 +268,23 @@ export default {
|
|||
return 500 - this.newGuild.description.length;
|
||||
},
|
||||
title () {
|
||||
if (this.creatingParty) return this.$t('createParty');
|
||||
if (!this.newGuild.id) return this.$t('createGuild');
|
||||
return this.$t('updateGuild');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addMemberToInvite () {
|
||||
// @TODO: determine type
|
||||
this.membersToInvite.push(this.newMemberToInvite);
|
||||
this.newMemberToInvite = {
|
||||
value: '',
|
||||
type: '',
|
||||
};
|
||||
},
|
||||
removeMemberToInvite (index) {
|
||||
this.membersToInvite.splice(index, 1);
|
||||
},
|
||||
toggleCategorySelect () {
|
||||
this.showCategorySelect = !this.showCategorySelect;
|
||||
},
|
||||
|
|
@ -311,7 +309,7 @@ export default {
|
|||
}
|
||||
|
||||
// @TODO: Add proper notifications
|
||||
if (!confirm(this.$t('confirmGuild'))) return;
|
||||
if (!this.newGuild.id && !confirm(this.$t('confirmGuild'))) return;
|
||||
|
||||
if (!this.newGuild.privateGuild) {
|
||||
this.newGuild.privacy = 'public';
|
||||
|
|
@ -342,6 +340,8 @@ export default {
|
|||
privateGuild: true,
|
||||
allowGuildInvationsFromNonMembers: true,
|
||||
};
|
||||
|
||||
this.$root.$emit('hide::modal', 'guild-form');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,25 +1,21 @@
|
|||
<template lang="pug">
|
||||
// TODO this is necessary until we have a way to wait for data to be loaded from the server
|
||||
.row(v-if="guild")
|
||||
.row(v-if="group")
|
||||
group-form-modal
|
||||
.clearfix.col-8
|
||||
.row
|
||||
.col-6.title-details
|
||||
h1 {{group.name}}
|
||||
strong.float-left(v-once) {{$t('groupLeader')}}
|
||||
span.float-left(v-once, v-if='group.leader.profile') : {{group.leader.profile.name}}
|
||||
.col-6
|
||||
.float-left
|
||||
h2 {{guild.name}}
|
||||
strong.float-left(v-once) {{$t('groupLeader')}}
|
||||
span.float-left : {{guild.leader.profile.name}}
|
||||
.col-6
|
||||
.float-right
|
||||
.row.icon-row
|
||||
.col-6
|
||||
.svg-icon.shield(v-html="icons.goldGuildBadge")
|
||||
span.number {{guild.memberCount}}
|
||||
div(v-once) {{ $t('guildMembers') }}
|
||||
.col-6
|
||||
.item-with-icon
|
||||
.svg-icon.gem(v-html="icons.gem")
|
||||
span.number {{guild.memberCount}}
|
||||
div(v-once) {{ $t('guildBank') }}
|
||||
.row.icon-row
|
||||
.col-4(v-bind:class="{ 'offset-8': isParty }")
|
||||
members-modal(:group='group', v-if='isMember')
|
||||
.col-6(v-if='!isParty')
|
||||
.item-with-icon
|
||||
.svg-icon.gem(v-html="icons.gem")
|
||||
span.number {{group.memberCount}}
|
||||
div(v-once) {{ $t('guildBank') }}
|
||||
.row.chat-row
|
||||
.col-12
|
||||
h3(v-once) {{ $t('chat') }}
|
||||
|
|
@ -34,7 +30,7 @@
|
|||
.col-md-2
|
||||
.svg-icon(v-html="icons.like")
|
||||
.col-md-10
|
||||
.card(v-for="msg in guild.chat", :key="msg.id")
|
||||
.card(v-for="msg in group.chat", :key="msg.id")
|
||||
.card-block
|
||||
h3.leader Character name
|
||||
span 2 hours ago
|
||||
|
|
@ -62,146 +58,336 @@
|
|||
.col-md-4.sidebar
|
||||
.guild-background.row
|
||||
.col-6
|
||||
p Image here
|
||||
p(v-if='!isParty') Image here
|
||||
.col-6
|
||||
members-modal(:group='guild')
|
||||
br
|
||||
button.btn.btn-primary(:class="[isMember ? 'btn-danger' : 'btn-success']") {{ isMember ? $t('leave') : $t('join') }}
|
||||
br
|
||||
button.btn.btn-primary(v-once) {{$t('inviteToGuild')}}
|
||||
br
|
||||
button.btn.btn-primary(v-once) {{$t('messageGuildLeader')}}
|
||||
br
|
||||
button.btn.btn-primary(v-once) {{$t('donateGems')}}
|
||||
br
|
||||
button.btn.btn-primary(b-btn, @click="updateGuild", v-once) {{ $t('updateGuild') }}
|
||||
div
|
||||
h3(v-once) {{ $t('description') }}
|
||||
p(v-once) {{ guild.description }}
|
||||
p Life hacks are tricks, shortcuts, or methods that help increase productivity, efficiency, health, and so on. Generally, they get you to a better state of life. Life hacking is the process of utilizing and implementing these secrets. And, in this guild, we want to help everyone discover these improved ways of doing things.
|
||||
div
|
||||
h3(v-once) {{$t('guildInformation')}}
|
||||
h4 Welcome
|
||||
p Below are some resources that some members might find useful. Consider checking them out before posting any questions, as they just might help answer some of them! Feel free to share your life hacks in the guild chat, or ask any questions that you might have. Please peruse at your leisure, and remember: this guild is meant to help guide you in the right direction. Only you will know what works best for you.
|
||||
div
|
||||
h3 Challenges
|
||||
.card
|
||||
h4 Challenge
|
||||
.row
|
||||
.col-8
|
||||
p Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla scelerisque ultrices libero.
|
||||
.col-4
|
||||
.row
|
||||
.col-md-12
|
||||
span Tag
|
||||
span 100
|
||||
.button-container
|
||||
button.btn.btn-success(class='btn-success', v-if='isLeader') {{ $t('upgrade') }}
|
||||
.button-container
|
||||
button.btn.btn-primary(b-btn, @click="updateGuild", v-once, v-if='isLeader') {{ $t('edit') }}
|
||||
.button-container
|
||||
button.btn.btn-success(class='btn-success', v-if='!isMember') {{ $t('join') }}
|
||||
.button-container
|
||||
button.btn.btn-primary(v-once) {{$t('invite')}}
|
||||
.button-container
|
||||
button.btn.btn-primary(v-once, v-if='!isLeader') {{$t('messageGuildLeader')}}
|
||||
.button-container
|
||||
button.btn.btn-primary(v-once, v-if='isMember && !isParty') {{$t('donateGems')}}
|
||||
|
||||
.section-header
|
||||
.row
|
||||
.col-10
|
||||
h3(v-once) {{ $t('questDetailsTitle') }}
|
||||
.col-2
|
||||
.toggle-up(@click="sections.quest = !sections.quest", v-if="sections.quest")
|
||||
.svg-icon(v-html="icons.upIcon")
|
||||
.toggle-down(@click="sections.quest = !sections.quest", v-if="!sections.quest")
|
||||
.svg-icon(v-html="icons.downIcon")
|
||||
.section(v-if="sections.quest")
|
||||
.row.no-quest-section(v-if='isParty && !onPendingQuest && !onActiveQuest')
|
||||
.col-12.text-center
|
||||
.svg-icon(v-html="icons.questIcon")
|
||||
h4(v-once) {{ $t('yourNotOnQuest') }}
|
||||
p(v-once) {{ $t('questDescription') }}
|
||||
button.btn.btn-secondary(v-once, @click="openStartQuestModal()") {{ $t('startAQuest') }}
|
||||
owned-quests-modal(:group='this.group')
|
||||
.row.quest-active-section(v-if='isParty && onPendingQuest && !onActiveQuest')
|
||||
h2 Pending quest
|
||||
button.btn.btn-secondary(v-once, @click="questForceStart()") {{ $t('begin') }}
|
||||
button.btn.btn-secondary(v-once, @click="questCancel()") {{ $t('cancel') }}
|
||||
.row.quest-active-section(v-if='isParty && !onPendingQuest && onActiveQuest')
|
||||
.col-12.text-center
|
||||
.quest-boss(:class="'quest_' + questData.key")
|
||||
h3(v-once) {{ questData.text() }}
|
||||
.quest-box.svg-icon(v-html="icons.questBackground")
|
||||
.collect-info(v-if='questData.collect')
|
||||
.row(v-for='(value, key) in questData.collect')
|
||||
.col-2
|
||||
div(:class="'quest_' + questData.key + '_' + key")
|
||||
.col-10
|
||||
strong {{value.text()}}
|
||||
.collect-progress-bar
|
||||
strong {{group.quest.progress.collect[key]}} / {{value.count}}
|
||||
.boss-info(v-if='questData.boss')
|
||||
.row
|
||||
.col-6
|
||||
h4.float-left(v-once) {{ questData.boss.name() }}
|
||||
.col-6
|
||||
span.float-right(v-once) {{ $t('participants') }}
|
||||
.row
|
||||
.col-12
|
||||
.boss-health-bar
|
||||
.row.boss-details
|
||||
.col-6
|
||||
span.float-left
|
||||
| {{group.quest.progress.hp}} / {{questData.boss.hp}}
|
||||
.col-6
|
||||
span.float-right 30 pending damage
|
||||
button.btn.btn-secondary(v-once, @click="questAbort()") {{ $t('abort') }}
|
||||
|
||||
.section-header
|
||||
.row
|
||||
.col-10
|
||||
h3(v-once) {{ $t('description') }}
|
||||
.col-2
|
||||
.toggle-up(@click="sections.description = !sections.description", v-if="sections.description")
|
||||
.svg-icon(v-html="icons.upIcon")
|
||||
.toggle-down(@click="sections.description = !sections.description", v-if="!sections.description")
|
||||
.svg-icon(v-html="icons.downIcon")
|
||||
.section(v-if="sections.description")
|
||||
p(v-once) {{ group.description }}
|
||||
p Life hacks are tricks, shortcuts, or methods that help increase productivity, efficiency, health, and so on. Generally, they get you to a better state of life. Life hacking is the process of utilizing and implementing these secrets. And, in this guild, we want to help everyone discover these improved ways of doing things.
|
||||
|
||||
.section-header
|
||||
.row
|
||||
.col-10
|
||||
h3(v-once) {{ $t('guildInformation') }}
|
||||
.col-2
|
||||
.toggle-up(@click="sections.information = !sections.information", v-if="sections.information")
|
||||
.svg-icon(v-html="icons.upIcon")
|
||||
.toggle-down(@click="sections.information = !sections.information", v-if="!sections.information")
|
||||
.svg-icon(v-html="icons.downIcon")
|
||||
.section(v-if="sections.information")
|
||||
h4 Welcome
|
||||
p Below are some resources that some members might find useful. Consider checking them out before posting any questions, as they just might help answer some of them! Feel free to share your life hacks in the guild chat, or ask any questions that you might have. Please peruse at your leisure, and remember: this guild is meant to help guide you in the right direction. Only you will know what works best for you.
|
||||
|
||||
.section-header.challenge
|
||||
.row
|
||||
.col-10.information-header
|
||||
h3(v-once)
|
||||
| {{ $t('challenges') }}
|
||||
b-tooltip.icon.tooltip-wrapper(:content="$t('privateDescription')")
|
||||
.svg-icon(v-html='icons.information')
|
||||
.col-2
|
||||
.toggle-up(@click="sections.challenges = !sections.challenges", v-if="sections.challenges")
|
||||
.svg-icon(v-html="icons.upIcon")
|
||||
.toggle-down(@click="sections.challenges = !sections.challenges", v-if="!sections.challenges")
|
||||
.svg-icon(v-html="icons.downIcon")
|
||||
.section(v-if="sections.challenges")
|
||||
.row.no-quest-section(v-if='!hasChallenges')
|
||||
.col-12.text-center
|
||||
.svg-icon(v-html="icons.challengeIcon")
|
||||
h4(v-once) {{ $t('haveNoChallenges') }}
|
||||
p(v-once) {{ $t('challengeDescription') }}
|
||||
button.btn.btn-secondary(v-once) {{ $t('createChallenge') }}
|
||||
div.text-center
|
||||
button.btn.btn-primary(class='btn-danger', v-if='isMember') {{ $t('leave') }}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.sidebar {
|
||||
background-color: $gray-600;
|
||||
}
|
||||
.button-container {
|
||||
margin-bottom: 1em;
|
||||
|
||||
.card {
|
||||
margin: 2em 0;
|
||||
padding: 1em;
|
||||
|
||||
h3.leader {
|
||||
color: $purple-200;
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
.item-with-icon {
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: $gray-600;
|
||||
padding-top: 2em;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 2em 0;
|
||||
padding: 1em;
|
||||
|
||||
h3.leader {
|
||||
color: $purple-200;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 16px;
|
||||
line-height: 1.43;
|
||||
color: $gray-50;
|
||||
}
|
||||
}
|
||||
|
||||
.guild-background {
|
||||
background-image: linear-gradient(to bottom, rgba($gray-600, 0), $gray-600);
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
background-color: $white;
|
||||
border: solid 1px $gray-400;
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
line-height: 1.43;
|
||||
color: $gray-50;
|
||||
color: $gray-300;
|
||||
padding: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.guild-background {
|
||||
background-image: linear-gradient(to bottom, rgba($gray-600, 0), $gray-600);
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
background-color: $white;
|
||||
border: solid 1px $gray-400;
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
line-height: 1.43;
|
||||
color: $gray-300;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.svg-icon.shield, .svg-icon.gem {
|
||||
width: 40px;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.icon-row {
|
||||
width: 200px;
|
||||
margin-top: 3em;
|
||||
margin-right: 3em;
|
||||
|
||||
.number {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-row {
|
||||
margin-top: 2em;
|
||||
|
||||
.send-chat {
|
||||
margin-top: -3.5em;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
.svg-icon.shield, .svg-icon.gem {
|
||||
width: 40px;
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.hr {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
border-bottom: 1px solid $gray-500;
|
||||
text-align: center;
|
||||
margin: 2em 0;
|
||||
}
|
||||
.title-details {
|
||||
padding-top: 1em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.hr-middle {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-family: 'Roboto Condensed';
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
color: $gray-200;
|
||||
background-color: $gray-700;
|
||||
padding: .2em;
|
||||
margin-top: .2em;
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
.icon-row {
|
||||
margin-top: 1em;
|
||||
|
||||
span.action {
|
||||
font-size: 14px;
|
||||
line-height: 1.33;
|
||||
color: $gray-200;
|
||||
font-weight: 500;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.number {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
span.action .icon {
|
||||
margin-right: .3em;
|
||||
}
|
||||
.chat-row {
|
||||
margin-top: 2em;
|
||||
|
||||
.send-chat {
|
||||
margin-top: -3.5em;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.hr {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
border-bottom: 1px solid $gray-500;
|
||||
text-align: center;
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.hr-middle {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-family: 'Roboto Condensed';
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
color: $gray-200;
|
||||
background-color: $gray-700;
|
||||
padding: .2em;
|
||||
margin-top: .2em;
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
span.action {
|
||||
font-size: 14px;
|
||||
line-height: 1.33;
|
||||
color: $gray-200;
|
||||
font-weight: 500;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
span.action .icon {
|
||||
margin-right: .3em;
|
||||
}
|
||||
|
||||
.no-quest-section {
|
||||
padding: 2em;
|
||||
color: $gray-300;
|
||||
|
||||
h4 {
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.information-header {
|
||||
h3, .tooltip-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip-wrapper {
|
||||
margin-left: 2.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.quest-active-section {
|
||||
.quest-box {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
|
||||
svg: {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.boss-info, .collect-info {
|
||||
position: relative;
|
||||
top: -89px;
|
||||
left: 15px;
|
||||
width: 32em;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
border-top: 1px solid #e1e0e3;
|
||||
margin-top: 1em;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.section-header.challenge {
|
||||
border-bottom: 1px solid #e1e0e3;
|
||||
margin-bottom: 1em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.toggle-up, .toggle-down {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.quest-boss {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.boss-health-bar {
|
||||
width: 80%;
|
||||
background-color: red;
|
||||
height: 15px;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.collect-progress-bar {
|
||||
background-color: #24cc8f;
|
||||
height: 15px;
|
||||
width: 80%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import groupUtilities from 'client/mixins/groupsUtilities';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import membersModal from './membersModal';
|
||||
import ownedQuestsModal from './ownedQuestsModal';
|
||||
import { TAVERN_ID } from 'common/script/constants';
|
||||
import quests from 'common/script/content/quests';
|
||||
import percent from 'common/script/libs/percent';
|
||||
import groupFormModal from './groupFormModal';
|
||||
|
||||
import bCollapse from 'bootstrap-vue/lib/components/collapse';
|
||||
import bCard from 'bootstrap-vue/lib/components/card';
|
||||
import bToggle from 'bootstrap-vue/lib/directives/toggle';
|
||||
import bTooltip from 'bootstrap-vue/lib/components/tooltip';
|
||||
|
||||
import deleteIcon from 'assets/svg/delete.svg';
|
||||
import copyIcon from 'assets/svg/copy.svg';
|
||||
|
|
@ -209,43 +395,85 @@ import likeIcon from 'assets/svg/like.svg';
|
|||
import likedIcon from 'assets/svg/liked.svg';
|
||||
import reportIcon from 'assets/svg/report.svg';
|
||||
import gemIcon from 'assets/svg/gem.svg';
|
||||
import goldGuildBadgeIcon from 'assets/svg/gold-guild-badge.svg';
|
||||
import questIcon from 'assets/svg/quest.svg';
|
||||
import challengeIcon from 'assets/svg/challenge.svg';
|
||||
import informationIcon from 'assets/svg/information.svg';
|
||||
import questBackground from 'assets/svg/quest-background-border.svg';
|
||||
import upIcon from 'assets/svg/up.svg';
|
||||
import downIcon from 'assets/svg/down.svg';
|
||||
|
||||
export default {
|
||||
mixins: [groupUtilities],
|
||||
props: ['guildId'],
|
||||
components: {
|
||||
membersModal,
|
||||
ownedQuestsModal,
|
||||
bCollapse,
|
||||
bCard,
|
||||
bTooltip,
|
||||
groupFormModal,
|
||||
},
|
||||
directives: {
|
||||
bToggle,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
guild: null,
|
||||
group: null,
|
||||
icons: Object.freeze({
|
||||
like: likeIcon,
|
||||
copy: copyIcon,
|
||||
report: reportIcon,
|
||||
delete: deleteIcon,
|
||||
gem: gemIcon,
|
||||
goldGuildBadge: goldGuildBadgeIcon,
|
||||
liked: likedIcon,
|
||||
questIcon,
|
||||
challengeIcon,
|
||||
information: informationIcon,
|
||||
questBackground,
|
||||
upIcon,
|
||||
downIcon,
|
||||
}),
|
||||
questData: {},
|
||||
selectedQuest: {},
|
||||
sections: {
|
||||
quest: true,
|
||||
description: true,
|
||||
information: true,
|
||||
challenges: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
isParty () {
|
||||
return this.$route.path.startsWith('/party');
|
||||
},
|
||||
onPendingQuest () {
|
||||
return Boolean(this.group.quest.key) && !this.group.quest.active;
|
||||
},
|
||||
onActiveQuest () {
|
||||
return this.group.quest.active;
|
||||
},
|
||||
isLeader () {
|
||||
return this.user._id === this.group.leader._id;
|
||||
},
|
||||
isMember () {
|
||||
return this.isMemberOfGroup(this.user, this.guild);
|
||||
return this.isMemberOfGroup(this.user, this.group);
|
||||
},
|
||||
canEditQuest () {
|
||||
let isQuestLeader = this.group.quest && this.group.quest.leader === this.user._id;
|
||||
return isQuestLeader;
|
||||
},
|
||||
isMemberOfPendingQuest () {
|
||||
let userid = this.user._id;
|
||||
let group = this.guild;
|
||||
let group = this.group;
|
||||
if (!group.quest || !group.quest.members) return false;
|
||||
if (group.quest.active) return false; // quest is started, not pending
|
||||
return userid in group.quest.members && group.quest.members[userid] !== false;
|
||||
},
|
||||
isMemberOfRunningQuest () {
|
||||
let userid = this.user._id;
|
||||
let group = this.guild;
|
||||
let group = this.group;
|
||||
if (!group.quest || !group.quest.members) return false;
|
||||
if (!group.quest.active) return false; // quest is pending, not started
|
||||
return group.quest.members[userid];
|
||||
|
|
@ -265,8 +493,28 @@ export default {
|
|||
let userIsManager = Boolean(group.managers[userId]);
|
||||
return leader || userIsManager;
|
||||
},
|
||||
hasChallenges () {
|
||||
if (!this.group.challenges) return false;
|
||||
return this.group.challenges.length === 0;
|
||||
},
|
||||
bossHpPercent () {
|
||||
return percent(this.group.quest.progress.hp, this.questData.boss.hp);
|
||||
},
|
||||
},
|
||||
created () {
|
||||
if (this.isParty) {
|
||||
this.groupId = 'party';
|
||||
// @TODO: Set up from old client. Decide what we need and what we don't
|
||||
// Check Desktop notifs
|
||||
// Mark Chat seen
|
||||
// Load members
|
||||
// Load invites
|
||||
// Load challenges
|
||||
// Load group tasks for group plan
|
||||
// Load approvals for group plan
|
||||
} else if (this.$route.path.startsWith('/guilds/tavern')) {
|
||||
this.groupId = TAVERN_ID;
|
||||
}
|
||||
this.fetchGuild();
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -275,23 +523,28 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
updateGuild () {
|
||||
this.$store.state.editingGroup = this.guild;
|
||||
this.$store.state.editingGroup = this.group;
|
||||
this.$root.$emit('show::modal', 'guild-form');
|
||||
},
|
||||
async fetchGuild () {
|
||||
this.guild = await this.$store.dispatch('guilds:getGroup', {groupId: this.guildId});
|
||||
|
||||
this.guild.chat = [
|
||||
{
|
||||
text: '@CharacterName Vestibulum ultricies, lorem non bibendum consequat, nisl lacus semper nulla, hendrerit dignissim ipsum erat eu odio. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla at aliquet urna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla non est ut nisl interdum tincidunt in eu dui. Proin condimentum a.',
|
||||
},
|
||||
];
|
||||
let group = await this.$store.dispatch('guilds:getGroup', {groupId: this.groupId});
|
||||
if (this.isParty) {
|
||||
this.$store.party = group;
|
||||
this.group = this.$store.party;
|
||||
this.checkForAchievements();
|
||||
this.questData = quests.quests[this.group.quest.key];
|
||||
return;
|
||||
}
|
||||
this.group = group;
|
||||
},
|
||||
deleteAllMessages () {
|
||||
if (confirm(this.$t('confirmDeleteAllMessages'))) {
|
||||
// User.clearPMs();
|
||||
}
|
||||
},
|
||||
openStartQuestModal () {
|
||||
this.$root.$emit('show::modal', 'owned-quests-modal');
|
||||
},
|
||||
// inviteOrStartParty (group) {
|
||||
// Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'});
|
||||
|
||||
|
|
@ -314,6 +567,114 @@ export default {
|
|||
// },
|
||||
// });
|
||||
// },
|
||||
checkForAchievements () {
|
||||
// Checks if user's party has reached 2 players for the first time.
|
||||
if (!this.user.achievements.partyUp && this.group.memberCount >= 2) {
|
||||
// @TODO
|
||||
// User.set({'achievements.partyUp':true});
|
||||
// Achievement.displayAchievement('partyUp');
|
||||
}
|
||||
|
||||
// Checks if user's party has reached 4 players for the first time.
|
||||
if (!this.user.achievements.partyOn && this.group.memberCount >= 4) {
|
||||
// @TODO
|
||||
// User.set({'achievements.partyOn':true});
|
||||
// Achievement.displayAchievement('partyOn');
|
||||
}
|
||||
},
|
||||
// @TODO: This should be moved to notifications component
|
||||
async join () {
|
||||
if (this.group.cancelledPlan && !confirm(this.$t('aboutToJoinCancelledGroupPlan'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$store.dispatch('guilds:join', {groupId: this.group._id});
|
||||
|
||||
// @TODO: Implement
|
||||
// User.sync();
|
||||
// Analytics.updateUser({'partyID': party.id});
|
||||
// $rootScope.hardRedirect('/#/options/groups/party');
|
||||
},
|
||||
clickLeave () {
|
||||
// Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Leave Party'});
|
||||
// @TODO: Get challenges and ask to keep or remove
|
||||
let keep = true;
|
||||
this.leave(keep);
|
||||
},
|
||||
async leave (keepTasks) {
|
||||
let keepChallenges = 'remain-in-challenges';
|
||||
await this.$store.dispatch('guilds:leave', {
|
||||
groupId: this.group._id,
|
||||
keep: keepTasks,
|
||||
keepChallenges,
|
||||
});
|
||||
|
||||
// @TODO: Implement
|
||||
// Analytics.updateUser({'partySize':null,'partyID':null});
|
||||
// User.sync().then(function () {
|
||||
// $rootScope.hardRedirect('/#/options/groups/party');
|
||||
// });
|
||||
},
|
||||
// @TODO: Move to notificatin component
|
||||
async leaveOldPartyAndJoinNewParty () {
|
||||
let newPartyName = 'where does this come from';
|
||||
if (!confirm(`Are you sure you want to delete your party and join${newPartyName}?`)) return;
|
||||
|
||||
let keepChallenges = 'remain-in-challenges';
|
||||
await this.$store.dispatch('guilds:leave', {
|
||||
groupId: this.group._id,
|
||||
keep: false,
|
||||
keepChallenges,
|
||||
});
|
||||
|
||||
await this.$store.dispatch('guilds:join', {groupId: this.group._id});
|
||||
},
|
||||
// @TODO: Move to notificatin component
|
||||
async reject () {
|
||||
await this.$store.dispatch('guilds:rejectInvite', {groupId: this.group._id});
|
||||
// User.sync();
|
||||
},
|
||||
clickStartQuest () {
|
||||
// Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Start a Quest'});
|
||||
let hasQuests = find(this.user.items.quests, (quest) => {
|
||||
return quest > 0;
|
||||
});
|
||||
|
||||
if (hasQuests) {
|
||||
this.$root.$emit('show::modal', 'owned-quests-modal');
|
||||
return;
|
||||
}
|
||||
// $rootScope.$state.go('options.inventory.quests');
|
||||
},
|
||||
async questCancel () {
|
||||
if (!confirm(this.$t('sureCancel'))) return;
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/cancel'});
|
||||
this.group.quest = quest;
|
||||
},
|
||||
async questAbort () {
|
||||
if (!confirm(this.$t('sureAbort'))) return;
|
||||
if (!confirm(this.$t('doubleSureAbort'))) return;
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/abort'});
|
||||
this.group.quest = quest;
|
||||
},
|
||||
async questLeave () {
|
||||
if (!confirm(this.$t('sureLeave'))) return;
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/leave'});
|
||||
this.group.quest = quest;
|
||||
},
|
||||
async questAccept () {
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/accept'});
|
||||
this.group.quest = quest;
|
||||
},
|
||||
async questForceStart () {
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/force-start'});
|
||||
this.group.quest = quest;
|
||||
},
|
||||
// @TODO: Move to notificaitons component?
|
||||
async questReject () {
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/reject'});
|
||||
this.group.quest = quest;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
<template lang="pug">
|
||||
div
|
||||
button.btn.btn-primary(b-btn, @click="$root.$emit('show::modal','members-modal')") {{ $t('viewMembers') }}
|
||||
.item-with-icon(@click="$root.$emit('show::modal','members-modal')")
|
||||
.svg-icon.shield(v-html="icons.goldGuildBadgeIcon")
|
||||
span.number {{group.memberCount}}
|
||||
div(v-once) {{ $t('members') }}
|
||||
|
||||
b-modal#members-modal(:title="$t('createGuild')", size='lg')
|
||||
.header-wrap(slot="modal-header")
|
||||
|
|
@ -19,24 +22,25 @@ div
|
|||
b-dropdown-item(v-for='sortOption in sortOptions', @click='sort(sortOption.value)', :key='sortOption.value') {{sortOption.text}}
|
||||
.row(v-for='member in members', :key='member', )
|
||||
.col-8.offset-1
|
||||
member-details(:user='member')
|
||||
member-details(:member='member')
|
||||
.col-3.actions
|
||||
b-dropdown(:text="$t('sort')", right=true)
|
||||
b-dropdown-item(@click='sort(option.value)')
|
||||
img.action-icon(src='~assets/members/remove.svg')
|
||||
.svg-icon(v-html="icons.removeIcon")
|
||||
| {{$t('removeMember')}}
|
||||
b-dropdown-item(@click='sort(option.value)')
|
||||
img.action-icon(src='~assets/members/message.svg')
|
||||
.svg-icon(v-html="icons.messageIcon")
|
||||
| {{$t('sendMessage')}}
|
||||
b-dropdown-item(@click='sort(option.value)')
|
||||
img.action-icon(src='~assets/members/star.svg')
|
||||
.svg-icon(v-html="icons.starIcon")
|
||||
| {{$t('promoteToLeader')}}
|
||||
b-dropdown-item(@click='sort(option.value)')
|
||||
img.action-icon(src='~assets/members/star.svg')
|
||||
.svg-icon(v-html="icons.starIcon")
|
||||
| {{$t('addManager')}}
|
||||
b-dropdown-item(@click='sort(option.value)')
|
||||
img.action-icon(src='~assets/members/remove.svg')
|
||||
.svg-icon(v-html="icons.removeIcon")
|
||||
| {{$t('removeManager2')}}
|
||||
.row-fluid.gradient
|
||||
|
||||
b-modal#remove-member(:title="$t('confirmRemoveMember')")
|
||||
button(@click='confirmRemoveMember(member)', v-once) {{$t('remove')}}
|
||||
|
|
@ -45,7 +49,7 @@ div
|
|||
button(@click='confirmRemoveMember(member)', v-once) {{$t('remove')}}
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
<style lang='scss' scoped>
|
||||
header {
|
||||
background-color: #edecee;
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
|
@ -66,6 +70,49 @@ div
|
|||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
#members-modal_modal_body {
|
||||
padding: 0;
|
||||
|
||||
.col-8 {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.member-details {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
width: 382px;
|
||||
height: 147px;
|
||||
}
|
||||
|
||||
.gradient {
|
||||
background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0), #ffffff);
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.item-with-icon {
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
padding: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.svg-icon.shield, .svg-icon.gem {
|
||||
width: 40px;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
@ -74,6 +121,10 @@ import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
|||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
|
||||
import MemberDetails from '../memberDetails';
|
||||
import removeIcon from 'assets/members/remove.svg';
|
||||
import messageIcon from 'assets/members/message.svg';
|
||||
import starIcon from 'assets/members/star.svg';
|
||||
import goldGuildBadgeIcon from 'assets/svg/gold-guild-badge.svg';
|
||||
|
||||
export default {
|
||||
props: ['group'],
|
||||
|
|
@ -109,6 +160,12 @@ export default {
|
|||
},
|
||||
],
|
||||
searchTerm: '',
|
||||
icons: Object.freeze({
|
||||
removeIcon,
|
||||
messageIcon,
|
||||
starIcon,
|
||||
goldGuildBadgeIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
54
website/client/components/guilds/ownedQuestsModal.vue
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<template lang="pug">
|
||||
div
|
||||
b-modal#owned-quests-modal(title="Which quest do you want to start?", size='md', hide-footer=true)
|
||||
.row.content
|
||||
.quest(v-for='(value, key, index) in user.items.quests', :class="'inventory_quest_scroll_' + key", @click='selectQuest(key)')
|
||||
button.btn.btn-primary(@click='confirm()') Confirm
|
||||
start-quest-modal(:group='group', :selectedQuest='selectedQuest')
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.content {
|
||||
padding: 2em;
|
||||
|
||||
.quest {
|
||||
margin-right: 1em;
|
||||
margin-bottom: 1em;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
|
||||
import startQuestModal from './startQuestModal';
|
||||
|
||||
export default {
|
||||
props: ['group'],
|
||||
components: {
|
||||
bModal,
|
||||
startQuestModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
selectedQuest: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
},
|
||||
methods: {
|
||||
selectQuest (quest) {
|
||||
this.selectedQuest = quest;
|
||||
},
|
||||
confirm () {
|
||||
this.$root.$emit('show::modal', 'start-quest-modal');
|
||||
this.$root.$emit('hide::modal', 'owned-quests-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -35,20 +35,6 @@
|
|||
box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1);
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.category-label {
|
||||
min-width: 100px;
|
||||
border-radius: 100px;
|
||||
background-color: $gray-600;
|
||||
padding: .5em;
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
.recommend-text {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
|
|
|
|||
156
website/client/components/guilds/startQuestModal.vue
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<template lang="pug">
|
||||
b-modal#start-quest-modal(title="Empty", size='md', hide-footer=true, v-if='questData')
|
||||
.quest-image(:class="'quest_' + questData.key")
|
||||
h2 {{questData.text()}}
|
||||
//- span by: Keith Holliday @TODO: Add author
|
||||
p {{questData.notes()}}
|
||||
div.quest-details
|
||||
div(v-if=' questData.collect')
|
||||
Strong {{$t('collect')}}:
|
||||
span(v-for="(value, key, index) in questData.collect")
|
||||
| {{$t('collectionItems', { number: questData.collect[key].count, items: questData.collect[key].text() })}}
|
||||
div
|
||||
Strong {{$t('collect')}}:
|
||||
span
|
||||
.svg-icon(v-html="icons.difficultyStarIcon")
|
||||
div
|
||||
button.btn.btn-primary(@click='questInit()') {{$t('inviteToPartyOrQuest')}}
|
||||
div
|
||||
p {{$t('inviteInformation')}}
|
||||
.side-panel
|
||||
h4.text-center {{$t('rewards')}}
|
||||
.box
|
||||
.svg-icon.rewards-icon(v-html="icons.starIcon")
|
||||
strong {{questData.drop.exp}} {{$t('experience')}}
|
||||
.box
|
||||
.svg-icon.rewards-icon(v-html="icons.goldIcon")
|
||||
strong {{questData.drop.gp}} {{$t('gold')}}
|
||||
h4.text-center(v-if='questData.drop.items') {{$t('questOwnerRewards')}}
|
||||
.box(v-for='item in questData.drop.items')
|
||||
.rewards-icon(v-if='item.type === "quest"', :class="'quest_' + item.key")
|
||||
.drop-rewards-icon(v-if='item.type === "gear"', :class="'shop_' + item.key")
|
||||
strong.quest-reward-text {{item.text()}}
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
header {
|
||||
background-color: $white !important;
|
||||
border: none !important;
|
||||
|
||||
h5 {
|
||||
text-indent: -99999px;
|
||||
}
|
||||
}
|
||||
|
||||
.quest-image {
|
||||
margin: 0 auto;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.quest-details {
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.side-panel {
|
||||
background: #edecee;
|
||||
position: absolute;
|
||||
height: 460px;
|
||||
width: 320px;
|
||||
top: -1.8em;
|
||||
left: 35em;
|
||||
z-index: -1;
|
||||
padding-top: 1em;
|
||||
border-radius: 4px;
|
||||
|
||||
.drop-rewards-icon {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.rewards-icon {
|
||||
float: left;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
|
||||
svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.quest-reward-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 220px;
|
||||
height: 64px;
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 1em;
|
||||
padding: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
|
||||
import quests from 'common/script/content/quests';
|
||||
|
||||
import copyIcon from 'assets/svg/copy.svg';
|
||||
import greyBadgeIcon from 'assets/svg/grey-badge.svg';
|
||||
import qrCodeIcon from 'assets/svg/qrCode.svg';
|
||||
import facebookIcon from 'assets/svg/facebook.svg';
|
||||
import twitterIcon from 'assets/svg/twitter.svg';
|
||||
import starIcon from 'assets/svg/star.svg';
|
||||
import goldIcon from 'assets/svg/gold.svg';
|
||||
import difficultyStarIcon from 'assets/svg/difficulty-star.svg';
|
||||
|
||||
export default {
|
||||
props: ['group', 'selectedQuest'],
|
||||
components: {
|
||||
bModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
copy: copyIcon,
|
||||
greyBadge: greyBadgeIcon,
|
||||
qrCode: qrCodeIcon,
|
||||
facebook: facebookIcon,
|
||||
twitter: twitterIcon,
|
||||
starIcon,
|
||||
goldIcon,
|
||||
difficultyStarIcon,
|
||||
}),
|
||||
shareUserIdShown: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
questData () {
|
||||
return quests.quests[this.selectedQuest];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async questInit () {
|
||||
let key = this.selectedQuest;
|
||||
// Analytics.updateUser({'partyID': party._id, 'partySize': party.memberCount});
|
||||
let response = await this.$store.dispatch('guilds:inviteToQuest', {groupId: this.group._id, key});
|
||||
let quest = response.data.data;
|
||||
this.$store.party.quest = quest;
|
||||
this.$root.$emit('hide::modal', 'start-quest-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -91,8 +91,7 @@
|
|||
}
|
||||
|
||||
.drawer-slider {
|
||||
padding: 12px 0 0 24px;
|
||||
margin-left: -24px;
|
||||
padding: 12px 0 0 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
|
|
|||
|
|
@ -16,13 +16,16 @@ b-popover(
|
|||
.item-wrapper
|
||||
.item
|
||||
slot(name="itemBadge", :item="item")
|
||||
span.item-content(:class="itemContentClass")
|
||||
span.item-content(
|
||||
:class="itemContentClass",
|
||||
:draggable="draggable",
|
||||
@dragstart="onDrag"
|
||||
)
|
||||
span.item-label(v-if="label") {{ label }}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -46,16 +49,22 @@ export default {
|
|||
type: String,
|
||||
default: 'bottom',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
ATTRIBUTES: 'constants.ATTRIBUTES',
|
||||
}),
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
click () {
|
||||
this.$emit('click', this.item);
|
||||
},
|
||||
onDrag (ev) {
|
||||
if (this.draggable) {
|
||||
this.$emit('onDrag', ev);
|
||||
} else {
|
||||
ev.preventDefault();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,131 +0,0 @@
|
|||
<template lang="pug">
|
||||
.row
|
||||
.col-2.standard-sidebar
|
||||
.form-group
|
||||
input.form-control(type="text", :placeholder="$t('search')")
|
||||
|
||||
.form
|
||||
h3(v-once) {{ $t('filter') }}
|
||||
.form-group
|
||||
.form-check
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
strong {{ $t('pets') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('hatchingPotions') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('quest') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('special') }}
|
||||
.form-group
|
||||
.form-check
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
strong {{ $t('mounts') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('hatchingPotions') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('quest') }}
|
||||
.form-check.nested-field
|
||||
label.form-check-label(v-once)
|
||||
input.form-check-input(type="checkbox")
|
||||
span {{ $t('special') }}
|
||||
|
||||
.col-10.standard-page
|
||||
h4 Pets
|
||||
.inventory-item-container(v-for="pet in dropPets")
|
||||
.PixelPaw
|
||||
.btn.btn-secondary.d-block(@click="open.dropPets = !open.dropPets") {{ open.dropPets ? 'Close' : 'Open' }}
|
||||
|
||||
h2 Magic Potions Pets
|
||||
.inventory-item-container(v-for="pet in magicPets")
|
||||
.PixelPaw
|
||||
.btn.btn-secondary.d-block(@click="open.magicPets = !open.magicPets") {{ open.magicPets ? 'Close' : 'Open' }}
|
||||
|
||||
h2 Quest Pets
|
||||
.inventory-item-container(v-for="pet in questPets")
|
||||
.PixelPaw
|
||||
.btn.btn-secondary.d-block(@click="open.questPets = !open.questPets") {{ open.questPets ? 'Close' : 'Open' }}
|
||||
|
||||
h2 Rare Pets
|
||||
.inventory-item-container(v-for="pet in rarePets")
|
||||
.PixelPaw
|
||||
.btn.btn-secondary.d-block(@click="open.rarePets = !open.rarePets") {{ open.rarePets ? 'Close' : 'Open' }}
|
||||
|
||||
h2 Mounts
|
||||
h2 Quest Mounts
|
||||
h2 Rare Mounts
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.inventory-item-container {
|
||||
padding: 20px;
|
||||
border: 1px solid;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
import each from 'lodash/each';
|
||||
|
||||
// TODO Normalize special pets and mounts
|
||||
// import Store from 'client/store';
|
||||
// import deepFreeze from 'client/libs/deepFreeze';
|
||||
// const specialMounts =
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
open: {
|
||||
dropPets: false,
|
||||
magicPets: false,
|
||||
questPets: false,
|
||||
rarePets: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['content']),
|
||||
dropPets () {
|
||||
return this.listAnimals('pet', this.content.dropEggs, this.content.dropHatchingPotions, this.open.dropPets);
|
||||
},
|
||||
magicPets () {
|
||||
return this.listAnimals('pet', this.content.dropEggs, this.content.premiumHatchingPotions, this.open.magicPets);
|
||||
},
|
||||
questPets () {
|
||||
return this.listAnimals('pet', this.content.questEggs, this.content.dropHatchingPotions, this.open.questPets);
|
||||
},
|
||||
rarePets () {
|
||||
return this.listAnimals('pet', this.content.dropEggs, this.content.dropHatchingPotions, this.open.rarePets);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
listAnimals (type, eggSource, potionSource, isOpen = false) {
|
||||
let animals = [];
|
||||
let iteration = 0;
|
||||
|
||||
each(eggSource, (egg) => {
|
||||
if (iteration === 1 && !isOpen) return false;
|
||||
iteration++;
|
||||
each(potionSource, (potion) => {
|
||||
let animalKey = `${egg.key}-${potion.key}`;
|
||||
animals.push(this.content[`${type}Info`][animalKey].text());
|
||||
});
|
||||
});
|
||||
|
||||
return animals;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
33
website/client/components/inventory/stable/countBadge.vue
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<template lang="pug">
|
||||
span.badge.badge-pill.badge-item.badge-count(
|
||||
v-if="show",
|
||||
) {{ count }}
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.badge-count {
|
||||
right: -9px;
|
||||
color: $white;
|
||||
background: $orange-100;
|
||||
padding: 4.5px 6px;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
box-shadow: 0 1px 1px 0 rgba($black, 0.12);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
174
website/client/components/inventory/stable/drawerSlider.vue
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<template lang="pug">
|
||||
div.slider-root(
|
||||
v-bind:class="{'scrollButtonsVisible': scrollButtonsVisible}",
|
||||
)
|
||||
div.slider-button-area.left-button(
|
||||
v-if="scrollButtonsVisible",
|
||||
@mousedown.left="lastPage"
|
||||
)
|
||||
a.slider-button
|
||||
.svg-icon(v-html="icons.previous")
|
||||
div.slider-button-area.right-button(
|
||||
v-if="scrollButtonsVisible",
|
||||
@mousedown.left="nextPage"
|
||||
)
|
||||
a.slider-button
|
||||
.svg-icon(v-html="icons.next")
|
||||
|
||||
// 120 = width of the left/right buttons
|
||||
div.sliding-content(v-resize="500", @resized="currentWidth = $event.width - 120")
|
||||
.items.items-one-line
|
||||
template(v-for="item in pages[currentPage]")
|
||||
div.vertical-divider(v-if="item.ofNextPage")
|
||||
|
||||
slot(
|
||||
name="item",
|
||||
:item="item",
|
||||
)
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
$buttonAreaWidth: 60;
|
||||
|
||||
.slider-root {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.slider-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
position: absolute;
|
||||
top: calc((100% - 40px) / 2);
|
||||
|
||||
.svg-icon {
|
||||
color: #a5a1ac;
|
||||
margin: auto 0;
|
||||
position: absolute;
|
||||
top: calc((100% - 12px) / 2);
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.scrollButtonsVisible {
|
||||
|
||||
.sliding-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-button-area {
|
||||
width: $buttonAreaWidth+px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
|
||||
&.left-button {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.right-button {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sliding-content .items {
|
||||
padding-top: 10px;
|
||||
margin-left: $buttonAreaWidth+ px;
|
||||
|
||||
& > div:last-of-type {
|
||||
margin-right: $buttonAreaWidth + 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical-divider {
|
||||
height: 92px;
|
||||
width: 1px;
|
||||
background: #34313a;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import previous from 'assets/svg/previous.svg';
|
||||
import next from 'assets/svg/next.svg';
|
||||
import ResizeDirective from 'client/directives/resize.directive';
|
||||
|
||||
import _chunk from 'lodash/chunk';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
resize: ResizeDirective,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
previous,
|
||||
next,
|
||||
}),
|
||||
currentWidth: 0,
|
||||
currentPage: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pages () {
|
||||
return _chunk(this.items, this.itemsPerPage() - 1).map((content, index, array) => {
|
||||
let resultData = [...content];
|
||||
|
||||
if (array[index + 1]) {
|
||||
resultData.push({
|
||||
...array[index + 1][0],
|
||||
ofNextPage: true,
|
||||
});
|
||||
}
|
||||
|
||||
return resultData;
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
lastPage () {
|
||||
if (this.currentPage > 0) {
|
||||
this.currentPage--;
|
||||
} else {
|
||||
this.currentPage = this.pages.length - 1;
|
||||
}
|
||||
},
|
||||
|
||||
nextPage () {
|
||||
if (this.currentPage < this.pages.length - 1) {
|
||||
this.currentPage++;
|
||||
} else {
|
||||
this.currentPage = 0;
|
||||
}
|
||||
},
|
||||
|
||||
itemsPerPage () {
|
||||
return Math.floor(this.currentWidth / (this.itemWidth + this.itemMargin));
|
||||
},
|
||||
},
|
||||
props: {
|
||||
scrollButtonsVisible: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
},
|
||||
itemWidth: {
|
||||
type: Number,
|
||||
},
|
||||
itemMargin: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
48
website/client/components/inventory/stable/foodItem.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<template lang="pug">
|
||||
b-popover(
|
||||
:triggers="['hover']",
|
||||
:placement="'top'",
|
||||
)
|
||||
span(slot="content")
|
||||
h4.popover-content-title {{ item.text() }}
|
||||
div.popover-content-text(v-html="item.notes()")
|
||||
|
||||
.item-wrapper
|
||||
.item
|
||||
countBadge(
|
||||
:show="true",
|
||||
:count="itemCount"
|
||||
)
|
||||
span.item-content(
|
||||
:class="'Pet_Food_'+item.key",
|
||||
v-drag.food="item.key"
|
||||
)
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import DragDropDirective from 'client/directives/dragdrop.directive';
|
||||
|
||||
import CountBadge from './countBadge';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
bPopover,
|
||||
CountBadge,
|
||||
},
|
||||
directives: {
|
||||
drag: DragDropDirective,
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
},
|
||||
itemCount: {
|
||||
type: Number,
|
||||
},
|
||||
itemContentClass: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
721
website/client/components/inventory/stable/index.vue
Normal file
|
|
@ -0,0 +1,721 @@
|
|||
<template lang="pug">
|
||||
.row.stable
|
||||
.standard-sidebar
|
||||
div
|
||||
b-popover(
|
||||
:triggers="['hover']",
|
||||
:placement="'right'"
|
||||
)
|
||||
span(slot="content")
|
||||
h4.popover-content-title(v-once) {{ $t('mattBoch') }}
|
||||
.popover-content-text(v-once) {{ $t('mattBochText1') }}
|
||||
|
||||
div.npc_matt
|
||||
|
||||
.form-group
|
||||
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
|
||||
|
||||
.form
|
||||
h2(v-once) {{ $t('filter') }}
|
||||
h3(v-once) {{ $t('pets') }}
|
||||
.form-group
|
||||
.form-check(
|
||||
v-for="petGroup in petGroups",
|
||||
v-if="viewOptions[petGroup.key].animalCount != 0",
|
||||
:key="petGroup.key"
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="viewOptions[petGroup.key].selected")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ petGroup.label }}
|
||||
h3(v-once) {{ $t('mounts') }}
|
||||
.form-group
|
||||
.form-check(
|
||||
v-for="mountGroup in mountGroups",
|
||||
v-if="viewOptions[mountGroup.key].animalCount != 0",
|
||||
:key="mountGroup.key"
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="viewOptions[mountGroup.key].selected")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ mountGroup.label }}
|
||||
|
||||
div.form-group.clearfix
|
||||
h3.float-left Hide Missing
|
||||
toggle-switch.float-right.hideMissing(
|
||||
:label="''",
|
||||
:checked="hideMissing",
|
||||
@change="updateHideMissing"
|
||||
)
|
||||
|
||||
.standard-page(v-resize="500", @resized="availableContentWidth = $event.width - 48")
|
||||
.clearfix
|
||||
h1.float-left.mb-0.page-header(v-once) {{ $t('stable') }}
|
||||
|
||||
div.float-right
|
||||
span.dropdown-label {{ $t('sortBy') }}
|
||||
b-dropdown(:text="$t(selectedSortBy)", right=true)
|
||||
b-dropdown-item(
|
||||
v-for="sort in sortByItems",
|
||||
@click="selectedSortBy = sort",
|
||||
:active="selectedSortBy === sort",
|
||||
:key="sort"
|
||||
) {{ $t(sort) }}
|
||||
|
||||
h2
|
||||
| {{ $t('pets') }}
|
||||
|
|
||||
span.badge.badge-pill.badge-default {{countOwnedAnimals(petGroups[0], 'pet')}}
|
||||
|
||||
div(
|
||||
v-for="petGroup in petGroups",
|
||||
v-if="viewOptions[petGroup.key].selected",
|
||||
:key="petGroup.key"
|
||||
)
|
||||
h4(v-if="viewOptions[petGroup.key].animalCount != 0") {{ petGroup.label }}
|
||||
|
||||
div.items
|
||||
div(
|
||||
v-for="pet in pets(petGroup, viewOptions[petGroup.key].open, hideMissing, selectedSortBy, searchTextThrottled, availableContentWidth)",
|
||||
:key="pet.key",
|
||||
v-drag.drop.food="pet.key",
|
||||
@dragover="onDragOver($event, pet)",
|
||||
@dropped="onDrop($event, pet)",
|
||||
)
|
||||
petItem(
|
||||
:item="pet",
|
||||
:itemContentClass="getPetItemClass(pet)",
|
||||
:popoverPosition="'top'",
|
||||
:progress="pet.progress",
|
||||
:emptyItem="!pet.isOwned()",
|
||||
:showPopover="pet.isOwned() || pet.isHatchable()",
|
||||
@hatchPet="hatchPet",
|
||||
)
|
||||
span(slot="popoverContent")
|
||||
div(v-if="pet.isOwned()")
|
||||
h4.popover-content-title {{ pet.name }}
|
||||
div.hatchablePopover(v-else-if="pet.isHatchable()")
|
||||
h4.popover-content-title {{ pet.name }}
|
||||
div.popover-content-text(v-html="$t('haveHatchablePet', { potion: pet.potionName, egg: pet.eggName })")
|
||||
div.potionEggGroup
|
||||
div.potionEggBackground
|
||||
div(:class="'Pet_HatchingPotion_'+pet.potionKey")
|
||||
div.potionEggBackground
|
||||
div(:class="'Pet_Egg_'+pet.eggKey")
|
||||
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
starBadge(
|
||||
:selected="ctx.item.key === currentPet",
|
||||
:show="ctx.item.isOwned()",
|
||||
@click="selectPet(ctx.item)",
|
||||
)
|
||||
|
||||
.btn.btn-show-more(
|
||||
@click="viewOptions[petGroup.key].open = !viewOptions[petGroup.key].open",
|
||||
v-if="viewOptions[petGroup.key].animalCount != 0"
|
||||
) {{ $t(viewOptions[petGroup.key].open ? 'showLessAnimals' : 'showAllAnimals', { color: petGroup.label, type: $t('pets')}) }}
|
||||
|
||||
h2
|
||||
| {{ $t('mounts') }}
|
||||
|
|
||||
span.badge.badge-pill.badge-default {{countOwnedAnimals(mountGroups[0], 'mount')}}
|
||||
|
||||
div(
|
||||
v-for="mountGroup in mountGroups",
|
||||
v-if="viewOptions[mountGroup.key].selected",
|
||||
:key="mountGroup.key"
|
||||
)
|
||||
h4(v-if="viewOptions[mountGroup.key].animalCount != 0") {{ mountGroup.label }}
|
||||
|
||||
div.items
|
||||
item(
|
||||
v-for="mount in mounts(mountGroup, viewOptions[mountGroup.key].open, hideMissing, selectedSortBy, searchTextThrottled, availableContentWidth)",
|
||||
:item="mount",
|
||||
:itemContentClass="mount.isOwned() ? ('Mount_Icon_' + mount.key) : 'PixelPaw greyedOut'",
|
||||
:key="mount.key",
|
||||
:popoverPosition="'top'"
|
||||
)
|
||||
span(slot="popoverContent")
|
||||
h4.popover-content-title {{ mount.name }}
|
||||
template(slot="itemBadge", scope="ctx")
|
||||
starBadge(
|
||||
:selected="ctx.item.key === currentMount",
|
||||
:show="mount.isOwned()",
|
||||
@click="selectMount(ctx.item)",
|
||||
)
|
||||
|
||||
.btn.btn-show-more(
|
||||
@click="viewOptions[mountGroup.key].open = !viewOptions[mountGroup.key].open",
|
||||
v-if="viewOptions[mountGroup.key].animalCount != 0"
|
||||
) {{ $t(viewOptions[mountGroup.key].open ? 'showLessAnimals' : 'showAllAnimals', { color: mountGroup.label, type: $t('mounts')}) }}
|
||||
|
||||
drawer(
|
||||
:title="$t('quickInventory')",
|
||||
:errorMessage="(!hasDrawerTabItems(selectedDrawerTab)) ? $t('noFoodAvailable') : null"
|
||||
)
|
||||
div(slot="drawer-header")
|
||||
.drawer-tab-container
|
||||
.drawer-tab.text-right
|
||||
a.drawer-tab-text(
|
||||
@click="selectedDrawerTab = 0",
|
||||
:class="{'drawer-tab-text-active': selectedDrawerTab === 0}",
|
||||
) {{ drawerTabs[0].label }}
|
||||
.clearfix
|
||||
.drawer-tab.float-left
|
||||
a.drawer-tab-text(
|
||||
@click="selectedDrawerTab = 1",
|
||||
:class="{ 'drawer-tab-text-active': selectedDrawerTab === 1 }",
|
||||
) {{ drawerTabs[1].label }}
|
||||
|
||||
b-popover(
|
||||
:triggers="['hover']",
|
||||
:placement="'top'"
|
||||
)
|
||||
span(slot="content")
|
||||
.popover-content-text Test Popover
|
||||
|
||||
div.float-right What does my pet like to eat?
|
||||
|
||||
|
||||
drawer-slider(
|
||||
:items="drawerTabs[selectedDrawerTab].items",
|
||||
:scrollButtonsVisible="hasDrawerTabItems(selectedDrawerTab)",
|
||||
slot="drawer-slider",
|
||||
:itemWidth=94,
|
||||
:itemMargin=24,
|
||||
)
|
||||
template(slot="item", scope="ctx")
|
||||
foodItem(
|
||||
:item="ctx.item",
|
||||
:itemCount="userItems.food[ctx.item.key]",
|
||||
)
|
||||
|
||||
b-modal#welcome-modal(
|
||||
:ok-only="true",
|
||||
:ok-title="$t('gotIt')",
|
||||
:visible="!hideDialog",
|
||||
:hide-header="true"
|
||||
)
|
||||
div.content
|
||||
div.npc_matt
|
||||
h1.page-header(v-once) {{ $t('welcomeStable') }}
|
||||
div.content-text(v-once) {{ $t('welcomeStableText') }}
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.inventory-item-container {
|
||||
padding: 20px;
|
||||
border: 1px solid;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.stable .item .item-content.Pet {
|
||||
position: absolute;
|
||||
top: -28px;
|
||||
}
|
||||
|
||||
.toggle-switch-container.hideMissing {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.hatchablePopover {
|
||||
width: 180px
|
||||
}
|
||||
|
||||
.potionEggGroup {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.potionEggBackground {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 2px;
|
||||
background-color: #4e4a57;
|
||||
|
||||
&:first-child {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
& div {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.GreyedOut {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.item.item-empty {
|
||||
width: 94px;
|
||||
height: 92px;
|
||||
border-radius: 2px;
|
||||
background-color: #edecee;
|
||||
}
|
||||
|
||||
.npc_matt {
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
|
||||
.stable {
|
||||
|
||||
.standard-page {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.drawer-container {
|
||||
// 3% padding + 252px sidebar width
|
||||
left: calc(3% + 252px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-slider .items {
|
||||
height: 114px;
|
||||
}
|
||||
|
||||
|
||||
div#welcome-modal {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
header, footer {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.npc_matt {
|
||||
margin: 0 auto 21px auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
|
||||
// the modal already has 15px padding
|
||||
margin-left: 33px;
|
||||
margin-right: 33px;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
line-height: 1.43;
|
||||
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-backdrop.fade.show {
|
||||
background-color: $purple-50;
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'client/libs/store';
|
||||
|
||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
|
||||
import _each from 'lodash/each';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _take from 'lodash/take';
|
||||
import _filter from 'lodash/filter';
|
||||
import _drop from 'lodash/drop';
|
||||
import _flatMap from 'lodash/flatMap';
|
||||
import _throttle from 'lodash/throttle';
|
||||
|
||||
import Item from '../item';
|
||||
import PetItem from './petItem';
|
||||
import FoodItem from './foodItem';
|
||||
import Drawer from 'client/components/inventory/drawer';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
import StarBadge from 'client/components/inventory/starBadge';
|
||||
import CountBadge from './countBadge';
|
||||
import DrawerSlider from './drawerSlider';
|
||||
|
||||
import ResizeDirective from 'client/directives/resize.directive';
|
||||
import DragDropDirective from 'client/directives/dragdrop.directive';
|
||||
|
||||
// TODO Normalize special pets and mounts
|
||||
// import Store from 'client/store';
|
||||
// import deepFreeze from 'client/libs/deepFreeze';
|
||||
// const specialMounts =
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PetItem,
|
||||
Item,
|
||||
FoodItem,
|
||||
Drawer,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
bPopover,
|
||||
bModal,
|
||||
toggleSwitch,
|
||||
StarBadge,
|
||||
CountBadge,
|
||||
DrawerSlider,
|
||||
},
|
||||
directives: {
|
||||
resize: ResizeDirective,
|
||||
drag: DragDropDirective,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
viewOptions: {},
|
||||
hideMissing: false,
|
||||
|
||||
searchText: null,
|
||||
searchTextThrottled: '',
|
||||
|
||||
// sort has the translation-keys as values
|
||||
selectedSortBy: 'standard',
|
||||
sortByItems: [
|
||||
'standard',
|
||||
'AZ',
|
||||
'sortByColor',
|
||||
'sortByHatchable',
|
||||
],
|
||||
|
||||
selectedDrawerTab: 0,
|
||||
availableContentWidth: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
searchText: _throttle(function throttleSearch () {
|
||||
let search = this.searchText.toLowerCase();
|
||||
|
||||
this.searchTextThrottled = search;
|
||||
}, 250),
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
content: 'content',
|
||||
currentPet: 'user.data.items.currentPet',
|
||||
currentMount: 'user.data.items.currentMount',
|
||||
userItems: 'user.data.items',
|
||||
hideDialog: 'user.data.flags.tutorial.common.mounts',
|
||||
}),
|
||||
|
||||
petGroups () {
|
||||
let petGroups = [
|
||||
{
|
||||
label: this.$t('filterByStandard'),
|
||||
key: 'standardPets',
|
||||
petSource: {
|
||||
eggs: this.content.dropEggs,
|
||||
potions: this.content.dropHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('filterByMagicPotion'),
|
||||
key: 'magicPets',
|
||||
petSource: {
|
||||
eggs: this.content.dropEggs,
|
||||
potions: this.content.premiumHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('filterByQuest'),
|
||||
key: 'questPets',
|
||||
petSource: {
|
||||
eggs: this.content.questEggs,
|
||||
potions: this.content.dropHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('special'),
|
||||
key: 'specialPets',
|
||||
petSource: {
|
||||
special: this.content.specialPets,
|
||||
},
|
||||
alwaysHideMissing: true,
|
||||
},
|
||||
];
|
||||
|
||||
petGroups.map((petGroup) => {
|
||||
this.$set(this.viewOptions, petGroup.key, {
|
||||
selected: true,
|
||||
open: false,
|
||||
animalCount: 0,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return petGroups;
|
||||
},
|
||||
mountGroups () {
|
||||
let mountGroups = [
|
||||
{
|
||||
label: this.$t('filterByStandard'),
|
||||
key: 'standardMounts',
|
||||
petSource: {
|
||||
eggs: this.content.dropEggs,
|
||||
potions: this.content.dropHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('filterByMagicPotion'),
|
||||
key: 'magicMounts',
|
||||
petSource: {
|
||||
eggs: this.content.dropEggs,
|
||||
potions: this.content.premiumHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('filterByQuest'),
|
||||
key: 'questMounts',
|
||||
petSource: {
|
||||
eggs: this.content.questEggs,
|
||||
potions: this.content.dropHatchingPotions,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.$t('special'),
|
||||
key: 'specialMounts',
|
||||
petSource: {
|
||||
special: this.content.specialMounts,
|
||||
},
|
||||
alwaysHideMissing: true,
|
||||
},
|
||||
];
|
||||
|
||||
mountGroups.map((mountGroup) => {
|
||||
this.$set(this.viewOptions, mountGroup.key, {
|
||||
selected: true,
|
||||
open: false,
|
||||
animalCount: 0,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return mountGroups;
|
||||
},
|
||||
|
||||
drawerTabs () {
|
||||
return [
|
||||
{
|
||||
label: this.$t('food'),
|
||||
items: _filter(this.content.food, f => {
|
||||
return f.key !== 'Saddle' && this.userItems.food[f.key];
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: this.$t('special'),
|
||||
items: _filter(this.content.food, f => {
|
||||
return f.key === 'Saddle' && this.userItems.food[f.key];
|
||||
}),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
getAnimalList (animalGroup, type) {
|
||||
let key = animalGroup.key;
|
||||
|
||||
this.cachedAnimalList = this.cachedAnimalList || {};
|
||||
if (this.cachedAnimalList[key]) {
|
||||
return this.cachedAnimalList[key];
|
||||
}
|
||||
|
||||
let animals = [];
|
||||
let userItems = this.userItems;
|
||||
|
||||
switch (key) {
|
||||
case 'specialPets':
|
||||
case 'specialMounts': {
|
||||
_each(animalGroup.petSource.special, (value, specialKey) => {
|
||||
let eggKey = specialKey.split('-')[0];
|
||||
let potionKey = specialKey.split('-')[1];
|
||||
|
||||
animals.push({
|
||||
key: specialKey,
|
||||
eggKey,
|
||||
potionKey,
|
||||
pet: this.content[`${type}Info`][specialKey].text(),
|
||||
isOwned () {
|
||||
return [`${type}s`][this.key] > 0;
|
||||
},
|
||||
isHatchable () {
|
||||
return false;
|
||||
},
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
_each(animalGroup.petSource.eggs, (egg) => {
|
||||
_each(animalGroup.petSource.potions, (potion) => {
|
||||
let animalKey = `${egg.key}-${potion.key}`;
|
||||
|
||||
animals.push({
|
||||
key: animalKey,
|
||||
eggKey: egg.key,
|
||||
eggName: egg.text(),
|
||||
potionKey: potion.key,
|
||||
potionName: potion.text(),
|
||||
name: this.content[`${type}Info`][animalKey].text(),
|
||||
isOwned () {
|
||||
return userItems[`${type}s`][animalKey] > 0;
|
||||
},
|
||||
isHatchable () {
|
||||
return userItems.eggs[egg.key] > 0 && userItems.hatchingPotions[potion.key] > 0;
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedAnimalList[key] = animals;
|
||||
|
||||
return animals;
|
||||
},
|
||||
|
||||
listAnimals (animalGroup, type, isOpen, hideMissing, sort, searchText, availableSpace) {
|
||||
let animals = this.getAnimalList(animalGroup, type);
|
||||
let isPetList = type === 'pet';
|
||||
let withProgress = isPetList && animalGroup.key !== 'specialPets';
|
||||
|
||||
// 1. Filter
|
||||
if (hideMissing || animalGroup.alwaysHideMissing) {
|
||||
animals = _filter(animals, (a) => {
|
||||
return a.isOwned();
|
||||
});
|
||||
}
|
||||
|
||||
if (searchText && searchText !== '') {
|
||||
animals = _filter(animals, (a) => {
|
||||
return a.name.toLowerCase().indexOf(searchText) !== -1;
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Sort
|
||||
switch (sort) {
|
||||
case 'AZ':
|
||||
animals = _sortBy(animals, ['pet']);
|
||||
break;
|
||||
|
||||
case 'sortByColor':
|
||||
animals = _sortBy(animals, ['potionName']);
|
||||
break;
|
||||
|
||||
case 'sortByHatchable': {
|
||||
if (isPetList) {
|
||||
let sortFunc = (i) => {
|
||||
return i.isHatchable() ? 0 : 1;
|
||||
};
|
||||
|
||||
animals = _sortBy(animals, [sortFunc]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let animalRows = [];
|
||||
|
||||
let itemsPerRow = Math.floor(availableSpace / (94 + 24));
|
||||
|
||||
let rowsToShow = isOpen ? Math.ceil(animals.length / itemsPerRow) : 1;
|
||||
|
||||
for (let i = 0; i < rowsToShow; i++) {
|
||||
let skipped = _drop(animals, i * itemsPerRow);
|
||||
let row = _take(skipped, itemsPerRow);
|
||||
|
||||
let rowWithProgressData = withProgress ? _flatMap(row, (a) => {
|
||||
let progress = this.userItems[`${type}s`][a.key];
|
||||
|
||||
return {
|
||||
...a,
|
||||
progress,
|
||||
};
|
||||
}) : row;
|
||||
|
||||
animalRows.push(...rowWithProgressData);
|
||||
}
|
||||
|
||||
this.viewOptions[animalGroup.key].animalCount = animals.length;
|
||||
|
||||
return animalRows;
|
||||
},
|
||||
|
||||
countOwnedAnimals (animalGroup, type) {
|
||||
let animals = this.getAnimalList(animalGroup, type);
|
||||
|
||||
let countAll = animals.length;
|
||||
let countOwned = _filter(animals, (a) => {
|
||||
return a.isOwned();
|
||||
});
|
||||
|
||||
return `${countOwned.length}/${countAll}`;
|
||||
},
|
||||
|
||||
pets (animalGroup, showAll, hideMissing, sortBy, searchText, availableSpace) {
|
||||
return this.listAnimals(animalGroup, 'pet', showAll, hideMissing, sortBy, searchText, availableSpace);
|
||||
},
|
||||
|
||||
mounts (animalGroup, showAll, hideMissing, sortBy, searchText, availableSpace) {
|
||||
return this.listAnimals(animalGroup, 'mount', showAll, hideMissing, sortBy, searchText, availableSpace);
|
||||
},
|
||||
|
||||
getPetItemClass (pet) {
|
||||
if (pet.isOwned()) {
|
||||
return `Pet Pet-${pet.key}`;
|
||||
}
|
||||
|
||||
if (pet.isHatchable()) {
|
||||
return 'PixelPaw';
|
||||
}
|
||||
|
||||
return 'GreyedOut PixelPaw';
|
||||
},
|
||||
|
||||
hasDrawerTabItems (index) {
|
||||
return this.drawerTabs && this.drawerTabs[index].items.length !== 0;
|
||||
},
|
||||
|
||||
// Actions
|
||||
updateHideMissing (newVal) {
|
||||
this.hideMissing = newVal;
|
||||
},
|
||||
|
||||
selectPet (item) {
|
||||
this.$store.dispatch('common:equip', {key: item.key, type: 'pet'});
|
||||
},
|
||||
|
||||
selectMount (item) {
|
||||
this.$store.dispatch('common:equip', {key: item.key, type: 'mount'});
|
||||
},
|
||||
|
||||
hatchPet (pet) {
|
||||
this.$store.dispatch('common:hatch', {egg: pet.eggKey, hatchingPotion: pet.potionKey});
|
||||
},
|
||||
|
||||
onDragOver (ev, pet) {
|
||||
if (this.userItems.mounts[pet.key]) {
|
||||
ev.dropable = false;
|
||||
}
|
||||
},
|
||||
|
||||
onDrop (ev, pet) {
|
||||
this.$store.dispatch('common:feed', {pet: pet.key, food: ev.draggingKey});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
117
website/client/components/inventory/stable/petItem.vue
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<template lang="pug">
|
||||
b-popover(
|
||||
:triggers="[showPopover?'hover':'']",
|
||||
:placement="popoverPosition",
|
||||
)
|
||||
span(slot="content")
|
||||
slot(name="popoverContent", :item="item")
|
||||
|
||||
.item-wrapper
|
||||
.item(
|
||||
:class="{'item-empty': emptyItem}",
|
||||
@mouseup="holdStop",
|
||||
@mouseleave="holdStop",
|
||||
@mousedown.left="holdStart"
|
||||
)
|
||||
slot(name="itemBadge", :item="item")
|
||||
span.item-content(:class="itemContentClass")
|
||||
span.pet-progress-background(v-if="progress > 0")
|
||||
div.pet-progress-bar(v-bind:style="{width: 100 * progress/50 + '%' }")
|
||||
span.pet-progress-background(v-if="holdProgress > 0")
|
||||
div.pet-progress-bar.hold(v-bind:style="{width: 100 * holdProgress/5 + '%' }")
|
||||
span.item-label(v-if="label") {{ label }}
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.pet-progress-background {
|
||||
width: 62px;
|
||||
height: 4px;
|
||||
background-color: #e1e0e3;
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: calc((100% - 62px) / 2);
|
||||
}
|
||||
|
||||
.pet-progress-bar {
|
||||
height: 4px;
|
||||
background-color: #24cc8f;
|
||||
}
|
||||
|
||||
.pet-progress-bar.hold {
|
||||
background-color: #54c3cc;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
import {mapState} from 'client/libs/store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
bPopover,
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
},
|
||||
itemContentClass: {
|
||||
type: String,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
},
|
||||
progress: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
emptyItem: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
popoverPosition: {
|
||||
type: String,
|
||||
default: 'bottom',
|
||||
},
|
||||
showPopover: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
holdProgress: -1,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
ATTRIBUTES: 'constants.ATTRIBUTES',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
holdStart () {
|
||||
let pet = this.item;
|
||||
if (pet.isOwned() || !pet.isHatchable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.holdProgress = 1;
|
||||
|
||||
this.currentHoldingTimer = setInterval(() => {
|
||||
if (this.holdProgress === 5) {
|
||||
this.holdStop();
|
||||
this.$emit('hatchPet', pet);
|
||||
}
|
||||
|
||||
this.holdProgress += 1;
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
holdStop () {
|
||||
if (this.currentHoldingTimer) {
|
||||
clearInterval(this.currentHoldingTimer);
|
||||
this.holdProgress = -1;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
30
website/client/components/static/home.vue
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<template lang="pug">
|
||||
nav
|
||||
router-link(:to="{name: 'login'}") Login
|
||||
router-link(:to="{name: 'register'}") Register
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
play () {
|
||||
// @TODO:
|
||||
// Auth play click
|
||||
},
|
||||
// passwordReset (email) {
|
||||
// if(email == null || email.length == 0) {
|
||||
// alert(window.env.t('invalidEmail'));
|
||||
// } else {
|
||||
// $http.post(ApiUrl.get() + '/api/v3/user/reset-password', {email:email})
|
||||
// .success(function(){
|
||||
// alert(window.env.t('newPassSent'));
|
||||
// })
|
||||
// .error(function(data){
|
||||
// alert(data.err);
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
<template lang="pug">
|
||||
li
|
||||
ul
|
||||
li
|
||||
strong {{task.text}}
|
||||
li(v-if="task.type === 'habit'") up: {{task.up}}, down: {{task.down}}
|
||||
li value: {{task.value}}
|
||||
template(v-if="task.type === 'daily' || task.type === 'todo'")
|
||||
li completed: {{task.completed}}
|
||||
li
|
||||
span checklist
|
||||
ul
|
||||
li(v-for="checklist in task.checklist") {{checklist.text}}
|
||||
template(v-if="task.type === 'daily'")
|
||||
li streak: {{task.streak}}
|
||||
li repeat: {{task.repeat}}
|
||||
li(v-if="task.type === 'todo'") due date: {{task.date}}
|
||||
li attribute {{task.attribute}}
|
||||
li difficulty {{task.priority}}
|
||||
li tags {{getTagsFor(task)}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'client/libs/store';
|
||||
|
||||
export default {
|
||||
props: ['task'],
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
...mapGetters({getTagsFor: 'tasks:getTagsFor'}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
194
website/client/components/tasks/column.vue
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
<template lang="pug">
|
||||
.tasks-column
|
||||
.d-flex
|
||||
h2.tasks-column-title(v-once) {{ $t(types[type].label) }}
|
||||
.filters.d-flex.justify-content-end
|
||||
.filter.small-text(
|
||||
v-for="filter in types[type].filters",
|
||||
:class="{active: activeFilter.label === filter.label}",
|
||||
@click="activeFilter = filter",
|
||||
) {{ $t(filter.label) }}
|
||||
.tasks-list
|
||||
task(v-for="task in tasks[`${type}s`]", :key="task.id", :task="task", v-if="activeFilter.filter(task)")
|
||||
.bottom-gradient
|
||||
.column-background(v-if="isUser === true", :class="{'initial-description': tasks[`${type}s`].length === 0}")
|
||||
.svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once)
|
||||
h3(v-once) {{$t('theseAreYourTasks', {taskType: `${type}s`})}}
|
||||
.small-text {{$t(`${type}sDesc`)}}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.tasks-column {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.tasks-list {
|
||||
border-radius: 4px;
|
||||
background: $gray-600;
|
||||
padding: 8px;
|
||||
// not sure why but this is necessary or the last task will overflow the container
|
||||
padding-bottom: 0.1px;
|
||||
position: relative;
|
||||
height: calc(100% - 64px);
|
||||
}
|
||||
|
||||
.bottom-gradient {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: -0px;
|
||||
height: 42px;
|
||||
background-image: linear-gradient(to bottom, rgba($gray-10, 0), rgba($gray-10, 0.24));
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tasks-column-title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.filter {
|
||||
font-weight: bold;
|
||||
color: $gray-100;
|
||||
font-style: normal;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $purple-200;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $purple-200;
|
||||
border-bottom: 2px solid $purple-200;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.column-background {
|
||||
position: absolute;
|
||||
bottom: 32px;
|
||||
z-index: 7;
|
||||
|
||||
&.initial-description {
|
||||
top: 30%;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
margin: 0 auto;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h3, .small-text {
|
||||
color: $gray-300;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: normal;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.small-text {
|
||||
font-style: normal;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-habit {
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.icon-daily {
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.icon-todo {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.icon-reward {
|
||||
width: 26px;
|
||||
height: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Task from './task';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import { shouldDo } from 'common/script/cron';
|
||||
import habitIcon from 'assets/svg/habit.svg';
|
||||
import dailyIcon from 'assets/svg/daily.svg';
|
||||
import todoIcon from 'assets/svg/todo.svg';
|
||||
import rewardIcon from 'assets/svg/reward.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Task,
|
||||
},
|
||||
props: ['type', 'isUser'],
|
||||
data () {
|
||||
const types = Object.freeze({
|
||||
habit: {
|
||||
label: 'habits',
|
||||
filters: [
|
||||
{label: 'all', filter: () => true, default: true},
|
||||
{label: 'yellowred', filter: t => t.value < 1}, // weak
|
||||
{label: 'greenblue', filter: t => t.value >= 1}, // strong
|
||||
],
|
||||
},
|
||||
daily: {
|
||||
label: 'dailies',
|
||||
filters: [
|
||||
{label: 'all', filter: () => true, default: true},
|
||||
{label: 'due', filter: t => !t.completed && shouldDo(new Date(), t, this.userPreferences)},
|
||||
{label: 'notDue', filter: t => t.completed || !shouldDo(new Date(), t, this.userPreferences)},
|
||||
],
|
||||
},
|
||||
todo: {
|
||||
label: 'todos',
|
||||
filters: [
|
||||
{label: 'remaining', filter: t => !t.completed, default: true}, // active
|
||||
{label: 'scheduled', filter: t => !t.completed && t.date},
|
||||
{label: 'complete2', filter: t => t.completed},
|
||||
],
|
||||
},
|
||||
reward: {
|
||||
label: 'rewards',
|
||||
filters: [
|
||||
{label: 'all', filter: () => true, default: true},
|
||||
{label: 'custom', filter: () => true}, // all rewards made by the user
|
||||
{label: 'wishlist', filter: () => false}, // not user tasks
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const icons = Object.freeze({
|
||||
habit: habitIcon,
|
||||
daily: dailyIcon,
|
||||
todo: todoIcon,
|
||||
reward: rewardIcon,
|
||||
});
|
||||
|
||||
return {
|
||||
types,
|
||||
activeFilter: types[this.type].filters.find(f => f.default === true),
|
||||
icons,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
tasks: 'tasks.data',
|
||||
userPreferences: 'user.data.preferences',
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
293
website/client/components/tasks/task.vue
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
<template lang="pug">
|
||||
.task.d-flex
|
||||
// Habits left side control
|
||||
.left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.up")
|
||||
.task-control.habit-control(:class="controlClass.up + '-control-habit'")
|
||||
.svg-icon.positive(v-html="icons.positive")
|
||||
// Dailies and todos left side control
|
||||
.left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'daily' || task.type === 'todo'", :class="controlClass")
|
||||
.task-control.daily-todo-control(:class="controlClass + '-control-daily-todo'")
|
||||
.svg-icon.check(v-html="icons.check", v-if="task.completed")
|
||||
// Task title, description and icons
|
||||
.task-content(:class="contentClass")
|
||||
h3.task-title(:class="{ 'has-notes': task.notes }") {{task.text}}
|
||||
.task-notes.small-text {{task.notes}}
|
||||
.icons.small-text.d-flex.align-items-center
|
||||
.d-flex.align-items-center(v-if="task.type === 'todo' && task.date", :class="{'due-overdue': isDueOverdue}")
|
||||
.svg-icon.calendar(v-html="icons.calendar")
|
||||
span {{dueIn}}
|
||||
.icons-right.d-flex.justify-content-end
|
||||
.d-flex.align-items-center(v-if="showStreak")
|
||||
.svg-icon.streak(v-html="icons.streak")
|
||||
span(v-if="task.type === 'daily'") {{task.streak}}
|
||||
span(v-if="task.type === 'habit'")
|
||||
span.m-0(v-if="task.up") +{{task.counterUp}}
|
||||
span.m-0(v-if="task.up && task.down") |
|
||||
span.m-0(v-if="task.down") -{{task.counterDown}}
|
||||
.d-flex.align-items-center(v-if="task.challenge && task.challenge.id")
|
||||
.svg-icon.challenge(v-html="icons.challenge")
|
||||
b-popover.tags-popover.no-span-margin(
|
||||
:triggers="['hover']",
|
||||
:placement="'bottom'",
|
||||
:popover-style="{'max-width': '1000px'}",
|
||||
)
|
||||
.d-flex.align-items-center(slot="content")
|
||||
.tags-popover-title(v-once) {{ `${$t('tags')}:` }}
|
||||
.tag-label(v-for="tag in getTagsFor(task)") {{tag}}
|
||||
.d-flex.align-items-center(v-if="task.tags && task.tags.length > 0")
|
||||
.svg-icon.tags(v-html="icons.tags")
|
||||
|
||||
// Habits right side control
|
||||
.right-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.down")
|
||||
.task-control.habit-control(:class="controlClass.down + '-control-habit'")
|
||||
.svg-icon.negative(v-html="icons.negative")
|
||||
// Rewards right side control
|
||||
.right-control.d-flex.align-items-center.justify-content-center.reward-control(v-if="task.type === 'reward'", :class="controlClass")
|
||||
.svg-icon(v-html="icons.gold")
|
||||
.small-text {{task.value}}
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.task {
|
||||
margin-bottom: 8px;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
background: $white;
|
||||
border-radius: 2px;
|
||||
z-index: 9;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
margin-bottom: 8px;
|
||||
color: $gray-10;
|
||||
font-weight: normal;
|
||||
|
||||
&.has-notes {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.task-notes {
|
||||
color: $gray-100;
|
||||
font-style: normal;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.task-content {
|
||||
padding: 8px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.icons {
|
||||
color: $gray-300;
|
||||
font-style: normal;
|
||||
|
||||
&-right {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.icons-right .svg-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.icons span {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.no-span-margin span {
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
|
||||
.svg-icon.streak {
|
||||
width: 11.6px;
|
||||
height: 7.1px;
|
||||
}
|
||||
|
||||
.tags.svg-icon, .calendar.svg-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.tags:hover {
|
||||
color: $purple-500;
|
||||
}
|
||||
|
||||
.due-overdue {
|
||||
color: $red-50;
|
||||
}
|
||||
|
||||
.calendar.svg-icon {
|
||||
margin-right: 2px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.challenge.svg-icon {
|
||||
width: 14px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.check.svg-icon {
|
||||
width: 12.3px;
|
||||
height: 9.8px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.left-control, .right-control {
|
||||
width: 40px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.left-control {
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
}
|
||||
|
||||
.right-control {
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
.task-control {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.habit-control {
|
||||
border-radius: 100px;
|
||||
color: $white;
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.positive {
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.negative {
|
||||
margin-top: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.daily-todo-control {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.reward-control {
|
||||
flex-direction: column;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.svg-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.small-text {
|
||||
margin-top: 4px;
|
||||
color: $yellow-10;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss"> // not working as scoped css
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.tags-popover {
|
||||
// TODO fix padding, see https://github.com/bootstrap-vue/bootstrap-vue/issues/559#issuecomment-311150335
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tags-popover-title {
|
||||
margin-right: 4px;
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.tag-label {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-left: 4px;
|
||||
border-radius: 100px;
|
||||
background-color: $gray-50;
|
||||
padding: 4px 10px;
|
||||
color: $gray-300;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'client/libs/store';
|
||||
import moment from 'moment';
|
||||
|
||||
import positiveIcon from 'assets/svg/positive.svg';
|
||||
import negativeIcon from 'assets/svg/negative.svg';
|
||||
import goldIcon from 'assets/svg/gold.svg';
|
||||
import streakIcon from 'assets/svg/streak.svg';
|
||||
import calendarIcon from 'assets/svg/calendar.svg';
|
||||
import challengeIcon from 'assets/svg/challenge.svg';
|
||||
import tagsIcon from 'assets/svg/tags.svg';
|
||||
import checkIcon from 'assets/svg/check.svg';
|
||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
bPopover,
|
||||
},
|
||||
props: ['task'],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
positive: positiveIcon,
|
||||
negative: negativeIcon,
|
||||
gold: goldIcon,
|
||||
streak: streakIcon,
|
||||
calendar: calendarIcon,
|
||||
challenge: challengeIcon,
|
||||
tags: tagsIcon,
|
||||
check: checkIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
...mapGetters({
|
||||
getTagsFor: 'tasks:getTagsFor',
|
||||
getTaskClasses: 'tasks:getTaskClasses',
|
||||
}),
|
||||
leftControl () {
|
||||
const task = this.task;
|
||||
if (task.type === 'reward') return false;
|
||||
return true;
|
||||
},
|
||||
rightControl () {
|
||||
const task = this.task;
|
||||
if (task.type === 'reward') return true;
|
||||
if (task.type === 'habit') return true;
|
||||
return false;
|
||||
},
|
||||
controlClass () {
|
||||
return this.getTaskClasses(this.task, 'control');
|
||||
},
|
||||
contentClass () {
|
||||
return this.getTaskClasses(this.task, 'content');
|
||||
},
|
||||
showStreak () {
|
||||
if (this.task.streak !== undefined) return true;
|
||||
if (this.task.type === 'habit' && (this.task.up || this.task.down)) return true;
|
||||
return false;
|
||||
},
|
||||
isDueOverdue () {
|
||||
return moment().diff(this.task.date, 'days') >= 0;
|
||||
},
|
||||
dueIn () {
|
||||
const dueIn = moment().to(this.task.date);
|
||||
return this.$t('dueIn', {dueIn});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
56
website/client/components/tasks/user.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<template lang="pug">
|
||||
.row.user-tasks-page
|
||||
.col-12
|
||||
.row.tasks-navigation
|
||||
.col-4.offset-4
|
||||
input.form-control.input-search(type="text", :placeholder="$t('search')")
|
||||
.col-1.offset-3
|
||||
button.btn.btn-success(v-once)
|
||||
.svg-icon.positive(v-html="icons.positive")
|
||||
| {{ $t('create') }}
|
||||
.row.tasks-columns
|
||||
task-column.col-3(v-for="column in columns", :type="column", :key="column", :isUser="true")
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.user-tasks-page {
|
||||
padding-top: 31px;
|
||||
}
|
||||
|
||||
.tasks-navigation {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.positive {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
color: $green-500;
|
||||
margin-right: 8px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.tasks-columns {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Column from './column';
|
||||
import positiveIcon from 'assets/svg/positive.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TaskColumn: Column,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
columns: ['habit', 'daily', 'todo', 'reward'],
|
||||
icons: Object.freeze({
|
||||
positive: positiveIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -4,8 +4,9 @@
|
|||
.toggle-switch.float-left
|
||||
input.toggle-switch-checkbox(
|
||||
type='checkbox', :id="id",
|
||||
@change="$emit('change', $event.target.checked)",
|
||||
:checked="checked",
|
||||
@change="handleChange",
|
||||
:checked="isChecked",
|
||||
:value="value",
|
||||
)
|
||||
label.toggle-switch-label(:for="id")
|
||||
span.toggle-switch-inner
|
||||
|
|
@ -13,88 +14,88 @@
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.toggle-switch-container {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.toggle-switch-container {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
user-select: none;
|
||||
margin-left: 9px;
|
||||
}
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
user-select: none;
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
.toggle-switch-description {
|
||||
height: 20px;
|
||||
border-bottom: 1px dashed $gray-200;
|
||||
}
|
||||
.toggle-switch-description {
|
||||
height: 20px;
|
||||
border-bottom: 1px dashed $gray-200;
|
||||
}
|
||||
|
||||
.toggle-switch-checkbox {
|
||||
display: none;
|
||||
}
|
||||
.toggle-switch-checkbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-switch-label {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border-radius: 100px;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.toggle-switch-label {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border-radius: 100px;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.toggle-switch-inner {
|
||||
display: block;
|
||||
width: 200%;
|
||||
margin-left: -100%;
|
||||
transition: margin 0.3s ease-in 0s;
|
||||
}
|
||||
.toggle-switch-inner {
|
||||
display: block;
|
||||
width: 200%;
|
||||
margin-left: -100%;
|
||||
transition: margin 0.3s ease-in 0s;
|
||||
}
|
||||
|
||||
.toggle-switch-inner:before, .toggle-switch-inner:after {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 50%;
|
||||
height: 16px;
|
||||
padding: 0;
|
||||
}
|
||||
.toggle-switch-inner:before, .toggle-switch-inner:after {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 50%;
|
||||
height: 16px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toggle-switch-inner:before {
|
||||
content: "";
|
||||
padding-left: 10px;
|
||||
background-color: $purple-400;
|
||||
}
|
||||
.toggle-switch-inner:before {
|
||||
content: "";
|
||||
padding-left: 10px;
|
||||
background-color: $purple-400;
|
||||
}
|
||||
|
||||
.toggle-switch-inner:after {
|
||||
content: "";
|
||||
padding-right: 10px;
|
||||
background-color: $gray-200;
|
||||
text-align: right;
|
||||
}
|
||||
.toggle-switch-inner:after {
|
||||
content: "";
|
||||
padding-right: 10px;
|
||||
background-color: $gray-200;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.toggle-switch-switch {
|
||||
box-shadow: 0 1px 2px 0 rgba($black, 0.32);
|
||||
display: block;
|
||||
width: 20px;
|
||||
margin: -2px;
|
||||
margin-top: 1px;
|
||||
height: 20px;
|
||||
background: $white;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 22px;
|
||||
border-radius: 100px;
|
||||
transition: all 0.3s ease-in 0s;
|
||||
}
|
||||
.toggle-switch-switch {
|
||||
box-shadow: 0 1px 2px 0 rgba($black, 0.32);
|
||||
display: block;
|
||||
width: 20px;
|
||||
margin: -2px;
|
||||
margin-top: 1px;
|
||||
height: 20px;
|
||||
background: $white;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 22px;
|
||||
border-radius: 100px;
|
||||
transition: all 0.3s ease-in 0s;
|
||||
}
|
||||
|
||||
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-inner {
|
||||
margin-left: 0;
|
||||
}
|
||||
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-inner {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-switch {
|
||||
right: 0px;
|
||||
}
|
||||
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-switch {
|
||||
right: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
@ -105,7 +106,14 @@ export default {
|
|||
id: Math.random(),
|
||||
};
|
||||
},
|
||||
model: {
|
||||
prop: 'checked',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
default: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -115,5 +123,15 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isChecked () {
|
||||
return this.checked === this.value;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleChange ({ target: { checked } }) {
|
||||
this.$emit('change', checked ? this.value : this.uncheckedValue);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,163 +0,0 @@
|
|||
<template lang="pug">
|
||||
.row
|
||||
.col-12
|
||||
.row
|
||||
.col-3.p-4
|
||||
h3 Input
|
||||
input.form-control(type="text", placeholder="Placeholder")
|
||||
.col-3.p-4
|
||||
h3 Input Disabled
|
||||
input.form-control(type="text", placeholder="Placeholder", disabled)
|
||||
.col-3.p-4
|
||||
h3 Input With Icon
|
||||
input.form-control.input-search(type="text", placeholder="Placeholder")
|
||||
.col-3.p-4
|
||||
h3 Input With Icon Disabled
|
||||
input.form-control.input-search(type="text", placeholder="Placeholder", disabled)
|
||||
.col-3.p-4
|
||||
h3 Input Valid
|
||||
input.form-control.input-valid(type="text", placeholder="Placeholder")
|
||||
.col-3.p-4
|
||||
h3 Input Invalid
|
||||
input.form-control.input-invalid(type="text", placeholder="Placeholder")
|
||||
.row
|
||||
.col-6.p-4
|
||||
h3 Textarea
|
||||
textarea.form-control(rows="5", cols="50")
|
||||
.col-6.p-4
|
||||
h3 Textarea Disabled
|
||||
textarea.form-control(disabled, rows="10", cols="50")
|
||||
.row
|
||||
.col-2.p-4
|
||||
toggleSwitch(label="Toggle Switch")
|
||||
.row
|
||||
.col-3.p-4
|
||||
h3 Checkbox
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type='checkbox')
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Check this custom checkbox
|
||||
.col-3.p-4
|
||||
h3 Checkbox Disabled Checked
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type='checkbox', disabled, checked)
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Check this custom checkbox
|
||||
.col-3.p-4
|
||||
h3 Checkbox Disabled Not Checked
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type='checkbox', disabled)
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Check this custom checkbox
|
||||
.col-6.p-4
|
||||
h3 Radio Button
|
||||
form
|
||||
label.custom-control.custom-radio
|
||||
input#radio1.custom-control-input(name='radio', type='radio')
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Toggle this custom radio
|
||||
label.custom-control.custom-radio
|
||||
input#radio2.custom-control-input(name='radio', type='radio')
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Toggle this custom radio
|
||||
.col-3.p-4
|
||||
h3 Radio Button Disabled Checked
|
||||
form
|
||||
label.custom-control.custom-radio
|
||||
input#radio3.custom-control-input(name='radio', type='radio', disabled, checked)
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Toggle this custom radio
|
||||
.col-3.p-4
|
||||
h3 Radio Button Disabled Not Checked
|
||||
form
|
||||
label.custom-control.custom-radio
|
||||
input#radio3.custom-control-input(name='radio', type='radio', disabled)
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Toggle this custom radio
|
||||
.row
|
||||
.col-3.p-4
|
||||
h3 Main Button
|
||||
button.btn.btn-primary Button
|
||||
.col-3.p-4
|
||||
h3 Secondary Button
|
||||
button.btn.btn-secondary Button
|
||||
.col-3.p-4
|
||||
h3 Green Button
|
||||
button.btn.btn-success Button
|
||||
.col-3.p-4
|
||||
h3 Blue Button
|
||||
button.btn.btn-info Button
|
||||
.col-3.p-4
|
||||
h3 Red Button
|
||||
button.btn.btn-danger Button
|
||||
.row
|
||||
.col-3.p-4
|
||||
h3 Main Button Disabled
|
||||
button.btn.btn-primary(disabled=true) Button
|
||||
.col-3.p-4
|
||||
h3 Secondary Button Disabled
|
||||
button.btn.btn-secondary(disabled=true) Button
|
||||
.col-3.p-4
|
||||
h3 Green Button Disabled
|
||||
button.btn.btn-success(disabled=true) Button
|
||||
.col-3.p-4
|
||||
h3 Blue Button Disabled
|
||||
button.btn.btn-info(disabled=true) Button
|
||||
.col-3.p-4
|
||||
h3 Red Button Disabled
|
||||
button.btn.btn-danger(disabled=true) Button
|
||||
.row
|
||||
.col-6.p-4
|
||||
h3 Dropdown Menu
|
||||
b-dropdown(text="Menu", right=false)
|
||||
b-dropdown-item(href="#") Menu item 1
|
||||
b-dropdown-item(href="#") Menu item 2
|
||||
b-dropdown-item(href="#") Menu item 3
|
||||
b-dropdown-item(href="#") Menu item 4
|
||||
.col-6.p-4
|
||||
h3 Dropdown Menu Disabled
|
||||
b-dropdown(text="Menu", disabled)
|
||||
b-dropdown-item(href="#") Menu item 1
|
||||
b-dropdown-item(href="#") Menu item 2
|
||||
b-dropdown-item(href="#") Menu item 3
|
||||
b-dropdown-item(href="#") Menu item 4
|
||||
.row
|
||||
.col-6.p-4
|
||||
h1 Heading 1
|
||||
h2 Heading 2
|
||||
h3 Heading 3
|
||||
h4 Heading 4
|
||||
.col-6.p-4
|
||||
p Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vehicula, purus sit amet sodales pharetra, ipsum ipsum mollis orci, id pharetra velit diam et dui. Sed placerat ipsum eget pharetra rutrum. Ut vitae rutrum lacus, eu imperdiet velit. Pellentesque eu velit cursus, scelerisque dui quis, dapibus magna. Vestibulum molestie sed sapien et ultricies. Nam porta ipsum leo, non congue magna vestibulum a. Etiam dictum felis sit amet augue varius tincidunt. Sed eget urna auctor, convallis felis in, pretium justo. Curabitur aliquet, ligula id tincidunt ullamcorper, orci lorem pharetra neque, in ornare arcu magna accumsan arcu. Maecenas dignissim lorem sed eros accumsan scelerisque.
|
||||
p.small-text Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vehicula, purus sit amet sodales pharetra, ipsum ipsum mollis orci, id pharetra velit diam et dui.
|
||||
.row
|
||||
.col(v-for="taskType in tasksTypes")
|
||||
h3 {{taskType}}s
|
||||
ul
|
||||
task(v-for="task in tasks", v-if="task.type === taskType", :key="task.id", :task="task")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Task from './task';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Task,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
toggleSwitch,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tasksTypes: ['habit', 'daily', 'todo', 'reward'],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({tasks: 'tasks.data'}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
8
website/client/directives/directive.common.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// https://stackoverflow.com/a/40720172/1298154
|
||||
export const emit = (vnode, emitName, data) => {
|
||||
let handlers = vnode.data && vnode.data.on || vnode.componentOptions && vnode.componentOptions.listeners;
|
||||
|
||||
if (handlers && handlers[emitName]) {
|
||||
handlers[emitName].fns(data);
|
||||
}
|
||||
};
|
||||
63
website/client/directives/dragdrop.directive.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import {emit} from './directive.common';
|
||||
|
||||
import _keys from 'lodash/keys';
|
||||
import _without from 'lodash/without';
|
||||
|
||||
/**
|
||||
* DRAG_GROUP is a static custom value
|
||||
* KEY_OF_ITEM
|
||||
*
|
||||
* v-drag.DRAG_GROUP="KEY_OF_ITEM"
|
||||
* v-drag.drop.DRAG_GROUP="KEY_OF_ITEM" @dropped="callback" @dragover="optional"
|
||||
*/
|
||||
|
||||
const DROPPED_EVENT_NAME = 'dropped';
|
||||
const DRAGOVER_EVENT_NAME = 'dragover';
|
||||
|
||||
export default {
|
||||
bind (el, binding, vnode) {
|
||||
el.isDropHandler = binding.modifiers.drop === true;
|
||||
el.dragGroup = _without(_keys(binding.modifiers), 'drop')[0];
|
||||
el.key = binding.value;
|
||||
|
||||
if (!el.isDropHandler) {
|
||||
el.draggable = true;
|
||||
el.handleDrag = (ev) => {
|
||||
ev.dataTransfer.setData('KEY', binding.value);
|
||||
};
|
||||
el.addEventListener('dragstart', el.handleDrag);
|
||||
} else {
|
||||
el.handleDragOver = (ev) => {
|
||||
let dragOverEventData = {
|
||||
dropable: true,
|
||||
draggingKey: ev.dataTransfer.getData('KEY'),
|
||||
};
|
||||
|
||||
emit(vnode, DRAGOVER_EVENT_NAME, dragOverEventData);
|
||||
|
||||
if (dragOverEventData.dropable) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
};
|
||||
el.handleDrop = (ev) => {
|
||||
let dropEventData = {
|
||||
draggingKey: ev.dataTransfer.getData('KEY'),
|
||||
};
|
||||
|
||||
emit(vnode, DROPPED_EVENT_NAME, dropEventData);
|
||||
};
|
||||
|
||||
el.addEventListener('dragover', el.handleDragOver);
|
||||
el.addEventListener('drop', el.handleDrop);
|
||||
}
|
||||
},
|
||||
|
||||
unbind (el) {
|
||||
if (!el.isDropHandler) {
|
||||
el.removeEventListener('drag', el.handleDrag);
|
||||
} else {
|
||||
el.removeEventListener('dragover', el.handleDragOver);
|
||||
el.removeEventListener('drop', el.handleDrop);
|
||||
}
|
||||
},
|
||||
};
|
||||
31
website/client/directives/resize.directive.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import _throttle from 'lodash/throttle';
|
||||
|
||||
import { emit } from './directive.common';
|
||||
|
||||
/**
|
||||
* v-resize="throttleTimeout", @resized="callback()"
|
||||
*/
|
||||
|
||||
const EVENT_NAME = 'resized';
|
||||
|
||||
export default {
|
||||
bind (el, binding, vnode) {
|
||||
el.handleWindowResize = _throttle(() => {
|
||||
emit(vnode, EVENT_NAME, {
|
||||
width: el.clientWidth,
|
||||
height: el.clientHeight,
|
||||
});
|
||||
}, binding.value);
|
||||
|
||||
window.addEventListener('resize', el.handleWindowResize);
|
||||
|
||||
// send the first width
|
||||
Vue.nextTick(el.handleWindowResize);
|
||||
},
|
||||
|
||||
unbind (el) {
|
||||
window.removeEventListener('resize', el.handleWindowResize);
|
||||
},
|
||||
};
|
||||
|
|
@ -4,14 +4,9 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Habitica</title>
|
||||
<!-- TODO load google fonts separately as @import is slow, find alternative -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed:700|Roboto:400,400i,700,700i" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed:400,400i,700,700i|Roboto:400,400i,700,700i" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- #loading-screen needs to be rendered before vue, will be deleted once app is loaded -->
|
||||
<div id="loading-screen" class="h-100 w-100 d-flex justify-content-center align-items-center">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -36,8 +36,11 @@ export function loadAsyncResource ({store, path, url, deserialize, forceLoad = f
|
|||
} else if (loadingStatus === 'NOT_LOADED' || loadingStatus === 'LOADED' && forceLoad) {
|
||||
return axios.get(url).then(response => { // TODO support more params
|
||||
resource.loadingStatus = 'LOADED';
|
||||
resource.data = deserialize(response);
|
||||
return resource;
|
||||
// deserialize can be a promise
|
||||
return Promise.resolve(deserialize(response)).then(deserializedData => {
|
||||
resource.data = deserializedData;
|
||||
return resource;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return Promise.reject(new Error(`Invalid loading status "${loadingStatus} for resource at "${path}".`));
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@
|
|||
require('babel-polyfill');
|
||||
|
||||
import Vue from 'vue';
|
||||
import axios from 'axios';
|
||||
import AppComponent from './app';
|
||||
import router from './router';
|
||||
import generateStore from './store';
|
||||
import getStore from './store';
|
||||
import StoreModule from './libs/store';
|
||||
import './filters/registerGlobals';
|
||||
import i18n from './libs/i18n';
|
||||
|
|
@ -26,43 +25,9 @@ Vue.config.productionTip = IS_PRODUCTION;
|
|||
Vue.use(i18n);
|
||||
Vue.use(StoreModule);
|
||||
|
||||
// TODO just until we have proper authentication
|
||||
let authSettings = localStorage.getItem('habit-mobile-settings');
|
||||
|
||||
if (authSettings) {
|
||||
authSettings = JSON.parse(authSettings);
|
||||
axios.defaults.headers.common['x-api-user'] = authSettings.auth.apiId;
|
||||
axios.defaults.headers.common['x-api-key'] = authSettings.auth.apiToken;
|
||||
}
|
||||
|
||||
export default new Vue({
|
||||
el: '#app',
|
||||
router,
|
||||
store: generateStore(),
|
||||
store: getStore(),
|
||||
render: h => h(AppComponent),
|
||||
beforeCreate () {
|
||||
// Setup listener for title
|
||||
this.$store.watch(state => state.title, (title) => {
|
||||
document.title = title;
|
||||
});
|
||||
|
||||
// Mount the app when user and tasks are loaded
|
||||
const userDataWatcher = this.$store.watch(state => [state.user.data, state.tasks.data], ([user, tasks]) => {
|
||||
if (user && user._id && Array.isArray(tasks)) {
|
||||
userDataWatcher(); // remove the watcher
|
||||
this.$mount('#app');
|
||||
}
|
||||
});
|
||||
|
||||
// Load the user and the user tasks
|
||||
Promise.all([
|
||||
this.$store.dispatch('user:fetch'),
|
||||
this.$store.dispatch('tasks:fetchUserTasks'),
|
||||
]).catch((err) => {
|
||||
console.error('Impossible to fetch user. Copy into localStorage a valid habit-mobile-settings object.', err); // eslint-disable-line no-console
|
||||
});
|
||||
},
|
||||
mounted () { // Remove the loading screen when the app is mounted
|
||||
let loadingScreen = document.getElementById('loading-screen');
|
||||
if (loadingScreen) document.body.removeChild(loadingScreen);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
#Running
|
||||
- Open a terminal and type `npm run client:dev`
|
||||
- Open a second terminal and type `npm start`
|
||||
|
||||
#Preparation Reading
|
||||
- Vue 2 (https://vuejs.org)
|
||||
|
||||
- Webpack (https://webpack.github.io/) is the build system and it includes plugins for code transformation, right now we have: BabelJS for ES6 transpilation, eslint for code style, less and postcss for css compilation. The code comes from https://github.com/vuejs-templates/webpack which is a Webpack template for Vue, with some small modifications to adapt it to our use case. Docs http://vuejs-templates.github.io/webpack/
|
||||
|
||||
- We’re using `.vue` files that make it possible to have HTML, JS and CSS for each component together in a single location. They’re implemented as a webpack plugin and the docs can be found here http://vue-loader.vuejs.org/en/
|
||||
|
||||
- SemanticUI is the UI framework http://semantic-ui.com/. So far I’ve only used the CSS part, it also has JS plugins but I’ve yet to use them. It supports theming so if it’s not too difficult we’ll want to customize the base theme with our own styles instead of writing CSS rules to override the original styling.
|
||||
|
||||
The code is in `/website/client`. We’re using something very similar to Vuex (equivalent of React’s Redux) for state management http://vuex.vuejs.org/en/index.html
|
||||
|
||||
The API is almost the same except that we don’t use mutations but only actions because it would make it difficult to work with common code
|
||||
|
||||
The project is developed directly in the `develop` branch as long as we’ll be able to avoid splitting it into a different branch.
|
||||
|
||||
So far most of the work has been on the template, so there’s no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it’s basically a Flux implementation: there’s a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
|
||||
#Running
|
||||
- Open a terminal and type `npm run client:dev`
|
||||
- Open a second terminal and type `npm start`
|
||||
|
||||
#Preparation Reading
|
||||
- Vue 2 (https://vuejs.org)
|
||||
|
||||
- Webpack (https://webpack.github.io/) is the build system and it includes plugins for code transformation, right now we have: BabelJS for ES6 transpilation, eslint for code style, less and postcss for css compilation. The code comes from https://github.com/vuejs-templates/webpack which is a Webpack template for Vue, with some small modifications to adapt it to our use case. Docs http://vuejs-templates.github.io/webpack/
|
||||
|
||||
- We’re using `.vue` files that make it possible to have HTML, JS and CSS for each component together in a single location. They’re implemented as a webpack plugin and the docs can be found here http://vue-loader.vuejs.org/en/
|
||||
|
||||
- SemanticUI is the UI framework http://semantic-ui.com/. So far I’ve only used the CSS part, it also has JS plugins but I’ve yet to use them. It supports theming so if it’s not too difficult we’ll want to customize the base theme with our own styles instead of writing CSS rules to override the original styling.
|
||||
|
||||
The code is in `/website/client`. We’re using something very similar to Vuex (equivalent of React’s Redux) for state management http://vuex.vuejs.org/en/index.html
|
||||
|
||||
The API is almost the same except that we don’t use mutations but only actions because it would make it difficult to work with common code
|
||||
|
||||
The project is developed directly in the `develop` branch as long as we’ll be able to avoid splitting it into a different branch.
|
||||
|
||||
So far most of the work has been on the template, so there’s no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it’s basically a Flux implementation: there’s a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import getStore from 'client/store';
|
||||
|
||||
import EmptyView from './components/emptyView';
|
||||
|
||||
|
|
@ -7,19 +8,23 @@ import EmptyView from './components/emptyView';
|
|||
import ParentPage from './components/parentPage';
|
||||
import Page from './components/page';
|
||||
|
||||
// Tasks
|
||||
import UserTasks from './components/userTasks';
|
||||
// Static Pages
|
||||
const Home = () => import(/* webpackChunkName: "static" */'./components/static/home');
|
||||
const RegisterLogin = () => import(/* webpackChunkName: "auth" */'./components/auth/registerLogin');
|
||||
|
||||
// Except for tasks that are always loaded all the other main level
|
||||
// All the main level
|
||||
// components are loaded in separate webpack chunks.
|
||||
// See https://webpack.js.org/guides/code-splitting-async/
|
||||
// for docs
|
||||
|
||||
// Tasks
|
||||
const UserTasks = () => import(/* webpackChunkName: "userTasks" */'./components/tasks/user');
|
||||
|
||||
// Inventory
|
||||
const InventoryContainer = () => import(/* webpackChunkName: "inventory" */'./components/inventory/index');
|
||||
const ItemsPage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/items/index');
|
||||
const EquipmentPage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/equipment/index');
|
||||
const StablePage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/stable');
|
||||
const StablePage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/stable/index');
|
||||
|
||||
// Social
|
||||
const InboxPage = () => import(/* webpackChunkName: "inbox" */ './components/social/inbox/index');
|
||||
|
|
@ -27,14 +32,14 @@ const InboxConversationPage = () => import(/* webpackChunkName: "inbox" */ './co
|
|||
|
||||
// Guilds
|
||||
const GuildIndex = () => import(/* webpackChunkName: "guilds" */ './components/guilds/index');
|
||||
const TavernPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/tavern');
|
||||
// const TavernPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/tavern');
|
||||
const MyGuilds = () => import(/* webpackChunkName: "guilds" */ './components/guilds/myGuilds');
|
||||
const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/discovery');
|
||||
const GuildPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/guild');
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
export default new VueRouter({
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: process.env.NODE_ENV === 'production' ? '/new-app' : __dirname, // eslint-disable-line no-process-env
|
||||
linkActiveClass: 'active',
|
||||
|
|
@ -43,7 +48,11 @@ export default new VueRouter({
|
|||
scrollBehavior () {
|
||||
return { x: 0, y: 0 };
|
||||
},
|
||||
// requiresLogin is true by default, isStatic false
|
||||
routes: [
|
||||
{ name: 'home', path: '/home', component: Home, meta: {requiresLogin: false} },
|
||||
{ name: 'register', path: '/register', component: RegisterLogin, meta: {requiresLogin: false} },
|
||||
{ name: 'login', path: '/login', component: RegisterLogin, meta: {requiresLogin: false} },
|
||||
{ name: 'tasks', path: '/', component: UserTasks },
|
||||
{
|
||||
path: '/inventory',
|
||||
|
|
@ -55,11 +64,12 @@ export default new VueRouter({
|
|||
],
|
||||
},
|
||||
{ name: 'shops', path: '/shops', component: Page },
|
||||
{ name: 'party', path: '/party', component: GuildPage },
|
||||
{
|
||||
path: '/guilds',
|
||||
component: GuildIndex,
|
||||
children: [
|
||||
{ name: 'tavern', path: 'tavern', component: TavernPage },
|
||||
{ name: 'tavern', path: 'tavern', component: GuildPage },
|
||||
{
|
||||
name: 'myGuilds',
|
||||
path: 'myGuilds',
|
||||
|
|
@ -79,7 +89,6 @@ export default new VueRouter({
|
|||
],
|
||||
},
|
||||
{ name: 'challenges', path: 'challenges', component: Page },
|
||||
{ name: 'party', path: 'party', component: Page },
|
||||
{
|
||||
path: '/user',
|
||||
component: ParentPage,
|
||||
|
|
@ -108,3 +117,22 @@ export default new VueRouter({
|
|||
},
|
||||
],
|
||||
});
|
||||
|
||||
const store = getStore();
|
||||
|
||||
router.beforeEach(function routerGuard (to, from, next) {
|
||||
const isUserLoggedIn = store.state.isUserLoggedIn;
|
||||
const routeRequiresLogin = to.meta.requiresLogin !== false;
|
||||
|
||||
if (!isUserLoggedIn && routeRequiresLogin) {
|
||||
// Redirect to the login page unless the user is trying to reach the
|
||||
// root of the website, in which case show the home page.
|
||||
// TODO when redirecting to login if user login then redirect back to initial page
|
||||
// so if you tried to go to /party you'll be redirected to /party after login/signup
|
||||
return next({name: to.path === '/' ? 'home' : 'login'});
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
83
website/client/store/actions/auth.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import axios from 'axios';
|
||||
|
||||
export async function register (store, params) {
|
||||
let url = '/api/v3/user/auth/local/register';
|
||||
let result = await axios.post(url, {
|
||||
username: params.username,
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
confirmPassword: params.passwordConfirm,
|
||||
});
|
||||
|
||||
let user = result.data.data;
|
||||
|
||||
let userLocalData = JSON.stringify({
|
||||
auth: {
|
||||
apiId: user._id,
|
||||
apiToken: user.apiToken,
|
||||
},
|
||||
});
|
||||
localStorage.setItem('habit-mobile-settings', userLocalData);
|
||||
|
||||
// @TODO: I think we just need analytics here
|
||||
// Auth.runAuth(res.data._id, res.data.apiToken);
|
||||
// Analytics.register();
|
||||
// $scope.registrationInProgress = false;
|
||||
// Alert.authErrorAlert(data, status, headers, config)
|
||||
// Analytics.login();
|
||||
// Analytics.updateUser();
|
||||
|
||||
store.state.user.data = user;
|
||||
}
|
||||
|
||||
export async function login (store, params) {
|
||||
let url = '/api/v3/user/auth/local/login';
|
||||
let result = await axios.post(url, {
|
||||
username: params.username,
|
||||
// email: params.email,
|
||||
password: params.password,
|
||||
});
|
||||
|
||||
let user = result.data.data;
|
||||
|
||||
let userLocalData = JSON.stringify({
|
||||
auth: {
|
||||
apiId: user.id,
|
||||
apiToken: user.apiToken,
|
||||
},
|
||||
});
|
||||
|
||||
localStorage.setItem('habit-mobile-settings', userLocalData);
|
||||
|
||||
// @TODO: I think we just need analytics here
|
||||
// Auth.runAuth(res.data._id, res.data.apiToken);
|
||||
// Analytics.register();
|
||||
// $scope.registrationInProgress = false;
|
||||
// Alert.authErrorAlert(data, status, headers, config)
|
||||
// Analytics.login();
|
||||
// Analytics.updateUser();
|
||||
|
||||
// @TODO: Update the api to return the user?
|
||||
// store.state.user.data = user;
|
||||
}
|
||||
|
||||
export async function socialAuth (store, params) {
|
||||
let url = '/api/v3/user/auth/social';
|
||||
let result = await axios.post(url, {
|
||||
network: params.auth.network,
|
||||
authResponse: params.auth.authResponse,
|
||||
});
|
||||
|
||||
// @TODO: Analytics
|
||||
|
||||
let user = result.data.data;
|
||||
|
||||
let userLocalData = JSON.stringify({
|
||||
auth: {
|
||||
apiId: user.id,
|
||||
apiToken: user.apiToken,
|
||||
},
|
||||
});
|
||||
|
||||
localStorage.setItem('habit-mobile-settings', userLocalData);
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import axios from 'axios';
|
||||
import equipOp from 'common/script/ops/equip';
|
||||
import hatchOp from 'common/script/ops/hatch';
|
||||
import feedOp from 'common/script/ops/feed';
|
||||
|
||||
export function equip (store, params) {
|
||||
const user = store.state.user.data;
|
||||
|
|
@ -9,4 +11,24 @@ export function equip (store, params) {
|
|||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
}
|
||||
|
||||
export function hatch (store, params) {
|
||||
const user = store.state.user.data;
|
||||
hatchOp(user, {params});
|
||||
axios
|
||||
.post(`/api/v3/user/hatch/${params.egg}/${params.hatchingPotion}`);
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
|
||||
export function feed (store, params) {
|
||||
const user = store.state.user.data;
|
||||
feedOp(user, {params});
|
||||
axios
|
||||
.post(`/api/v3/user/feed/${params.pet}/${params.food}`);
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import * as tasks from './tasks';
|
|||
import * as guilds from './guilds';
|
||||
import * as party from './party';
|
||||
import * as members from './members';
|
||||
import * as auth from './auth';
|
||||
import * as quests from './quests';
|
||||
|
||||
// Actions should be named as 'actionName' and can be accessed as 'namespace:actionName'
|
||||
// Example: fetch in user.js -> 'user:fetch'
|
||||
|
|
@ -17,6 +19,8 @@ const actions = flattenAndNamespace({
|
|||
guilds,
|
||||
party,
|
||||
members,
|
||||
auth,
|
||||
quests,
|
||||
});
|
||||
|
||||
export default actions;
|
||||
|
|
|
|||
16
website/client/store/actions/quests.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import axios from 'axios';
|
||||
// export async function initQuest (store) {
|
||||
// }
|
||||
|
||||
export async function sendAction (store, payload) {
|
||||
// Analytics.updateUser({
|
||||
// partyID: party._id,
|
||||
// partySize: party.memberCount
|
||||
// });
|
||||
let response = await axios.post(`/api/v3/groups/${payload.groupId}/${payload.action}`);
|
||||
|
||||
// @TODO: Update user?
|
||||
// User.sync();
|
||||
|
||||
return response.data.quest || response.data.data;
|
||||
}
|
||||
|
|
@ -1,13 +1,54 @@
|
|||
import { loadAsyncResource } from 'client/libs/asyncResource';
|
||||
|
||||
import compact from 'lodash/compact';
|
||||
|
||||
export function fetchUserTasks (store, forceLoad = false) {
|
||||
return loadAsyncResource({
|
||||
store,
|
||||
path: 'tasks',
|
||||
url: '/api/v3/tasks/user',
|
||||
deserialize (response) {
|
||||
return response.data.data;
|
||||
// Wait for the user to be loaded before deserializing
|
||||
// because user.tasksOrder is necessary
|
||||
return store.dispatch('user:fetch').then((userResource) => {
|
||||
return store.dispatch('tasks:order', [response.data.data, userResource.data.tasksOrder]);
|
||||
});
|
||||
},
|
||||
forceLoad,
|
||||
});
|
||||
}
|
||||
|
||||
export function order (store, [rawTasks, tasksOrder]) {
|
||||
const tasks = {
|
||||
habits: [],
|
||||
dailys: [],
|
||||
todos: [],
|
||||
rewards: [],
|
||||
};
|
||||
|
||||
rawTasks.forEach(task => {
|
||||
tasks[`${task.type}s`].push(task);
|
||||
});
|
||||
|
||||
Object.keys(tasks).forEach((type) => {
|
||||
let tasksOfType = tasks[type];
|
||||
|
||||
const orderOfType = tasksOrder[type];
|
||||
const orderedTasks = new Array(tasksOfType.length);
|
||||
const unorderedTasks = []; // what we want to add later
|
||||
|
||||
tasksOfType.forEach((task, index) => {
|
||||
const taskId = task._id;
|
||||
const i = orderOfType[index] === taskId ? index : orderOfType.indexOf(taskId);
|
||||
if (i === -1) {
|
||||
unorderedTasks.push(task);
|
||||
} else {
|
||||
orderedTasks[i] = task;
|
||||
}
|
||||
});
|
||||
|
||||
tasks[type] = compact(orderedTasks).concat(unorderedTasks);
|
||||
});
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
|
@ -1,6 +1,64 @@
|
|||
import { shouldDo } from 'common/script/cron';
|
||||
|
||||
// Return all the tags belonging to an user task
|
||||
export function getTagsFor (store) {
|
||||
return (task) => store.state.user.data.tags
|
||||
.filter(tag => task.tags.indexOf(tag.id) !== -1)
|
||||
.map(tag => tag.name);
|
||||
}
|
||||
|
||||
function getTaskColorByValue (value) {
|
||||
if (value < -20) {
|
||||
return 'task-worst';
|
||||
} else if (value < -10) {
|
||||
return 'task-worse';
|
||||
} else if (value < -1) {
|
||||
return 'task-bad';
|
||||
} else if (value < 1) {
|
||||
return 'task-neutral';
|
||||
} else if (value < 5) {
|
||||
return 'task-good';
|
||||
} else if (value < 10) {
|
||||
return 'task-better';
|
||||
} else {
|
||||
return 'task-best';
|
||||
}
|
||||
}
|
||||
|
||||
export function getTaskClasses (store) {
|
||||
const userPreferences = store.state.user.data.preferences;
|
||||
|
||||
// Purpose is one of 'controls', 'editModal', 'createModal', 'content'
|
||||
return (task, purpose) => {
|
||||
const type = task.type;
|
||||
|
||||
switch (purpose) {
|
||||
case 'createModal':
|
||||
return 'task-purple';
|
||||
case 'editModal':
|
||||
return type === 'reward' ? 'task-purple' : getTaskColorByValue(task.value);
|
||||
case 'control':
|
||||
switch (type) {
|
||||
case 'daily':
|
||||
if (task.completed || !shouldDo(new Date(), task, userPreferences)) return 'task-daily-todo-disabled';
|
||||
return getTaskColorByValue(task.value);
|
||||
case 'todo':
|
||||
if (task.completed) return 'task-daily-todo-disabled';
|
||||
return getTaskColorByValue(task.value);
|
||||
case 'habit':
|
||||
return {
|
||||
up: task.up ? getTaskColorByValue(task.value) : 'task-habit-disabled',
|
||||
down: task.down ? getTaskColorByValue(task.value) : 'task-habit-disabled',
|
||||
};
|
||||
case 'reward':
|
||||
return 'task-reward';
|
||||
}
|
||||
break;
|
||||
case 'content':
|
||||
if (type === 'daily' && (task.completed || !task.isDue) || type === 'todo' && task.completed) {
|
||||
return 'task-daily-todo-content-disabled';
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -3,21 +3,44 @@ import deepFreeze from 'client/libs/deepFreeze';
|
|||
import content from 'common/script/content/index';
|
||||
import * as constants from 'common/script/constants';
|
||||
import { asyncResourceFactory } from 'client/libs/asyncResource';
|
||||
import axios from 'axios';
|
||||
|
||||
import actions from './actions';
|
||||
import getters from './getters';
|
||||
|
||||
const IS_TEST = process.env.NODE_ENV === 'test'; // eslint-disable-line no-process-env
|
||||
|
||||
// Load user auth parameters and determine if it's logged in
|
||||
// before trying to load data
|
||||
let isUserLoggedIn = false;
|
||||
|
||||
let AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
|
||||
|
||||
if (AUTH_SETTINGS) {
|
||||
AUTH_SETTINGS = JSON.parse(AUTH_SETTINGS);
|
||||
axios.defaults.headers.common['x-api-user'] = AUTH_SETTINGS.auth.apiId;
|
||||
axios.defaults.headers.common['x-api-key'] = AUTH_SETTINGS.auth.apiToken;
|
||||
isUserLoggedIn = true;
|
||||
}
|
||||
|
||||
// Export a function that generates the store and not the store directly
|
||||
// so that we can regenerate it multiple times for testing
|
||||
// so that we can regenerate it multiple times for testing, when not testing
|
||||
// always export the same route
|
||||
|
||||
let existingStore;
|
||||
export default function () {
|
||||
return new Store({
|
||||
if (!IS_TEST && existingStore) return existingStore;
|
||||
|
||||
existingStore = new Store({
|
||||
actions,
|
||||
getters,
|
||||
state: {
|
||||
title: 'Habitica',
|
||||
isUserLoggedIn,
|
||||
user: asyncResourceFactory(),
|
||||
tasks: asyncResourceFactory(), // user tasks
|
||||
party: {
|
||||
quest: {},
|
||||
members: asyncResourceFactory(),
|
||||
},
|
||||
myGuilds: [],
|
||||
|
|
@ -29,4 +52,6 @@ export default function () {
|
|||
constants: deepFreeze(constants),
|
||||
},
|
||||
});
|
||||
|
||||
return existingStore;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@
|
|||
"messageRequired": "A message is required.",
|
||||
"toUserIDRequired": "A User ID is required",
|
||||
"gemAmountRequired": "A number of gems is required",
|
||||
"notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
|
||||
"notAuthorizedToSendMessageToThisUser": "You can't send a message to this player because they have chosen to block messages.",
|
||||
"privateMessageGiftGemsMessage": "Hello <%= receiverName %>, <%= senderName %> has sent you <%= gemAmount %> gems!",
|
||||
"privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription! ",
|
||||
"cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,16 @@
|
|||
"guildBank": "Guild Bank",
|
||||
"chatPlaceHolder": "Type your message to Guild members here",
|
||||
"today": "Today",
|
||||
"theseAreYourTasks": "These are your <%= taskType %>",
|
||||
"habitsDesc": "Habits don't have a rigid schedule. You can check them off multiple times per day.",
|
||||
"dailysDesc": "Dailies repeat on a regular basis. Choose the schedule that works best for you!",
|
||||
"todosDesc": "To-Dos need to be completed once. Add checklists to your To-Dos to increase their value.",
|
||||
"rewardsDesc": "Rewards are a great way to use Habitica and complete your tasks. Try adding a few today!",
|
||||
"dueIn": "Due <%= dueIn %>",
|
||||
"complete2": "Complete",
|
||||
"custom": "Custom",
|
||||
"wishlist": "Wishlist",
|
||||
"scheduled": "Scheduled",
|
||||
"like": "Like",
|
||||
"copyAsTodo": "Copy as To-Do",
|
||||
"report": "Report",
|
||||
|
|
@ -25,6 +35,14 @@
|
|||
"groupBy2": "Group By",
|
||||
"quantity": "Quantity",
|
||||
"AZ": "A-Z",
|
||||
"costumeDisabled": "You have disabled your costume.",
|
||||
"filterByStandard": "Standard",
|
||||
"filterByMagicPotion": "Magin Potion",
|
||||
"filterByQuest": "Quest",
|
||||
"standard": "Standard",
|
||||
"sortByColor": "Color",
|
||||
"sortByHatchable": "Hatchable",
|
||||
"haveHatchablePet": "You have a <%= potion %> and <%= egg %> to hatch this pet! <b>Click and hold</b> the paw print to hatch.",
|
||||
"editAvatar": "Edit Avatar",
|
||||
"sort": "Sort",
|
||||
"memberCount": "Member Count",
|
||||
|
|
@ -53,8 +71,8 @@
|
|||
"silverTier": "Silver Tier",
|
||||
"bronzeTier": "Bronze Tier",
|
||||
"privacySettings": "Privacy Settings",
|
||||
"onlyLeaderCreatesChallenges": "Only the Guild Leader can create Guild Challenges",
|
||||
"guildLeaderCantBeMessaged": "Guild Leader can not be messaged directly",
|
||||
"onlyLeaderCreatesChallenges": "Only the Leader can create Challenges",
|
||||
"guildLeaderCantBeMessaged": "Leader can not be messaged directly",
|
||||
"privateGuild": "Private Guild",
|
||||
"allowGuildInvationsFromNonMembers": "Allow Guild invitations from non-members",
|
||||
"charactersRemaining": "characters remaining",
|
||||
|
|
@ -69,5 +87,45 @@
|
|||
"sendMessage": "Send Message",
|
||||
"removeManager2": "Remove Manager",
|
||||
"promoteToLeader": "Promote to Leader",
|
||||
"inviteFriendsParty": "Inviting friends to your party will grant you an exclusive <br/> Quest Scroll to battle the Basi-List together!"
|
||||
"inviteFriendsParty": "Inviting friends to your party will grant you an exclusive <br/> Quest Scroll to battle the Basi-List together!",
|
||||
"upgradeParty": "Upgrade Party",
|
||||
"questDetailsTitle": "Quest Details",
|
||||
"yourNotOnQuest": "You're not on a quest",
|
||||
"questDescription": "Quests allow players to focus on long-term, in-game goals with the members of their party.",
|
||||
"haveNoChallenges": "You don’t have any Challenges",
|
||||
"challengeDescription": "Challenges are community events in which players compete and earn prizes by completing a group of related tasks.",
|
||||
"createParty": "Create a Party",
|
||||
"partyDescriptionPlaceHolder": "This is our party’s description. It describes what we do in this party. If you want to learn more about what we do in this party, read the description. Party on.",
|
||||
"inviteMembersNow": "Would you like to invite users now?",
|
||||
"playInPartyTitle": "Play Habitica in a Party!",
|
||||
"playInPartyDescription": "Take on amazing quests with friends or on your own. Battle monsters, create Challenges, and help yourself stay accountable through Parties.",
|
||||
"startYourOwnPartyTitle": "Start your own Party",
|
||||
"startYourOwnPartyDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam accumsan sagittis tellus tempor euismod. Sed imperdiet facilisis tortor in malesuada.",
|
||||
"shartUserId": "Share User ID",
|
||||
"wantToJoinPartyTitle": "Want to join a Party?",
|
||||
"wantToJoinPartyDescription": "Aenean non mattis eros, quis semper ipsum. Phasellus vulputate in nibh et suscipit. In hac habitasse platea dictumst.",
|
||||
"copy": "Copy",
|
||||
"lookingForGroup": "Looking for Group",
|
||||
"inviteToPartyOrQuest": "Invite Party to Quest",
|
||||
"inviteInformation": "Clicking “Invite” will send an invitation to your party members. When all members have accepted or denied, the Quest begins.",
|
||||
"questOwnerRewards": "Quest Owner Rewards",
|
||||
"guildInformationPlaceHolder": "This is our party’s description. It describes what we do in this party. If you want to learn more about what we do in this party, read the description. Party on.",
|
||||
"updateParty": "Update Party",
|
||||
"upgrade": "Upgrade",
|
||||
"signUpWithSocial": "Sign up with <%= social %>",
|
||||
"loginWithSocial": "Login with <%= social %>",
|
||||
"confirmPassword": "Confirm Password",
|
||||
"usernamePlaceholder": "e.g., HabitRabbit",
|
||||
"emailPlaceholder": "e.g., rabbit@habitica.com",
|
||||
"passwordPlaceholder": "e.g., •••••••••••• ",
|
||||
"confirmPasswordPlaceholder": "Make sure it’s the same password!",
|
||||
"termsAndAgreement": "By clicking the button below, you are indicating that you have read and agree to the <a href=''>Terms of Service</a> and <a href=''>Privacy Policy</a>.",
|
||||
"joinHabitica": "Join Habitica",
|
||||
"showAllAnimals": "Show All <%= color %> <%= type %>",
|
||||
"showLessAnimals": "Show Less <%= color %> <%= type %>",
|
||||
"quickInventory": "Quick Inventory",
|
||||
"noFoodAvailable": "You don't have any food.",
|
||||
"gotIt": "Got it!",
|
||||
"welcomeStable": "Welcome to the Stable!",
|
||||
"welcomeStableText": "I'm Matt, the Beast Master. Starting at level 3, you can hatch Pets from Eggs by using Potions you find! When you hatch a Pet from your Inventory, it will appear here! Click a Pet's to add it to your avatar. Feed them with the Food you find in your Inventory after level 3, and they'll grow into hardy Mounts."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@
|
|||
"questGoldenknight1DropGoldenknight2Quest": "The Golden Knight Part 2: Gold Knight (Scroll)",
|
||||
|
||||
"questGoldenknight2Text": "The Golden Knight, Part 2: Gold Knight",
|
||||
"questGoldenknight2Notes": "Armed with hundreds of Habitican's testimonies, you finally confront the Golden Knight. You begin to recite the Habitcan's complaints to her, one by one. \"And @Pfeffernusse says that your constant bragging-\" The knight raises her hand to silence you and scoffs, \"Please, these people are merely jealous of my success. Instead of complaining, they should simply work as hard as I! Perhaps I shall show you the power you can attain through diligence such as mine!\" She raises her morningstar and prepares to attack you!",
|
||||
"questGoldenknight2Notes": "Armed with dozens of Habiticans' testimonies, you finally confront the Golden Knight. You begin to recite the Habitcans' complaints to her, one by one. \"And @Pfeffernusse says that your constant bragging-\" The knight raises her hand to silence you and scoffs, \"Please, these people are merely jealous of my success. Instead of complaining, they should simply work as hard as I! Perhaps I shall show you the power you can attain through diligence such as mine!\" She raises her morningstar and prepares to attack you!",
|
||||
"questGoldenknight2Boss": "Gold Knight",
|
||||
"questGoldenknight2DropGoldenknight3Quest": "The Golden Knight Part 3: The Iron Knight (Scroll)",
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"rebirthInList1": "Tasks, history, equipment, and settings remain.",
|
||||
"rebirthInList2": "Challenge, Guild, and Party memberships remain.",
|
||||
"rebirthInList3": "Gems, backer tiers, and contributor levels remain.",
|
||||
"rebirthInList4": "Items obtained from Gems or drops (such as pets and mounts) remain, though you cannot access them until you unlock them again.",
|
||||
"rebirthInList4": "Items obtained from Gems or drops (such as pets and mounts) remain.",
|
||||
"rebirthEarnAchievement": "You also earn an Achievement for beginning a new adventure!",
|
||||
"beReborn": "Be Reborn",
|
||||
"rebirthAchievement": "You've begun a new adventure! This is Rebirth <%= number %> for you, and the highest Level you've attained is <%= level %>. To stack this Achievement, begin your next new adventure when you've reached an even higher Level!",
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
"subscribed": "Subscribed",
|
||||
"manageSub": "Click to manage subscription",
|
||||
"cancelSub": "Cancel Subscription",
|
||||
"cancelSubInfoGoogle": "Please go to the \"My apps & games\" > \"Subscriptions\" section of the Google Play Store app to cancel your subscription or to see your subscription's termination date if you have already cancelled it. This screen is not able to show you whether your subscription has been cancelled.",
|
||||
"cancelSubInfoGoogle": "Please go to the \"Account\" > \"Subscriptions\" section of the Google Play Store app to cancel your subscription or to see your subscription's termination date if you have already cancelled it. This screen is not able to show you whether your subscription has been cancelled.",
|
||||
"cancelSubInfoApple": "Please follow <a href=\"https://support.apple.com/en-us/HT202039\">Apple’s official instructions</a> to cancel your subscription or to see your subscription's termination date if you have already cancelled it. This screen is not able to show you whether your subscription has been cancelled.",
|
||||
"canceledSubscription": "Canceled Subscription",
|
||||
"cancelingSubscription": "Canceling the subscription",
|
||||
|
|
@ -171,5 +171,6 @@
|
|||
"missingPaymentId": "Missing req.query.paymentId",
|
||||
"missingCustomerId": "Missing req.query.customerId",
|
||||
"missingPaypalBlock": "Missing req.session.paypalBlock",
|
||||
"missingSubKey": "Missing req.query.sub"
|
||||
"missingSubKey": "Missing req.query.sub",
|
||||
"paypalCanceled": "Your subscription has been canceled"
|
||||
}
|
||||
|
|
|
|||