Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae0df2242a | |||
| 5b06b28c97 | |||
| 54443a2980 | |||
| 00dc990974 | |||
| 3737aa045d | |||
| b03ddf6f7d | |||
| 9ef7c45241 | |||
| e628c5dc3b | |||
| 9eaa531f66 | |||
| 3ffea4332e | |||
| 791c19b5f1 | |||
| 7193cc6bae | |||
| 1845bd1e35 | |||
| 5f468d16b7 | |||
| 20a99e526d | |||
| 1eb0f5baa5 | |||
| b28189fff5 | |||
| 82497e4041 | |||
| 2a5e9c0780 | |||
| c8ca67aa64 | |||
| 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 | |||
| bea813b318 | |||
| feb7ab8345 | |||
| bba2e71af3 | |||
| 26123ac6ae | |||
| addee73e4d | |||
| 9736ef0d25 | |||
| 638259b885 | |||
| 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -20,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.6.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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.7.0",
|
||||
"version": "4.12.3",
|
||||
"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,13 @@
|
||||
"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",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker#825a866b6a9c52dd8c588a3e8b900880875ce914",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-merge": "^4.0.0",
|
||||
"winston": "^2.1.0",
|
||||
@@ -136,7 +136,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 +148,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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -264,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
|
||||
@@ -286,13 +289,15 @@ 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];
|
||||
@@ -307,7 +312,7 @@ 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});
|
||||
if (modalOnTop) this.$root.$emit('bv::show::modal', modalOnTop, {fromRoot: true});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
@@ -333,8 +338,8 @@ export default {
|
||||
if (this.user.party._id) {
|
||||
this.selectedSpellToBuy = item;
|
||||
|
||||
this.$root.$emit('hide::modal', 'buy-modal');
|
||||
this.$root.$emit('show::modal', 'select-member-modal');
|
||||
this.$root.$emit('bv::hide::modal', 'buy-modal');
|
||||
this.$root.$emit('bv::show::modal', 'select-member-modal');
|
||||
} else {
|
||||
this.error(this.$t('errorNotInParty'));
|
||||
}
|
||||
@@ -346,7 +351,7 @@ export default {
|
||||
|
||||
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');
|
||||
@@ -357,6 +362,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,6 +1,42 @@
|
||||
.promo_jackolanterns {
|
||||
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
|
||||
background-position: 0px 0px;
|
||||
width: 140px;
|
||||
.promo_mystery_201711 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -499px -202px;
|
||||
width: 141px;
|
||||
height: 294px;
|
||||
}
|
||||
.promo_potions_thunderstorm {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -842px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -641px -202px;
|
||||
width: 114px;
|
||||
height: 87px;
|
||||
}
|
||||
.promo_turkey_day_2017 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -515px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.scene_guilds {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 498px;
|
||||
height: 249px;
|
||||
}
|
||||
.scene_habit_cycle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -250px;
|
||||
width: 302px;
|
||||
height: 264px;
|
||||
}
|
||||
.scene_money {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -499px 0px;
|
||||
width: 342px;
|
||||
height: 201px;
|
||||
}
|
||||
|
||||
@@ -1,246 +1,744 @@
|
||||
.Pet-Wolf-Desert {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -82px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Ember {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -164px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Fairy {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -82px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Floral {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -164px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Ghost {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: 0px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -82px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Holly {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -164px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Peppermint {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -246px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Red {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -246px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-RoyalPurple {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: 0px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shade {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: 0px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shimmer {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -164px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Skeleton {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -246px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Spooky {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -328px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Thunderstorm {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -328px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Veteran {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -328px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-White {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: 0px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Zombie {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TRex-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet_HatchingPotion_Aquatic {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
.Pet-TRex-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TRex-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Shimmer {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Spooky {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Thunderstorm {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-TigerCub-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Treeling-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Triceratops-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turkey-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turkey-Gilded {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Turtle-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Unicorn-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Whale-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Aquatic {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Cupid {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Ember {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Fairy {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Floral {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Ghost {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Holly {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Peppermint {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-RoyalPurple {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -200px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -300px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Shimmer {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -400px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -500px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Spooky {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -600px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Thunderstorm {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -700px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Veteran {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -82px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Wolf-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -164px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Base {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -246px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-CottonCandyBlue {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -328px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-CottonCandyPink {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -410px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Desert {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -492px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Golden {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -574px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Red {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -656px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Shade {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -738px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Skeleton {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -820px -800px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-White {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px 0px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Yarn-Zombie {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -100px;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet_HatchingPotion_Aquatic {
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -269px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Base {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -315px -300px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -69px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_CottonCandyBlue {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -207px -469px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -338px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_CottonCandyPink {
|
||||
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: -902px -407px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Cupid {
|
||||
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: -902px -476px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Desert {
|
||||
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: -902px -545px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Ember {
|
||||
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: -902px -614px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Fairy {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: 0px -400px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -683px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Floral {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -69px -400px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -752px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Ghost {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -138px -400px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -821px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Golden {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -207px -400px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: 0px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Holly {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -276px -400px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -902px -200px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Peppermint {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -345px -400px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -138px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Purple {
|
||||
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: -207px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Red {
|
||||
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: -276px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_RoyalPurple {
|
||||
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: -345px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shade {
|
||||
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: -414px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Shimmer {
|
||||
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: -483px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Skeleton {
|
||||
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: -552px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Spooky {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: 0px -469px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -621px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Thunderstorm {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -69px -469px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -690px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_White {
|
||||
background-image: url(/static/sprites/spritesmith-main-20.png);
|
||||
background-position: -138px -469px;
|
||||
background-image: url('~assets/images/sprites/spritesmith-main-20.png');
|
||||
background-position: -759px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.Pet_HatchingPotion_Zombie {
|
||||
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: -828px -900px;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
|
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 |
|
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 |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 535 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 308 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 334 KiB |
@@ -20,3 +20,11 @@
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
}
|
||||
|
||||
.badge-purple {
|
||||
position: absolute;
|
||||
color: $white;
|
||||
background: $purple-400;
|
||||
line-height: 1.2;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||