Compare commits
225 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d136162d48 | ||
|
|
2be8ddb60d | ||
|
|
ea6b78b7ca | ||
|
|
401067bfed | ||
|
|
54c2441934 | ||
|
|
9e8807c40d | ||
|
|
860efefdb2 | ||
|
|
1fc84c2357 | ||
|
|
95f9479d7a | ||
|
|
af095d8450 | ||
|
|
470495387c | ||
|
|
bdef1ca23c | ||
|
|
1835804e86 | ||
|
|
cb58994bdf | ||
|
|
44f3b73183 | ||
|
|
7e23fdc22a | ||
|
|
e138d2b67b | ||
|
|
3e3248fecb | ||
|
|
78ee60611a | ||
|
|
3c7aaa605b | ||
|
|
00343da266 | ||
|
|
56d09411d9 | ||
|
|
ae0df2242a | ||
|
|
5b06b28c97 | ||
|
|
c6a3bfb291 | ||
|
|
7797794cd5 | ||
|
|
d9e09a5f3d | ||
|
|
4e73c8513e | ||
|
|
9421fd7ced | ||
|
|
699de64328 | ||
|
|
6f9cbf9ca1 | ||
|
|
a097819b72 | ||
|
|
77f71b5415 | ||
|
|
ced3621dea | ||
|
|
e321d85b3c | ||
|
|
d72b40d5b0 | ||
|
|
54443a2980 | ||
|
|
00dc990974 | ||
|
|
3737aa045d | ||
|
|
b03ddf6f7d | ||
|
|
4ab89fd3e0 | ||
|
|
f1e200c0f5 | ||
|
|
218664dfcc | ||
|
|
a0f29e970d | ||
|
|
200cd66d66 | ||
|
|
dd05a8d608 | ||
|
|
299e88233c | ||
|
|
26bde1f766 | ||
|
|
d95836b881 | ||
|
|
fac81bb9ee | ||
|
|
b323abd225 | ||
|
|
b3870e5f34 | ||
|
|
29dc56c12f | ||
|
|
b62f08d500 | ||
|
|
f62177fb1a | ||
|
|
885f2998ae | ||
|
|
2afd96e11c | ||
|
|
cd92f44365 | ||
|
|
863177902a | ||
|
|
96974461e5 | ||
|
|
8895b70ffa | ||
|
|
03480ebfc7 | ||
|
|
9b8676f02e | ||
|
|
3e7738b5b1 | ||
|
|
33a235b46c | ||
|
|
137d6c1f9d | ||
|
|
1a5e820d88 | ||
|
|
0c7f9ca6bb | ||
|
|
3e6b3ce3ff | ||
|
|
ea5ba965e7 | ||
|
|
7215a550b5 | ||
|
|
3235dfa236 | ||
|
|
9baf7a7c67 | ||
|
|
cd629ef7fa | ||
|
|
9ef7c45241 | ||
|
|
fef3d09f2d | ||
|
|
53c83c585a | ||
|
|
e628c5dc3b | ||
|
|
9eaa531f66 | ||
|
|
3ffea4332e | ||
|
|
4618fd8954 | ||
|
|
791c19b5f1 | ||
|
|
7193cc6bae | ||
|
|
1845bd1e35 | ||
|
|
5f468d16b7 | ||
|
|
20a99e526d | ||
|
|
1e69f42d0f | ||
|
|
9c2f5213cb | ||
|
|
c06d5107ac | ||
|
|
1eb0f5baa5 | ||
|
|
b28189fff5 | ||
|
|
82497e4041 | ||
|
|
2a5e9c0780 | ||
|
|
c8ca67aa64 | ||
|
|
89e4cbcffe | ||
|
|
67564317fb | ||
|
|
dc2269a307 | ||
|
|
0c713ab368 | ||
|
|
be5d776fb4 | ||
|
|
b0051c45b4 | ||
|
|
b82044e07b | ||
|
|
b4c4769208 | ||
|
|
42a05446c0 | ||
|
|
9f7c0b4861 | ||
|
|
4814b0c52b | ||
|
|
67b16d91a3 | ||
|
|
8d444980de | ||
|
|
08ccd595f2 | ||
|
|
6b625a60ab | ||
|
|
6bdb695616 | ||
|
|
b999d46142 | ||
|
|
ce0f5af08d | ||
|
|
c5296d4cb0 | ||
|
|
22b683b1d9 | ||
|
|
229fd06ee3 | ||
|
|
eb8f84aae0 | ||
|
|
69b69e9d27 | ||
|
|
8033e7c0a0 | ||
|
|
e39b80bb9a | ||
|
|
2038d5e7c8 | ||
|
|
46a8ee52d4 | ||
|
|
52064f6b2a | ||
|
|
f15a27a7f1 | ||
|
|
fcf0dd87f9 | ||
|
|
ab974675b9 | ||
|
|
cb612d99d7 | ||
|
|
cb5a47ec7b | ||
|
|
838b9a5822 | ||
|
|
6c65056e2b | ||
|
|
2bf0fdf4a2 | ||
|
|
cd5ff04ee4 | ||
|
|
dbc5b9f850 | ||
|
|
ca3437d676 | ||
|
|
c43ca62bc4 | ||
|
|
eaa91b2a09 | ||
|
|
6259b68b4f | ||
|
|
08073acf11 | ||
|
|
bddafd4392 | ||
|
|
3ab14e4e5a | ||
|
|
d9830950aa | ||
|
|
45696a6273 | ||
|
|
3f92317b9e | ||
|
|
848883736d | ||
|
|
69e0ab11c0 | ||
|
|
d8d7a81edf | ||
|
|
03a09b7546 | ||
|
|
0d2737572d | ||
|
|
28149202db | ||
|
|
4b23cd9f23 | ||
|
|
8aaabdc086 | ||
|
|
6e6ca05352 | ||
|
|
74d8ecc732 | ||
|
|
63c8a09e22 | ||
|
|
865f623c99 | ||
|
|
061968dd1a | ||
|
|
21bc91c3ae | ||
|
|
0bfc6608c1 | ||
|
|
4108a22d78 | ||
|
|
34f6b63968 | ||
|
|
8d1ebff7e9 | ||
|
|
993df72708 | ||
|
|
50d3226a86 | ||
|
|
17ce2febf9 | ||
|
|
0caa195c6f | ||
|
|
f964e3c0a5 | ||
|
|
9d69d4b863 | ||
|
|
19500600bc | ||
|
|
f25fe9e263 | ||
|
|
5f37487c23 | ||
|
|
12fd79059b | ||
|
|
232061a629 | ||
|
|
3fcc1c522d | ||
|
|
8302c50302 | ||
|
|
6eb06fb054 | ||
|
|
286c8c7530 | ||
|
|
47ab8f2073 | ||
|
|
83353f6481 | ||
|
|
9cbd7ad62d | ||
|
|
3485a1d0bc | ||
|
|
2e5106fda1 | ||
|
|
2e5f5714e4 | ||
|
|
3cf7b2c96c | ||
|
|
286db39478 | ||
|
|
4d4c1cfaf3 | ||
|
|
d7ad3efabf | ||
|
|
f8876fe055 | ||
|
|
b973335d69 | ||
|
|
3b6fce0708 | ||
|
|
ff6bd6de71 | ||
|
|
042afe1df3 | ||
|
|
a208ba4aba | ||
|
|
0e958fd306 | ||
|
|
d98fe79e9c | ||
|
|
0e5a811b98 | ||
|
|
a28aea65f8 | ||
|
|
0f92349902 | ||
|
|
d4881cb73a | ||
|
|
b3216fdb85 | ||
|
|
3e37941e0a | ||
|
|
32088767ac | ||
|
|
f4d021ab8c | ||
|
|
8532203717 | ||
|
|
365daba6fc | ||
|
|
70692752c7 | ||
|
|
95d4016678 | ||
|
|
061457b268 | ||
|
|
e7ec9a6d65 | ||
|
|
d1396e7bc6 | ||
|
|
d5cedaa925 | ||
|
|
bea813b318 | ||
|
|
b31268fbc2 | ||
|
|
35727228f0 | ||
|
|
feb7ab8345 | ||
|
|
f4422b8d6c | ||
|
|
2d4dc9e23c | ||
|
|
bba2e71af3 | ||
|
|
26123ac6ae | ||
|
|
addee73e4d | ||
|
|
c39505d41c | ||
|
|
9736ef0d25 | ||
|
|
638259b885 | ||
|
|
c24545cae5 | ||
|
|
6bc70ca471 | ||
|
|
d2f0d7b20b | ||
|
|
3c9f7ff9d8 |
@@ -6,6 +6,9 @@ website/transpiled-babel/
|
||||
website/common/transpiled-babel/
|
||||
dist/
|
||||
dist-client/
|
||||
apidoc_build/
|
||||
content_cache/
|
||||
node_modules/
|
||||
|
||||
# Not linted
|
||||
website/client-old/
|
||||
@@ -16,5 +19,3 @@ migrations/*
|
||||
scripts/*
|
||||
website/common/browserify.js
|
||||
Gruntfile.js
|
||||
gulpfile.js
|
||||
gulp
|
||||
2
.github/CONTRIBUTING.md
vendored
@@ -4,7 +4,7 @@
|
||||
|
||||
# Pull Request
|
||||
|
||||
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Habitica_Git#Pull_Request)
|
||||
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
|
||||
|
||||
# Requesting a feature
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -6,7 +6,7 @@
|
||||
|
||||
[//]: # (For more guidelines see https://github.com/HabitRPG/habitica/issues/2760)
|
||||
|
||||
[//]: # (Fill out relevant information - UUID is found in Settings -> API)
|
||||
[//]: # (Fill out relevant information - UUID is found from the Habitia website at User Icon > Settings > API)
|
||||
### General Info
|
||||
* UUID:
|
||||
* Browser:
|
||||
|
||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,4 +1,4 @@
|
||||
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Habitica_Git#Pull_Request for more info)
|
||||
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
|
||||
|
||||
[//]: # (Put Issue # or URL here, if applicable. This will automatically close the issue if your PR is merged in)
|
||||
Fixes put_issue_url_here
|
||||
@@ -8,7 +8,7 @@ Fixes put_issue_url_here
|
||||
|
||||
|
||||
|
||||
[//]: # (Put User ID in here - found in Settings -> API)
|
||||
[//]: # (Put User ID in here - found on the Habitica website at User Icon > Settings > API)
|
||||
|
||||
----
|
||||
UUID:
|
||||
|
||||
@@ -34,5 +34,4 @@ env:
|
||||
- TEST="test:sanity"
|
||||
- TEST="test:content" COVERAGE=true
|
||||
- TEST="test:common" COVERAGE=true
|
||||
- TEST="client:unit" COVERAGE=true
|
||||
- TEST="apidoc"
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
FROM node:boron
|
||||
|
||||
ENV ADMIN_EMAIL admin@habitica.com
|
||||
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
|
||||
ENV AMAZON_PAYMENTS_SELLER_ID AMQ3SB4SG5E91
|
||||
ENV AMPLITUDE_KEY e8d4c24b3d6ef3ee73eeba715023dd43
|
||||
ENV BASE_URL https://habitica.com
|
||||
ENV FACEBOOK_KEY 128307497299777
|
||||
ENV GA_ID UA-33510635-1
|
||||
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
|
||||
ENV NODE_ENV production
|
||||
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
|
||||
|
||||
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
|
||||
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
|
||||
RUN yarn global add npm@5
|
||||
@@ -9,7 +20,7 @@ RUN npm install -g gulp mocha
|
||||
# Clone Habitica repo and install dependencies
|
||||
RUN mkdir -p /usr/src/habitrpg
|
||||
WORKDIR /usr/src/habitrpg
|
||||
RUN git clone --branch v4.0.3 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN git clone --branch v4.11.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||
RUN npm install
|
||||
RUN gulp build:prod --force
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true,
|
||||
},
|
||||
"extends": [
|
||||
"habitrpg/server",
|
||||
"habitrpg/babel"
|
||||
],
|
||||
}
|
||||
@@ -22,5 +22,5 @@ gulp.task('apidoc', ['apidoc:clean'], (done) => {
|
||||
});
|
||||
|
||||
gulp.task('apidoc:watch', ['apidoc'], () => {
|
||||
return gulp.watch(APIDOC_SRC_PATH + '/**/*.js', ['apidoc']);
|
||||
return gulp.watch(`${APIDOC_SRC_PATH}/**/*.js`, ['apidoc']);
|
||||
});
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import gulp from 'gulp';
|
||||
import fs from 'fs';
|
||||
|
||||
// Copy Bootstrap 4 config variables from /website /node_modules so we can check
|
||||
// them into Git
|
||||
|
||||
const BOOSTRAP_NEW_CONFIG_PATH = 'website/client/assets/scss/bootstrap_config.scss';
|
||||
const BOOTSTRAP_ORIGINAL_CONFIG_PATH = 'node_modules/bootstrap/scss/_custom.scss';
|
||||
|
||||
// https://stackoverflow.com/a/14387791/969528
|
||||
function copyFile(source, target, cb) {
|
||||
let cbCalled = false;
|
||||
|
||||
function done(err) {
|
||||
if (!cbCalled) {
|
||||
cb(err);
|
||||
cbCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
let rd = fs.createReadStream(source);
|
||||
rd.on('error', done);
|
||||
let wr = fs.createWriteStream(target);
|
||||
wr.on('error', done);
|
||||
wr.on('close', () => done());
|
||||
rd.pipe(wr);
|
||||
}
|
||||
|
||||
gulp.task('bootstrap', (done) => {
|
||||
// use new config
|
||||
copyFile(
|
||||
BOOSTRAP_NEW_CONFIG_PATH,
|
||||
BOOTSTRAP_ORIGINAL_CONFIG_PATH,
|
||||
done,
|
||||
);
|
||||
});
|
||||
@@ -1,10 +1,9 @@
|
||||
import gulp from 'gulp';
|
||||
import runSequence from 'run-sequence';
|
||||
import babel from 'gulp-babel';
|
||||
import webpackProductionBuild from '../webpack/build';
|
||||
|
||||
gulp.task('build', () => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
|
||||
gulp.start('build:prod');
|
||||
}
|
||||
});
|
||||
@@ -24,15 +23,15 @@ gulp.task('build:common', () => {
|
||||
gulp.task('build:server', ['build:src', 'build:common']);
|
||||
|
||||
// Client Production Build
|
||||
gulp.task('build:client', ['bootstrap'], (done) => {
|
||||
gulp.task('build:client', (done) => {
|
||||
webpackProductionBuild((err, output) => {
|
||||
if (err) return done(err);
|
||||
console.log(output);
|
||||
console.log(output); // eslint-disable-line no-console
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('build:prod', [
|
||||
'build:server',
|
||||
'build:server',
|
||||
'build:client',
|
||||
'apidoc',
|
||||
]);
|
||||
|
||||
@@ -7,10 +7,11 @@ import gulp from 'gulp';
|
||||
|
||||
// Add additional properties to the repl's context
|
||||
let improveRepl = (context) => {
|
||||
|
||||
// Let "exit" and "quit" terminate the console
|
||||
['exit', 'quit'].forEach((term) => {
|
||||
Object.defineProperty(context, term, { get () { process.exit(); }});
|
||||
Object.defineProperty(context, term, { get () {
|
||||
process.exit();
|
||||
}});
|
||||
});
|
||||
|
||||
// "clear" clears the screen
|
||||
@@ -18,12 +19,12 @@ let improveRepl = (context) => {
|
||||
process.stdout.write('\u001B[2J\u001B[0;0f');
|
||||
}});
|
||||
|
||||
context.Challenge = require('../website/server/models/challenge').model;
|
||||
context.Group = require('../website/server/models/group').model;
|
||||
context.User = require('../website/server/models/user').model;
|
||||
context.Challenge = require('../website/server/models/challenge').model; // eslint-disable-line global-require
|
||||
context.Group = require('../website/server/models/group').model; // eslint-disable-line global-require
|
||||
context.User = require('../website/server/models/user').model; // eslint-disable-line global-require
|
||||
|
||||
var isProd = nconf.get('NODE_ENV') === 'production';
|
||||
var mongooseOptions = !isProd ? {} : {
|
||||
const isProd = nconf.get('NODE_ENV') === 'production';
|
||||
const mongooseOptions = !isProd ? {} : {
|
||||
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
|
||||
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
|
||||
};
|
||||
@@ -31,16 +32,15 @@ let improveRepl = (context) => {
|
||||
mongoose.connect(
|
||||
nconf.get('NODE_DB_URI'),
|
||||
mongooseOptions,
|
||||
function (err) {
|
||||
(err) => {
|
||||
if (err) throw err;
|
||||
logger.info('Connected with Mongoose');
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
gulp.task('console', (cb) => {
|
||||
gulp.task('console', () => {
|
||||
improveRepl(repl.start({
|
||||
prompt: 'Habitica > ',
|
||||
}).context);
|
||||
|
||||
@@ -11,78 +11,38 @@ import {each} from 'lodash';
|
||||
// https://github.com/Ensighten/grunt-spritesmith/issues/67#issuecomment-34786248
|
||||
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
|
||||
|
||||
const IMG_DIST_PATH = 'website/static/sprites/';
|
||||
const IMG_DIST_PATH = 'website/client/assets/images/sprites/';
|
||||
const CSS_DIST_PATH = 'website/client/assets/css/sprites/';
|
||||
|
||||
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
|
||||
function checkForSpecialTreatment (name) {
|
||||
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
|
||||
return name.match(regex) || name === 'head_0';
|
||||
}
|
||||
|
||||
gulp.task('sprites:main', () => {
|
||||
let mainSrc = sync('website/raw_sprites/spritesmith/**/*.png');
|
||||
return createSpritesStream('main', mainSrc);
|
||||
});
|
||||
function calculateImgDimensions (img, addPadding) {
|
||||
let dims = sizeOf(img);
|
||||
|
||||
gulp.task('sprites:largeSprites', () => {
|
||||
let largeSrc = sync('website/raw_sprites/spritesmith_large/**/*.png');
|
||||
return createSpritesStream('largeSprites', largeSrc);
|
||||
});
|
||||
|
||||
gulp.task('sprites:clean', (done) => {
|
||||
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
|
||||
});
|
||||
|
||||
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
|
||||
console.log('Verifiying that images do not exceed max dimensions');
|
||||
|
||||
let numberOfSheetsThatAreTooBig = 0;
|
||||
|
||||
let distSpritesheets = sync(`${IMG_DIST_PATH}*.png`);
|
||||
|
||||
each(distSpritesheets, (img, index) => {
|
||||
let spriteSize = calculateImgDimensions(img);
|
||||
|
||||
if (spriteSize > MAX_SPRITESHEET_SIZE) {
|
||||
numberOfSheetsThatAreTooBig++;
|
||||
let name = basename(img, '.png');
|
||||
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (numberOfSheetsThatAreTooBig > 0) {
|
||||
console.error(`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle them, but there is a margin of error in these calculations so it is probably okay. Mention this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); // https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
|
||||
} else {
|
||||
console.log('All images are within the correct dimensions');
|
||||
let requiresSpecialTreatment = checkForSpecialTreatment(img);
|
||||
if (requiresSpecialTreatment) {
|
||||
let newWidth = dims.width < 90 ? 90 : dims.width;
|
||||
let newHeight = dims.height < 90 ? 90 : dims.height;
|
||||
dims = {
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function createSpritesStream (name, src) {
|
||||
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
|
||||
let stream = mergeStream();
|
||||
let padding = 0;
|
||||
|
||||
each(spritesheetSliceIndicies, (start, index) => {
|
||||
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
|
||||
if (addPadding) {
|
||||
padding = dims.width * 8 + dims.height * 8;
|
||||
}
|
||||
|
||||
let spriteData = gulp.src(slicedSrc)
|
||||
.pipe(spritesmith({
|
||||
imgName: `spritesmith-${name}-${index}.png`,
|
||||
cssName: `spritesmith-${name}-${index}.css`,
|
||||
algorithm: 'binary-tree',
|
||||
padding: 1,
|
||||
cssTemplate: 'website/raw_sprites/css/css.template.handlebars',
|
||||
cssVarMap: cssVarMap,
|
||||
}));
|
||||
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims); // eslint-disable-line no-console
|
||||
|
||||
let imgStream = spriteData.img
|
||||
.pipe(imagemin())
|
||||
.pipe(gulp.dest(IMG_DIST_PATH));
|
||||
let totalPixelSize = dims.width * dims.height + padding;
|
||||
|
||||
let cssStream = spriteData.css
|
||||
.pipe(gulp.dest(CSS_DIST_PATH));
|
||||
|
||||
stream.add(imgStream);
|
||||
stream.add(cssStream);
|
||||
});
|
||||
|
||||
return stream;
|
||||
return totalPixelSize;
|
||||
}
|
||||
|
||||
function calculateSpritesheetsSrcIndicies (src) {
|
||||
@@ -102,37 +62,6 @@ function calculateSpritesheetsSrcIndicies (src) {
|
||||
return slices;
|
||||
}
|
||||
|
||||
function calculateImgDimensions (img, addPadding) {
|
||||
let dims = sizeOf(img);
|
||||
|
||||
let requiresSpecialTreatment = checkForSpecialTreatment(img);
|
||||
if (requiresSpecialTreatment) {
|
||||
let newWidth = dims.width < 90 ? 90 : dims.width;
|
||||
let newHeight = dims.height < 90 ? 90 : dims.height;
|
||||
dims = {
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
};
|
||||
}
|
||||
|
||||
let padding = 0;
|
||||
|
||||
if (addPadding) {
|
||||
padding = (dims.width * 8) + (dims.height * 8);
|
||||
}
|
||||
|
||||
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims);
|
||||
|
||||
let totalPixelSize = (dims.width * dims.height) + padding;
|
||||
|
||||
return totalPixelSize;
|
||||
}
|
||||
|
||||
function checkForSpecialTreatment (name) {
|
||||
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
|
||||
return name.match(regex) || name === 'head_0';
|
||||
}
|
||||
|
||||
function cssVarMap (sprite) {
|
||||
// For hair, skins, beards, etc. we want to output a '.customize-options.WHATEVER' class, which works as a
|
||||
// 60x60 image pointing at the proper part of the 90x90 sprite.
|
||||
@@ -141,18 +70,93 @@ function cssVarMap (sprite) {
|
||||
if (requiresSpecialTreatment) {
|
||||
sprite.custom = {
|
||||
px: {
|
||||
offset_x: `-${ sprite.x + 25 }px`,
|
||||
offset_y: `-${ sprite.y + 15 }px`,
|
||||
offsetX: `-${ sprite.x + 25 }px`,
|
||||
offsetY: `-${ sprite.y + 15 }px`,
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
},
|
||||
};
|
||||
}
|
||||
if (~sprite.name.indexOf('shirt'))
|
||||
sprite.custom.px.offset_y = `-${ sprite.y + 30 }px`; // even more for shirts
|
||||
if (~sprite.name.indexOf('hair_base')) {
|
||||
let styleArray = sprite.name.split('_').slice(2,3);
|
||||
if (sprite.name.indexOf('shirt') !== -1)
|
||||
sprite.custom.px.offsetY = `-${ sprite.y + 35 }px`; // even more for shirts
|
||||
if (sprite.name.indexOf('hair_base') !== -1) {
|
||||
let styleArray = sprite.name.split('_').slice(2, 3);
|
||||
if (Number(styleArray[0]) > 14)
|
||||
sprite.custom.px.offset_y = `-${ sprite.y }px`; // don't crop updos
|
||||
sprite.custom.px.offsetY = `-${ sprite.y }px`; // don't crop updos
|
||||
}
|
||||
}
|
||||
|
||||
function createSpritesStream (name, src) {
|
||||
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
|
||||
let stream = mergeStream();
|
||||
|
||||
each(spritesheetSliceIndicies, (start, index) => {
|
||||
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
|
||||
|
||||
let spriteData = gulp.src(slicedSrc)
|
||||
.pipe(spritesmith({
|
||||
imgName: `spritesmith-${name}-${index}.png`,
|
||||
cssName: `spritesmith-${name}-${index}.css`,
|
||||
algorithm: 'binary-tree',
|
||||
padding: 1,
|
||||
cssTemplate: 'website/raw_sprites/css/css.template.handlebars',
|
||||
cssVarMap,
|
||||
}));
|
||||
|
||||
let imgStream = spriteData.img
|
||||
.pipe(imagemin())
|
||||
.pipe(gulp.dest(IMG_DIST_PATH));
|
||||
|
||||
let cssStream = spriteData.css
|
||||
.pipe(gulp.dest(CSS_DIST_PATH));
|
||||
|
||||
stream.add(imgStream);
|
||||
stream.add(cssStream);
|
||||
});
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
|
||||
|
||||
gulp.task('sprites:main', () => {
|
||||
let mainSrc = sync('website/raw_sprites/spritesmith/**/*.png');
|
||||
return createSpritesStream('main', mainSrc);
|
||||
});
|
||||
|
||||
gulp.task('sprites:largeSprites', () => {
|
||||
let largeSrc = sync('website/raw_sprites/spritesmith_large/**/*.png');
|
||||
return createSpritesStream('largeSprites', largeSrc);
|
||||
});
|
||||
|
||||
gulp.task('sprites:clean', (done) => {
|
||||
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
|
||||
});
|
||||
|
||||
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
|
||||
console.log('Verifiying that images do not exceed max dimensions'); // eslint-disable-line no-console
|
||||
|
||||
let numberOfSheetsThatAreTooBig = 0;
|
||||
|
||||
let distSpritesheets = sync(`${IMG_DIST_PATH}*.png`);
|
||||
|
||||
each(distSpritesheets, (img) => {
|
||||
let spriteSize = calculateImgDimensions(img);
|
||||
|
||||
if (spriteSize > MAX_SPRITESHEET_SIZE) {
|
||||
numberOfSheetsThatAreTooBig++;
|
||||
let name = basename(img, '.png');
|
||||
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`); // eslint-disable-line no-console
|
||||
}
|
||||
});
|
||||
|
||||
if (numberOfSheetsThatAreTooBig > 0) {
|
||||
// https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
|
||||
console.error( // eslint-disable-line no-console
|
||||
`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle
|
||||
them, but there is a margin of error in these calculations so it is probably okay. Mention
|
||||
this to an admin so they can test a staging site on mobile Safari after your PR is merged.`);
|
||||
} else {
|
||||
console.log('All images are within the correct dimensions'); // eslint-disable-line no-console
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import {
|
||||
pipe,
|
||||
awaitPort,
|
||||
kill,
|
||||
runMochaTests,
|
||||
} from './taskHelper';
|
||||
import { server as karma } from 'karma';
|
||||
import mongoose from 'mongoose';
|
||||
import { exec } from 'child_process';
|
||||
import psTree from 'ps-tree';
|
||||
import gulp from 'gulp';
|
||||
import Bluebird from 'bluebird';
|
||||
import runSequence from 'run-sequence';
|
||||
import os from 'os';
|
||||
import nconf from 'nconf';
|
||||
import fs from 'fs';
|
||||
|
||||
const i18n = require('../website/server/libs/i18n');
|
||||
|
||||
// TODO rewrite
|
||||
|
||||
@@ -24,7 +15,6 @@ let server;
|
||||
|
||||
const TEST_DB_URI = nconf.get('TEST_DB_URI');
|
||||
|
||||
const API_V3_TEST_COMMAND = 'npm run test:api-v3';
|
||||
const SANITY_TEST_COMMAND = 'npm run test:sanity';
|
||||
const COMMON_TEST_COMMAND = 'npm run test:common';
|
||||
const CONTENT_TEST_COMMAND = 'npm run test:content';
|
||||
@@ -34,14 +24,14 @@ const CONTENT_OPTIONS = {maxBuffer: 1024 * 500};
|
||||
let testResults = [];
|
||||
let testCount = (stdout, regexp) => {
|
||||
let match = stdout.match(regexp);
|
||||
return parseInt(match && match[1] || 0);
|
||||
return parseInt(match && match[1] || 0, 10);
|
||||
};
|
||||
|
||||
let testBin = (string, additionalEnvVariables = '') => {
|
||||
if (os.platform() === 'win32') {
|
||||
if (additionalEnvVariables != '') {
|
||||
if (additionalEnvVariables !== '') {
|
||||
additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set ');
|
||||
additionalEnvVariables = 'set ' + additionalEnvVariables + '&&';
|
||||
additionalEnvVariables = `set ${additionalEnvVariables}&&`;
|
||||
}
|
||||
return `set NODE_ENV=test&&${additionalEnvVariables}${string}`;
|
||||
} else {
|
||||
@@ -49,9 +39,9 @@ let testBin = (string, additionalEnvVariables = '') => {
|
||||
}
|
||||
};
|
||||
|
||||
gulp.task('test:nodemon', (done) => {
|
||||
process.env.PORT = TEST_SERVER_PORT;
|
||||
process.env.NODE_DB_URI = TEST_DB_URI;
|
||||
gulp.task('test:nodemon', () => {
|
||||
process.env.PORT = TEST_SERVER_PORT; // eslint-disable-line no-process-env
|
||||
process.env.NODE_DB_URI = TEST_DB_URI; // eslint-disable-line no-process-env
|
||||
|
||||
runSequence('nodemon');
|
||||
});
|
||||
@@ -68,8 +58,12 @@ gulp.task('test:prepare:mongo', (cb) => {
|
||||
gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
|
||||
if (!server) {
|
||||
server = exec(testBin('node ./website/server/index.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
|
||||
if (error) { throw `Problem with the server: ${error}`; }
|
||||
if (stderr) { console.error(stderr); }
|
||||
if (error) {
|
||||
throw new Error(`Problem with the server: ${error}`);
|
||||
}
|
||||
if (stderr) {
|
||||
console.error(stderr); // eslint-disable-line no-console
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -84,7 +78,7 @@ gulp.task('test:prepare', [
|
||||
gulp.task('test:sanity', (cb) => {
|
||||
let runner = exec(
|
||||
testBin(SANITY_TEST_COMMAND),
|
||||
(err, stdout, stderr) => {
|
||||
(err) => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -97,7 +91,7 @@ gulp.task('test:sanity', (cb) => {
|
||||
gulp.task('test:common', ['test:prepare:build'], (cb) => {
|
||||
let runner = exec(
|
||||
testBin(COMMON_TEST_COMMAND),
|
||||
(err, stdout, stderr) => {
|
||||
(err) => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -118,7 +112,7 @@ gulp.task('test:common:watch', ['test:common:clean'], () => {
|
||||
gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
|
||||
let runner = exec(
|
||||
testBin(COMMON_TEST_COMMAND),
|
||||
(err, stdout, stderr) => {
|
||||
(err, stdout) => { // eslint-disable-line handle-callback-err
|
||||
testResults.push({
|
||||
suite: 'Common Specs\t',
|
||||
pass: testCount(stdout, /(\d+) passing/),
|
||||
@@ -135,7 +129,7 @@ gulp.task('test:content', ['test:prepare:build'], (cb) => {
|
||||
let runner = exec(
|
||||
testBin(CONTENT_TEST_COMMAND),
|
||||
CONTENT_OPTIONS,
|
||||
(err, stdout, stderr) => {
|
||||
(err) => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -157,7 +151,7 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
|
||||
let runner = exec(
|
||||
testBin(CONTENT_TEST_COMMAND),
|
||||
CONTENT_OPTIONS,
|
||||
(err, stdout, stderr) => {
|
||||
(err, stdout) => { // eslint-disable-line handle-callback-err
|
||||
testResults.push({
|
||||
suite: 'Content Specs\t',
|
||||
pass: testCount(stdout, /(\d+) passing/),
|
||||
@@ -173,7 +167,7 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
|
||||
gulp.task('test:api-v3:unit', (done) => {
|
||||
let runner = exec(
|
||||
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-unit --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/unit --recursive --require ./test/helpers/start-server'),
|
||||
(err, stdout, stderr) => {
|
||||
(err) => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -192,7 +186,7 @@ gulp.task('test:api-v3:integration', (done) => {
|
||||
let runner = exec(
|
||||
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
|
||||
{maxBuffer: 500 * 1024},
|
||||
(err, stdout, stderr) => {
|
||||
(err) => {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -212,7 +206,7 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
|
||||
let runner = exec(
|
||||
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
|
||||
{maxBuffer: 500 * 1024},
|
||||
(err, stdout, stderr) => done(err)
|
||||
(err) => done(err)
|
||||
);
|
||||
|
||||
pipe(runner);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import fs from 'fs';
|
||||
import _ from 'lodash';
|
||||
import nconf from 'nconf';
|
||||
import gulp from 'gulp';
|
||||
import { postToSlack, conf } from './taskHelper';
|
||||
|
||||
@@ -12,8 +11,82 @@ const SLACK_CONFIG = {
|
||||
|
||||
const LOCALES = './website/common/locales/';
|
||||
const ENGLISH_LOCALE = `${LOCALES}en/`;
|
||||
|
||||
|
||||
function getArrayOfLanguages () {
|
||||
let languages = fs.readdirSync(LOCALES);
|
||||
languages.shift(); // Remove README.md from array of languages
|
||||
|
||||
return languages;
|
||||
}
|
||||
|
||||
const ALL_LANGUAGES = getArrayOfLanguages();
|
||||
|
||||
function stripOutNonJsonFiles (collection) {
|
||||
let onlyJson = _.filter(collection, (file) => {
|
||||
return file.match(/[a-zA-Z]*\.json/);
|
||||
});
|
||||
|
||||
return onlyJson;
|
||||
}
|
||||
|
||||
function eachTranslationFile (languages, cb) {
|
||||
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
|
||||
|
||||
_.each(languages, (lang) => {
|
||||
_.each(jsonFiles, (filename) => {
|
||||
let parsedTranslationFile;
|
||||
try {
|
||||
const translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
|
||||
parsedTranslationFile = JSON.parse(translationFile);
|
||||
} catch (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
|
||||
let parsedEnglishFile = JSON.parse(englishFile);
|
||||
|
||||
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function eachTranslationString (languages, cb) {
|
||||
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
|
||||
if (error) return;
|
||||
_.each(englishJSON, (string, key) => {
|
||||
const translationString = translationJSON[key];
|
||||
cb(language, filename, key, string, translationString);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function formatMessageForPosting (msg, items) {
|
||||
let body = `*Warning:* ${msg}`;
|
||||
body += '\n\n```\n';
|
||||
body += items.join('\n');
|
||||
body += '\n```';
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
function getStringsWith (json, interpolationRegex) {
|
||||
let strings = {};
|
||||
|
||||
_.each(json, (fileName) => {
|
||||
const rawFile = fs.readFileSync(ENGLISH_LOCALE + fileName);
|
||||
const parsedJson = JSON.parse(rawFile);
|
||||
|
||||
strings[fileName] = {};
|
||||
_.each(parsedJson, (value, key) => {
|
||||
const match = value.match(interpolationRegex);
|
||||
if (match) strings[fileName][key] = match;
|
||||
});
|
||||
});
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
const malformedStringExceptions = {
|
||||
messageDropFood: true,
|
||||
armoireFood: true,
|
||||
@@ -23,7 +96,6 @@ const malformedStringExceptions = {
|
||||
gulp.task('transifex', ['transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings']);
|
||||
|
||||
gulp.task('transifex:missingFiles', () => {
|
||||
|
||||
let missingStrings = [];
|
||||
|
||||
eachTranslationFile(ALL_LANGUAGES, (error) => {
|
||||
@@ -40,7 +112,6 @@ gulp.task('transifex:missingFiles', () => {
|
||||
});
|
||||
|
||||
gulp.task('transifex:missingStrings', () => {
|
||||
|
||||
let missingStrings = [];
|
||||
|
||||
eachTranslationString(ALL_LANGUAGES, (language, filename, key, englishString, translationString) => {
|
||||
@@ -58,7 +129,6 @@ gulp.task('transifex:missingStrings', () => {
|
||||
});
|
||||
|
||||
gulp.task('transifex:malformedStrings', () => {
|
||||
|
||||
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
|
||||
let interpolationRegex = /<%= [a-zA-Z]* %>/g;
|
||||
let stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex);
|
||||
@@ -66,25 +136,23 @@ gulp.task('transifex:malformedStrings', () => {
|
||||
let stringsWithMalformedInterpolations = [];
|
||||
let stringsWithIncorrectNumberOfInterpolations = [];
|
||||
|
||||
let count = 0;
|
||||
_.each(ALL_LANGUAGES, function (lang) {
|
||||
|
||||
_.each(stringsToLookFor, function (strings, file) {
|
||||
let translationFile = fs.readFileSync(LOCALES + lang + '/' + file);
|
||||
_.each(ALL_LANGUAGES, (lang) => {
|
||||
_.each(stringsToLookFor, (strings, filename) => {
|
||||
let translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
|
||||
let parsedTranslationFile = JSON.parse(translationFile);
|
||||
|
||||
_.each(strings, function (value, key) {
|
||||
_.each(strings, (value, key) => { // eslint-disable-line max-nested-callbacks
|
||||
let translationString = parsedTranslationFile[key];
|
||||
if (!translationString) return;
|
||||
|
||||
let englishOccurences = stringsToLookFor[file][key];
|
||||
let englishOccurences = stringsToLookFor[filename][key];
|
||||
let translationOccurences = translationString.match(interpolationRegex);
|
||||
|
||||
if (!translationOccurences) {
|
||||
let malformedString = `${lang} - ${file} - ${key} - ${translationString}`;
|
||||
let malformedString = `${lang} - ${filename} - ${key} - ${translationString}`;
|
||||
stringsWithMalformedInterpolations.push(malformedString);
|
||||
} else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) {
|
||||
let missingInterpolationString = `${lang} - ${file} - ${key} - ${translationString}`;
|
||||
let missingInterpolationString = `${lang} - ${filename} - ${key} - ${translationString}`;
|
||||
stringsWithIncorrectNumberOfInterpolations.push(missingInterpolationString);
|
||||
}
|
||||
});
|
||||
@@ -103,74 +171,3 @@ gulp.task('transifex:malformedStrings', () => {
|
||||
postToSlack(formattedMessage, SLACK_CONFIG);
|
||||
}
|
||||
});
|
||||
|
||||
function getArrayOfLanguages () {
|
||||
let languages = fs.readdirSync(LOCALES);
|
||||
languages.shift(); // Remove README.md from array of languages
|
||||
|
||||
return languages;
|
||||
}
|
||||
|
||||
function eachTranslationFile (languages, cb) {
|
||||
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
|
||||
|
||||
_.each(languages, (lang) => {
|
||||
_.each(jsonFiles, (filename) => {
|
||||
try {
|
||||
var translationFile = fs.readFileSync(LOCALES + lang + '/' + filename);
|
||||
var parsedTranslationFile = JSON.parse(translationFile);
|
||||
} catch (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
|
||||
let parsedEnglishFile = JSON.parse(englishFile);
|
||||
|
||||
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function eachTranslationString (languages, cb) {
|
||||
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
|
||||
if (error) return;
|
||||
_.each(englishJSON, (string, key) => {
|
||||
var translationString = translationJSON[key];
|
||||
cb(language, filename, key, string, translationString);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function formatMessageForPosting (msg, items) {
|
||||
let body = `*Warning:* ${msg}`;
|
||||
body += '\n\n```\n';
|
||||
body += items.join('\n');
|
||||
body += '\n```';
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
function getStringsWith (json, interpolationRegex) {
|
||||
var strings = {};
|
||||
|
||||
_.each(json, function (file_name) {
|
||||
var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name);
|
||||
var parsed_json = JSON.parse(raw_file);
|
||||
|
||||
strings[file_name] = {};
|
||||
_.each(parsed_json, function (value, key) {
|
||||
var match = value.match(interpolationRegex);
|
||||
if (match) strings[file_name][key] = match;
|
||||
});
|
||||
});
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
function stripOutNonJsonFiles (collection) {
|
||||
let onlyJson = _.filter(collection, (file) => {
|
||||
return file.match(/[a-zA-Z]*\.json/);
|
||||
});
|
||||
|
||||
return onlyJson;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { resolve } from 'path';
|
||||
* Get access to configruable values
|
||||
*/
|
||||
nconf.argv().env().file({ file: 'config.json' });
|
||||
export var conf = nconf;
|
||||
export const conf = nconf;
|
||||
|
||||
/*
|
||||
* Kill a child process and any sub-children that process may have spawned.
|
||||
@@ -26,11 +26,12 @@ export function kill (proc) {
|
||||
pids.forEach(kill); return;
|
||||
}
|
||||
try {
|
||||
exec(/^win/.test(process.platform)
|
||||
? `taskkill /PID ${pid} /T /F`
|
||||
: `kill -9 ${pid}`);
|
||||
exec(/^win/.test(process.platform) ?
|
||||
`taskkill /PID ${pid} /T /F` :
|
||||
`kill -9 ${pid}`);
|
||||
} catch (e) {
|
||||
console.log(e); // eslint-disable-line no-console
|
||||
}
|
||||
catch (e) { console.log(e); }
|
||||
});
|
||||
};
|
||||
|
||||
@@ -44,21 +45,25 @@ export function kill (proc) {
|
||||
* before failing.
|
||||
*/
|
||||
export function awaitPort (port, max = 60) {
|
||||
return new Bluebird((reject, resolve) => {
|
||||
let socket, timeout, interval;
|
||||
return new Bluebird((rej, res) => {
|
||||
let socket;
|
||||
let timeout;
|
||||
let interval;
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
reject(`Timed out after ${max} seconds`);
|
||||
rej(`Timed out after ${max} seconds`);
|
||||
}, max * 1000);
|
||||
|
||||
interval = setInterval(() => {
|
||||
socket = net.connect({port: port}, () => {
|
||||
socket = net.connect({port}, () => {
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout);
|
||||
socket.destroy();
|
||||
resolve();
|
||||
}).on('error', () => { socket.destroy; });
|
||||
res();
|
||||
}).on('error', () => {
|
||||
socket.destroy();
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
@@ -67,8 +72,12 @@ export function awaitPort (port, max = 60) {
|
||||
* Pipe the child's stdin and stderr to the parent process.
|
||||
*/
|
||||
export function pipe (child) {
|
||||
child.stdout.on('data', (data) => { process.stdout.write(data); });
|
||||
child.stderr.on('data', (data) => { process.stderr.write(data); });
|
||||
child.stdout.on('data', (data) => {
|
||||
process.stdout.write(data);
|
||||
});
|
||||
child.stderr.on('data', (data) => {
|
||||
process.stderr.write(data);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -78,8 +87,8 @@ export function postToSlack (msg, config = {}) {
|
||||
let slackUrl = nconf.get('SLACK_URL');
|
||||
|
||||
if (!slackUrl) {
|
||||
console.error('No slack post url specified. Your message was:');
|
||||
console.log(msg);
|
||||
console.error('No slack post url specified. Your message was:'); // eslint-disable-line no-console
|
||||
console.log(msg); // eslint-disable-line no-console
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -89,15 +98,15 @@ export function postToSlack (msg, config = {}) {
|
||||
channel: `#${config.channel || '#general'}`,
|
||||
username: config.username || 'gulp task',
|
||||
text: msg,
|
||||
icon_emoji: `:${config.emoji || 'gulp'}:`,
|
||||
icon_emoji: `:${config.emoji || 'gulp'}:`, // eslint-disable-line camelcase
|
||||
})
|
||||
.end((err, res) => {
|
||||
if (err) console.error('Unable to post to slack', err);
|
||||
.end((err) => {
|
||||
if (err) console.error('Unable to post to slack', err); // eslint-disable-line no-console
|
||||
});
|
||||
}
|
||||
|
||||
export function runMochaTests (files, server, cb) {
|
||||
require('../test/helpers/globals.helper');
|
||||
require('../test/helpers/globals.helper'); // eslint-disable-line global-require
|
||||
|
||||
let mocha = new Mocha({reporter: 'spec'});
|
||||
let tests = glob(files);
|
||||
@@ -108,7 +117,7 @@ export function runMochaTests (files, server, cb) {
|
||||
});
|
||||
|
||||
mocha.run((numberOfFailures) => {
|
||||
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) {
|
||||
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) { // eslint-disable-line no-process-env
|
||||
if (server) kill(server);
|
||||
process.exit(numberOfFailures);
|
||||
}
|
||||
|
||||
11
gulpfile.js
@@ -8,11 +8,10 @@
|
||||
|
||||
require('babel-register');
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
require('./gulp/gulp-apidoc');
|
||||
require('./gulp/gulp-build');
|
||||
require('./gulp/gulp-bootstrap');
|
||||
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
|
||||
require('./gulp/gulp-apidoc'); // eslint-disable-line global-require
|
||||
require('./gulp/gulp-build'); // eslint-disable-line global-require
|
||||
} else {
|
||||
require('glob').sync('./gulp/gulp-*').forEach(require);
|
||||
require('gulp').task('default', ['test']);
|
||||
require('glob').sync('./gulp/gulp-*').forEach(require); // eslint-disable-line global-require
|
||||
require('gulp').task('default', ['test']); // eslint-disable-line global-require
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ var migrationName = '20140831_increase_gems_for_previous_contributions';
|
||||
* https://github.com/HabitRPG/habitrpg/issues/3933
|
||||
* Increase Number of Gems for Contributors
|
||||
* author: Alys (d904bd62-da08-416b-a816-ba797c9ee265)
|
||||
*
|
||||
*
|
||||
* Increase everyone's gems per their contribution level.
|
||||
* Originally they were given 2 gems per tier.
|
||||
* Now they are given 3 gems per tier for tiers 1,2,3
|
||||
@@ -70,7 +70,7 @@ dbUsers.findEach(query, fields, function(err, user) {
|
||||
var extraGems = tier; // tiers 1,2,3
|
||||
if (tier > 3) { extraGems = 3 + (tier - 3) * 2; }
|
||||
if (tier == 8) { extraGems = 11; }
|
||||
extraBalance = extraGems / 4;
|
||||
var extraBalance = extraGems / 4;
|
||||
set['balance'] = user.balance + extraBalance;
|
||||
|
||||
// Capture current state of user:
|
||||
|
||||
@@ -39,7 +39,7 @@ function findUsers(gt){
|
||||
console.log('User: ', countUsers, user._id);
|
||||
|
||||
var update = {
|
||||
$set: {};
|
||||
$set: {}
|
||||
};
|
||||
|
||||
if(user.auth && user.auth.local) {
|
||||
@@ -60,4 +60,4 @@ function findUsers(gt){
|
||||
});
|
||||
};
|
||||
|
||||
findUsers();
|
||||
findUsers();
|
||||
|
||||
111
migrations/20171030_jackolanterns.js
Normal file
@@ -0,0 +1,111 @@
|
||||
var migrationName = '20171030_jackolanterns.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Award the Jack-O'-Lantern ladder:
|
||||
* Ghost Jack-O-Lantern Mount to owners of Ghost Jack-O-Lantern Pet
|
||||
* Ghost Jack-O-Lantern Pet to owners of Jack-O-Lantern Mount
|
||||
* Jack-O-Lantern Mount to owners of Jack-O-Lantern Pet
|
||||
* Jack-O-Lantern Pet to everyone else
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
'items.pets',
|
||||
'items.mounts',
|
||||
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
var set = {};
|
||||
var inc = {
|
||||
'items.food.Candy_Skeleton': 1,
|
||||
'items.food.Candy_Base': 1,
|
||||
'items.food.Candy_CottonCandyBlue': 1,
|
||||
'items.food.Candy_CottonCandyPink': 1,
|
||||
'items.food.Candy_Shade': 1,
|
||||
'items.food.Candy_White': 1,
|
||||
'items.food.Candy_Golden': 1,
|
||||
'items.food.Candy_Zombie': 1,
|
||||
'items.food.Candy_Desert': 1,
|
||||
'items.food.Candy_Red': 1,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) {
|
||||
set = {'migration':migrationName, 'items.mounts.JackOLantern-Ghost': true};
|
||||
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) {
|
||||
set = {'migration':migrationName, 'items.pets.JackOLantern-Ghost': 5};
|
||||
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) {
|
||||
set = {'migration':migrationName, 'items.mounts.JackOLantern-Base': true};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.pets.JackOLantern-Base': 5};
|
||||
}
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set:set, $inc:inc});
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
128
migrations/20171117_turkey_ladder.js
Normal file
@@ -0,0 +1,128 @@
|
||||
var migrationName = '20171117_turkey_ladder.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Award the Turkey Day ladder:
|
||||
* Grant Turkey Costume to those who have the Gilded Turkey mount
|
||||
* Grant Gilded Turkey mount to those who have the Gilded Turkey pet
|
||||
* Grant Gilded Turkey pet to those who have the Base Turkey mount
|
||||
* Grant Base Turkey mount to those who have the Base Turkey pet
|
||||
* Grant Base Turkey pet to those who have none of the above yet
|
||||
*/
|
||||
|
||||
var monk = require('monk');
|
||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
function processUsers(lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
'migration':{$ne:migrationName},
|
||||
'auth.timestamps.loggedin':{$gt:new Date('2017-11-01')},
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId
|
||||
}
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
'items.pets',
|
||||
'items.mounts',
|
||||
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
return exiting(1, 'ERROR! ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
var userPromises = users.map(updateUser);
|
||||
var lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(function () {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
var set = {};
|
||||
|
||||
if (user && user.items && user.items.mounts && user.items.mounts['Turkey-Gilded']) {
|
||||
set = {
|
||||
migration: migrationName,
|
||||
'items.gear.owned.head_special_turkeyHelmBase': false,
|
||||
'items.gear.owned.armor_special_turkeyArmorBase': false,
|
||||
'items.gear.owned.back_special_turkeyTailBase': false,
|
||||
};
|
||||
var push = [
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.head_special_turkeyHelmBase',
|
||||
_id: monk.id(),
|
||||
},
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.armor_special_turkeyArmorBase',
|
||||
_id: monk.id(),
|
||||
},
|
||||
{
|
||||
type: 'marketGear',
|
||||
path: 'gear.flat.back_special_turkeyTailBase',
|
||||
_id: monk.id(),
|
||||
},
|
||||
];
|
||||
} else if (user && user.items && user.items.pets && user.items.pets['Turkey-Gilded']) {
|
||||
set = {'migration':migrationName, 'items.mounts.Turkey-Gilded':true};
|
||||
} else if (user && user.items && user.items.mounts && user.items.mounts['Turkey-Base']) {
|
||||
set = {'migration':migrationName, 'items.pets.Turkey-Gilded':5};
|
||||
} else if (user && user.items && user.items.pets && user.items.pets['Turkey-Base']) {
|
||||
set = {'migration':migrationName, 'items.mounts.Turkey-Base':true};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.pets.Turkey-Base':5};
|
||||
}
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set});
|
||||
if (push) {
|
||||
dbUsers.update({_id: user._id}, {$push: {pinnedItems: {$each: push}}});
|
||||
}
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
}
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
@@ -17,8 +17,12 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
var processUsers = require('./groups/update-groups-with-group-plans');
|
||||
const processUsers = require('./users/account-transfer');
|
||||
processUsers()
|
||||
.catch(function (err) {
|
||||
console.log(err)
|
||||
.then(() => {
|
||||
process.exit();
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.log(err);
|
||||
process.exit();
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ var _id = '';
|
||||
var update = {
|
||||
$addToSet: {
|
||||
'purchased.plan.mysteryItems':{
|
||||
$each:['armor_mystery_201710','head_mystery_201710']
|
||||
$each:['armor_mystery_201711','body_mystery_201711']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -65,19 +65,29 @@ function updateUser (user) {
|
||||
set = {'migration':migrationName};
|
||||
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.back_special_takeThis':false};
|
||||
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', '_id': monk.id()}};
|
||||
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.body_special_takeThis':false};
|
||||
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', '_id': monk.id()}};
|
||||
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.head_special_takeThis':false};
|
||||
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', '_id': monk.id()}};
|
||||
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.armor_special_takeThis':false};
|
||||
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', '_id': monk.id()}};
|
||||
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
|
||||
set = {'migration':migrationName, 'items.gear.owned.weapon_special_takeThis':false};
|
||||
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', '_id': monk.id()}};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.gear.owned.shield_special_takeThis':false};
|
||||
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', '_id': monk.id()}};
|
||||
}
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set:set});
|
||||
if (push) {
|
||||
dbUsers.update({_id: user._id}, {$set: set, $push: push});
|
||||
} else {
|
||||
dbUsers.update({_id: user._id}, {$set: set});
|
||||
}
|
||||
|
||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
|
||||
38
migrations/users/account-transfer.js
Normal file
@@ -0,0 +1,38 @@
|
||||
var migrationName = 'AccountTransfer';
|
||||
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
|
||||
*/
|
||||
|
||||
const monk = require('monk');
|
||||
const connectionString = '';
|
||||
const Users = monk(connectionString).get('users', { castIds: false });
|
||||
|
||||
import uniq from 'lodash/uniq';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
|
||||
module.exports = async function accountTransfer () {
|
||||
const fromAccountId = '';
|
||||
const toAccountId = '';
|
||||
|
||||
const fromAccount = await Users.findOne({_id: fromAccountId});
|
||||
const toAccount = await Users.findOne({_id: toAccountId});
|
||||
|
||||
const newMounts = Object.assign({}, fromAccount.items.mounts, toAccount.items.mounts);
|
||||
const newPets = Object.assign({}, fromAccount.items.pets, toAccount.items.pets);
|
||||
const newBackgrounds = Object.assign({}, fromAccount.purchased.background, toAccount.purchased.background);
|
||||
|
||||
await Users.update({_id: toAccountId}, {
|
||||
$set: {
|
||||
'items.pets': newPets,
|
||||
'items.mounts': newMounts,
|
||||
'purchased.background': newBackgrounds,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
console.log(result);
|
||||
});
|
||||
};
|
||||
2598
package-lock.json
generated
21
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.6.0",
|
||||
"version": "4.13.2",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -31,8 +31,8 @@
|
||||
"bcrypt": "^1.0.2",
|
||||
"bluebird": "^3.3.5",
|
||||
"body-parser": "^1.15.0",
|
||||
"bootstrap": "4.0.0-alpha.6",
|
||||
"bootstrap-vue": "1.0.0-beta.7",
|
||||
"bootstrap": "4.0.0-beta.2",
|
||||
"bootstrap-vue": "^1.0.2",
|
||||
"browserify": "~12.0.1",
|
||||
"compression": "^1.6.1",
|
||||
"connect-ratelimit": "0.0.7",
|
||||
@@ -116,13 +116,14 @@
|
||||
"validator": "^4.9.0",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"vue": "^2.1.0",
|
||||
"vue-loader": "^11.0.0",
|
||||
"vue": "^2.5.2",
|
||||
"vue-loader": "^13.3.0",
|
||||
"vue-mugen-scroll": "^0.2.1",
|
||||
"vue-router": "^2.0.0-rc.5",
|
||||
"vue-router": "^3.0.0",
|
||||
"vue-style-loader": "^3.0.0",
|
||||
"vue-template-compiler": "^2.1.10",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker#45e607a7bccf4e3e089761b3b7b33e3f2c5dc21f",
|
||||
"vue-template-compiler": "^2.5.2",
|
||||
"vuedraggable": "^2.15.0",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker#825a866b6a9c52dd8c588a3e8b900880875ce914",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-merge": "^4.0.0",
|
||||
"winston": "^2.1.0",
|
||||
@@ -136,7 +137,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.vue .",
|
||||
"test": "npm run lint && gulp test && npm run client:unit && gulp apidoc",
|
||||
"test": "npm run lint && gulp test && gulp apidoc",
|
||||
"test:build": "gulp test:prepare:build",
|
||||
"test:api-v3": "gulp test:api-v3",
|
||||
"test:api-v3:unit": "gulp test:api-v3:unit",
|
||||
@@ -148,7 +149,7 @@
|
||||
"test:nodemon": "gulp test:nodemon",
|
||||
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
|
||||
"sprites": "gulp sprites:compile",
|
||||
"client:dev": "gulp bootstrap && node webpack/dev-server.js",
|
||||
"client:dev": "node webpack/dev-server.js",
|
||||
"client:build": "gulp build:client",
|
||||
"client:unit": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js --single-run",
|
||||
"client:unit:watch": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js",
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||
|
||||
describe('GET challenges/groups/:groupId', () => {
|
||||
context('Public Guild', () => {
|
||||
@@ -181,4 +182,123 @@ describe('GET challenges/groups/:groupId', () => {
|
||||
expect(foundChallengeIndex).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
context('Party', () => {
|
||||
let party, user, nonMember, challenge, challenge2;
|
||||
|
||||
before(async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'TestParty',
|
||||
type: 'party',
|
||||
},
|
||||
});
|
||||
|
||||
party = group;
|
||||
user = groupLeader;
|
||||
|
||||
nonMember = await generateUser();
|
||||
|
||||
challenge = await generateChallenge(user, group);
|
||||
challenge2 = await generateChallenge(user, group);
|
||||
});
|
||||
|
||||
it('should prevent non-member from seeing challenges', async () => {
|
||||
await expect(nonMember.get(`/challenges/groups/${party._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('groupNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return group challenges for member with populated leader', async () => {
|
||||
let challenges = await user.get(`/challenges/groups/${party._id}`);
|
||||
|
||||
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql({
|
||||
_id: party.leader._id,
|
||||
id: party.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql({
|
||||
_id: party.leader._id,
|
||||
id: party.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return group challenges for member using ID "party"', async () => {
|
||||
let challenges = await user.get('/challenges/groups/party');
|
||||
|
||||
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql({
|
||||
_id: party.leader._id,
|
||||
id: party.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql({
|
||||
_id: party.leader._id,
|
||||
id: party.leader._id,
|
||||
profile: {name: user.profile.name},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Tavern', () => {
|
||||
let tavern, user, challenge, challenge2;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
await user.update({balance: 0.5});
|
||||
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
||||
|
||||
challenge = await generateChallenge(user, tavern, {prize: 1});
|
||||
challenge2 = await generateChallenge(user, tavern, {prize: 1});
|
||||
});
|
||||
|
||||
it('should return tavern challenges with populated leader', async () => {
|
||||
let challenges = await user.get(`/challenges/groups/${TAVERN_ID}`);
|
||||
|
||||
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql({
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: {name: user.profile.name},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql({
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: {name: user.profile.name},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return tavern challenges using ID "habitrpg', async () => {
|
||||
let challenges = await user.get('/challenges/groups/habitrpg');
|
||||
|
||||
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||
expect(foundChallenge1).to.exist;
|
||||
expect(foundChallenge1.leader).to.eql({
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: {name: user.profile.name},
|
||||
});
|
||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||
expect(foundChallenge2).to.exist;
|
||||
expect(foundChallenge2.leader).to.eql({
|
||||
_id: user._id,
|
||||
id: user._id,
|
||||
profile: {name: user.profile.name},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { getMatchesByWordArray, removePunctuationFromString } from '../../../../../website/server/libs/stringUtils';
|
||||
import bannedWords from '../../../../../website/server/libs/bannedWords';
|
||||
import guildsAllowingBannedWords from '../../../../../website/server/libs/guildsAllowingBannedWords';
|
||||
import * as email from '../../../../../website/server/libs/email';
|
||||
import { IncomingWebhook } from '@slack/client';
|
||||
import nconf from 'nconf';
|
||||
@@ -96,6 +97,24 @@ describe('POST /chat', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when chat message contains a banned word in a public guild', async () => {
|
||||
let { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
await expect(members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: bannedWordErrorMessage,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when word is part of a phrase', async () => {
|
||||
let wordInPhrase = `phrase ${testBannedWordMessage} end`;
|
||||
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase}))
|
||||
@@ -161,7 +180,7 @@ describe('POST /chat', () => {
|
||||
expect(message.message.id).to.exist;
|
||||
});
|
||||
|
||||
it('does not error when sending a chat message containing a banned word to a public guild', async () => {
|
||||
it('does not error when sending a chat message containing a banned word to a public guild in which banned words are allowed', async () => {
|
||||
let { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'public guild',
|
||||
@@ -171,6 +190,8 @@ describe('POST /chat', () => {
|
||||
members: 1,
|
||||
});
|
||||
|
||||
guildsAllowingBannedWords[group._id] = true;
|
||||
|
||||
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
|
||||
|
||||
expect(message.message.id).to.exist;
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import config from '../../../../../config.json';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
@@ -74,7 +75,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
});
|
||||
|
||||
it('can unflag a system message', async () => {
|
||||
it('can\'t flag a system message', async () => {
|
||||
let { group, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'party',
|
||||
@@ -95,13 +96,15 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
await member.post('/user/class/cast/mpheal');
|
||||
|
||||
let [skillMsg] = await member.get(`/groups/${group.id}/chat`);
|
||||
|
||||
await member.post(`/groups/${group._id}/chat/${skillMsg.id}/flag`);
|
||||
await admin.post(`/groups/${group._id}/chat/${skillMsg.id}/clearflags`);
|
||||
|
||||
let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
expect(messages[0].id).to.eql(skillMsg.id);
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
await expect(member.post(`/groups/${group._id}/chat/${skillMsg.id}/flag`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('messageCannotFlagSystemMessages', {communityManagerEmail: config.EMAILS.COMMUNITY_MANAGER_EMAIL}),
|
||||
});
|
||||
// let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||
// expect(messages[0].id).to.eql(skillMsg.id);
|
||||
// expect(messages[0].flagCount).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
resetHabiticaDB,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
|
||||
describe('GET /coupons/', () => {
|
||||
let user;
|
||||
@@ -19,7 +19,7 @@ describe('GET /coupons/', () => {
|
||||
await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('noSudoAccess'),
|
||||
message: apiMessages('noSudoAccess'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
resetHabiticaDB,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import couponCode from 'coupon-code';
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
|
||||
describe('POST /coupons/generate/:event', () => {
|
||||
let user;
|
||||
@@ -25,7 +26,7 @@ describe('POST /coupons/generate/:event', () => {
|
||||
await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('noSudoAccess'),
|
||||
message: apiMessages('noSudoAccess'),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import { v4 as generateUUID } from 'uuid';
|
||||
import {
|
||||
each,
|
||||
} from 'lodash';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import * as payments from '../../../../../website/server/libs/payments';
|
||||
|
||||
describe('POST /groups/:groupId/leave', () => {
|
||||
let typesOfGroups = {
|
||||
@@ -264,4 +266,45 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
expect(userWithNonExistentParty.party).to.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
context('Leaving a group plan', () => {
|
||||
it('cancels the free subscription', async () => {
|
||||
// Create group
|
||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
name: 'Test Private Guild',
|
||||
type: 'guild',
|
||||
},
|
||||
members: 1,
|
||||
});
|
||||
|
||||
let leader = groupLeader;
|
||||
let member = members[0];
|
||||
let userWithFreePlan = await User.findById(leader._id).exec();
|
||||
|
||||
// Create subscription
|
||||
let paymentData = {
|
||||
user: userWithFreePlan,
|
||||
groupId: group._id,
|
||||
sub: {
|
||||
key: 'basic_3mo',
|
||||
},
|
||||
customerId: 'customer-id',
|
||||
paymentMethod: 'Payment Method',
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
};
|
||||
await payments.createSubscription(paymentData);
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.planId).to.equal('group_plan_auto');
|
||||
expect(member.purchased.plan.dateTerminated).to.not.exist;
|
||||
|
||||
// Leave
|
||||
await member.post(`/groups/${group._id}/leave`);
|
||||
await member.sync();
|
||||
expect(member.purchased.plan.dateTerminated).to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,14 +13,44 @@ describe('POST /notifications/:notificationId/read', () => {
|
||||
|
||||
it('errors when notification is not found', async () => {
|
||||
let dummyId = generateUUID();
|
||||
await expect(user.post(`/notifications/${dummyId}/read`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
|
||||
await expect(user.post(`/notifications/${dummyId}/read`)).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
xit('removes a notification', async () => {
|
||||
it('removes a notification', async () => {
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
|
||||
const id = generateUUID();
|
||||
const id2 = generateUUID();
|
||||
|
||||
await user.update({
|
||||
notifications: [{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
data: {},
|
||||
}, {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
}],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(2);
|
||||
|
||||
const res = await user.post(`/notifications/${id}/read`);
|
||||
expect(res).to.deep.equal([{
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
}]);
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(user.notifications[0].id).to.equal(id2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-v3-integration.helper';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
describe('POST /notifications/:notificationId/read', () => {
|
||||
let user;
|
||||
|
||||
before(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('errors when notification is not found', async () => {
|
||||
let dummyId = generateUUID();
|
||||
|
||||
await expect(user.post('/notifications/read', {
|
||||
notificationIds: [dummyId],
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
error: 'NotFound',
|
||||
message: t('messageNotificationNotFound'),
|
||||
});
|
||||
});
|
||||
|
||||
it('removes multiple notifications', async () => {
|
||||
expect(user.notifications.length).to.equal(0);
|
||||
|
||||
const id = generateUUID();
|
||||
const id2 = generateUUID();
|
||||
const id3 = generateUUID();
|
||||
|
||||
await user.update({
|
||||
notifications: [{
|
||||
id,
|
||||
type: 'DROPS_ENABLED',
|
||||
data: {},
|
||||
}, {
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
}, {
|
||||
id: id3,
|
||||
type: 'CRON',
|
||||
data: {},
|
||||
}],
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(3);
|
||||
|
||||
const res = await user.post('/notifications/read', {
|
||||
notificationIds: [id, id3],
|
||||
});
|
||||
|
||||
expect(res).to.deep.equal([{
|
||||
id: id2,
|
||||
type: 'LOGIN_INCENTIVE',
|
||||
data: {},
|
||||
}]);
|
||||
|
||||
await user.sync();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(user.notifications[0].id).to.equal(id2);
|
||||
});
|
||||
});
|
||||
@@ -141,6 +141,16 @@ describe('DELETE /tasks/:id', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('removes a task from user.tasksOrder'); // TODO
|
||||
it('removes a task from user.tasksOrder', async () => {
|
||||
let task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
});
|
||||
|
||||
await user.del(`/tasks/${task._id}`);
|
||||
await user.sync();
|
||||
|
||||
expect(user.tasksOrder.habits.indexOf(task._id)).to.eql(-1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -130,6 +130,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
|
||||
it('uncompletes todo when direction is down', async () => {
|
||||
await user.post(`/tasks/${todo._id}/score/up`);
|
||||
await user.post(`/tasks/${todo._id}/score/down`);
|
||||
let updatedTask = await user.get(`/tasks/${todo._id}`);
|
||||
|
||||
@@ -137,9 +138,23 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
expect(updatedTask.dateCompleted).to.be.a('undefined');
|
||||
});
|
||||
|
||||
it('scores up todo even if it is already completed'); // Yes?
|
||||
it('doesn\'t let a todo be completed twice', async () => {
|
||||
await user.post(`/tasks/${todo._id}/score/up`);
|
||||
await expect(user.post(`/tasks/${todo._id}/score/up`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('sessionOutdated'),
|
||||
});
|
||||
});
|
||||
|
||||
it('scores down todo even if it is already uncompleted'); // Yes?
|
||||
it('doesn\'t let a todo be uncompleted twice', async () => {
|
||||
await expect(user.post(`/tasks/${todo._id}/score/down`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('sessionOutdated'),
|
||||
});
|
||||
});
|
||||
|
||||
context('user stats when direction is up', () => {
|
||||
let updatedUser;
|
||||
@@ -163,23 +178,25 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
|
||||
context('user stats when direction is down', () => {
|
||||
let updatedUser;
|
||||
let updatedUser, initialUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
await user.post(`/tasks/${todo._id}/score/up`);
|
||||
initialUser = await user.get('/user');
|
||||
await user.post(`/tasks/${todo._id}/score/down`);
|
||||
updatedUser = await user.get('/user');
|
||||
});
|
||||
|
||||
it('decreases user\'s mp', () => {
|
||||
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
|
||||
expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
|
||||
});
|
||||
|
||||
it('decreases user\'s exp', () => {
|
||||
expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp);
|
||||
expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
|
||||
});
|
||||
|
||||
it('decreases user\'s gold', () => {
|
||||
expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp);
|
||||
expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -202,6 +219,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
|
||||
it('uncompletes daily when direction is down', async () => {
|
||||
await user.post(`/tasks/${daily._id}/score/up`);
|
||||
await user.post(`/tasks/${daily._id}/score/down`);
|
||||
let task = await user.get(`/tasks/${daily._id}`);
|
||||
|
||||
@@ -222,9 +240,22 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
expect(task.nextDue.length).to.eql(6);
|
||||
});
|
||||
|
||||
it('scores up daily even if it is already completed'); // Yes?
|
||||
it('doesn\'t let a daily be completed twice', async () => {
|
||||
await user.post(`/tasks/${daily._id}/score/up`);
|
||||
await expect(user.post(`/tasks/${daily._id}/score/up`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('sessionOutdated'),
|
||||
});
|
||||
});
|
||||
|
||||
it('scores down daily even if it is already uncompleted'); // Yes?
|
||||
it('doesn\'t let a daily be uncompleted twice', async () => {
|
||||
await expect(user.post(`/tasks/${daily._id}/score/down`)).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('sessionOutdated'),
|
||||
});
|
||||
});
|
||||
|
||||
context('user stats when direction is up', () => {
|
||||
let updatedUser;
|
||||
@@ -248,23 +279,25 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
|
||||
context('user stats when direction is down', () => {
|
||||
let updatedUser;
|
||||
let updatedUser, initialUser;
|
||||
|
||||
beforeEach(async () => {
|
||||
await user.post(`/tasks/${daily._id}/score/up`);
|
||||
initialUser = await user.get('/user');
|
||||
await user.post(`/tasks/${daily._id}/score/down`);
|
||||
updatedUser = await user.get('/user');
|
||||
});
|
||||
|
||||
it('decreases user\'s mp', () => {
|
||||
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
|
||||
expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
|
||||
});
|
||||
|
||||
it('decreases user\'s exp', () => {
|
||||
expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp);
|
||||
expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
|
||||
});
|
||||
|
||||
it('decreases user\'s gold', () => {
|
||||
expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp);
|
||||
expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,9 +40,13 @@ describe('POST /tasks/:taskId/move/to/:position', () => {
|
||||
|
||||
let taskToMove = tasks[1];
|
||||
expect(taskToMove.text).to.equal('habit 2');
|
||||
|
||||
let newOrder = await user.post(`/tasks/${tasks[1]._id}/move/to/3`);
|
||||
await user.sync();
|
||||
|
||||
expect(newOrder[3]).to.equal(taskToMove._id);
|
||||
expect(newOrder.length).to.equal(5);
|
||||
expect(user.tasksOrder.habits).to.eql(newOrder);
|
||||
});
|
||||
|
||||
it('can move task to new position using alias', async () => {
|
||||
|
||||
@@ -82,6 +82,13 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
|
||||
it('should update the history', async () => {
|
||||
let newCron = new Date(2015, 11, 20);
|
||||
|
||||
await user.post('/debug/set-cron', {
|
||||
lastCron: newCron,
|
||||
});
|
||||
|
||||
await user.post('/cron');
|
||||
await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
|
||||
|
||||
let tasks = await user.get(`/tasks/challenge/${challenge._id}`);
|
||||
|
||||
@@ -75,15 +75,6 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when non leader tries to create a task', async () => {
|
||||
await expect(member.post(`/tasks/${task._id}/unassign/${member._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanEditTasks'),
|
||||
});
|
||||
});
|
||||
|
||||
it('unassigns a user from a task', async () => {
|
||||
await user.post(`/tasks/${task._id}/unassign/${member._id}`);
|
||||
|
||||
@@ -129,4 +120,26 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
|
||||
expect(groupTask[0].group.assignedUsers).to.not.contain(member._id);
|
||||
expect(syncedTask).to.not.exist;
|
||||
});
|
||||
|
||||
it('allows a user to unassign themselves', async () => {
|
||||
await member.post(`/tasks/${task._id}/unassign/${member._id}`);
|
||||
|
||||
let groupTask = await user.get(`/tasks/group/${guild._id}`);
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
expect(groupTask[0].group.assignedUsers).to.not.contain(member._id);
|
||||
expect(syncedTask).to.not.exist;
|
||||
});
|
||||
|
||||
// @TODO: Which do we want? The user to unassign themselves or not. This test was in
|
||||
// here, but then we had a request to allow to unaissgn.
|
||||
xit('returns error when non leader tries to unassign their a task', async () => {
|
||||
await expect(member.post(`/tasks/${task._id}/unassign/${member._id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('onlyGroupLeaderCanEditTasks'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -308,7 +308,7 @@ describe('DELETE /user', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('incorrectDeletePhrase'),
|
||||
message: t('incorrectDeletePhrase', {magicWord: 'DELETE'}),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /user/allocate', () => {
|
||||
let user;
|
||||
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /user/allocate-bulk', () => {
|
||||
let user;
|
||||
const statsUpdate = {
|
||||
stats: {
|
||||
con: 1,
|
||||
str: 2,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
|
||||
it('returns an error if user does not have enough points', async () => {
|
||||
await expect(user.post('/user/allocate-bulk', statsUpdate))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('notEnoughAttrPoints'),
|
||||
});
|
||||
});
|
||||
|
||||
it('allocates attribute points', async () => {
|
||||
await user.update({'stats.points': 3});
|
||||
|
||||
await user.post('/user/allocate-bulk', statsUpdate);
|
||||
await user.sync();
|
||||
|
||||
expect(user.stats.con).to.equal(1);
|
||||
expect(user.stats.str).to.equal(2);
|
||||
expect(user.stats.points).to.equal(0);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /user/allocate-now', () => {
|
||||
// More tests in common code unit tests
|
||||
@@ -365,6 +365,72 @@ describe('cron', () => {
|
||||
|
||||
expect(user.history.todos).to.be.lengthOf(1);
|
||||
});
|
||||
|
||||
it('should remove completed todos from users taskOrder list', () => {
|
||||
tasksByType.todos = [];
|
||||
user.tasksOrder.todos = [];
|
||||
let todo = {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
value: 0,
|
||||
};
|
||||
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
|
||||
tasksByType.todos.push(task);
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
|
||||
tasksByType.todos.push(task);
|
||||
tasksByType.todos[0].completed = true;
|
||||
|
||||
user.tasksOrder.todos = tasksByType.todos.map(taskTodo => {
|
||||
return taskTodo._id;
|
||||
});
|
||||
// Since ideally tasksByType should not contain completed todos, fake ids should be filtered too
|
||||
user.tasksOrder.todos.push('00000000-0000-0000-0000-000000000000');
|
||||
|
||||
expect(tasksByType.todos).to.be.lengthOf(2);
|
||||
expect(user.tasksOrder.todos).to.be.lengthOf(3);
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
// user.tasksOrder.todos should be filtered while tasks by type remains unchanged
|
||||
expect(tasksByType.todos).to.be.lengthOf(2);
|
||||
expect(user.tasksOrder.todos).to.be.lengthOf(1);
|
||||
});
|
||||
|
||||
it('should preserve todos order in task list', () => {
|
||||
tasksByType.todos = [];
|
||||
user.tasksOrder.todos = [];
|
||||
let todo = {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
value: 0,
|
||||
};
|
||||
|
||||
let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
|
||||
tasksByType.todos.push(task);
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
|
||||
tasksByType.todos.push(task);
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line new-cap
|
||||
tasksByType.todos.push(task);
|
||||
|
||||
// Set up user.tasksOrder list in a specific order
|
||||
user.tasksOrder.todos = tasksByType.todos.map(todoTask => {
|
||||
return todoTask._id;
|
||||
}).reverse();
|
||||
let original = user.tasksOrder.todos; // Preserve the original order
|
||||
|
||||
cron({user, tasksByType, daysMissed, analytics});
|
||||
|
||||
let listsAreEqual = true;
|
||||
user.tasksOrder.todos.forEach((taskId, index) => {
|
||||
if (original[index]._id !== taskId) {
|
||||
listsAreEqual = false;
|
||||
}
|
||||
});
|
||||
|
||||
expect(listsAreEqual);
|
||||
expect(user.tasksOrder.todos).to.be.lengthOf(original.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dailys', () => {
|
||||
|
||||
@@ -475,7 +475,7 @@ describe('Purchasing a group plan for group', () => {
|
||||
|
||||
let updatedUser = await User.findById(recipient._id).exec();
|
||||
|
||||
expect(updatedUser.purchased.plan.extraMonths).to.within(3, 4);
|
||||
expect(updatedUser.purchased.plan.extraMonths).to.within(3, 5);
|
||||
});
|
||||
|
||||
it('adds months to members with existing recurring subscription (Paypal)', async () => {
|
||||
|
||||
@@ -13,6 +13,9 @@ import analyticsService from '../../../../../website/server/libs/analyticsServic
|
||||
import * as cronLib from '../../../../../website/server/libs/cron';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
const CRON_TIMEOUT_WAIT = new Date(60 * 60 * 1000).getTime();
|
||||
const CRON_TIMEOUT_UNIT = new Date(60 * 1000).getTime();
|
||||
|
||||
describe('cron middleware', () => {
|
||||
let res, req;
|
||||
let user;
|
||||
@@ -235,7 +238,13 @@ describe('cron middleware', () => {
|
||||
sandbox.spy(cronLib, 'recoverCron');
|
||||
|
||||
sandbox.stub(User, 'update')
|
||||
.withArgs({ _id: user._id, _cronSignature: 'NOT_RUNNING' })
|
||||
.withArgs({
|
||||
_id: user._id,
|
||||
$or: [
|
||||
{_cronSignature: 'NOT_RUNNING'},
|
||||
{_cronSignature: {$lt: sinon.match.number}},
|
||||
],
|
||||
})
|
||||
.returns({
|
||||
exec () {
|
||||
return Promise.resolve(updatedUser);
|
||||
@@ -251,4 +260,48 @@ describe('cron middleware', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('cronSignature less than an hour ago should error', async () => {
|
||||
user.lastCron = moment(new Date()).subtract({days: 2});
|
||||
let now = new Date();
|
||||
await User.update({
|
||||
_id: user._id,
|
||||
}, {
|
||||
$set: {
|
||||
_cronSignature: now.getTime() - CRON_TIMEOUT_WAIT + CRON_TIMEOUT_UNIT,
|
||||
},
|
||||
}).exec();
|
||||
await user.save();
|
||||
let expectedErrMessage = `Impossible to recover from cron for user ${user._id}.`;
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, (err) => {
|
||||
if (!err) return reject(new Error('Cron should have failed.'));
|
||||
expect(err.message).to.be.equal(expectedErrMessage);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('cronSignature longer than an hour ago should allow cron', async () => {
|
||||
user.lastCron = moment(new Date()).subtract({days: 2});
|
||||
let now = new Date();
|
||||
await User.update({
|
||||
_id: user._id,
|
||||
}, {
|
||||
$set: {
|
||||
_cronSignature: now.getTime() - CRON_TIMEOUT_WAIT - CRON_TIMEOUT_UNIT,
|
||||
},
|
||||
}).exec();
|
||||
await user.save();
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
cronMiddleware(req, res, (err) => {
|
||||
if (err) return reject(err);
|
||||
expect(moment(now).isSame(user.auth.timestamps.loggedin, 'day'));
|
||||
expect(user._cronSignature).to.be.equal('NOT_RUNNING');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import i18n from '../../../../../website/common/script/i18n';
|
||||
import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/ensureAccessRight';
|
||||
import { NotAuthorized } from '../../../../../website/server/libs/errors';
|
||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||
|
||||
describe('ensure access middlewares', () => {
|
||||
let res, req, next;
|
||||
@@ -42,7 +43,7 @@ describe('ensure access middlewares', () => {
|
||||
|
||||
ensureSudo(req, res, next);
|
||||
|
||||
expect(next).to.be.calledWith(new NotAuthorized(i18n.t('noSudoAccess')));
|
||||
expect(next).to.be.calledWith(new NotAuthorized(apiMessages('noSudoAccess')));
|
||||
});
|
||||
|
||||
it('passes when user is a sudo user', () => {
|
||||
|
||||
31
test/client/unit/specs/components/groups/membersModal.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import Vue from 'vue';
|
||||
import MembersModalComponent from 'client/components/groups/membersModal.vue';
|
||||
|
||||
describe('Members Modal Component', () => {
|
||||
describe('Party Sort', () => {
|
||||
let CTor;
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
CTor = Vue.extend(MembersModalComponent);
|
||||
vm = new CTor().$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('should have an empty object as sort-option at start', () => {
|
||||
const defaultData = vm.data();
|
||||
expect(defaultData.sortOption).to.eq({});
|
||||
});
|
||||
|
||||
it('should accept sort-option object', () => {
|
||||
const sortOption = vm.data().sortOption[0];
|
||||
vm.sort(sortOption);
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.data().sortOption).to.eq(sortOption);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -127,14 +127,6 @@ describe('shared.ops.addTask', () => {
|
||||
expect(addTask(user)._edit).to.not.be.ok;
|
||||
});
|
||||
|
||||
it('respects tagsCollapsed preference', () => {
|
||||
user.preferences.tagsCollapsed = true;
|
||||
expect(addTask(user)._tags).to.not.be.ok;
|
||||
|
||||
user.preferences.tagsCollapsed = false;
|
||||
expect(addTask(user)._tags).to.be.ok;
|
||||
});
|
||||
|
||||
it('respects advancedCollapsed preference', () => {
|
||||
user.preferences.advancedCollapsed = true;
|
||||
expect(addTask(user)._advanced).not.be.ok;
|
||||
|
||||
@@ -231,13 +231,30 @@ describe('shared.ops.purchase', () => {
|
||||
context('bulk purchase', () => {
|
||||
let userGemAmount = 10;
|
||||
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
user.balance = userGemAmount;
|
||||
user.stats.gp = goldPoints;
|
||||
user.purchased.plan.gemsBought = 0;
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
});
|
||||
|
||||
it('errors when user does not have enough gems', (done) => {
|
||||
user.balance = 1;
|
||||
let type = 'eggs';
|
||||
let key = 'TigerCub';
|
||||
|
||||
try {
|
||||
purchase(user, {
|
||||
params: {type, key},
|
||||
quantity: 2,
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('notEnoughGems'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('makes bulk purchases of gems', () => {
|
||||
let [, message] = purchase(user, {
|
||||
params: {type: 'gems', key: 'gem'},
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import allocate from '../../../website/common/script/ops/allocate';
|
||||
import allocate from '../../../../website/common/script/ops/stats/allocate';
|
||||
import {
|
||||
BadRequest,
|
||||
NotAuthorized,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
} from '../../../helpers/common.helper';
|
||||
|
||||
describe('shared.ops.allocate', () => {
|
||||
let user;
|
||||
98
test/common/ops/stats/allocateBulk.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import allocateBulk from '../../../../website/common/script/ops/stats/allocateBulk';
|
||||
import {
|
||||
BadRequest,
|
||||
NotAuthorized,
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/common.helper';
|
||||
|
||||
describe('shared.ops.allocateBulk', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
});
|
||||
|
||||
it('throws an error if an invalid attribute is supplied', (done) => {
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
body: {
|
||||
stats: {
|
||||
invalid: 1,
|
||||
str: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidAttribute', {attr: 'invalid'}));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the stats are not supplied', (done) => {
|
||||
try {
|
||||
allocateBulk(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('statsObjectRequired'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user doesn\'t have attribute points', (done) => {
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
body: {
|
||||
stats: {
|
||||
int: 1,
|
||||
str: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('notEnoughAttrPoints'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if the user doesn\'t have enough attribute points', (done) => {
|
||||
user.stats.points = 1;
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
body: {
|
||||
stats: {
|
||||
int: 1,
|
||||
str: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('notEnoughAttrPoints'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('allocates attribute points', () => {
|
||||
user.stats.points = 3;
|
||||
expect(user.stats.int).to.equal(0);
|
||||
expect(user.stats.str).to.equal(0);
|
||||
|
||||
allocateBulk(user, {
|
||||
body: {
|
||||
stats: {
|
||||
int: 1,
|
||||
str: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(user.stats.str).to.equal(2);
|
||||
expect(user.stats.int).to.equal(1);
|
||||
expect(user.stats.points).to.equal(0);
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import allocateNow from '../../../website/common/script/ops/allocateNow';
|
||||
import allocateNow from '../../../../website/common/script/ops/stats/allocateNow';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
} from '../../../helpers/common.helper';
|
||||
|
||||
describe('shared.ops.allocateNow', () => {
|
||||
let user;
|
||||
@@ -644,7 +644,27 @@ describe('shouldDo', () => {
|
||||
day = moment();
|
||||
dailyTask.repeat[DAY_MAPPING[day.day()]] = true;
|
||||
dailyTask.everyX = 3;
|
||||
let threeWeeksFromToday = day.add(6, 'weeks').day(day.day()).toDate();
|
||||
const threeWeeksFromToday = day.add(6, 'weeks').day(day.day()).toDate();
|
||||
|
||||
expect(shouldDo(threeWeeksFromToday, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
it('activates Daily on every (x) week on weekday across a year', () => {
|
||||
dailyTask.repeat = {
|
||||
su: false,
|
||||
s: false,
|
||||
f: false,
|
||||
th: false,
|
||||
w: false,
|
||||
t: false,
|
||||
m: false,
|
||||
};
|
||||
|
||||
day = moment('2017-11-19');
|
||||
dailyTask.startDate = day.toDate();
|
||||
dailyTask.repeat[DAY_MAPPING[day.day()]] = true;
|
||||
dailyTask.everyX = 3;
|
||||
const threeWeeksFromToday = moment('2018-01-21');
|
||||
|
||||
expect(shouldDo(threeWeeksFromToday, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
@@ -970,7 +990,7 @@ describe('shouldDo', () => {
|
||||
m: false,
|
||||
};
|
||||
|
||||
let today = moment('2017-01-26');
|
||||
let today = moment('2017-01-26:00:00.000-00:00');
|
||||
let week = today.monthWeek();
|
||||
let dayOfWeek = today.day();
|
||||
dailyTask.startDate = today.toDate();
|
||||
@@ -979,7 +999,7 @@ describe('shouldDo', () => {
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.frequency = 'monthly';
|
||||
|
||||
day = moment('2017-03-24');
|
||||
day = moment('2017-03-24:00:00.000-00:00');
|
||||
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(false);
|
||||
});
|
||||
@@ -995,7 +1015,7 @@ describe('shouldDo', () => {
|
||||
m: false,
|
||||
};
|
||||
|
||||
let today = moment('2017-01-27');
|
||||
let today = moment('2017-01-27:00:00.000-00:00');
|
||||
let week = today.monthWeek();
|
||||
let dayOfWeek = today.day();
|
||||
dailyTask.startDate = today.toDate();
|
||||
@@ -1004,7 +1024,7 @@ describe('shouldDo', () => {
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.frequency = 'monthly';
|
||||
|
||||
day = moment('2017-03-24');
|
||||
day = moment('2017-03-24:00:00.000-00:00');
|
||||
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
@@ -1020,7 +1040,7 @@ describe('shouldDo', () => {
|
||||
m: false,
|
||||
};
|
||||
|
||||
let today = moment('2017-01-27');
|
||||
let today = moment('2017-01-27:00:00.000-00:00');
|
||||
let week = today.monthWeek();
|
||||
let dayOfWeek = today.day();
|
||||
dailyTask.startDate = today.toDate();
|
||||
@@ -1029,7 +1049,7 @@ describe('shouldDo', () => {
|
||||
dailyTask.everyX = 2;
|
||||
dailyTask.frequency = 'monthly';
|
||||
|
||||
day = moment('2017-03-24');
|
||||
day = moment('2017-03-24:00:00.000-00:00');
|
||||
expect(shouldDo(day, dailyTask, options)).to.equal(true);
|
||||
});
|
||||
|
||||
|
||||
0
vagrant_scripts/install_node.sh
Normal file → Executable file
@@ -3,6 +3,14 @@ const path = require('path');
|
||||
const staticAssetsDirectory = './website/static/.'; // The folder where static files (not processed) live
|
||||
const prodEnv = require('./prod.env');
|
||||
const devEnv = require('./dev.env');
|
||||
const nconf = require('nconf');
|
||||
const setupNconf = require('../../website/server/libs/setupNconf');
|
||||
|
||||
let configFile = path.join(path.resolve(__dirname, '../../config.json'));
|
||||
|
||||
setupNconf(configFile);
|
||||
|
||||
const DEV_BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
module.exports = {
|
||||
build: {
|
||||
@@ -33,25 +41,25 @@ module.exports = {
|
||||
assetsPublicPath: '/',
|
||||
staticAssetsDirectory,
|
||||
proxyTable: {
|
||||
// proxy all requests starting with /api/v3 to localhost:3000
|
||||
// proxy all requests starting with /api/v3 to IP:PORT as specified in the top-level config
|
||||
'/api/v3': {
|
||||
target: 'http://localhost:3000',
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/stripe': {
|
||||
target: 'http://localhost:3000',
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/amazon': {
|
||||
target: 'http://localhost:3000',
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/paypal': {
|
||||
target: 'http://localhost:3000',
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/logout': {
|
||||
target: 'http://localhost:3000',
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
const nconf = require('nconf');
|
||||
const { join, resolve } = require('path');
|
||||
const setupNconf = require('../../website/server/libs/setupNconf');
|
||||
|
||||
const PATH_TO_CONFIG = join(resolve(__dirname, '../../config.json'));
|
||||
let configFile = PATH_TO_CONFIG;
|
||||
|
||||
nconf
|
||||
.argv()
|
||||
.env()
|
||||
.file('user', configFile);
|
||||
|
||||
nconf.set('IS_PROD', nconf.get('NODE_ENV') === 'production');
|
||||
nconf.set('IS_DEV', nconf.get('NODE_ENV') === 'development');
|
||||
nconf.set('IS_TEST', nconf.get('NODE_ENV') === 'test');
|
||||
setupNconf(configFile);
|
||||
|
||||
// @TODO: Check if we can import from client. Items like admin emails can be imported
|
||||
// and that should be prefered
|
||||
|
||||
@@ -83,7 +83,7 @@ const baseConfig = {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('img/[name].[hash:7].[ext]'),
|
||||
name: utils.assetsPath('images/[name].[hash:7].[ext]'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -109,6 +109,7 @@ const baseConfig = {
|
||||
loader: 'svg-url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('svg/[name].[hash:7].[ext]'),
|
||||
},
|
||||
},
|
||||
{ loader: 'svgo-loader' },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template lang="pug">
|
||||
#app(:class='{"casting-spell": castingSpell}')
|
||||
amazon-payments-modal
|
||||
snackbars
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
@@ -71,8 +72,8 @@
|
||||
import axios from 'axios';
|
||||
import { loadProgressBar } from 'axios-progress-bar';
|
||||
|
||||
import AppMenu from './components/appMenu';
|
||||
import AppHeader from './components/appHeader';
|
||||
import AppMenu from './components/header/menu';
|
||||
import AppHeader from './components/header/index';
|
||||
import AppFooter from './components/appFooter';
|
||||
import notificationsDisplay from './components/notifications';
|
||||
import snackbars from './components/snackbars/notifications';
|
||||
@@ -82,6 +83,7 @@ import BuyModal from './components/shops/buyModal.vue';
|
||||
import SelectMembersModal from 'client/components/selectMembersModal.vue';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
import { setup as setupPayments } from 'client/libs/payments';
|
||||
import amazonPaymentsModal from 'client/components/payments/amazonModal';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
@@ -94,6 +96,7 @@ export default {
|
||||
snackbars,
|
||||
BuyModal,
|
||||
SelectMembersModal,
|
||||
amazonPaymentsModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -135,12 +138,12 @@ export default {
|
||||
// @TODO: I'm not sure these should be at the app level. Can we move these back into shop/inventory or maybe they need a lateral move?
|
||||
this.$root.$on('buyModal::showItem', (item) => {
|
||||
this.selectedItemToBuy = item;
|
||||
this.$root.$emit('show::modal', 'buy-modal');
|
||||
this.$root.$emit('bv::show::modal', 'buy-modal');
|
||||
});
|
||||
|
||||
this.$root.$on('selectMembersModal::showItem', (item) => {
|
||||
this.selectedSpellToBuy = item;
|
||||
this.$root.$emit('show::modal', 'select-member-modal');
|
||||
this.$root.$emit('bv::show::modal', 'select-member-modal');
|
||||
});
|
||||
|
||||
// @TODO split up this file, it's too big
|
||||
@@ -216,15 +219,11 @@ export default {
|
||||
// Verify the client is updated
|
||||
// const serverAppVersion = response.data.appVersion;
|
||||
// let serverAppVersionState = this.$store.state.serverAppVersion;
|
||||
// let deniedUpdate = this.$store.state.deniedUpdate;
|
||||
// if (isApiCall && !serverAppVersionState) {
|
||||
// this.$store.state.serverAppVersion = serverAppVersion;
|
||||
// } else if (isApiCall && serverAppVersionState !== serverAppVersion && !deniedUpdate || isCron) {
|
||||
// // For reload on cron
|
||||
// if (isCron || confirm(this.$t('habiticaHasUpdated'))) {
|
||||
// } else if (isApiCall && serverAppVersionState !== serverAppVersion) {
|
||||
// if (document.activeElement.tagName !== 'INPUT' || confirm(this.$t('habiticaHasUpdated'))) {
|
||||
// location.reload(true);
|
||||
// } else {
|
||||
// this.$store.state.deniedUpdate = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -268,7 +267,7 @@ export default {
|
||||
}
|
||||
|
||||
// Manage modals
|
||||
this.$root.$on('show::modal', (modalId, data = {}) => {
|
||||
this.$root.$on('bv::show::modal', (modalId, data = {}) => {
|
||||
if (data.fromRoot) return;
|
||||
|
||||
// Track opening of gems modal unless it's been already tracked
|
||||
@@ -290,15 +289,16 @@ export default {
|
||||
this.$store.state.modalStack.push(modalId);
|
||||
|
||||
// Hide the previous top modal
|
||||
if (modalOnTop) this.$root.$emit('hide::modal', modalOnTop, {fromRoot: true});
|
||||
if (modalOnTop) this.$root.$emit('bv::hide::modal', modalOnTop, {fromRoot: true});
|
||||
});
|
||||
|
||||
// @TODO: This part is hacky and could be solved with two options:
|
||||
// 1 - Find a way to pass fromRoot to hidden
|
||||
// 2 - Enforce that all modals use the hide::modal event
|
||||
this.$root.$on('hidden::modal', (modalId) => {
|
||||
this.$root.$on('bv::modal::hidden', (bvEvent) => {
|
||||
const modalId = bvEvent.target.id;
|
||||
|
||||
let modalStackLength = this.$store.state.modalStack.length;
|
||||
let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
|
||||
let modalSecondToTop = this.$store.state.modalStack[modalStackLength - 2];
|
||||
// Don't remove modal if hid was called from main app
|
||||
// @TODO: I'd reather use this, but I don't know how to pass data to hidden event
|
||||
@@ -310,13 +310,15 @@ export default {
|
||||
|
||||
// Recalculate and show the last modal if there is one
|
||||
modalStackLength = this.$store.state.modalStack.length;
|
||||
modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
|
||||
if (modalOnTop) this.$root.$emit('show::modal', modalOnTop, {fromRoot: true});
|
||||
let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
|
||||
if (modalOnTop) this.$root.$emit('bv::show::modal', modalOnTop, {fromRoot: true});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
resetItemToBuy ($event) {
|
||||
if (!$event) {
|
||||
// @TODO: Do we need this? I think selecting a new item
|
||||
// overwrites. @negue might know
|
||||
if (!$event && this.selectedItemToBuy.purchaseType !== 'card') {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
},
|
||||
@@ -334,23 +336,21 @@ export default {
|
||||
},
|
||||
customPurchase (item) {
|
||||
if (item.purchaseType === 'card') {
|
||||
if (this.user.party._id) {
|
||||
this.selectedSpellToBuy = item;
|
||||
this.selectedSpellToBuy = item;
|
||||
|
||||
this.$root.$emit('hide::modal', 'buy-modal');
|
||||
this.$root.$emit('show::modal', 'select-member-modal');
|
||||
} else {
|
||||
this.error(this.$t('errorNotInParty'));
|
||||
}
|
||||
this.$root.$emit('bv::hide::modal', 'buy-modal');
|
||||
this.$root.$emit('bv::show::modal', 'select-member-modal');
|
||||
}
|
||||
},
|
||||
async memberSelected (member) {
|
||||
this.$store.dispatch('user:castSpell', {key: this.selectedSpellToBuy.key, targetId: member.id});
|
||||
this.selectedSpellToBuy = null;
|
||||
|
||||
this.$store.dispatch('party:getMembers', {forceLoad: true});
|
||||
if (this.user.party._id) {
|
||||
this.$store.dispatch('party:getMembers', {forceLoad: true});
|
||||
}
|
||||
|
||||
this.$root.$emit('hide::modal', 'select-member-modal');
|
||||
this.$root.$emit('bv::hide::modal', 'select-member-modal');
|
||||
},
|
||||
hideLoadingScreen () {
|
||||
const loadingScreen = document.getElementById('loading-screen');
|
||||
@@ -361,6 +361,27 @@ export default {
|
||||
</script>
|
||||
|
||||
<style src="intro.js/minified/introjs.min.css"></style>
|
||||
<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/sprites/spritesmith-largeSprites-0.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-0.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-1.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-2.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-3.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-4.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-5.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-6.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-7.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-8.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-9.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-10.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-11.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-12.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-13.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-14.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-15.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-16.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-17.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-18.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-19.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-20.css"></style>
|
||||
<style src="assets/css/sprites.css"></style>
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
@import './sprites/*.css';
|
||||
@import './sprites.css';
|
||||
@@ -1,12 +1,12 @@
|
||||
.promo_mystery_201710 {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px -244px;
|
||||
width: 93px;
|
||||
height: 90px;
|
||||
}
|
||||
.scene_positivity {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
.promo_armoire_backgrounds_201712 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 531px;
|
||||
height: 243px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px 0px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
|
||||
@@ -1,222 +1,834 @@
|
||||
.Pet-Wolf-Ghost {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TRex-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -328px -100px;
|
||||
.Pet-TigerCub-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Holly {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -246px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Peppermint {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-Cupid {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-RoyalPurple {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-Ember {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shimmer {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-Fairy {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: 0px 0px;
|
||||
.Pet-TigerCub-Floral {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Spooky {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-Ghost {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Thunderstorm {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Veteran {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-Holly {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-Peppermint {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet_HatchingPotion_Aquatic {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TigerCub-RoyalPurple {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Shimmer {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Spooky {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Thunderstorm {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turkey-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turkey-Gilded {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Aquatic {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Cupid {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Ember {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Fairy {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Floral {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Ghost {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Holly {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Peppermint {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-RoyalPurple {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shimmer {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Spooky {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Thunderstorm {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Veteran {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -984px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -984px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -984px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -984px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -984px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -984px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -984px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -984px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet_HatchingPotion_Aquatic {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: 0px -300px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -690px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -69px -300px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -69px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_CottonCandyPink {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -138px -300px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -138px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Cupid {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -479px -345px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -207px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -276px -300px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -276px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Ember {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -410px 0px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -345px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Fairy {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -410px -69px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -414px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Floral {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -410px -138px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -483px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Ghost {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -410px -207px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -552px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -410px -276px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -621px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Holly {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: 0px -369px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -984px -800px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Peppermint {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -69px -369px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -759px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Purple {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -138px -369px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -828px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -207px -369px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -897px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_RoyalPurple {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -276px -369px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -966px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -345px -369px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -969px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shimmer {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -479px 0px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -69px -969px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -479px -69px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -138px -969px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Spooky {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -479px -138px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -207px -969px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Thunderstorm {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -479px -207px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -276px -969px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_White {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -479px -276px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -345px -969px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -207px -300px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -414px -969px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
BIN
website/client/assets/images/npc/habitoween/quest_shop_npc.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
BIN
website/client/assets/images/npc/habitoween/tavern_npc.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 11 KiB |
BIN
website/client/assets/images/npc/thanksgiving/quest_shop_npc.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 15 KiB |