lint common

This commit is contained in:
Matteo Pagliazzi
2019-10-09 20:08:36 +02:00
parent 0c27fb24a5
commit e0e9811ab6
330 changed files with 6885 additions and 7668 deletions

6
package-lock.json generated
View File

@@ -4476,9 +4476,9 @@
} }
}, },
"eslint-config-habitrpg": { "eslint-config-habitrpg": {
"version": "6.0.7", "version": "6.0.8",
"resolved": "https://registry.npmjs.org/eslint-config-habitrpg/-/eslint-config-habitrpg-6.0.7.tgz", "resolved": "https://registry.npmjs.org/eslint-config-habitrpg/-/eslint-config-habitrpg-6.0.8.tgz",
"integrity": "sha512-hda2IKsfwE/94CjLtPI3Cgsq0capQCFKCiqPPNM7foeO/E6cZpXCSvMHdGt5TXy66DQxF5h+kz4xcheHdcaRcA==", "integrity": "sha512-jQ62H3+Gkie4CK8uFfV37SX+yNs6yu+SHP27hIYIlnZ21HCaQnabEQfhrYzbpQUn9fNd2y1gUNqGOIG8ph84vg==",
"dev": true, "dev": true,
"requires": { "requires": {
"eslint": "^6.5.1", "eslint": "^6.5.1",

View File

@@ -72,7 +72,7 @@
"npm": "^6" "npm": "^6"
}, },
"scripts": { "scripts": {
"lint": "eslint --ext .js --fix ./website/server", "lint": "eslint --ext .js --fix ./website/common",
"test": "npm run lint && gulp test && gulp apidoc", "test": "npm run lint && gulp test && gulp apidoc",
"test:build": "gulp test:prepare:build", "test:build": "gulp test:prepare:build",
"test:api-v3": "gulp test:api-v3", "test:api-v3": "gulp test:api-v3",
@@ -103,7 +103,7 @@
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chalk": "^2.4.1", "chalk": "^2.4.1",
"eslint": "^6.5.1", "eslint": "^6.5.1",
"eslint-config-habitrpg": "^6.0.7", "eslint-config-habitrpg": "^6.0.8",
"eslint-plugin-mocha": "^5.0.0", "eslint-plugin-mocha": "^5.0.0",
"expect.js": "^0.3.1", "expect.js": "^0.3.1",
"istanbul": "^1.1.0-alpha.1", "istanbul": "^1.1.0-alpha.1",

View File

@@ -1,47 +0,0 @@
/* global env:true, rm:true, mkdir:true, cp:true */
// https://github.com/shelljs/shelljs
require('shelljs/global');
const path = require('path');
const config = require('./config');
const ora = require('ora');
const webpack = require('webpack');
const webpackConfig = require('./webpack.prod.conf');
module.exports = function webpackProductionBuild (callback) {
env.NODE_ENV = 'production';
console.log( // eslint-disable-line no-console
' Tip:\n' +
' Built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
);
const spinner = ora('building for production...');
spinner.start();
const assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory);
rm('-rf', assetsPath);
mkdir('-p', assetsPath);
cp('-R', config.build.staticAssetsDirectory, assetsPath);
webpack(webpackConfig, (err, stats) => {
spinner.stop();
const output = `${stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
})}\n`;
if (callback) {
return err ? callback(err) : callback(null, output);
} else {
if (err) throw err;
process.stdout.write(output);
}
});
};

View File

@@ -1,6 +0,0 @@
const merge = require('webpack-merge');
const prodEnv = require('./prod.env');
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
});

View File

@@ -1,81 +0,0 @@
// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path');
const staticAssetsDirectory = './website/static/.'; // The folder where static files (not processed) live
const prodEnv = require('./prod.env');
const devEnv = require('./dev.env');
const nconf = require('nconf');
const setupNconf = require('../../website/server/libs/setupNconf');
let configFile = path.join(path.resolve(__dirname, '../../config.json'));
setupNconf(configFile);
const DEV_BASE_URL = nconf.get('BASE_URL');
module.exports = {
build: {
env: prodEnv,
index: path.resolve(__dirname, '../../dist-client/index.html'),
assetsRoot: path.resolve(__dirname, '../../dist-client'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
staticAssetsDirectory,
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run client:build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report, // eslint-disable-line no-process-env
},
dev: {
env: devEnv,
port: 8080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
staticAssetsDirectory,
proxyTable: {
// proxy all requests to the server at IP:PORT as specified in the top-level config
'/api/v3': {
target: DEV_BASE_URL,
changeOrigin: true,
},
'/api/v4': {
target: DEV_BASE_URL,
changeOrigin: true,
},
'/stripe': {
target: DEV_BASE_URL,
changeOrigin: true,
},
'/amazon': {
target: DEV_BASE_URL,
changeOrigin: true,
},
'/paypal': {
target: DEV_BASE_URL,
changeOrigin: true,
},
'/logout-server': {
target: DEV_BASE_URL,
changeOrigin: true,
},
'/export': {
target: DEV_BASE_URL,
changeOrigin: true,
},
},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false,
},
};

View File

@@ -1,45 +0,0 @@
const nconf = require('nconf');
const { join, resolve } = require('path');
const setupNconf = require('../../website/server/libs/setupNconf');
const PATH_TO_CONFIG = join(resolve(__dirname, '../../config.json'));
let configFile = PATH_TO_CONFIG;
setupNconf(configFile);
// @TODO: Check if we can import from client. Items like admin emails can be imported
// and that should be prefered
// To avoid stringifying more data then we need,
// items from `env` used on the client will have to be specified in this array
// @TODO: Do we need? const CLIENT_VARS = ['language', 'isStaticPage', 'availableLanguages', 'translations',
// 'FACEBOOK_KEY', 'GOOGLE_CLIENT_ID', 'NODE_ENV', 'BASE_URL', 'GA_ID',
// 'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
// 'worldDmg', 'mods', 'IS_MOBILE'];
const AMAZON_SELLER_ID = nconf.get('AMAZON_PAYMENTS_SELLER_ID');
const AMAZON_CLIENT_ID = nconf.get('AMAZON_PAYMENTS_CLIENT_ID');
const AMAZON_MODE = nconf.get('AMAZON_PAYMENTS_MODE');
let env = {
NODE_ENV: '"production"',
// clientVars: CLIENT_VARS,
AMAZON_PAYMENTS: {
SELLER_ID: `"${AMAZON_SELLER_ID}"`,
CLIENT_ID: `"${AMAZON_CLIENT_ID}"`,
MODE: `"${AMAZON_MODE}"`,
},
EMAILS: {
COMMUNITY_MANAGER_EMAIL: `"${nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL')}"`,
TECH_ASSISTANCE_EMAIL: `"${nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL')}"`,
PRESS_ENQUIRY_EMAIL: `"${nconf.get('EMAILS_PRESS_ENQUIRY_EMAIL')}"`,
},
};
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY GOOGLE_CLIENT_ID AMPLITUDE_KEY LOGGLY_CLIENT_TOKEN'
.split(' ')
.forEach(key => {
env[key] = `"${nconf.get(key)}"`;
});
module.exports = env;

View File

@@ -1,6 +0,0 @@
const merge = require('webpack-merge');
const devEnv = require('./dev.env');
module.exports = merge(devEnv, {
NODE_ENV: '"test"',
});

View File

@@ -1,10 +0,0 @@
/* global window:true */
require('eventsource-polyfill');
const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true&overlay=false');
hotClient.subscribe(event => {
if (event.action === 'reload') {
window.location.reload();
}
});

View File

@@ -1,72 +0,0 @@
/* eslint-disable no-process-env, no-console */
const path = require('path');
const express = require('express');
const webpack = require('webpack');
const config = require('./config');
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV);
}
const proxyMiddleware = require('http-proxy-middleware');
const webpackConfig = process.env.NODE_ENV === 'test' ?
require('./webpack.prod.conf') :
require('./webpack.dev.conf');
// default port where dev server listens for incoming traffic
const port = process.env.PORT || config.dev.port;
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
const proxyTable = config.dev.proxyTable;
const app = express();
const compiler = webpack(webpackConfig);
const devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true,
chunks: false,
},
});
const hotMiddleware = require('webpack-hot-middleware')(compiler);
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', (compilation) => {
compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({ action: 'reload' });
cb();
});
});
// proxy api requests
Object.keys(proxyTable).forEach((context) => {
let options = proxyTable[context];
if (typeof options === 'string') {
options = { target: options };
}
app.use(proxyMiddleware(options.filter || context, options));
});
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')());
// serve webpack bundle output
app.use(devMiddleware);
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware);
// serve pure static assets
const staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory);
app.use(staticPath, express.static(config.dev.staticAssetsDirectory));
module.exports = app.listen(port, (err) => {
if (err) {
console.log(err);
return;
}
console.log(`Listening at http://localhost:${port}\n`);
});

View File

@@ -1,66 +0,0 @@
/* eslint-disable no-process-env, no-console */
const path = require('path');
const config = require('./config');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
exports.assetsPath = (_path) => {
const assetsSubDirectory = process.env.NODE_ENV === 'production' ?
config.build.assetsSubDirectory :
config.dev.assetsSubDirectory;
return path.posix.join(assetsSubDirectory, _path);
};
exports.cssLoaders = function cssLoaders (options) {
options = options || {};
// generate loader string to be used with extract text plugin
function generateLoaders (loaders) {
let sourceLoader = loaders.map((loader) => {
let extraParamChar;
if (/\?/.test(loader)) {
loader = loader.replace(/\?/, '-loader?');
extraParamChar = '&';
} else {
loader = `${loader}-loader`;
extraParamChar = '?';
}
return loader + (options.sourceMap ? `${extraParamChar}sourceMap` : '');
}).join('!');
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: sourceLoader,
fallback: 'vue-style-loader',
});
} else {
return ['vue-style-loader', sourceLoader].join('!');
}
}
// http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
return {
css: generateLoaders(['css']),
postcss: generateLoaders(['css']),
less: generateLoaders(['css', 'less']),
sass: generateLoaders(['css', 'sass?indentedSyntax']),
scss: generateLoaders(['css', 'sass']),
stylus: generateLoaders(['css', 'stylus']),
styl: generateLoaders(['css', 'stylus']),
};
};
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = (options) => {
const output = [];
const loaders = exports.cssLoaders(options);
for (let extension in loaders) {
const loader = loaders[extension];
output.push({
test: new RegExp(`\\.${extension}$`),
loader,
});
}
return output;
};

View File

@@ -1,154 +0,0 @@
/* eslint-disable no-process-env, no-console */
const path = require('path');
const config = require('./config');
const utils = require('./utils');
const webpack = require('webpack');
const projectRoot = path.resolve(__dirname, '../');
const autoprefixer = require('autoprefixer');
const postcssEasyImport = require('postcss-easy-import');
const IS_PROD = process.env.NODE_ENV === 'production';
const baseConfig = {
entry: {
app: ['babel-polyfill', './website/client/main.js'],
},
output: {
path: config.build.assetsRoot,
publicPath: IS_PROD ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
filename: '[name].js',
devtoolModuleFilenameTemplate (info) {
// Fix source maps, code from
// https://github.com/Darkside73/bbsmile.com.ua/commit/3596d3c42ef91b69d8380359c3e8908edc08acdb
let filename = info.resourcePath;
if (info.resource.match(/\.vue$/) && !info.allLoaders.match(/type=script/)) {
filename = 'generated';
}
return filename;
},
},
resolve: {
extensions: ['*', '.js', '.vue', '.json'],
modules: [
path.join(projectRoot, 'website'),
path.join(projectRoot, 'test/client/unit'),
path.join(projectRoot, 'node_modules'),
],
alias: {
website: path.resolve(projectRoot, 'website'),
common: path.resolve(projectRoot, 'website/common'),
client: path.resolve(projectRoot, 'website/client'),
assets: path.resolve(projectRoot, 'website/client/assets'),
components: path.resolve(projectRoot, 'website/client/components'),
},
},
plugins: [
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(NOT_EXISTING)$/),
],
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: utils.cssLoaders({
sourceMap: IS_PROD ?
config.build.productionSourceMap :
config.dev.cssSourceMap,
extract: IS_PROD,
}),
postcss: [
autoprefixer({
overrideBrowserslist: ['last 2 versions'],
}),
postcssEasyImport(),
],
},
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [
path.join(projectRoot, 'test'),
path.join(projectRoot, 'website'),
path.join(projectRoot, 'node_modules', 'bootstrap-vue'),
],
options: {
cacheDirectory: true,
},
},
{
test: /\.(png|jpe?g|gif)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: utils.assetsPath('images/[name].[hash:7].[ext]'),
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]'),
},
},
{
test: /\.svg$/,
use: [
{ loader: 'svg-inline-loader' },
{
loader: 'svgo-loader',
options: {
plugins: [
{removeViewBox: false},
{convertPathData: {noSpaceAfterFlags: false}},
],
},
},
],
exclude: [path.resolve(projectRoot, 'website/client/assets/svg/for-css')],
},
{
test: /\.svg$/,
use: [
{
loader: 'svg-url-loader',
options: {
limit: 10000,
name: utils.assetsPath('svg/[name].[hash:7].[ext]'),
},
},
{
loader: 'svgo-loader',
options: {
plugins: [
{removeViewBox: false},
{convertPathData: {noSpaceAfterFlags: false}},
],
},
},
],
include: [path.resolve(projectRoot, 'website/client/assets/svg/for-css')],
},
],
},
};
if (!IS_PROD) {
const eslintFriendlyFormatter = require('eslint-friendly-formatter'); // eslint-disable-line global-require
baseConfig.module.rules.unshift({
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: projectRoot,
options: {
formatter: eslintFriendlyFormatter,
emitWarning: true,
},
exclude: /node_modules/,
});
}
module.exports = baseConfig;

View File

@@ -1,32 +0,0 @@
const config = require('./config');
const webpack = require('webpack');
const merge = require('webpack-merge');
const utils = require('./utils');
const baseWebpackConfig = require('./webpack.base.conf');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach((name) => {
baseWebpackConfig.entry[name] = baseWebpackConfig.entry[name].concat('./webpack/dev-client');
});
module.exports = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }),
},
// cheap-module-eval-source-map is faster for development
devtool: '#cheap-module-eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env,
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: './website/client/index.html',
inject: true,
}),
],
});

View File

@@ -1,101 +0,0 @@
/* eslint-disable no-process-env, no-console */
const path = require('path');
const config = require('./config');
const utils = require('./utils');
const webpack = require('webpack');
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const env = process.env.NODE_ENV === 'test' ?
require('./config/test.env') :
config.build.env;
const webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }),
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js'),
},
plugins: [
// http://vuejs.github.io/vue-loader/workflow/production.html
new webpack.DefinePlugin({
'process.env': env,
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
sourceMap: true,
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: process.env.NODE_ENV === 'test' ?
'index.html' :
config.build.index,
template: './website/client/index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency',
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (scriptModule) {
// any required modules inside node_modules are extracted to vendor
return (
scriptModule.resource &&
/\.js$/.test(scriptModule.resource) &&
scriptModule.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
);
},
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor'],
}),
],
});
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin'); // eslint-disable-line global-require
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(`\\.(${config.build.productionGzipExtensions.join('|')})$`),
threshold: 10240,
minRatio: 0.8,
})
);
}
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; // eslint-disable-line global-require
webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}
module.exports = webpackConfig;

View File

@@ -1,24 +0,0 @@
// This is the webpack config used for unit tests.
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.conf');
const utils = require('./utils');
const webpack = require('webpack');
const testEnv = require('./config/test.env');
const webpackConfig = merge(baseConfig, {
// use inline sourcemap for karma-sourcemap-loader
module: {
rules: utils.styleLoaders(),
},
devtool: '#inline-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': testEnv,
}),
],
});
// no need for app entry during tests
delete webpackConfig.entry;
module.exports = webpackConfig;

View File

@@ -1,27 +1,26 @@
module.exports = { module.exports = {
root: true, root: true,
env: { env: {
node: true node: true,
}, },
'extends': [ extends: [
'plugin:vue/essential', 'habitrpg/lib/vue',
'eslint:recommended'
], ],
rules: { rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
}, },
parserOptions: { parserOptions: {
parser: 'babel-eslint' parser: 'babel-eslint',
}, },
overrides: [ overrides: [
{ {
files: [ files: [
'**/__tests__/*.{j,t}s?(x)' '**/__tests__/*.{j,t}s?(x)',
], ],
env: { env: {
mocha: true mocha: true,
} },
} },
] ],
} };

View File

@@ -1,5 +1,5 @@
module.exports = { module.exports = {
presets: [ presets: [
'@vue/cli-plugin-babel/preset' '@vue/cli-plugin-babel/preset',
] ],
} };

View File

@@ -1,5 +1,5 @@
module.exports = { module.exports = {
plugins: { plugins: {
autoprefixer: {} autoprefixer: {},
} },
} };

View File

@@ -201,8 +201,7 @@ import bannedAccountModal from '@/components/bannedAccountModal';
const COMMUNITY_MANAGER_EMAIL = process.env.EMAILS_COMMUNITY_MANAGER_EMAIL; // eslint-disable-line const COMMUNITY_MANAGER_EMAIL = process.env.EMAILS_COMMUNITY_MANAGER_EMAIL; // eslint-disable-line
export default { export default {
mixins: [notifications, spellsMixin], name: 'App',
name: 'app',
components: { components: {
AppMenu, AppMenu,
AppHeader, AppHeader,
@@ -217,6 +216,7 @@ export default {
subCancelModalConfirm, subCancelModalConfirm,
subCanceledModal, subCanceledModal,
}, },
mixins: [notifications, spellsMixin],
data () { data () {
return { return {
icons: Object.freeze({ icons: Object.freeze({
@@ -235,9 +235,9 @@ export default {
}, },
computed: { computed: {
...mapState(['isUserLoggedIn', 'browserTimezoneOffset', 'isUserLoaded']), ...mapState(['isUserLoggedIn', 'browserTimezoneOffset', 'isUserLoaded']),
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
isStaticPage () { isStaticPage () {
return this.$route.meta.requiresLogin === false ? true : false; return this.$route.meta.requiresLogin === false;
}, },
castingSpell () { castingSpell () {
return this.$store.state.spellOptions.castingSpell; return this.$store.state.spellOptions.castingSpell;
@@ -256,14 +256,14 @@ export default {
}, },
}, },
created () { created () {
this.$root.$on('playSound', (sound) => { this.$root.$on('playSound', sound => {
let theme = this.user.preferences.sound; const theme = this.user.preferences.sound;
if (!theme || theme === 'off') { if (!theme || theme === 'off') {
return; return;
} }
let file = `/static/audio/${theme}/${sound}`; const file = `/static/audio/${theme}/${sound}`;
if (this.audioSuffix === null) { if (this.audioSuffix === null) {
this.audioSource = document.createElement('source'); this.audioSource = document.createElement('source');
@@ -284,12 +284,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? // @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.$root.$on('buyModal::showItem', item => {
this.selectedItemToBuy = item; this.selectedItemToBuy = item;
this.$root.$emit('bv::show::modal', 'buy-modal'); this.$root.$emit('bv::show::modal', 'buy-modal');
}); });
this.$root.$on('selectMembersModal::showItem', (item) => { this.$root.$on('selectMembersModal::showItem', item => {
this.selectedSpellToBuy = item; this.selectedSpellToBuy = item;
this.$root.$emit('bv::show::modal', 'select-member-modal'); this.$root.$emit('bv::show::modal', 'select-member-modal');
}); });
@@ -301,18 +301,18 @@ export default {
}); });
// Set up Error interceptors // Set up Error interceptors
axios.interceptors.response.use((response) => { axios.interceptors.response.use(response => {
if (this.user && response.data && response.data.notifications) { if (this.user && response.data && response.data.notifications) {
this.$set(this.user, 'notifications', response.data.notifications); this.$set(this.user, 'notifications', response.data.notifications);
} }
return response; return response;
}, (error) => { }, error => {
if (error.response.status >= 400) { if (error.response.status >= 400) {
this.checkForBannedUser(error); this.checkForBannedUser(error);
// Don't show errors from getting user details. These users have delete their account, // Don't show errors from getting user details. These users have delete their account,
// but their chat message still exists. // but their chat message still exists.
let configExists = Boolean(error.response) && Boolean(error.response.config); const configExists = Boolean(error.response) && Boolean(error.response.config);
if (configExists && error.response.config.method === 'get' && error.response.config.url.indexOf('/api/v4/members/') !== -1) { if (configExists && error.response.config.method === 'get' && error.response.config.url.indexOf('/api/v4/members/') !== -1) {
// @TODO: We resolve the promise because we need our caching to cache this user as tried // @TODO: We resolve the promise because we need our caching to cache this user as tried
// Chat paging should help this, but maybe we can also find another solution.. // Chat paging should help this, but maybe we can also find another solution..
@@ -333,11 +333,11 @@ export default {
let snackbarTimeout = false; let snackbarTimeout = false;
if (error.response.status === 502) snackbarTimeout = true; if (error.response.status === 502) snackbarTimeout = true;
let errorsToShow = []; const errorsToShow = [];
// show only the first error for each param // show only the first error for each param
let paramErrorsFound = {}; const paramErrorsFound = {};
if (errorData.errors) { if (errorData.errors) {
for (let e of errorData.errors) { for (const e of errorData.errors) {
if (!paramErrorsFound[e.param]) { if (!paramErrorsFound[e.param]) {
errorsToShow.push(e.message); errorsToShow.push(e.message);
paramErrorsFound[e.param] = true; paramErrorsFound[e.param] = true;
@@ -362,11 +362,11 @@ export default {
return Promise.reject(error); return Promise.reject(error);
}); });
axios.interceptors.response.use((response) => { axios.interceptors.response.use(response => {
// Verify that the user was not updated from another browser/app/client // Verify that the user was not updated from another browser/app/client
// If it was, sync // If it was, sync
const url = response.config.url; const { url } = response.config;
const method = response.config.method; const { method } = response.config;
const isApiCall = url.indexOf('api/v4') !== -1; const isApiCall = url.indexOf('api/v4') !== -1;
const userV = response.data && response.data.userV; const userV = response.data && response.data.userV;
@@ -380,14 +380,14 @@ export default {
const isUserSync = url.indexOf('/api/v4/user') === 0 && method === 'get'; const isUserSync = url.indexOf('/api/v4/user') === 0 && method === 'get';
const isTasksSync = url.indexOf('/api/v4/tasks/user') === 0 && method === 'get'; const isTasksSync = url.indexOf('/api/v4/tasks/user') === 0 && method === 'get';
// exclude chat seen requests because with real time chat they would be too many // exclude chat seen requests because with real time chat they would be too many
const isChatSeen = url.indexOf('/chat/seen') !== -1 && method === 'post'; const isChatSeen = url.indexOf('/chat/seen') !== -1 && method === 'post';
// exclude POST /api/v4/cron because the user is synced automatically after cron runs // exclude POST /api/v4/cron because the user is synced automatically after cron runs
// Something has changed on the user object that was not tracked here, sync the user // Something has changed on the user object that was not tracked here, sync the user
if (userV - oldUserV > 1 && !isCron && !isChatSeen && !isUserSync && !isTasksSync) { if (userV - oldUserV > 1 && !isCron && !isChatSeen && !isUserSync && !isTasksSync) {
Promise.all([ Promise.all([
this.$store.dispatch('user:fetch', {forceLoad: true}), this.$store.dispatch('user:fetch', { forceLoad: true }),
this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true}), this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true }),
]); ]);
} }
} }
@@ -407,7 +407,7 @@ export default {
}); });
// Setup listener for title // Setup listener for title
this.$store.watch(state => state.title, (title) => { this.$store.watch(state => state.title, title => {
document.title = title; document.title = title;
}); });
this.$nextTick(() => { this.$nextTick(() => {
@@ -446,7 +446,7 @@ export default {
// Load external scripts after the app has been rendered // Load external scripts after the app has been rendered
setupPayments(); setupPayments();
}); });
}).catch((err) => { }).catch(err => {
console.error('Impossible to fetch user. Clean up localStorage and refresh.', err); // eslint-disable-line no-console console.error('Impossible to fetch user. Clean up localStorage and refresh.', err); // eslint-disable-line no-console
}); });
} else { } else {
@@ -491,17 +491,17 @@ export default {
// Manage modals // Manage modals
this.$root.$on('bv::show::modal', (modalId, data = {}) => { this.$root.$on('bv::show::modal', (modalId, data = {}) => {
if (data.fromRoot) return; if (data.fromRoot) return;
const modalStack = this.$store.state.modalStack; const { modalStack } = this.$store.state;
this.trackGemPurchase(modalId, data); this.trackGemPurchase(modalId, data);
// Add new modal to the stack // Add new modal to the stack
const prev = modalStack[modalStack.length - 1]; const prev = modalStack[modalStack.length - 1];
const prevId = prev ? prev.modalId : undefined; const prevId = prev ? prev.modalId : undefined;
modalStack.push({modalId, prev: prevId}); modalStack.push({ modalId, prev: prevId });
}); });
this.$root.$on('bv::modal::hidden', (bvEvent) => { this.$root.$on('bv::modal::hidden', bvEvent => {
let modalId = bvEvent.target && bvEvent.target.id; let modalId = bvEvent.target && bvEvent.target.id;
// sometimes the target isn't passed to the hidden event, fallback is the vueTarget // sometimes the target isn't passed to the hidden event, fallback is the vueTarget
@@ -513,7 +513,7 @@ export default {
return; return;
} }
const modalStack = this.$store.state.modalStack; const { modalStack } = this.$store.state;
const modalOnTop = modalStack[modalStack.length - 1]; const modalOnTop = modalStack[modalStack.length - 1];
@@ -529,7 +529,7 @@ export default {
// Get previous modal // Get previous modal
const modalBefore = modalOnTop ? modalOnTop.prev : undefined; const modalBefore = modalOnTop ? modalOnTop.prev : undefined;
if (modalBefore) this.$root.$emit('bv::show::modal', modalBefore, {fromRoot: true}); if (modalBefore) this.$root.$emit('bv::show::modal', modalBefore, { fromRoot: true });
}); });
}, },
validStack (modalStack) { validStack (modalStack) {
@@ -537,7 +537,7 @@ export default {
const modalCount = {}; const modalCount = {};
const prevAndCurrent = 2; const prevAndCurrent = 2;
for (let index in modalStack) { for (const index in modalStack) {
const current = modalStack[index]; const current = modalStack[index];
if (!modalCount[current.modalId]) modalCount[current.modalId] = 0; if (!modalCount[current.modalId]) modalCount[current.modalId] = 0;
@@ -574,11 +574,9 @@ export default {
this.selectedItemToBuy = item; this.selectedItemToBuy = item;
}, },
genericPurchase (item) { genericPurchase (item) {
if (!item) if (!item) return false;
return false;
if (['card', 'debuffPotion'].includes(item.purchaseType)) if (['card', 'debuffPotion'].includes(item.purchaseType)) return false;
return false;
return true; return true;
}, },
@@ -609,7 +607,7 @@ export default {
this.selectedSpellToBuy = null; this.selectedSpellToBuy = null;
if (this.user.party._id) { if (this.user.party._id) {
this.$store.dispatch('party:getMembers', {forceLoad: true}); this.$store.dispatch('party:getMembers', { forceLoad: true });
} }
this.$root.$emit('bv::hide::modal', 'select-member-modal'); this.$root.$emit('bv::hide::modal', 'select-member-modal');

View File

@@ -3,8 +3,8 @@
.col-12.text-center .col-12.text-center
// @TODO i18n. How to setup the strings with the router-link inside? // @TODO i18n. How to setup the strings with the router-link inside?
img.not-found-img(src='~@/assets/images/404.png') img.not-found-img(src='~@/assets/images/404.png')
h1.not-found Sometimes even the bravest adventurer gets lost. h1.not-found Sometimes even the bravest adventurer gets lost.
h2.not-found Looks like this link is broken or the page may have moved, sorry! h2.not-found Looks like this link is broken or the page may have moved, sorry!
h2.not-found Head back to the <router-link to="/">Homepage</router-link> or <router-link :to="contactUsLink">Contact Us</router-link> about the issue. h2.not-found Head back to the <router-link to="/">Homepage</router-link> or <router-link :to="contactUsLink">Contact Us</router-link> about the issue.
</template> </template>
@@ -16,10 +16,9 @@ export default {
...mapState(['isUserLoggedIn']), ...mapState(['isUserLoggedIn']),
contactUsLink () { contactUsLink () {
if (this.isUserLoggedIn) { if (this.isUserLoggedIn) {
return {name: 'guild', params: {groupId: 'a29da26b-37de-4a71-b0c6-48e72a900dac'} }; return { name: 'guild', params: { groupId: 'a29da26b-37de-4a71-b0c6-48e72a900dac' } };
} else {
return {name: 'contact'};
} }
return { name: 'contact' };
}, },
}, },
}; };
@@ -52,4 +51,4 @@ h2.not-found {
margin-bottom: 0px; margin-bottom: 0px;
margin-top: 0px; margin-top: 0px;
} }
</style> </style>

View File

@@ -56,13 +56,14 @@
<script> <script>
// @TODO: // @TODO:
let BASE_URL = 'https://habitica.com';
import twitter from '@/assets/svg/twitter.svg'; import twitter from '@/assets/svg/twitter.svg';
import facebook from '@/assets/svg/facebook.svg'; import facebook from '@/assets/svg/facebook.svg';
const BASE_URL = 'https://habitica.com';
export default { export default {
data () { data () {
let tweet = this.$t('achievementShare'); const tweet = this.$t('achievementShare');
return { return {
icons: Object.freeze({ icons: Object.freeze({

View File

@@ -136,6 +136,9 @@ export default {
classes: 'content.classes', classes: 'content.classes',
}), }),
}, },
directives: {
markdown: markdownDirective,
},
data () { data () {
return { return {
icons: Object.freeze({ icons: Object.freeze({
@@ -147,16 +150,13 @@ export default {
selectedClass: 'warrior', selectedClass: 'warrior',
}; };
}, },
directives: {
markdown: markdownDirective,
},
methods: { methods: {
close () { close () {
this.$root.$emit('bv::hide::modal', 'choose-class'); this.$root.$emit('bv::hide::modal', 'choose-class');
}, },
clickSelectClass (heroClass) { clickSelectClass (heroClass) {
if (this.user.flags.classSelected && !confirm(this.$t('changeClassConfirmCost'))) return; if (this.user.flags.classSelected && !confirm(this.$t('changeClassConfirmCost'))) return;
this.$store.dispatch('user:changeClass', {query: {class: heroClass}}); this.$store.dispatch('user:changeClass', { query: { class: heroClass } });
}, },
clickDisableClasses () { clickDisableClasses () {
this.$store.dispatch('user:disableClasses'); this.$store.dispatch('user:disableClasses');
@@ -170,13 +170,13 @@ export default {
shield: 'shield_special_fall2019Rogue', shield: 'shield_special_fall2019Rogue',
weapon: 'weapon_special_fall2019Rogue', weapon: 'weapon_special_fall2019Rogue',
}; };
} else if (heroClass === 'wizard') { } if (heroClass === 'wizard') {
return { return {
armor: 'armor_special_fall2019Mage', armor: 'armor_special_fall2019Mage',
head: 'head_special_fall2019Mage', head: 'head_special_fall2019Mage',
weapon: 'weapon_special_fall2019Mage', weapon: 'weapon_special_fall2019Mage',
}; };
} else if (heroClass === 'healer') { } if (heroClass === 'healer') {
return { return {
armor: 'armor_special_fall2019Healer', armor: 'armor_special_fall2019Healer',
eyewear: 'eyewear_special_fall2019Healer', eyewear: 'eyewear_special_fall2019Healer',
@@ -184,14 +184,13 @@ export default {
shield: 'shield_special_fall2019Healer', shield: 'shield_special_fall2019Healer',
weapon: 'weapon_special_fall2019Healer', weapon: 'weapon_special_fall2019Healer',
}; };
} else {
return {
armor: 'armor_special_fall2019Warrior',
head: 'head_special_fall2019Warrior',
shield: 'shield_special_fall2019Warrior',
weapon: 'weapon_special_fall2019Warrior',
};
} }
return {
armor: 'armor_special_fall2019Warrior',
head: 'head_special_fall2019Warrior',
shield: 'shield_special_fall2019Warrior',
weapon: 'weapon_special_fall2019Warrior',
};
}, },
selectionBox (selectedClass, heroClass) { selectionBox (selectedClass, heroClass) {
if (selectedClass === heroClass) { if (selectedClass === heroClass) {

View File

@@ -34,7 +34,7 @@ export default {
achievementAvatar, achievementAvatar,
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
methods: { methods: {
close () { close () {

View File

@@ -41,7 +41,7 @@ import axios from 'axios';
import Avatar from '../avatar'; import Avatar from '../avatar';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import percent from '@/../../common/script/libs/percent'; import percent from '@/../../common/script/libs/percent';
import {MAX_HEALTH as maxHealth} from '@/../../common/script/constants'; import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants';
import revive from '@/../../common/script/ops/revive'; import revive from '@/../../common/script/ops/revive';
export default { export default {
@@ -54,7 +54,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
barStyle () { barStyle () {
return { return {
width: `${percent(this.user.stats.hp, maxHealth)}%`, width: `${percent(this.user.stats.hp, maxHealth)}%`,

View File

@@ -30,7 +30,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
firstDropText () { firstDropText () {
return this.$t('firstDrop', { return this.$t('firstDrop', {
eggText: this.eggs.all.Wolf.text(), eggText: this.eggs.all.Wolf.text(),

View File

@@ -19,24 +19,24 @@
</style> </style>
<script> <script>
import achievementFooter from './achievementFooter'; import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar'; import achievementAvatar from './achievementAvatar';
import {mapState} from '@/libs/store'; import { mapState } from '@/libs/store';
export default { export default {
components: { components: {
achievementFooter, achievementFooter,
achievementAvatar, achievementAvatar,
},
props: ['data'],
computed: {
...mapState({ user: 'user.data' }),
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'generic-achievement');
}, },
props: ['data'], },
computed: { };
...mapState({user: 'user.data'}),
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'generic-achievement');
},
},
};
</script> </script>

View File

@@ -19,28 +19,28 @@
</style> </style>
<script> <script>
import achievementFooter from './achievementFooter'; import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar'; import achievementAvatar from './achievementAvatar';
import {mapState} from '@/libs/store'; import { mapState } from '@/libs/store';
export default { export default {
components: { components: {
achievementFooter, achievementFooter,
achievementAvatar, achievementAvatar,
},
computed: {
...mapState({ user: 'user.data' }),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementJustAddWater')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'just-add-water');
}, },
computed: { },
...mapState({user: 'user.data'}), };
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementJustAddWater')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'just-add-water');
},
},
};
</script> </script>

View File

@@ -116,20 +116,20 @@
<script> <script>
import Avatar from '../avatar'; import Avatar from '../avatar';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import {MAX_HEALTH as maxHealth} from '@/../../common/script/constants'; import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants';
import styleHelper from '@/mixins/styleHelper'; import styleHelper from '@/mixins/styleHelper';
import twitter from '@/assets/svg/twitter.svg'; import twitter from '@/assets/svg/twitter.svg';
import facebook from '@/assets/svg/facebook.svg'; import facebook from '@/assets/svg/facebook.svg';
let BASE_URL = 'https://habitica.com'; const BASE_URL = 'https://habitica.com';
export default { export default {
mixins: [styleHelper],
components: { components: {
Avatar, Avatar,
}, },
mixins: [styleHelper],
data () { data () {
let tweet = this.$t('levelUpShare'); const tweet = this.$t('levelUpShare');
return { return {
icons: Object.freeze({ icons: Object.freeze({
twitter, twitter,
@@ -147,7 +147,7 @@ export default {
this.loadWidgets(); this.loadWidgets();
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
showAllocation () { showAllocation () {
return this.$store.getters['members:hasClass'](this.user) && !this.user.preferences.automaticAllocation; return this.$store.getters['members:hasClass'](this.user) && !this.user.preferences.automaticAllocation;
}, },

View File

@@ -19,28 +19,28 @@
</style> </style>
<script> <script>
import achievementFooter from './achievementFooter'; import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar'; import achievementAvatar from './achievementAvatar';
import {mapState} from '@/libs/store'; import { mapState } from '@/libs/store';
export default { export default {
components: { components: {
achievementFooter, achievementFooter,
achievementAvatar, achievementAvatar,
},
computed: {
...mapState({ user: 'user.data' }),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementLostMasterclasser')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'lost-masterclasser');
}, },
computed: { },
...mapState({user: 'user.data'}), };
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementLostMasterclasser')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'lost-masterclasser');
},
},
};
</script> </script>

View File

@@ -59,7 +59,7 @@
import Avatar from '../avatar'; import Avatar from '../avatar';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import percent from '@/../../common/script/libs/percent'; import percent from '@/../../common/script/libs/percent';
import {MAX_HEALTH as maxHealth} from '@/../../common/script/constants'; import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants';
export default { export default {
components: { components: {
@@ -71,7 +71,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
barStyle () { barStyle () {
return { return {
width: `${percent(this.user.stats.hp, maxHealth)}%`, width: `${percent(this.user.stats.hp, maxHealth)}%`,

View File

@@ -19,28 +19,28 @@
</style> </style>
<script> <script>
import achievementFooter from './achievementFooter'; import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar'; import achievementAvatar from './achievementAvatar';
import {mapState} from '@/libs/store'; import { mapState } from '@/libs/store';
export default { export default {
components: { components: {
achievementFooter, achievementFooter,
achievementAvatar, achievementAvatar,
},
computed: {
...mapState({ user: 'user.data' }),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementMindOverMatter')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'mind-over-matter');
}, },
computed: { },
...mapState({user: 'user.data'}), };
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementMindOverMatter')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'mind-over-matter');
},
},
};
</script> </script>

View File

@@ -24,37 +24,37 @@
</style> </style>
<script> <script>
import axios from 'axios'; import axios from 'axios';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
export default { export default {
data () { data () {
return { return {
html: '', html: '',
}; };
},
computed: {
...mapState({ user: 'user.data' }),
},
async mounted () {
this.$root.$on('bv::show::modal', async modalId => {
if (modalId !== 'new-stuff') return;
const response = await axios.get('/api/v4/news');
this.html = response.data.html;
});
},
destroyed () {
this.$root.$off('bv::show::modal');
},
methods: {
tellMeLater () {
this.$store.dispatch('user:newStuffLater');
this.$root.$emit('bv::hide::modal', 'new-stuff');
}, },
computed: { dismissAlert () {
...mapState({user: 'user.data'}), this.$store.dispatch('user:set', { 'flags.newStuff': false });
this.$root.$emit('bv::hide::modal', 'new-stuff');
}, },
async mounted () { },
this.$root.$on('bv::show::modal', async (modalId) => { };
if (modalId !== 'new-stuff') return;
let response = await axios.get('/api/v4/news');
this.html = response.data.html;
});
},
destroyed () {
this.$root.$off('bv::show::modal');
},
methods: {
tellMeLater () {
this.$store.dispatch('user:newStuffLater');
this.$root.$emit('bv::hide::modal', 'new-stuff');
},
dismissAlert () {
this.$store.dispatch('user:set', {'flags.newStuff': false});
this.$root.$emit('bv::hide::modal', 'new-stuff');
},
},
};
</script> </script>

View File

@@ -28,7 +28,7 @@ import questDialogDrops from '@/components/shops/quests/questDialogDrops';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import percent from '@/../../common/script/libs/percent'; import percent from '@/../../common/script/libs/percent';
import {MAX_HEALTH as maxHealth} from '@/../../common/script/constants'; import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants';
export default { export default {
components: { components: {
@@ -41,7 +41,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
questData () { questData () {
return this.quests.quests[this.user.party.quest.completed]; return this.quests.quests[this.user.party.quest.completed];
}, },
@@ -62,7 +62,7 @@ export default {
this.close(); this.close();
}, },
hide () { hide () {
this.$store.dispatch('user:set', {'party.quest.completed': ''}); this.$store.dispatch('user:set', { 'party.quest.completed': '' });
}, },
}, },
}; };

View File

@@ -48,7 +48,7 @@
import * as quests from '@/../../common/script/content/quests'; import * as quests from '@/../../common/script/content/quests';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import percent from '@/../../common/script/libs/percent'; import percent from '@/../../common/script/libs/percent';
import {MAX_HEALTH as maxHealth} from '@/../../common/script/constants'; import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants';
export default { export default {
data () { data () {
@@ -58,7 +58,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
barStyle () { barStyle () {
return { return {
width: `${percent(this.user.stats.hp, maxHealth)}%`, width: `${percent(this.user.stats.hp, maxHealth)}%`,

View File

@@ -24,23 +24,23 @@
</style> </style>
<script> <script>
import achievementFooter from './achievementFooter'; import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar'; import achievementAvatar from './achievementAvatar';
import {mapState} from '@/libs/store'; import { mapState } from '@/libs/store';
export default { export default {
components: { components: {
achievementFooter, achievementFooter,
achievementAvatar, achievementAvatar,
},
computed: {
...mapState({ user: 'user.data' }),
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'rebirth');
}, },
computed: { },
...mapState({user: 'user.data'}), };
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'rebirth');
},
},
};
</script> </script>

View File

@@ -21,7 +21,7 @@ import { mapState } from '@/libs/store';
export default { export default {
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
methods: { methods: {
close () { close () {

View File

@@ -38,15 +38,15 @@ export default {
achievementAvatar, achievementAvatar,
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
methods: { methods: {
close () { close () {
this.$root.$emit('bv::hide::modal', 'streak'); this.$root.$emit('bv::hide::modal', 'streak');
}, },
suppressModals () { suppressModals () {
let surpress = this.user.preferences.suppressModals.streak ? true : false; const surpress = !!this.user.preferences.suppressModals.streak;
this.$store.dispatch('user:set', {'preferences.suppressModals.streak': surpress}); this.$store.dispatch('user:set', { 'preferences.suppressModals.streak': surpress });
}, },
}, },
}; };

View File

@@ -56,7 +56,7 @@ export default {
achievementAvatar, achievementAvatar,
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
methods: { methods: {
close () { close () {

View File

@@ -49,10 +49,10 @@ export default {
markdown: markdownDirective, markdown: markdownDirective,
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
data () { data () {
let tweet = this.$t('wonChallengeShare'); const tweet = this.$t('wonChallengeShare');
return { return {
tweet, tweet,
}; };

View File

@@ -295,7 +295,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
...mapState(['isUserLoaded']), ...mapState(['isUserLoaded']),
getDataDisplayToolUrl () { getDataDisplayToolUrl () {
const base = 'https://oldgods.net/habitrpg/habitrpg_user_data_display.html'; const base = 'https://oldgods.net/habitrpg/habitrpg_user_data_display.html';
@@ -318,7 +318,7 @@ export default {
async addMissedDay (numberOfDays) { async addMissedDay (numberOfDays) {
if (!confirm(`Are you sure you want to reset the day by ${numberOfDays} day(s)?`)) return; if (!confirm(`Are you sure you want to reset the day by ${numberOfDays} day(s)?`)) return;
let date = moment(this.user.lastCron).subtract(numberOfDays, 'days').toDate(); const date = moment(this.user.lastCron).subtract(numberOfDays, 'days').toDate();
await axios.post('/api/v4/debug/set-cron', { await axios.post('/api/v4/debug/set-cron', {
lastCron: date, lastCron: date,
@@ -349,18 +349,18 @@ export default {
addLevelsAndGold () { addLevelsAndGold () {
this.$store.dispatch('user:set', { this.$store.dispatch('user:set', {
'stats.exp': this.user.stats.exp + 10000, 'stats.exp': this.user.stats.exp + 10000,
'stats.gp': this.user.stats.gp + 10000, 'stats.gp': this.user.stats.gp + 10000,
'stats.mp': this.user.stats.mp + 10000, 'stats.mp': this.user.stats.mp + 10000,
}); });
}, },
addExp () { addExp () {
// @TODO: Name these variables better // @TODO: Name these variables better
let exp = 0; let exp = 0;
let five = 10 * this.user.stats.lvl; const five = 10 * this.user.stats.lvl;
let four = Math.pow(this.user.stats.lvl, 2) * 0.25; const four = Math.pow(this.user.stats.lvl, 2) * 0.25;
let three = four + five + 139.75; const three = four + five + 139.75;
let two = three / 10; const two = three / 10;
let one = Math.round(two) * 10; const one = Math.round(two) * 10;
exp = this.user.stats.exp + one; exp = this.user.stats.exp + one;
this.$store.dispatch('user:set', { this.$store.dispatch('user:set', {
@@ -394,7 +394,7 @@ export default {
eventAction: 'click', eventAction: 'click',
eventLabel: 'Gems > Donate', eventLabel: 'Gems > Donate',
}); });
this.$root.$emit('bv::show::modal', 'buy-gems', {alreadyTracked: true}); this.$root.$emit('bv::show::modal', 'buy-gems', { alreadyTracked: true });
}, },
}, },
}; };

View File

@@ -80,9 +80,9 @@
<script> <script>
import hello from 'hellojs'; import hello from 'hellojs';
import { setUpAxios } from '@/libs/auth';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import isEmail from 'validator/lib/isEmail'; import isEmail from 'validator/lib/isEmail';
import { setUpAxios } from '@/libs/auth';
import facebookSquareIcon from '@/assets/svg/facebook-square.svg'; import facebookSquareIcon from '@/assets/svg/facebook-square.svg';
import googleIcon from '@/assets/svg/google.svg'; import googleIcon from '@/assets/svg/google.svg';
@@ -90,7 +90,7 @@ import googleIcon from '@/assets/svg/google.svg';
export default { export default {
name: 'AuthForm', name: 'AuthForm',
data () { data () {
let data = { const data = {
registering: true, registering: true,
username: '', username: '',
email: '', email: '',
@@ -106,13 +106,6 @@ export default {
return data; return data;
}, },
mounted () {
hello.init({
facebook: process.env.FACEBOOK_KEY, // eslint-disable-line
// windows: WINDOWS_CLIENT_ID,
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
});
},
computed: { computed: {
emailValid () { emailValid () {
if (this.email.length <= 3) return false; if (this.email.length <= 3) return false;
@@ -141,6 +134,13 @@ export default {
this.validateUsername(this.username); this.validateUsername(this.username);
}, },
}, },
mounted () {
hello.init({
facebook: process.env.FACEBOOK_KEY, // eslint-disable-line
// windows: WINDOWS_CLIENT_ID,
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
});
},
methods: { methods: {
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
validateUsername: debounce(function (username) { validateUsername: debounce(function (username) {
@@ -165,7 +165,7 @@ export default {
try { try {
const redirectUrl = `${window.location.protocol}//${window.location.host}`; const redirectUrl = `${window.location.protocol}//${window.location.host}`;
let auth = await hello(network).login({ const auth = await hello(network).login({
scope: 'email', scope: 'email',
redirect_uri: redirectUrl, // eslint-disable-line camelcase redirect_uri: redirectUrl, // eslint-disable-line camelcase
}); });
@@ -212,7 +212,7 @@ export default {
async finishAuth () { async finishAuth () {
setUpAxios(); setUpAxios();
await this.$store.dispatch('user:fetch', {forceLoad: true}); await this.$store.dispatch('user:fetch', { forceLoad: true });
this.$emit('authenticate'); this.$emit('authenticate');
}, },

View File

@@ -1,13 +1,13 @@
<script> <script>
export default { export default {
components: {}, components: {},
methods: { created () {
async logout () { this.logout();
return await this.$store.dispatch('auth:logout'); },
}, methods: {
async logout () {
return await this.$store.dispatch('auth:logout');
}, },
created () { },
this.logout(); };
},
};
</script> </script>

View File

@@ -304,7 +304,7 @@ import googleIcon from '@/assets/svg/google.svg';
export default { export default {
data () { data () {
let data = { const data = {
username: '', username: '',
email: '', email: '',
password: '', password: '',
@@ -364,29 +364,22 @@ export default {
return !this.passwordConfirmValid; return !this.passwordConfirmValid;
}, },
}, },
mounted () {
hello.init({
facebook: process.env.FACEBOOK_KEY, // eslint-disable-line
// windows: WINDOWS_CLIENT_ID,
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
});
},
watch: { watch: {
$route: { $route: {
handler () { handler () {
if (this.resetPasswordSetNewOne) { if (this.resetPasswordSetNewOne) {
const query = this.$route.query; const { query } = this.$route;
const code = query.code; const { code } = query;
const hasError = query.hasError === 'true' ? true : false; const hasError = query.hasError === 'true';
if (hasError) { if (hasError) {
alert(query.message); alert(query.message);
this.$router.push({name: 'login'}); this.$router.push({ name: 'login' });
return; return;
} }
if (!code) { if (!code) {
alert(this.$t('invalidPasswordResetCode')); alert(this.$t('invalidPasswordResetCode'));
this.$router.push({name: 'login'}); this.$router.push({ name: 'login' });
return; return;
} }
@@ -400,6 +393,13 @@ export default {
this.validateUsername(this.username); this.validateUsername(this.username);
}, },
}, },
mounted () {
hello.init({
facebook: process.env.FACEBOOK_KEY, // eslint-disable-line
// windows: WINDOWS_CLIENT_ID,
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
});
},
methods: { methods: {
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
validateUsername: debounce(function (username) { validateUsername: debounce(function (username) {
@@ -488,7 +488,7 @@ export default {
} catch (e) {} // eslint-disable-line } catch (e) {} // eslint-disable-line
const redirectUrl = `${window.location.protocol}//${window.location.host}`; const redirectUrl = `${window.location.protocol}//${window.location.host}`;
let auth = await hello(network).login({ const auth = await hello(network).login({
scope: 'email', scope: 'email',
// explicitly pass the redirect url or it might redirect to /home // explicitly pass the redirect url or it might redirect to /home
redirect_uri: redirectUrl, // eslint-disable-line camelcase redirect_uri: redirectUrl, // eslint-disable-line camelcase
@@ -568,7 +568,7 @@ export default {
this.passwordConfirm = ''; this.passwordConfirm = '';
this.resetPasswordSetNewOneData.code = ''; this.resetPasswordSetNewOneData.code = '';
this.resetPasswordSetNewOneData.hasError = false; this.resetPasswordSetNewOneData.hasError = false;
this.$router.push({name: 'login'}); this.$router.push({ name: 'login' });
}, },
}, },
}; };

View File

@@ -146,9 +146,9 @@ export default {
return val; return val;
}, },
backgroundClass () { backgroundClass () {
let background = this.member.preferences.background; const { background } = this.member.preferences;
let allowToShowBackground = !this.avatarOnly || this.withBackground; const allowToShowBackground = !this.avatarOnly || this.withBackground;
if (this.overrideAvatarGear && this.overrideAvatarGear.background) { if (this.overrideAvatarGear && this.overrideAvatarGear.background) {
return `background_${this.overrideAvatarGear.background}`; return `background_${this.overrideAvatarGear.background}`;
@@ -169,7 +169,7 @@ export default {
}; };
}, },
skinClass () { skinClass () {
let baseClass = `skin_${this.member.preferences.skin}`; const baseClass = `skin_${this.member.preferences.skin}`;
return `${baseClass}${this.member.preferences.sleep ? '_sleep' : ''}`; return `${baseClass}${this.member.preferences.sleep ? '_sleep' : ''}`;
}, },
@@ -196,19 +196,19 @@ export default {
}, },
hideGear (gearType) { hideGear (gearType) {
if (gearType === 'weapon') { if (gearType === 'weapon') {
let equippedWeapon = this.member.items.gear[this.costumeClass][gearType]; const equippedWeapon = this.member.items.gear[this.costumeClass][gearType];
if (!equippedWeapon) { if (!equippedWeapon) {
return false; return false;
} }
let equippedIsTwoHanded = this.flatGear[equippedWeapon].twoHanded; const equippedIsTwoHanded = this.flatGear[equippedWeapon].twoHanded;
let hasOverrideShield = this.overrideAvatarGear && this.overrideAvatarGear.shield; const hasOverrideShield = this.overrideAvatarGear && this.overrideAvatarGear.shield;
return equippedIsTwoHanded && hasOverrideShield; return equippedIsTwoHanded && hasOverrideShield;
} else if (gearType === 'shield') { } if (gearType === 'shield') {
let overrideWeapon = this.overrideAvatarGear && this.overrideAvatarGear.weapon; const overrideWeapon = this.overrideAvatarGear && this.overrideAvatarGear.weapon;
let overrideIsTwoHanded = overrideWeapon && this.flatGear[overrideWeapon].twoHanded; const overrideIsTwoHanded = overrideWeapon && this.flatGear[overrideWeapon].twoHanded;
return overrideIsTwoHanded; return overrideIsTwoHanded;
} }
@@ -218,10 +218,9 @@ export default {
this.$root.$emit('castEnd', this.member, 'user', e); this.$root.$emit('castEnd', this.member, 'user', e);
}, },
showAvatar () { showAvatar () {
if (!this.showVisualBuffs) if (!this.showVisualBuffs) return true;
return true;
let buffs = this.member.stats.buffs; const { buffs } = this.member.stats;
return !buffs.snowball && !buffs.spookySparkles && !buffs.shinySeed && !buffs.seafoam; return !buffs.snowball && !buffs.spookySparkles && !buffs.shinySeed && !buffs.seafoam;
}, },

View File

@@ -21,70 +21,70 @@
</template> </template>
<script> <script>
import appearance from '@/../../common/script/content/appearance'; import appearance from '@/../../common/script/content/appearance';
import {subPageMixin} from '../../mixins/subPage'; import { subPageMixin } from '../../mixins/subPage';
import {userStateMixin} from '../../mixins/userState'; import { userStateMixin } from '../../mixins/userState';
import {avatarEditorUtilies} from '../../mixins/avatarEditUtilities'; import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities';
import subMenu from './sub-menu'; import subMenu from './sub-menu';
import customizeOptions from './customize-options'; import customizeOptions from './customize-options';
import gem from '@/assets/svg/gem.svg'; import gem from '@/assets/svg/gem.svg';
const freeShirtKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price === 0); const freeShirtKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price === 0);
const specialShirtKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price !== 0); const specialShirtKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price !== 0);
export default { export default {
props: [ components: {
'editing', subMenu,
], customizeOptions,
components: { },
subMenu, mixins: [
customizeOptions, subPageMixin,
userStateMixin,
avatarEditorUtilies,
],
props: [
'editing',
],
data () {
return {
specialShirtKeys,
icons: Object.freeze({
gem,
}),
items: [
{
id: 'size',
label: this.$t('size'),
},
{
id: 'shirt',
label: this.$t('shirt'),
},
],
};
},
computed: {
sizes () {
return ['slim', 'broad'].map(s => this.mapKeysToFreeOption(s, 'size'));
}, },
mixins: [ freeShirts () {
subPageMixin, return freeShirtKeys.map(s => this.mapKeysToFreeOption(s, 'shirt'));
userStateMixin,
avatarEditorUtilies,
],
data () {
return {
specialShirtKeys,
icons: Object.freeze({
gem,
}),
items: [
{
id: 'size',
label: this.$t('size'),
},
{
id: 'shirt',
label: this.$t('shirt'),
},
],
};
}, },
computed: { specialShirts () {
sizes () {
return ['slim', 'broad'].map(s => this.mapKeysToFreeOption(s, 'size'));
},
freeShirts () {
return freeShirtKeys.map(s => this.mapKeysToFreeOption(s, 'shirt'));
},
specialShirts () {
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let keys = this.specialShirtKeys; const keys = this.specialShirtKeys;
let options = keys.map(key => this.mapKeysToOption(key, 'shirt')); const options = keys.map(key => this.mapKeysToOption(key, 'shirt'));
return options; return options;
},
}, },
mounted () { },
this.changeSubPage('size'); mounted () {
}, this.changeSubPage('size');
methods: { },
methods: {
}, },
}; };
</script> </script>
<style scoped> <style scoped>

View File

@@ -23,29 +23,29 @@
</template> </template>
<script> <script>
import gem from '@/assets/svg/gem.svg'; import gem from '@/assets/svg/gem.svg';
import gold from '@/assets/svg/gold.svg'; import gold from '@/assets/svg/gold.svg';
import {avatarEditorUtilies} from '../../mixins/avatarEditUtilities'; import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities';
export default { export default {
props: ['items', 'currentValue', 'fullSet'], mixins: [
mixins: [ avatarEditorUtilies,
avatarEditorUtilies, ],
], props: ['items', 'currentValue', 'fullSet'],
data () { data () {
return { return {
icons: Object.freeze({ icons: Object.freeze({
gem, gem,
gold, gold,
}), }),
}; };
},
methods: {
unlock () {
this.$emit('unlock');
}, },
methods: { },
unlock () { };
this.$emit('unlock');
},
},
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -36,235 +36,228 @@
</template> </template>
<script> <script>
import appearance from '@/../../common/script/content/appearance'; import appearance from '@/../../common/script/content/appearance';
import {subPageMixin} from '../../mixins/subPage'; import { subPageMixin } from '../../mixins/subPage';
import {userStateMixin} from '../../mixins/userState'; import { userStateMixin } from '../../mixins/userState';
import {avatarEditorUtilies} from '../../mixins/avatarEditUtilities'; import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities';
import subMenu from './sub-menu'; import subMenu from './sub-menu';
import customizeOptions from './customize-options'; import customizeOptions from './customize-options';
import gem from '@/assets/svg/gem.svg'; import gem from '@/assets/svg/gem.svg';
const freeShirtKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price === 0); const freeShirtKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price === 0);
const specialShirtKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price !== 0); const specialShirtKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price !== 0);
export default { export default {
props: [ components: {
'editing', subMenu,
], customizeOptions,
components: { },
subMenu, mixins: [
customizeOptions, subPageMixin,
}, userStateMixin,
mixins: [ avatarEditorUtilies,
subPageMixin, ],
userStateMixin, props: [
avatarEditorUtilies, 'editing',
], ],
data () { data () {
return { return {
animalItemKeys: { animalItemKeys: {
back: ['bearTail', 'cactusTail', 'foxTail', 'lionTail', 'pandaTail', 'pigTail', 'tigerTail', 'wolfTail'], back: ['bearTail', 'cactusTail', 'foxTail', 'lionTail', 'pandaTail', 'pigTail', 'tigerTail', 'wolfTail'],
headAccessory: ['bearEars', 'cactusEars', 'foxEars', 'lionEars', 'pandaEars', 'pigEars', 'tigerEars', 'wolfEars'], headAccessory: ['bearEars', 'cactusEars', 'foxEars', 'lionEars', 'pandaEars', 'pigEars', 'tigerEars', 'wolfEars'],
},
chairKeys: ['none', 'black', 'blue', 'green', 'pink', 'red', 'yellow', 'handleless_black', 'handleless_blue', 'handleless_green', 'handleless_pink', 'handleless_red', 'handleless_yellow'],
specialShirtKeys,
icons: Object.freeze({
gem,
}),
items: [
{
id: 'size',
label: this.$t('size'),
}, },
chairKeys: ['none', 'black', 'blue', 'green', 'pink', 'red', 'yellow', 'handleless_black', 'handleless_blue', 'handleless_green', 'handleless_pink', 'handleless_red', 'handleless_yellow'], {
specialShirtKeys, id: 'shirt',
icons: Object.freeze({ label: this.$t('shirt'),
gem, },
}), ],
items: [ };
{ },
id: 'size', computed: {
label: this.$t('size'), extraSubMenuItems () {
}, const items = [];
{
id: 'shirt',
label: this.$t('shirt'),
},
],
};
},
computed: {
extraSubMenuItems () {
const items = [];
if (this.editing) { if (this.editing) {
items.push({ items.push({
id: 'glasses', id: 'glasses',
label: this.$t('glasses'), label: this.$t('glasses'),
});
}
items.push(
{
id: 'wheelchair',
label: this.$t('wheelchair'),
},
{
id: 'flower',
label: this.$t('accent'),
},
);
if (this.editing) {
items.push({
id: 'ears',
label: this.$t('animalEars'),
});
items.push({
id: 'tails',
label: this.$t('animalTails'),
});
items.push({
id: 'headband',
label: this.$t('headband'),
});
}
return items;
},
eyewear () {
let keys = [
'blackTopFrame', 'blueTopFrame', 'greenTopFrame', 'pinkTopFrame', 'redTopFrame', 'whiteTopFrame', 'yellowTopFrame',
'blackHalfMoon', 'blueHalfMoon', 'greenHalfMoon', 'pinkHalfMoon', 'redHalfMoon', 'whiteHalfMoon', 'yellowHalfMoon',
];
let options = keys.map(key => {
let newKey = `eyewear_special_${key}`;
let option = {};
option.key = key;
option.active = this.user.preferences.costume ? this.user.items.gear.costume.eyewear === newKey : this.user.items.gear.equipped.eyewear === newKey;
option.class = `eyewear_special_${key}`;
option.click = () => {
let type = this.user.preferences.costume ? 'costume' : 'equipped';
return this.equip(newKey, type);
};
return option;
}); });
return options; }
},
freeShirts () { items.push(
return freeShirtKeys.map(s => this.mapKeysToFreeOption(s, 'shirt')); {
}, id: 'wheelchair',
specialShirts () { label: this.$t('wheelchair'),
},
{
id: 'flower',
label: this.$t('accent'),
},
);
if (this.editing) {
items.push({
id: 'ears',
label: this.$t('animalEars'),
});
items.push({
id: 'tails',
label: this.$t('animalTails'),
});
items.push({
id: 'headband',
label: this.$t('headband'),
});
}
return items;
},
eyewear () {
const keys = [
'blackTopFrame', 'blueTopFrame', 'greenTopFrame', 'pinkTopFrame', 'redTopFrame', 'whiteTopFrame', 'yellowTopFrame',
'blackHalfMoon', 'blueHalfMoon', 'greenHalfMoon', 'pinkHalfMoon', 'redHalfMoon', 'whiteHalfMoon', 'yellowHalfMoon',
];
const options = keys.map(key => {
const newKey = `eyewear_special_${key}`;
const option = {};
option.key = key;
option.active = this.user.preferences.costume ? this.user.items.gear.costume.eyewear === newKey : this.user.items.gear.equipped.eyewear === newKey;
option.class = `eyewear_special_${key}`;
option.click = () => {
const type = this.user.preferences.costume ? 'costume' : 'equipped';
return this.equip(newKey, type);
};
return option;
});
return options;
},
freeShirts () {
return freeShirtKeys.map(s => this.mapKeysToFreeOption(s, 'shirt'));
},
specialShirts () {
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let keys = this.specialShirtKeys; const keys = this.specialShirtKeys;
let options = keys.map(key => this.mapKeysToOption(key, 'shirt')); const options = keys.map(key => this.mapKeysToOption(key, 'shirt'));
return options; return options;
},
headbands () {
let keys = ['blackHeadband', 'blueHeadband', 'greenHeadband', 'pinkHeadband', 'redHeadband', 'whiteHeadband', 'yellowHeadband'];
let options = keys.map(key => {
let newKey = `headAccessory_special_${key}`;
let option = {};
option.key = key;
option.active = this.user.preferences.costume ? this.user.items.gear.costume.headAccessory === newKey : this.user.items.gear.equipped.headAccessory === newKey;
option.class = `headAccessory_special_${option.key} headband`;
option.click = () => {
let type = this.user.preferences.costume ? 'costume' : 'equipped';
return this.equip(newKey, type);
};
return option;
});
return options;
},
chairs () {
let options = this.chairKeys.map(key => {
let option = {};
option.key = key;
if (key === 'none') {
option.none = true;
}
option.active = this.user.preferences.chair === key;
option.class = `button_chair_${key} chair ${key.includes('handleless_') ? 'handleless' : ''}`;
option.click = () => {
return this.set({'preferences.chair': key});
};
return option;
});
return options;
},
flowers () {
let keys = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let options = keys.map(key => {
let option = {};
option.key = key;
if (key === 0) {
option.none = true;
}
option.active = this.user.preferences.hair.flower === key;
option.class = `hair_flower_${key} flower`;
option.click = () => {
return this.set({'preferences.hair.flower': key});
};
return option;
});
return options;
},
}, },
mounted () { headbands () {
this.changeSubPage(this.extraSubMenuItems[0].id); const keys = ['blackHeadband', 'blueHeadband', 'greenHeadband', 'pinkHeadband', 'redHeadband', 'whiteHeadband', 'yellowHeadband'];
const options = keys.map(key => {
const newKey = `headAccessory_special_${key}`;
const option = {};
option.key = key;
option.active = this.user.preferences.costume ? this.user.items.gear.costume.headAccessory === newKey : this.user.items.gear.equipped.headAccessory === newKey;
option.class = `headAccessory_special_${option.key} headband`;
option.click = () => {
const type = this.user.preferences.costume ? 'costume' : 'equipped';
return this.equip(newKey, type);
};
return option;
});
return options;
}, },
methods: { chairs () {
animalItems (category) { const options = this.chairKeys.map(key => {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now const option = {};
option.key = key;
if (key === 'none') {
option.none = true;
}
option.active = this.user.preferences.chair === key;
option.class = `button_chair_${key} chair ${key.includes('handleless_') ? 'handleless' : ''}`;
option.click = () => this.set({ 'preferences.chair': key });
return option;
});
return options;
},
flowers () {
const keys = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
const options = keys.map(key => {
const option = {};
option.key = key;
if (key === 0) {
option.none = true;
}
option.active = this.user.preferences.hair.flower === key;
option.class = `hair_flower_${key} flower`;
option.click = () => this.set({ 'preferences.hair.flower': key });
return option;
});
return options;
},
},
mounted () {
this.changeSubPage(this.extraSubMenuItems[0].id);
},
methods: {
animalItems (category) {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let keys = this.animalItemKeys[category]; const keys = this.animalItemKeys[category];
let options = keys.map(key => { const options = keys.map(key => {
let newKey = `${category}_special_${key}`; const newKey = `${category}_special_${key}`;
let userPurchased = this.user.items.gear.owned[newKey]; const userPurchased = this.user.items.gear.owned[newKey];
let option = {}; const option = {};
option.key = key; option.key = key;
option.active = this.user.preferences.costume ? this.user.items.gear.costume[category] === newKey : this.user.items.gear.equipped[category] === newKey; option.active = this.user.preferences.costume ? this.user.items.gear.costume[category] === newKey : this.user.items.gear.equipped[category] === newKey;
option.class = `headAccessory_special_${option.key} ${category}`; option.class = `headAccessory_special_${option.key} ${category}`;
if (category === 'back') { if (category === 'back') {
option.class = `icon_back_special_${option.key} back`; option.class = `icon_back_special_${option.key} back`;
} }
option.gemLocked = userPurchased === undefined; option.gemLocked = userPurchased === undefined;
option.goldLocked = userPurchased === false; option.goldLocked = userPurchased === false;
if (option.goldLocked) { if (option.goldLocked) {
option.gold = 20; option.gold = 20;
} }
if (option.gemLocked) {
option.gem = 2;
}
option.locked = option.gemLocked || option.goldLocked;
option.click = () => {
if (option.gemLocked) { if (option.gemLocked) {
option.gem = 2; return this.unlock(`items.gear.owned.${newKey}`);
} if (option.goldLocked) {
return this.buy(newKey);
} }
option.locked = option.gemLocked || option.goldLocked; const type = this.user.preferences.costume ? 'costume' : 'equipped';
option.click = () => { return this.equip(newKey, type);
if (option.gemLocked) { };
return this.unlock(`items.gear.owned.${newKey}`); return option;
} else if (option.goldLocked) { });
return this.buy(newKey); return options;
} else { },
let type = this.user.preferences.costume ? 'costume' : 'equipped'; animalItemsUnlockString (category) {
return this.equip(newKey, type); const keys = this.animalItemKeys[category].map(key => `items.gear.owned.${category}_special_${key}`);
}
};
return option;
});
return options;
},
animalItemsUnlockString (category) {
const keys = this.animalItemKeys[category].map(key => {
return `items.gear.owned.${category}_special_${key}`;
});
return keys.join(','); return keys.join(',');
}, },
animalItemsOwned (category) { animalItemsOwned (category) {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now // @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let own = true; let own = true;
this.animalItemKeys[category].forEach(key => { this.animalItemKeys[category].forEach(key => {
if (this.user.items.gear.owned[`${category}_special_${key}`] === undefined) own = false; if (this.user.items.gear.owned[`${category}_special_${key}`] === undefined) own = false;
}); });
return own; return own;
},
}, },
}; },
};
</script> </script>
<style scoped> <style scoped>

View File

@@ -42,298 +42,286 @@
</template> </template>
<script> <script>
import appearance from '@/../../common/script/content/appearance'; import groupBy from 'lodash/groupBy';
import {subPageMixin} from '../../mixins/subPage'; import appearance from '@/../../common/script/content/appearance';
import {userStateMixin} from '../../mixins/userState'; import { subPageMixin } from '../../mixins/subPage';
import {avatarEditorUtilies} from '../../mixins/avatarEditUtilities'; import { userStateMixin } from '../../mixins/userState';
import subMenu from './sub-menu'; import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities';
import customizeOptions from './customize-options'; import subMenu from './sub-menu';
import gem from '@/assets/svg/gem.svg'; import customizeOptions from './customize-options';
import appearanceSets from '@/../../common/script/content/appearance/sets'; import gem from '@/assets/svg/gem.svg';
import groupBy from 'lodash/groupBy'; import appearanceSets from '@/../../common/script/content/appearance/sets';
const hairColorBySet = groupBy(appearance.hair.color, 'set.key'); const hairColorBySet = groupBy(appearance.hair.color, 'set.key');
const freeHairColorKeys = hairColorBySet[undefined].map(s => s.key); const freeHairColorKeys = hairColorBySet[undefined].map(s => s.key);
export default { export default {
props: [ components: {
'editing', subMenu,
], customizeOptions,
components: { },
subMenu, mixins: [
customizeOptions, subPageMixin,
}, userStateMixin,
mixins: [ avatarEditorUtilies,
subPageMixin, ],
userStateMixin, props: [
avatarEditorUtilies, 'editing',
], ],
data () { data () {
return { return {
freeHairColorKeys, freeHairColorKeys,
icons: Object.freeze({ icons: Object.freeze({
gem, gem,
}), }),
baseHair1: [1, 3], baseHair1: [1, 3],
baseHair2Keys: [2, 4, 5, 6, 7, 8], baseHair2Keys: [2, 4, 5, 6, 7, 8],
baseHair3Keys: [9, 10, 11, 12, 13, 14], baseHair3Keys: [9, 10, 11, 12, 13, 14],
baseHair4Keys: [15, 16, 17, 18, 19, 20], baseHair4Keys: [15, 16, 17, 18, 19, 20],
baseHair5Keys: [1, 2], baseHair5Keys: [1, 2],
baseHair6Keys: [1, 2, 3], baseHair6Keys: [1, 2, 3],
}; };
}, },
computed: { computed: {
hairSubMenuItems () { hairSubMenuItems () {
const items = [ const items = [
{ {
id: 'color', id: 'color',
label: this.$t('color'), label: this.$t('color'),
}, },
{ {
id: 'bangs', id: 'bangs',
label: this.$t('bangs'), label: this.$t('bangs'),
}, },
{ {
id: 'style', id: 'style',
label: this.$t('style'), label: this.$t('style'),
}, },
]; ];
if (this.editing) { if (this.editing) {
items.push({ items.push({
id: 'facialhair', id: 'facialhair',
label: this.$t('facialhair'), label: this.$t('facialhair'),
});
}
return items;
},
freeHairColors () {
return freeHairColorKeys.map(s => this.mapKeysToFreeOption(s, 'hair', 'color'));
},
seasonalHairColors () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let seasonalHairColors = [];
for (let key in hairColorBySet) {
let set = hairColorBySet[key];
let keys = set.map(item => {
return item.key;
});
let options = keys.map(optionKey => {
const option = this.mapKeysToOption(optionKey, 'hair', 'color', key);
return option;
});
let text = this.$t(key);
if (appearanceSets[key] && appearanceSets[key].text) {
text = appearanceSets[key].text();
}
let compiledSet = {
key,
options,
keys,
text,
};
seasonalHairColors.push(compiledSet);
}
return seasonalHairColors;
},
premiumHairColors () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let keys = this.premiumHairColorKeys;
let options = keys.map(key => {
return this.mapKeysToOption(key, 'hair', 'color');
}); });
return options; }
},
baseHair2 () { return items;
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now },
freeHairColors () {
return freeHairColorKeys.map(s => this.mapKeysToFreeOption(s, 'hair', 'color'));
},
seasonalHairColors () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let keys = this.baseHair2Keys;
let options = keys.map(key => { const seasonalHairColors = [];
return this.mapKeysToOption(key, 'hair', 'base'); for (const key in hairColorBySet) {
}); const set = hairColorBySet[key];
return options;
}, const keys = set.map(item => item.key);
baseHair3 () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now const options = keys.map(optionKey => {
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line const option = this.mapKeysToOption(optionKey, 'hair', 'color', key);
let keys = this.baseHair3Keys;
let options = keys.map(key => {
const option = this.mapKeysToOption(key, 'hair', 'base');
return option; return option;
}); });
return options;
},
baseHair4 () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let keys = this.baseHair4Keys;
let options = keys.map(key => {
return this.mapKeysToOption(key, 'hair', 'base');
});
return options;
},
baseHair5 () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let keys = this.baseHair5Keys;
let options = keys.map(key => {
return this.mapKeysToOption(key, 'hair', 'mustache');
});
return options;
},
baseHair6 () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let keys = this.baseHair6Keys;
let options = keys.map(key => {
return this.mapKeysToOption(key, 'hair', 'beard');
});
return options;
},
hairBangs () {
const none = this.mapKeysToFreeOption(0, 'hair', 'bangs');
none.none = true;
const options = [1, 2, 3, 4].map(s => this.mapKeysToFreeOption(s, 'hair', 'bangs')); let text = this.$t(key);
if (appearanceSets[key] && appearanceSets[key].text) {
return [none, ...options]; text = appearanceSets[key].text();
},
mustacheList () {
const noneOption = this.mapKeysToFreeOption(0, 'hair', 'mustache');
noneOption.none = true;
return [noneOption, ...this.baseHair5];
},
beardList () {
const noneOption = this.mapKeysToFreeOption(0, 'hair', 'beard');
noneOption.none = true;
return [noneOption, ...this.baseHair6];
},
styleSets () {
const sets = [];
const emptyHairBase = {
...this.mapKeysToFreeOption(0, 'hair', 'base'),
none: true,
};
if (this.editing) {
sets.push({
fullSet: !this.userOwnsSet('hair', this.baseHair3Keys, 'base'),
unlock: () => this.unlock(`hair.base.${this.baseHair3Keys.join(',hair.base.')}`),
options: [
emptyHairBase,
...this.baseHair3,
],
});
sets.push({
fullSet: !this.userOwnsSet('hair', this.baseHair4Keys, 'base'),
unlock: () => this.unlock(`hair.base.${this.baseHair4Keys.join(',hair.base.')}`),
options: [
...this.baseHair4,
],
});
} }
const compiledSet = {
key,
options,
keys,
text,
};
seasonalHairColors.push(compiledSet);
}
return seasonalHairColors;
},
premiumHairColors () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
const keys = this.premiumHairColorKeys;
const options = keys.map(key => this.mapKeysToOption(key, 'hair', 'color'));
return options;
},
baseHair2 () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
const keys = this.baseHair2Keys;
const options = keys.map(key => this.mapKeysToOption(key, 'hair', 'base'));
return options;
},
baseHair3 () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
const keys = this.baseHair3Keys;
const options = keys.map(key => {
const option = this.mapKeysToOption(key, 'hair', 'base');
return option;
});
return options;
},
baseHair4 () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
const keys = this.baseHair4Keys;
const options = keys.map(key => this.mapKeysToOption(key, 'hair', 'base'));
return options;
},
baseHair5 () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
const keys = this.baseHair5Keys;
const options = keys.map(key => this.mapKeysToOption(key, 'hair', 'mustache'));
return options;
},
baseHair6 () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
const keys = this.baseHair6Keys;
const options = keys.map(key => this.mapKeysToOption(key, 'hair', 'beard'));
return options;
},
hairBangs () {
const none = this.mapKeysToFreeOption(0, 'hair', 'bangs');
none.none = true;
const options = [1, 2, 3, 4].map(s => this.mapKeysToFreeOption(s, 'hair', 'bangs'));
return [none, ...options];
},
mustacheList () {
const noneOption = this.mapKeysToFreeOption(0, 'hair', 'mustache');
noneOption.none = true;
return [noneOption, ...this.baseHair5];
},
beardList () {
const noneOption = this.mapKeysToFreeOption(0, 'hair', 'beard');
noneOption.none = true;
return [noneOption, ...this.baseHair6];
},
styleSets () {
const sets = [];
const emptyHairBase = {
...this.mapKeysToFreeOption(0, 'hair', 'base'),
none: true,
};
if (this.editing) {
sets.push({ sets.push({
fullSet: !this.userOwnsSet('hair', this.baseHair3Keys, 'base'),
unlock: () => this.unlock(`hair.base.${this.baseHair3Keys.join(',hair.base.')}`),
options: [ options: [
emptyHairBase, emptyHairBase,
...this.baseHair1.map(key => this.mapKeysToFreeOption(key, 'hair', 'base')), ...this.baseHair3,
], ],
}); });
if (this.editing) { sets.push({
sets.push({ fullSet: !this.userOwnsSet('hair', this.baseHair4Keys, 'base'),
fullSet: !this.userOwnsSet('hair', this.baseHair2Keys, 'base'), unlock: () => this.unlock(`hair.base.${this.baseHair4Keys.join(',hair.base.')}`),
unlock: () => this.unlock(`hair.base.${this.baseHair2Keys.join(',hair.base.')}`), options: [
options: [ ...this.baseHair4,
...this.baseHair2, ],
], });
}); }
}
return sets; sets.push({
}, options: [
emptyHairBase,
...this.baseHair1.map(key => this.mapKeysToFreeOption(key, 'hair', 'base')),
],
});
if (this.editing) {
sets.push({
fullSet: !this.userOwnsSet('hair', this.baseHair2Keys, 'base'),
unlock: () => this.unlock(`hair.base.${this.baseHair2Keys.join(',hair.base.')}`),
options: [
...this.baseHair2,
],
});
}
return sets;
}, },
mounted () { },
this.changeSubPage('color'); mounted () {
}, this.changeSubPage('color');
methods: { },
/** methods: {
/**
* Allows you to find out whether you need the "Purchase All" button or not. If there are more than 2 unpurchased items, returns true, otherwise returns false. * Allows you to find out whether you need the "Purchase All" button or not. If there are more than 2 unpurchased items, returns true, otherwise returns false.
* @param {string} category - The selected category. * @param {string} category - The selected category.
* @param {string[]} keySets - The items keySets. * @param {string[]} keySets - The items keySets.
* @param {string[]} [types] - The items types (subcategories). Optional. * @param {string[]} [types] - The items types (subcategories). Optional.
* @returns {boolean} - Determines whether the "Purchase All" button is needed (true) or not (false). * @returns {boolean} - Determines whether the "Purchase All" button is needed (true) or not (false).
*/ */
isPurchaseAllNeeded (category, keySets, types) { isPurchaseAllNeeded (category, keySets, types) {
const purchasedItemsLengths = []; const purchasedItemsLengths = [];
// If item types are specified, count them // If item types are specified, count them
if (types && types.length > 0) { if (types && types.length > 0) {
// Types can be undefined, so we must check them. // Types can be undefined, so we must check them.
types.forEach((type) => { types.forEach(type => {
if (this.user.purchased[category][type]) { if (this.user.purchased[category][type]) {
purchasedItemsLengths purchasedItemsLengths
.push(Object.keys(this.user.purchased[category][type]).length); .push(Object.keys(this.user.purchased[category][type]).length);
}
});
} else {
let purchasedItemsCounter = 0;
// If types are not specified, recursively
// search for purchased items in the category
const findPurchasedItems = (item) => {
if (typeof item === 'object') {
Object.values(item)
.forEach((innerItem) => {
if (typeof innerItem === 'boolean' && innerItem === true) {
purchasedItemsCounter += 1;
}
return findPurchasedItems(innerItem);
});
}
return purchasedItemsCounter;
};
findPurchasedItems(this.user.purchased[category]);
if (purchasedItemsCounter > 0) {
purchasedItemsLengths.push(purchasedItemsCounter);
} }
}
// We don't need to count the key sets (below)
// if there are no purchased items at all.
if (purchasedItemsLengths.length === 0) {
return true;
}
const allItemsLengths = [];
// Key sets must be specify correctly.
keySets.forEach((keySet) => {
allItemsLengths.push(Object.keys(this[keySet]).length);
}); });
} else {
let purchasedItemsCounter = 0;
// Simply sum all the length values and // If types are not specified, recursively
// write them into variables for the convenience. // search for purchased items in the category
const allItems = allItemsLengths.reduce((acc, val) => acc + val); const findPurchasedItems = item => {
const purchasedItems = purchasedItemsLengths.reduce((acc, val) => acc + val); if (typeof item === 'object') {
Object.values(item)
.forEach(innerItem => {
if (typeof innerItem === 'boolean' && innerItem === true) {
purchasedItemsCounter += 1;
}
return findPurchasedItems(innerItem);
});
}
return purchasedItemsCounter;
};
const unpurchasedItems = allItems - purchasedItems; findPurchasedItems(this.user.purchased[category]);
return unpurchasedItems > 2; if (purchasedItemsCounter > 0) {
}, purchasedItemsLengths.push(purchasedItemsCounter);
}
}
// We don't need to count the key sets (below)
// if there are no purchased items at all.
if (purchasedItemsLengths.length === 0) {
return true;
}
const allItemsLengths = [];
// Key sets must be specify correctly.
keySets.forEach(keySet => {
allItemsLengths.push(Object.keys(this[keySet]).length);
});
// Simply sum all the length values and
// write them into variables for the convenience.
const allItems = allItemsLengths.reduce((acc, val) => acc + val);
const purchasedItems = purchasedItemsLengths.reduce((acc, val) => acc + val);
const unpurchasedItems = allItems - purchasedItems;
return unpurchasedItems > 2;
}, },
}; },
};
</script> </script>
<style scoped> <style scoped>

View File

@@ -16,97 +16,95 @@
</template> </template>
<script> <script>
import appearance from '@/../../common/script/content/appearance'; import groupBy from 'lodash/groupBy';
import {subPageMixin} from '../../mixins/subPage'; import appearance from '@/../../common/script/content/appearance';
import {userStateMixin} from '../../mixins/userState'; import { subPageMixin } from '../../mixins/subPage';
import {avatarEditorUtilies} from '../../mixins/avatarEditUtilities'; import { userStateMixin } from '../../mixins/userState';
import appearanceSets from '@/../../common/script/content/appearance/sets'; import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities';
import subMenu from './sub-menu'; import appearanceSets from '@/../../common/script/content/appearance/sets';
import customizeOptions from './customize-options'; import subMenu from './sub-menu';
import gem from '@/assets/svg/gem.svg'; import customizeOptions from './customize-options';
import gem from '@/assets/svg/gem.svg';
import groupBy from 'lodash/groupBy';
const skinsBySet = groupBy(appearance.skin, 'set.key');
const freeSkinKeys = skinsBySet[undefined].map(s => s.key);
// const specialSkinKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price !== 0);
export default { const skinsBySet = groupBy(appearance.skin, 'set.key');
props: [
'editing', const freeSkinKeys = skinsBySet[undefined].map(s => s.key);
],
components: { // const specialSkinKeys = Object.keys(appearance.shirt).filter(k => appearance.shirt[k].price !== 0);
subMenu,
customizeOptions,
export default {
components: {
subMenu,
customizeOptions,
},
mixins: [
subPageMixin,
userStateMixin,
avatarEditorUtilies,
],
props: [
'editing',
],
data () {
return {
freeSkinKeys,
icons: Object.freeze({
gem,
}),
skinSubMenuItems: [
{
id: 'color',
label: this.$t('color'),
},
],
};
},
computed: {
freeSkins () {
return freeSkinKeys.map(s => this.mapKeysToFreeOption(s, 'skin'));
}, },
mixins: [ seasonalSkins () {
subPageMixin, // @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
userStateMixin,
avatarEditorUtilies,
],
data () {
return {
freeSkinKeys,
icons: Object.freeze({
gem,
}),
skinSubMenuItems: [
{
id: 'color',
label: this.$t('color'),
},
],
};
},
computed: {
freeSkins () {
return freeSkinKeys.map(s => this.mapKeysToFreeOption(s, 'skin'));
},
seasonalSkins () {
// @TODO: For some resonse when I use $set on the user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
let seasonalSkins = []; const seasonalSkins = [];
for (let setKey in skinsBySet) { for (const setKey in skinsBySet) {
let set = skinsBySet[setKey]; const set = skinsBySet[setKey];
let keys = set.map(item => { const keys = set.map(item => item.key);
return item.key;
});
let options = keys.map(optionKey => { const options = keys.map(optionKey => {
const option = this.mapKeysToOption(optionKey, 'skin', '', setKey); const option = this.mapKeysToOption(optionKey, 'skin', '', setKey);
return option; return option;
}); });
let text = this.$t(setKey); let text = this.$t(setKey);
if (appearanceSets[setKey] && appearanceSets[setKey].text) { if (appearanceSets[setKey] && appearanceSets[setKey].text) {
text = appearanceSets[setKey].text(); text = appearanceSets[setKey].text();
}
let compiledSet = {
key: setKey,
options,
keys,
text,
};
seasonalSkins.push(compiledSet);
} }
return seasonalSkins; const compiledSet = {
}, key: setKey,
}, options,
mounted () { keys,
this.changeSubPage('color'); text,
}, };
methods: { seasonalSkins.push(compiledSet);
}
return seasonalSkins;
}, },
}; },
mounted () {
this.changeSubPage('color');
},
methods: {
},
};
</script> </script>
<style scoped> <style scoped>

View File

@@ -10,9 +10,9 @@
</template> </template>
<script> <script>
export default { export default {
props: ['items', 'activeSubPage'], props: ['items', 'activeSubPage'],
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -13,22 +13,22 @@
</template> </template>
<script> <script>
export default { export default {
props: { props: {
categories: { categories: {
required: true, required: true,
},
owner: {
default: false,
},
member: {
default: false,
},
}, },
methods: { owner: {
isOfficial (category) { default: false,
return category.name === 'habitica_official';
},
}, },
}; member: {
</script> default: false,
},
},
methods: {
isOfficial (category) {
return category.name === 'habitica_official';
},
},
};
</script>

View File

@@ -167,8 +167,6 @@
</style> </style>
<script> <script>
const TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'history', 'id', 'streak', 'createdAt', 'challenge'];
import Vue from 'vue'; import Vue from 'vue';
import findIndex from 'lodash/findIndex'; import findIndex from 'lodash/findIndex';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
@@ -194,9 +192,9 @@ import gemIcon from '@/assets/svg/gem.svg';
import memberIcon from '@/assets/svg/member-icon.svg'; import memberIcon from '@/assets/svg/member-icon.svg';
import calendarIcon from '@/assets/svg/calendar.svg'; import calendarIcon from '@/assets/svg/calendar.svg';
const TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'history', 'id', 'streak', 'createdAt', 'challenge'];
export default { export default {
props: ['challengeId'],
mixins: [challengeMemberSearchMixin],
directives: { directives: {
markdown: markdownDirective, markdown: markdownDirective,
}, },
@@ -212,6 +210,8 @@ export default {
userLink, userLink,
groupLink, groupLink,
}, },
mixins: [challengeMemberSearchMixin],
props: ['challengeId'],
data () { data () {
return { return {
searchId: '', searchId: '',
@@ -238,7 +238,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
isMember () { isMember () {
return this.user.challenges.indexOf(this.challenge._id) !== -1; return this.user.challenges.indexOf(this.challenge._id) !== -1;
}, },
@@ -267,11 +267,11 @@ export default {
}, },
methods: { methods: {
cleanUpTask (task) { cleanUpTask (task) {
let cleansedTask = omit(task, TASK_KEYS_TO_REMOVE); const cleansedTask = omit(task, TASK_KEYS_TO_REMOVE);
// Copy checklists but reset to uncomplete and assign new id // Copy checklists but reset to uncomplete and assign new id
if (!cleansedTask.checklist) cleansedTask.checklist = []; if (!cleansedTask.checklist) cleansedTask.checklist = [];
cleansedTask.checklist.forEach((item) => { cleansedTask.checklist.forEach(item => {
item.completed = false; item.completed = false;
item.id = uuid(); item.id = uuid();
}); });
@@ -283,16 +283,16 @@ export default {
return cleansedTask; return cleansedTask;
}, },
async loadChallenge () { async loadChallenge () {
this.challenge = await this.$store.dispatch('challenges:getChallenge', {challengeId: this.searchId}); this.challenge = await this.$store.dispatch('challenges:getChallenge', { challengeId: this.searchId });
this.members = await this.loadMembers({ challengeId: this.searchId, includeAllPublicFields: true }); this.members = await this.loadMembers({ challengeId: this.searchId, includeAllPublicFields: true });
let tasks = await this.$store.dispatch('tasks:getChallengeTasks', {challengeId: this.searchId}); const tasks = await this.$store.dispatch('tasks:getChallengeTasks', { challengeId: this.searchId });
this.tasksByType = { this.tasksByType = {
habit: [], habit: [],
daily: [], daily: [],
todo: [], todo: [],
reward: [], reward: [],
}; };
tasks.forEach((task) => { tasks.forEach(task => {
this.tasksByType[task.type].push(task); this.tasksByType[task.type].push(task);
}); });
}, },
@@ -322,7 +322,7 @@ export default {
}, },
createTask (type) { createTask (type) {
this.taskFormPurpose = 'create'; this.taskFormPurpose = 'create';
this.creatingTask = taskDefaults({type, text: ''}, this.user); this.creatingTask = taskDefaults({ type, text: '' }, this.user);
this.workingTask = this.creatingTask; this.workingTask = this.creatingTask;
// Necessary otherwise the first time the modal is not rendered // Necessary otherwise the first time the modal is not rendered
Vue.nextTick(() => { Vue.nextTick(() => {
@@ -337,15 +337,11 @@ export default {
this.tasksByType[task.type].push(task); this.tasksByType[task.type].push(task);
}, },
taskEdited (task) { taskEdited (task) {
let index = findIndex(this.tasksByType[task.type], (taskItem) => { const index = findIndex(this.tasksByType[task.type], taskItem => taskItem._id === task._id);
return taskItem._id === task._id;
});
this.tasksByType[task.type].splice(index, 1, task); this.tasksByType[task.type].splice(index, 1, task);
}, },
taskDestroyed (task) { taskDestroyed (task) {
let index = findIndex(this.tasksByType[task.type], (taskItem) => { const index = findIndex(this.tasksByType[task.type], taskItem => taskItem._id === task._id);
return taskItem._id === task._id;
});
this.tasksByType[task.type].splice(index, 1); this.tasksByType[task.type].splice(index, 1);
}, },
showMemberModal () { showMemberModal () {
@@ -360,8 +356,8 @@ export default {
}, },
async joinChallenge () { async joinChallenge () {
this.user.challenges.push(this.searchId); this.user.challenges.push(this.searchId);
await this.$store.dispatch('challenges:joinChallenge', {challengeId: this.searchId}); await this.$store.dispatch('challenges:joinChallenge', { challengeId: this.searchId });
await this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true}); await this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true });
}, },
async leaveChallenge () { async leaveChallenge () {
this.$root.$emit('bv::show::modal', 'leave-challenge-modal'); this.$root.$emit('bv::show::modal', 'leave-challenge-modal');

View File

@@ -303,89 +303,89 @@
</style> </style>
<script> <script>
import { TAVERN_ID } from '@/../../common/script/constants'; import { TAVERN_ID } from '@/../../common/script/constants';
import userLink from '../userLink'; import userLink from '../userLink';
import groupLink from '../groupLink'; import groupLink from '../groupLink';
import categoryTags from '../categories/categoryTags'; import categoryTags from '../categories/categoryTags';
import markdownDirective from '@/directives/markdown'; import markdownDirective from '@/directives/markdown';
import {mapState} from '@/libs/store'; import { mapState } from '@/libs/store';
import gemIcon from '@/assets/svg/gem.svg'; import gemIcon from '@/assets/svg/gem.svg';
import memberIcon from '@/assets/svg/member-icon.svg'; import memberIcon from '@/assets/svg/member-icon.svg';
import calendarIcon from '@/assets/svg/calendar.svg'; import calendarIcon from '@/assets/svg/calendar.svg';
import habitIcon from '@/assets/svg/habit.svg'; import habitIcon from '@/assets/svg/habit.svg';
import todoIcon from '@/assets/svg/todo.svg'; import todoIcon from '@/assets/svg/todo.svg';
import dailyIcon from '@/assets/svg/daily.svg'; import dailyIcon from '@/assets/svg/daily.svg';
import rewardIcon from '@/assets/svg/reward.svg'; import rewardIcon from '@/assets/svg/reward.svg';
import officialIcon from '@/assets/svg/official.svg'; import officialIcon from '@/assets/svg/official.svg';
export default { export default {
props: { components: {
challenge: { userLink,
required: true, groupLink,
}, categoryTags,
fullLayout: { },
default: true, directives: {
}, markdown: markdownDirective,
},
props: {
challenge: {
required: true,
}, },
components: { fullLayout: {
userLink, default: true,
groupLink,
categoryTags,
}, },
directives: { },
markdown: markdownDirective, data () {
return {
icons: Object.freeze({
gemIcon,
memberIcon,
calendarIcon,
officialIcon,
}),
};
},
computed: {
...mapState({ user: 'user.data' }),
isOwner () {
return this.challenge.leader && this.challenge.leader._id === this.user._id;
}, },
data () { isMember () {
return { return this.user.challenges.indexOf(this.challenge._id) !== -1;
icons: Object.freeze({
gemIcon,
memberIcon,
calendarIcon,
officialIcon,
}),
};
}, },
computed: { isOfficial () {
...mapState({user: 'user.data'}), return this.challenge.official || this.challenge.categories.map(category => category.slug).includes('habitica_official');
isOwner () {
return this.challenge.leader && this.challenge.leader._id === this.user._id;
},
isMember () {
return this.user.challenges.indexOf(this.challenge._id) !== -1;
},
isOfficial () {
return this.challenge.official || this.challenge.categories.map(category => category.slug).includes('habitica_official');
},
tasksData () {
return [
{
icon: habitIcon,
label: 'habit',
value: this.challenge.tasksOrder.habits.length,
},
{
icon: dailyIcon,
label: 'daily',
value: this.challenge.tasksOrder.dailys.length,
},
{
icon: todoIcon,
label: 'todo',
value: this.challenge.tasksOrder.todos.length,
},
{
icon: rewardIcon,
label: 'reward',
value: this.challenge.tasksOrder.rewards.length,
},
];
},
}, },
methods: { tasksData () {
isTavern (group) { return [
return group._id === TAVERN_ID; {
}, icon: habitIcon,
label: 'habit',
value: this.challenge.tasksOrder.habits.length,
},
{
icon: dailyIcon,
label: 'daily',
value: this.challenge.tasksOrder.dailys.length,
},
{
icon: todoIcon,
label: 'todo',
value: this.challenge.tasksOrder.todos.length,
},
{
icon: rewardIcon,
label: 'reward',
value: this.challenge.tasksOrder.rewards.length,
},
];
}, },
}; },
methods: {
isTavern (group) {
return group._id === TAVERN_ID;
},
},
};
</script> </script>

View File

@@ -23,10 +23,10 @@ import axios from 'axios';
import Column from '../tasks/column'; import Column from '../tasks/column';
export default { export default {
props: ['challengeId'],
components: { components: {
TaskColumn: Column, TaskColumn: Column,
}, },
props: ['challengeId'],
data () { data () {
return { return {
columns: ['habit', 'daily', 'todo', 'reward'], columns: ['habit', 'daily', 'todo', 'reward'],
@@ -41,18 +41,6 @@ export default {
isAdmin: false, isAdmin: false,
}; };
}, },
mounted () {
this.$root.$on('habitica:challenge:member-progress', (data) => {
if (!data.progressMemberId) return;
this.memberId = data.progressMemberId;
this.isLeader = data.isLeader;
this.isAdmin = data.isAdmin;
this.$root.$emit('bv::show::modal', 'challenge-member-modal');
});
},
beforeDestroy () {
this.$root.$off('habitica:challenge:member-progress');
},
watch: { watch: {
async memberId (id) { async memberId (id) {
if (!id) return; if (!id) return;
@@ -63,13 +51,25 @@ export default {
reward: [], reward: [],
}; };
let response = await axios.get(`/api/v4/challenges/${this.challengeId}/members/${this.memberId}`); const response = await axios.get(`/api/v4/challenges/${this.challengeId}/members/${this.memberId}`);
let tasks = response.data.data.tasks; const { tasks } = response.data.data;
tasks.forEach((task) => { tasks.forEach(task => {
this.tasksByType[task.type].push(task); this.tasksByType[task.type].push(task);
}); });
}, },
}, },
mounted () {
this.$root.$on('habitica:challenge:member-progress', data => {
if (!data.progressMemberId) return;
this.memberId = data.progressMemberId;
this.isLeader = data.isLeader;
this.isAdmin = data.isAdmin;
this.$root.$emit('bv::show::modal', 'challenge-member-modal');
});
},
beforeDestroy () {
this.$root.$off('habitica:challenge:member-progress');
},
methods: { methods: {
async closeChallenge () { async closeChallenge () {
this.challenge = await this.$store.dispatch('challenges:selectChallengeWinner', { this.challenge = await this.$store.dispatch('challenges:selectChallengeWinner', {

View File

@@ -138,12 +138,12 @@ import { TAVERN_ID, MIN_SHORTNAME_SIZE_FOR_CHALLENGES, MAX_SUMMARY_SIZE_FOR_CHAL
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
export default { export default {
props: ['groupId'],
directives: { directives: {
markdown: markdownDirective, markdown: markdownDirective,
}, },
props: ['groupId'],
data () { data () {
let categoryOptions = [ const categoryOptions = [
{ {
label: 'habitica_official', label: 'habitica_official',
key: 'habitica_official', key: 'habitica_official',
@@ -201,11 +201,11 @@ export default {
key: 'time_management', key: 'time_management',
}, },
]; ];
let hashedCategories = {}; const hashedCategories = {};
categoryOptions.forEach((category) => { categoryOptions.forEach(category => {
hashedCategories[category.key] = category.label; hashedCategories[category.key] = category.label;
}); });
let categoriesHashByKey = hashedCategories; const categoriesHashByKey = hashedCategories;
return { return {
workingChallenge: { workingChallenge: {
@@ -233,24 +233,6 @@ export default {
groups: [], groups: [],
}; };
}, },
mounted () {
this.$root.$on('habitica:clone-challenge', (data) => {
if (!data.challenge) return;
this.cloning = true;
this.cloningChallengeId = data.challenge._id;
this.$store.state.challengeOptions.workingChallenge = Object.assign({}, this.$store.state.challengeOptions.workingChallenge, data.challenge);
this.$root.$emit('bv::show::modal', 'challenge-modal');
});
this.$root.$on('habitica:update-challenge', (data) => {
if (!data.challenge) return;
this.cloning = false;
this.$store.state.challengeOptions.workingChallenge = Object.assign({}, this.$store.state.challengeOptions.workingChallenge, data.challenge);
this.$root.$emit('bv::show::modal', 'challenge-modal');
});
},
beforeDestroy () {
this.$root.$off('habitica:clone-challenge');
},
watch: { watch: {
user () { user () {
if (!this.challenge) this.workingChallenge.leader = this.user._id; if (!this.challenge) this.workingChallenge.leader = this.user._id;
@@ -262,8 +244,26 @@ export default {
this.setUpWorkingChallenge(); this.setUpWorkingChallenge();
}, },
}, },
mounted () {
this.$root.$on('habitica:clone-challenge', data => {
if (!data.challenge) return;
this.cloning = true;
this.cloningChallengeId = data.challenge._id;
this.$store.state.challengeOptions.workingChallenge = { ...this.$store.state.challengeOptions.workingChallenge, ...data.challenge };
this.$root.$emit('bv::show::modal', 'challenge-modal');
});
this.$root.$on('habitica:update-challenge', data => {
if (!data.challenge) return;
this.cloning = false;
this.$store.state.challengeOptions.workingChallenge = { ...this.$store.state.challengeOptions.workingChallenge, ...data.challenge };
this.$root.$emit('bv::show::modal', 'challenge-modal');
});
},
beforeDestroy () {
this.$root.$off('habitica:clone-challenge');
},
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
creating () { creating () {
return !this.workingChallenge.id; return !this.workingChallenge.id;
}, },
@@ -274,19 +274,18 @@ export default {
return this.$t('editingChallenge'); return this.$t('editingChallenge');
}, },
charactersRemaining () { charactersRemaining () {
let currentLength = this.workingChallenge.summary ? this.workingChallenge.summary.length : 0; const currentLength = this.workingChallenge.summary ? this.workingChallenge.summary.length : 0;
return MAX_SUMMARY_SIZE_FOR_CHALLENGES - currentLength; return MAX_SUMMARY_SIZE_FOR_CHALLENGES - currentLength;
}, },
maxPrize () { maxPrize () {
let userBalance = this.user.balance || 0; let userBalance = this.user.balance || 0;
userBalance = userBalance * 4; userBalance *= 4;
let groupBalance = 0; let groupBalance = 0;
let group; let group;
this.groups.forEach(item => { this.groups.forEach(item => {
if (item._id === this.workingChallenge.group) { if (item._id === this.workingChallenge.group) {
group = item; group = item;
return;
} }
}); });
@@ -301,14 +300,13 @@ export default {
return 0; return 0;
}, },
insufficientGemsForTavernChallenge () { insufficientGemsForTavernChallenge () {
let balance = this.user.balance || 0; const balance = this.user.balance || 0;
let isForTavern = this.workingChallenge.group === TAVERN_ID; const isForTavern = this.workingChallenge.group === TAVERN_ID;
if (isForTavern) { if (isForTavern) {
return balance <= 0; return balance <= 0;
} else {
return false;
} }
return false;
}, },
challenge () { challenge () {
return this.$store.state.challengeOptions.workingChallenge; return this.$store.state.challengeOptions.workingChallenge;
@@ -342,7 +340,7 @@ export default {
if (!this.challenge) return; if (!this.challenge) return;
this.workingChallenge = Object.assign({}, this.workingChallenge, this.challenge); this.workingChallenge = { ...this.workingChallenge, ...this.challenge };
// @TODO: Should we use a separate field? I think the API expects `group` but it is confusing // @TODO: Should we use a separate field? I think the API expects `group` but it is confusing
this.workingChallenge.group = this.workingChallenge.group._id; this.workingChallenge.group = this.workingChallenge.group._id;
this.workingChallenge.categories = []; this.workingChallenge.categories = [];
@@ -381,7 +379,7 @@ export default {
async createChallenge () { async createChallenge () {
this.loading = true; this.loading = true;
// @TODO: improve error handling, add it to updateChallenge, make errors translatable. Suggestion: `<% fieldName %> is required` where possible, where `fieldName` is inserted as the translatable string that's used for the field header. // @TODO: improve error handling, add it to updateChallenge, make errors translatable. Suggestion: `<% fieldName %> is required` where possible, where `fieldName` is inserted as the translatable string that's used for the field header.
let errors = []; const errors = [];
if (!this.workingChallenge.name) errors.push(this.$t('nameRequired')); if (!this.workingChallenge.name) errors.push(this.$t('nameRequired'));
if (this.workingChallenge.shortName.length < MIN_SHORTNAME_SIZE_FOR_CHALLENGES) errors.push(this.$t('tagTooShort')); if (this.workingChallenge.shortName.length < MIN_SHORTNAME_SIZE_FOR_CHALLENGES) errors.push(this.$t('tagTooShort'));
@@ -398,17 +396,17 @@ export default {
} }
this.workingChallenge.timestamp = new Date().getTime(); this.workingChallenge.timestamp = new Date().getTime();
let categoryKeys = this.workingChallenge.categories; const categoryKeys = this.workingChallenge.categories;
let serverCategories = []; const serverCategories = [];
categoryKeys.forEach(key => { categoryKeys.forEach(key => {
let catName = this.categoriesHashByKey[key]; const catName = this.categoriesHashByKey[key];
serverCategories.push({ serverCategories.push({
slug: key, slug: key,
name: catName, name: catName,
}); });
}); });
let challengeDetails = clone(this.workingChallenge); const challengeDetails = clone(this.workingChallenge);
challengeDetails.categories = serverCategories; challengeDetails.categories = serverCategories;
let challenge; let challenge;
@@ -419,13 +417,11 @@ export default {
}); });
this.cloningChallengeId = ''; this.cloningChallengeId = '';
} else { } else {
challenge = await this.$store.dispatch('challenges:createChallenge', {challenge: challengeDetails}); challenge = await this.$store.dispatch('challenges:createChallenge', { challenge: challengeDetails });
} }
// Update Group Prize // Update Group Prize
let challengeGroup = this.groups.find(group => { const challengeGroup = this.groups.find(group => group._id === this.workingChallenge.group);
return group._id === this.workingChallenge.group;
});
// @TODO: Share with server // @TODO: Share with server
const prizeCost = this.workingChallenge.prize / 4; const prizeCost = this.workingChallenge.prize / 4;
@@ -435,7 +431,7 @@ export default {
// Group pays for all of prize // Group pays for all of prize
} else if (challengeGroup && userIsLeader && challengeGroup.balance > 0) { } else if (challengeGroup && userIsLeader && challengeGroup.balance > 0) {
// User pays remainder of prize cost after group // User pays remainder of prize cost after group
let remainder = prizeCost - challengeGroup.balance; const remainder = prizeCost - challengeGroup.balance;
this.user.balance -= remainder; this.user.balance -= remainder;
} else { } else {
// User pays for all of prize // User pays for all of prize
@@ -449,24 +445,24 @@ export default {
this.$router.push(`/challenges/${challenge._id}`); this.$router.push(`/challenges/${challenge._id}`);
}, },
updateChallenge () { updateChallenge () {
let categoryKeys = this.workingChallenge.categories; const categoryKeys = this.workingChallenge.categories;
let serverCategories = []; const serverCategories = [];
categoryKeys.forEach(key => { categoryKeys.forEach(key => {
let newKey = key.trim(); const newKey = key.trim();
let catName = this.categoriesHashByKey[newKey]; const catName = this.categoriesHashByKey[newKey];
serverCategories.push({ serverCategories.push({
slug: newKey, slug: newKey,
name: catName, name: catName,
}); });
}); });
let challengeDetails = clone(this.workingChallenge); const challengeDetails = clone(this.workingChallenge);
challengeDetails.categories = serverCategories; challengeDetails.categories = serverCategories;
this.$emit('updatedChallenge', { this.$emit('updatedChallenge', {
challenge: challengeDetails, challenge: challengeDetails,
}); });
this.$store.dispatch('challenges:updateChallenge', {challenge: challengeDetails}); this.$store.dispatch('challenges:updateChallenge', { challenge: challengeDetails });
this.resetWorkingChallenge(); this.resetWorkingChallenge();
this.$root.$emit('bv::hide::modal', 'challenge-modal'); this.$root.$emit('bv::hide::modal', 'challenge-modal');
}, },

View File

@@ -74,10 +74,10 @@ div
import memberSearchDropdown from '@/components/members/memberSearchDropdown'; import memberSearchDropdown from '@/components/members/memberSearchDropdown';
export default { export default {
props: ['challengeId', 'members', 'prize'],
components: { components: {
memberSearchDropdown, memberSearchDropdown,
}, },
props: ['challengeId', 'members', 'prize'],
data () { data () {
return { return {
winner: {}, winner: {},

View File

@@ -66,6 +66,8 @@
</style> </style>
<script> <script>
import MugenScroll from 'vue-mugen-scroll';
import debounce from 'lodash/debounce';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import Sidebar from './sidebar'; import Sidebar from './sidebar';
@@ -75,18 +77,15 @@ import challengeUtilities from '@/mixins/challengeUtilities';
import positiveIcon from '@/assets/svg/positive.svg'; import positiveIcon from '@/assets/svg/positive.svg';
import MugenScroll from 'vue-mugen-scroll';
import debounce from 'lodash/debounce';
export default { export default {
mixins: [challengeUtilities],
components: { components: {
Sidebar, Sidebar,
ChallengeItem, ChallengeItem,
challengeModal, challengeModal,
MugenScroll, MugenScroll,
}, },
mixins: [challengeUtilities],
data () { data () {
return { return {
loading: true, loading: true,
@@ -127,7 +126,7 @@ export default {
this.loadChallenges(); this.loadChallenges();
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
filteredChallenges () { filteredChallenges () {
return this.challenges; return this.challenges;
}, },

View File

@@ -38,55 +38,55 @@ div
</style> </style>
<script> <script>
import challengeModal from './challengeModal'; import challengeModal from './challengeModal';
import {mapState} from '@/libs/store'; import { mapState } from '@/libs/store';
import markdownDirective from '@/directives/markdown'; import markdownDirective from '@/directives/markdown';
import challengeItem from './challengeItem'; import challengeItem from './challengeItem';
import challengeIcon from '@/assets/svg/challenge.svg'; import challengeIcon from '@/assets/svg/challenge.svg';
export default { export default {
props: ['groupId'], components: {
components: { challengeModal,
challengeModal, challengeItem,
challengeItem, },
}, props: ['groupId'],
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
data () { data () {
return { return {
challenges: [], challenges: [],
icons: Object.freeze({ icons: Object.freeze({
challengeIcon, challengeIcon,
}), }),
groupIdForChallenges: '', groupIdForChallenges: '',
}; };
}, },
directives: { directives: {
markdown: markdownDirective, markdown: markdownDirective,
}, },
mounted () { watch: {
async groupId () {
this.loadChallenges(); this.loadChallenges();
}, },
watch: { },
async groupId () { mounted () {
this.loadChallenges(); this.loadChallenges();
}, },
methods: {
async loadChallenges () {
this.groupIdForChallenges = this.groupId;
if (this.groupId === 'party' && this.user.party._id) this.groupIdForChallenges = this.user.party._id;
this.challenges = await this.$store.dispatch('challenges:getGroupChallenges', { groupId: this.groupIdForChallenges });
}, },
methods: { createChallenge () {
async loadChallenges () { this.$root.$emit('bv::show::modal', 'challenge-modal');
this.groupIdForChallenges = this.groupId;
if (this.groupId === 'party' && this.user.party._id) this.groupIdForChallenges = this.user.party._id;
this.challenges = await this.$store.dispatch('challenges:getGroupChallenges', {groupId: this.groupIdForChallenges});
},
createChallenge () {
this.$root.$emit('bv::show::modal', 'challenge-modal');
},
challengeCreated (challenge) {
if (challenge.group._id !== this.groupIdForChallenges) return;
this.challenges.push(challenge);
},
}, },
}; challengeCreated (challenge) {
if (challenge.group._id !== this.groupIdForChallenges) return;
this.challenges.push(challenge);
},
},
};
</script> </script>

View File

@@ -19,22 +19,20 @@ import { mapState } from '@/libs/store';
import notifications from '@/mixins/notifications'; import notifications from '@/mixins/notifications';
export default { export default {
props: ['challengeId'],
mixins: [notifications], mixins: [notifications],
props: ['challengeId'],
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
methods: { methods: {
async leaveChallenge (keep) { async leaveChallenge (keep) {
let index = findIndex(this.user.challenges, (id) => { const index = findIndex(this.user.challenges, id => id === this.challengeId);
return id === this.challengeId;
});
this.user.challenges.splice(index, 1); this.user.challenges.splice(index, 1);
await this.$store.dispatch('challenges:leaveChallenge', { await this.$store.dispatch('challenges:leaveChallenge', {
challengeId: this.challengeId, challengeId: this.challengeId,
keep, keep,
}); });
await this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true}); await this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true });
this.close(); this.close();
}, },
close () { close () {

View File

@@ -80,6 +80,8 @@
</style> </style>
<script> <script>
import MugenScroll from 'vue-mugen-scroll';
import debounce from 'lodash/debounce';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import Sidebar from './sidebar'; import Sidebar from './sidebar';
import ChallengeItem from './challengeItem'; import ChallengeItem from './challengeItem';
@@ -88,18 +90,16 @@ import challengeUtilities from '@/mixins/challengeUtilities';
import challengeIcon from '@/assets/svg/challenge.svg'; import challengeIcon from '@/assets/svg/challenge.svg';
import positiveIcon from '@/assets/svg/positive.svg'; import positiveIcon from '@/assets/svg/positive.svg';
import MugenScroll from 'vue-mugen-scroll';
import debounce from 'lodash/debounce';
export default { export default {
mixins: [challengeUtilities],
components: { components: {
Sidebar, Sidebar,
ChallengeItem, ChallengeItem,
challengeModal, challengeModal,
MugenScroll, MugenScroll,
}, },
mixins: [challengeUtilities],
data () { data () {
return { return {
icons: Object.freeze({ icons: Object.freeze({
@@ -141,15 +141,15 @@ export default {
this.loadChallenges(); this.loadChallenges();
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
filteredChallenges () { filteredChallenges () {
const filters = this.filters; const { filters } = this;
const user = this.user; const { user } = this;
return this.challenges.filter(challenge => { return this.challenges.filter(challenge => {
let isMember = true; let isMember = true;
let filteringRole = filters.roles && filters.roles.length > 0; const filteringRole = filters.roles && filters.roles.length > 0;
if (filteringRole && filters.roles.indexOf('participating') !== -1) { if (filteringRole && filters.roles.indexOf('participating') !== -1) {
isMember = this.isMemberOfChallenge(user, challenge); isMember = this.isMemberOfChallenge(user, challenge);
} }

View File

@@ -66,6 +66,7 @@ import tier9 from '@/assets/svg/tier-staff.svg';
import tierNPC from '@/assets/svg/tier-npc.svg'; import tierNPC from '@/assets/svg/tier-npc.svg';
export default { export default {
mixins: [styleHelper],
props: ['selections', 'text', 'caretPosition', 'coords', 'chat', 'textbox'], props: ['selections', 'text', 'caretPosition', 'coords', 'chat', 'textbox'],
data () { data () {
return { return {
@@ -93,7 +94,7 @@ export default {
computed: { computed: {
autocompleteStyle () { autocompleteStyle () {
function heightToUse (textBox, topCoords) { function heightToUse (textBox, topCoords) {
let textBoxHeight = textBox['user-entry'].clientHeight; const textBoxHeight = textBox['user-entry'].clientHeight;
return topCoords < textBoxHeight ? topCoords + 30 : textBoxHeight + 10; return topCoords < textBoxHeight ? topCoords + 30 : textBoxHeight + 10;
} }
return { return {
@@ -113,15 +114,10 @@ export default {
this.currentSearch = this.atRegex.exec(this.text)[0]; // eslint-disable-line vue/no-side-effects-in-computed-properties this.currentSearch = this.atRegex.exec(this.text)[0]; // eslint-disable-line vue/no-side-effects-in-computed-properties
this.currentSearch = this.currentSearch.substring(1, this.currentSearch.length); // eslint-disable-line vue/no-side-effects-in-computed-properties this.currentSearch = this.currentSearch.substring(1, this.currentSearch.length); // eslint-disable-line vue/no-side-effects-in-computed-properties
return this.tmpSelections.filter((option) => { return this.tmpSelections.filter(option => option.displayName.toLowerCase().indexOf(this.currentSearch.toLowerCase()) !== -1 || option.username && option.username.toLowerCase().indexOf(this.currentSearch.toLowerCase()) !== -1).slice(0, 4);
return option.displayName.toLowerCase().indexOf(this.currentSearch.toLowerCase()) !== -1 || option.username && option.username.toLowerCase().indexOf(this.currentSearch.toLowerCase()) !== -1;
}).slice(0, 4);
}, },
}, },
mounted () {
this.grabUserNames();
},
watch: { watch: {
text (newText) { text (newText) {
if (!newText[newText.length - 1] || newText[newText.length - 1] === ' ') { if (!newText[newText.length - 1] || newText[newText.length - 1] === ' ') {
@@ -143,6 +139,9 @@ export default {
this.grabUserNames(); this.grabUserNames();
}, },
}, },
mounted () {
this.grabUserNames();
},
methods: { methods: {
resetDefaults () { resetDefaults () {
// Mounted is not called when switching between group pages because they have the // Mounted is not called when switching between group pages because they have the
@@ -153,9 +152,9 @@ export default {
this.resetSelection(); this.resetSelection();
}, },
grabUserNames () { grabUserNames () {
let usersThatMessage = groupBy(this.chat, 'user'); const usersThatMessage = groupBy(this.chat, 'user');
for (let userKey in usersThatMessage) { for (const userKey in usersThatMessage) {
let systemMessage = userKey === 'undefined'; const systemMessage = userKey === 'undefined';
if (!systemMessage && this.tmpSelections.indexOf(userKey) === -1) { if (!systemMessage && this.tmpSelections.indexOf(userKey) === -1) {
this.tmpSelections.push({ this.tmpSelections.push({
displayName: userKey, displayName: userKey,
@@ -204,18 +203,18 @@ export default {
selectNext () { selectNext () {
if (this.searchResults.length > 0) { if (this.searchResults.length > 0) {
this.clearHover(); this.clearHover();
this.selected = this.selected === null ? this.selected = this.selected === null
0 : ? 0
(this.selected + 1) % this.searchResults.length; : (this.selected + 1) % this.searchResults.length;
this.searchResults[this.selected].hover = true; this.searchResults[this.selected].hover = true;
} }
}, },
selectPrevious () { selectPrevious () {
if (this.searchResults.length > 0) { if (this.searchResults.length > 0) {
this.clearHover(); this.clearHover();
this.selected = this.selected === null ? this.selected = this.selected === null
this.searchResults.length - 1 : ? this.searchResults.length - 1
(this.selected - 1 + this.searchResults.length) % this.searchResults.length; : (this.selected - 1 + this.searchResults.length) % this.searchResults.length;
this.searchResults[this.selected].hover = true; this.searchResults[this.selected].hover = true;
} }
}, },
@@ -231,6 +230,5 @@ export default {
this.resetSelection(); this.resetSelection();
}, },
}, },
mixins: [styleHelper],
}; };
</script> </script>

View File

@@ -140,7 +140,16 @@ import { highlightUsers } from '../../libs/highlightUsers';
import { CHAT_FLAG_LIMIT_FOR_HIDING, CHAT_FLAG_FROM_SHADOW_MUTE } from '@/../../common/script/constants'; import { CHAT_FLAG_LIMIT_FOR_HIDING, CHAT_FLAG_FROM_SHADOW_MUTE } from '@/../../common/script/constants';
export default { export default {
components: {userLink}, components: { userLink },
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
date (value) {
// @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString();
},
},
props: { props: {
msg: {}, msg: {},
inbox: { inbox: {
@@ -161,20 +170,11 @@ export default {
reported: false, reported: false,
}; };
}, },
filters: {
timeAgo (value) {
return moment(value).fromNow();
},
date (value) {
// @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString();
},
},
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
isUserMentioned () { isUserMentioned () {
const message = this.msg; const message = this.msg;
const user = this.user; const { user } = this;
if (message.hasOwnProperty('highlight')) return message.highlight; if (message.hasOwnProperty('highlight')) return message.highlight;
@@ -190,7 +190,7 @@ export default {
const pattern = `@(${escapedUsername}|${escapedDisplayName})(\\b)`; const pattern = `@(${escapedUsername}|${escapedDisplayName})(\\b)`;
const precedingChar = messageText.substring(mentioned - 1, mentioned); const precedingChar = messageText.substring(mentioned - 1, mentioned);
if (mentioned === 0 || precedingChar.trim() === '' || precedingChar === '@') { if (mentioned === 0 || precedingChar.trim() === '' || precedingChar === '@') {
let regex = new RegExp(pattern, 'i'); const regex = new RegExp(pattern, 'i');
message.highlight = regex.test(messageText); message.highlight = regex.test(messageText);
} }
@@ -201,8 +201,8 @@ export default {
if (!message.likes) return 0; if (!message.likes) return 0;
let likeCount = 0; let likeCount = 0;
for (let key in message.likes) { for (const key in message.likes) {
let like = message.likes[key]; const like = message.likes[key];
if (like) likeCount += 1; if (like) likeCount += 1;
} }
return likeCount; return likeCount;
@@ -217,9 +217,14 @@ export default {
return 'Message hidden (shadow-muted)'; return 'Message hidden (shadow-muted)';
}, },
}, },
mounted () {
this.CHAT_FLAG_LIMIT_FOR_HIDING = CHAT_FLAG_LIMIT_FOR_HIDING;
this.CHAT_FLAG_FROM_SHADOW_MUTE = CHAT_FLAG_FROM_SHADOW_MUTE;
this.$emit('chat-card-mounted', this.msg.id);
},
methods: { methods: {
async like () { async like () {
let message = cloneDeep(this.msg); const message = cloneDeep(this.msg);
await this.$store.dispatch('chat:like', { await this.$store.dispatch('chat:like', {
groupId: this.groupId, groupId: this.groupId,
@@ -279,10 +284,5 @@ export default {
return habiticaMarkdown.render(String(text)); return habiticaMarkdown.render(String(text));
}, },
}, },
mounted () {
this.CHAT_FLAG_LIMIT_FOR_HIDING = CHAT_FLAG_LIMIT_FOR_HIDING;
this.CHAT_FLAG_FROM_SHADOW_MUTE = CHAT_FLAG_FROM_SHADOW_MUTE;
this.$emit('chat-card-mounted', this.msg.id);
},
}; };
</script> </script>

View File

@@ -140,15 +140,20 @@
<script> <script>
import moment from 'moment'; import moment from 'moment';
import axios from 'axios'; import axios from 'axios';
import { mapState } from '@/libs/store';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import findIndex from 'lodash/findIndex'; import findIndex from 'lodash/findIndex';
import { mapState } from '@/libs/store';
import Avatar from '../avatar'; import Avatar from '../avatar';
import copyAsTodoModal from './copyAsTodoModal'; import copyAsTodoModal from './copyAsTodoModal';
import chatCard from './chatCard'; import chatCard from './chatCard';
export default { export default {
components: {
copyAsTodoModal,
chatCard,
Avatar,
},
props: { props: {
chat: {}, chat: {},
inbox: { inbox: {
@@ -162,20 +167,6 @@ export default {
isLoading: Boolean, isLoading: Boolean,
canLoadMore: Boolean, canLoadMore: Boolean,
}, },
components: {
copyAsTodoModal,
chatCard,
Avatar,
},
mounted () {
this.loadProfileCache();
},
created () {
window.addEventListener('scroll', this.handleScroll);
},
destroyed () {
window.removeEventListener('scroll', this.handleScroll);
},
data () { data () {
return { return {
currentDayDividerDisplay: moment().day(), currentDayDividerDisplay: moment().day(),
@@ -187,8 +178,17 @@ export default {
lastOffset: -1, lastOffset: -1,
}; };
}, },
mounted () {
this.loadProfileCache();
},
created () {
window.addEventListener('scroll', this.handleScroll);
},
destroyed () {
window.removeEventListener('scroll', this.handleScroll);
},
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
// @TODO: We need a different lazy load mechnism. // @TODO: We need a different lazy load mechnism.
// But honestly, adding a paging route to chat would solve this // But honestly, adding a paging route to chat would solve this
messages () { messages () {
@@ -201,7 +201,7 @@ export default {
this.loadProfileCache(window.scrollY / 1000); this.loadProfileCache(window.scrollY / 1000);
}, },
async triggerLoad () { async triggerLoad () {
const container = this.$refs.container; const { container } = this.$refs;
// get current offset // get current offset
this.lastOffset = container.scrollTop - (container.scrollHeight - container.clientHeight); this.lastOffset = container.scrollTop - (container.scrollHeight - container.clientHeight);
@@ -226,7 +226,7 @@ export default {
if (this.loading) return; if (this.loading) return;
this.loading = true; this.loading = true;
let promises = []; const promises = [];
const noProfilesLoaded = Object.keys(this.cachedProfileData).length === 0; const noProfilesLoaded = Object.keys(this.cachedProfileData).length === 0;
// @TODO: write an explination // @TODO: write an explination
@@ -237,9 +237,9 @@ export default {
return; return;
} }
let aboutToCache = {}; const aboutToCache = {};
this.messages.forEach(message => { this.messages.forEach(message => {
let uuid = message.uuid; const { uuid } = message;
if (message.userStyles) { if (message.userStyles) {
this.$set(this.cachedProfileData, uuid, message.userStyles); this.$set(this.cachedProfileData, uuid, message.userStyles);
@@ -253,21 +253,21 @@ export default {
} }
}); });
let results = await Promise.all(promises); const results = await Promise.all(promises);
results.forEach(result => { results.forEach(result => {
// We could not load the user. Maybe they were deleted. So, let's cache empty so we don't try again // We could not load the user. Maybe they were deleted. So, let's cache empty so we don't try again
if (!result || !result.data || result.status >= 400) { if (!result || !result.data || result.status >= 400) {
return; return;
} }
let userData = result.data.data; const userData = result.data.data;
this.$set(this.cachedProfileData, userData._id, userData); this.$set(this.cachedProfileData, userData._id, userData);
}); });
// Merge in any attempts that were rejected so we don't attempt again // Merge in any attempts that were rejected so we don't attempt again
for (let uuid in aboutToCache) { for (const uuid in aboutToCache) {
if (!this.cachedProfileData[uuid]) { if (!this.cachedProfileData[uuid]) {
this.$set(this.cachedProfileData, uuid, {rejected: true}); this.$set(this.cachedProfileData, uuid, { rejected: true });
} }
} }
@@ -293,22 +293,21 @@ export default {
type: 'error', type: 'error',
timeout: false, timeout: false,
}); });
} else {
this.cachedProfileData[memberId] = result.data.data;
profile = result.data.data;
} }
this.cachedProfileData[memberId] = result.data.data;
profile = result.data.data;
} }
// Open the modal only if the data is available // Open the modal only if the data is available
if (profile && !profile.rejected) { if (profile && !profile.rejected) {
this.$router.push({name: 'userProfile', params: {userId: profile._id}}); this.$router.push({ name: 'userProfile', params: { userId: profile._id } });
} }
}, },
itemWasMounted: debounce(function itemWasMounted () { itemWasMounted: debounce(function itemWasMounted () {
if (this.handleScrollBack) { if (this.handleScrollBack) {
this.handleScrollBack = false; this.handleScrollBack = false;
const container = this.$refs.container; const { container } = this.$refs;
const offset = container.scrollHeight - container.clientHeight; const offset = container.scrollHeight - container.clientHeight;
const newOffset = offset + this.lastOffset; const newOffset = offset + this.lastOffset;
@@ -319,9 +318,7 @@ export default {
} }
}, 50), }, 50),
messageLiked (message) { messageLiked (message) {
const chatIndex = findIndex(this.chat, chatMessage => { const chatIndex = findIndex(this.chat, chatMessage => chatMessage.id === message.id);
return chatMessage.id === message.id;
});
this.chat.splice(chatIndex, 1, message); this.chat.splice(chatIndex, 1, message);
}, },
messageRemoved (message) { messageRemoved (message) {
@@ -330,9 +327,7 @@ export default {
return; return;
} }
const chatIndex = findIndex(this.chat, chatMessage => { const chatIndex = findIndex(this.chat, chatMessage => chatMessage.id === message.id);
return chatMessage.id === message.id;
});
this.chat.splice(chatIndex, 1); this.chat.splice(chatIndex, 1);
}, },
}, },

View File

@@ -59,11 +59,10 @@ export default {
groupPath () { groupPath () {
if (this.groupId === TAVERN_ID) { if (this.groupId === TAVERN_ID) {
return `${baseUrl}/groups/tavern`; return `${baseUrl}/groups/tavern`;
} else if (this.groupType === 'party') { } if (this.groupType === 'party') {
return `${baseUrl}/party`; return `${baseUrl}/party`;
} else {
return `${baseUrl}/groups/guild/${this.groupId}`;
} }
return `${baseUrl}/groups/guild/${this.groupId}`;
}, },
close () { close () {
this.$root.$emit('bv::hide::modal', 'copyAsTodo'); this.$root.$emit('bv::hide::modal', 'copyAsTodo');

View File

@@ -66,15 +66,15 @@ import notifications from '@/mixins/notifications';
import markdownDirective from '@/directives/markdown'; import markdownDirective from '@/directives/markdown';
export default { export default {
mixins: [notifications],
directives: { directives: {
markdown: markdownDirective, markdown: markdownDirective,
}, },
mixins: [notifications],
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
reportData () { reportData () {
let reportMessage = this.abuseObject.user; let reportMessage = this.abuseObject.user;
let isSystemMessage = this.abuseObject.uuid === 'system'; const isSystemMessage = this.abuseObject.uuid === 'system';
if (isSystemMessage) reportMessage = this.$t('systemMessage'); if (isSystemMessage) reportMessage = this.$t('systemMessage');
return { return {
name: `<span class='text-danger'>${reportMessage}</span>`, name: `<span class='text-danger'>${reportMessage}</span>`,
@@ -82,7 +82,7 @@ export default {
}, },
}, },
data () { data () {
let abuseFlagModalBody = { const abuseFlagModalBody = {
firstLinkStart: '<a href="/static/community-guidelines" target="_blank">', firstLinkStart: '<a href="/static/community-guidelines" target="_blank">',
secondLinkStart: '<a href="/static/terms" target="_blank">', secondLinkStart: '<a href="/static/terms" target="_blank">',
linkEnd: '</a>', linkEnd: '</a>',
@@ -108,7 +108,7 @@ export default {
async reportAbuse () { async reportAbuse () {
this.text(this.$t(this.groupId === 'privateMessage' ? 'pmReported' : 'abuseReported')); this.text(this.$t(this.groupId === 'privateMessage' ? 'pmReported' : 'abuseReported'));
let result = await this.$store.dispatch('chat:flag', { const result = await this.$store.dispatch('chat:flag', {
groupId: this.groupId, groupId: this.groupId,
chatId: this.abuseObject.id, chatId: this.abuseObject.id,
comment: this.reportComment, comment: this.reportComment,

View File

@@ -731,12 +731,11 @@ import pin from '@/assets/svg/pin.svg';
import arrowRight from '@/assets/svg/arrow_right.svg'; import arrowRight from '@/assets/svg/arrow_right.svg';
import arrowLeft from '@/assets/svg/arrow_left.svg'; import arrowLeft from '@/assets/svg/arrow_left.svg';
import isPinned from '@/../../common/script/libs/isPinned'; import isPinned from '@/../../common/script/libs/isPinned';
import {avatarEditorUtilies} from '../mixins/avatarEditUtilities'; import { avatarEditorUtilies } from '../mixins/avatarEditUtilities';
import content from '@/../../common/script/content/index'; import content from '@/../../common/script/content/index';
export default { export default {
mixins: [guide, notifications, avatarEditorUtilies],
components: { components: {
avatar, avatar,
toggleSwitch, toggleSwitch,
@@ -748,13 +747,9 @@ export default {
subMenu, subMenu,
}, },
mounted () { mixins: [guide, notifications, avatarEditorUtilies],
if (this.editing) this.modalPage = 2;
// Buy modal is global, so we listen at root. I'd like to not
this.$root.$on('buyModal::boughtItem', this.backgroundPurchased);
},
data () { data () {
let backgroundShopSets = shops.getBackgroundShopSets(); const backgroundShopSets = shops.getBackgroundShopSets();
return { return {
loading: false, loading: false,
@@ -786,12 +781,10 @@ export default {
}, },
], ],
bgSubMenuItems: ['2019', '2018', '2017', '2016', '2015', '2014'].map(y => bgSubMenuItems: ['2019', '2018', '2017', '2016', '2015', '2014'].map(y => ({
({ id: y,
id: y, label: y,
label: y, })),
})
),
}; };
}, },
watch: { watch: {
@@ -806,8 +799,13 @@ export default {
this.$store.state.avatarEditorOptions.subpage = ''; this.$store.state.avatarEditorOptions.subpage = '';
}, },
}, },
mounted () {
if (this.editing) this.modalPage = 2;
// Buy modal is global, so we listen at root. I'd like to not
this.$root.$on('buyModal::boughtItem', this.backgroundPurchased);
},
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
editing () { editing () {
@@ -818,7 +816,7 @@ export default {
}, },
backgroundShopSetsByYear () { backgroundShopSetsByYear () {
// @TODO: add dates to backgrounds // @TODO: add dates to backgrounds
let backgroundShopSetsByYear = { const backgroundShopSetsByYear = {
2014: [], 2014: [],
2015: [], 2015: [],
2016: [], 2016: [],
@@ -830,12 +828,12 @@ export default {
// Hack to force update for now until we restructure the data // Hack to force update for now until we restructure the data
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
this.backgroundShopSets.forEach((set) => { this.backgroundShopSets.forEach(set => {
let year = set.identifier.substr(set.identifier.length - 4); const year = set.identifier.substr(set.identifier.length - 4);
if (!backgroundShopSetsByYear[year]) return; if (!backgroundShopSetsByYear[year]) return;
let setOwnedByUser = false; let setOwnedByUser = false;
for (let key in set.items) { for (const key in set.items) {
if (this.user.purchased.background[key]) setOwnedByUser = true; if (this.user.purchased.background[key]) setOwnedByUser = true;
} }
set.userOwns = setOwnedByUser; set.userOwns = setOwnedByUser;
@@ -845,13 +843,13 @@ export default {
return backgroundShopSetsByYear; return backgroundShopSetsByYear;
}, },
ownedBackgrounds () { ownedBackgrounds () {
let ownedBackgrounds = []; const ownedBackgrounds = [];
// Hack to force update for now until we restructure the data // Hack to force update for now until we restructure the data
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
this.backgroundShopSets.forEach((set) => { this.backgroundShopSets.forEach(set => {
set.items.forEach((bg) => { set.items.forEach(bg => {
if (this.user.purchased.background[bg.key]) { if (this.user.purchased.background[bg.key]) {
ownedBackgrounds.push(bg); ownedBackgrounds.push(bg);
} }
@@ -885,7 +883,7 @@ export default {
this.loading = true; this.loading = true;
let tasksToCreate = [ let tasksToCreate = [
...content.tasksByCategory.defaults.map(t => ({ ...content.tasksByCategory.defaults.map(t => ({
...t, ...t,
text: t.text(), text: t.text(),
notes: t.notes && t.notes(), notes: t.notes && t.notes(),
@@ -900,8 +898,8 @@ export default {
}); });
// @TODO: Move to the action // @TODO: Move to the action
let response = await axios.post('/api/v4/tasks/user', tasksToCreate); const response = await axios.post('/api/v4/tasks/user', tasksToCreate);
let tasks = response.data.data; const tasks = response.data.data;
tasks.forEach(task => { tasks.forEach(task => {
this.$store.state.user.data.tasksOrder[`${task.type}s`].unshift(task._id); this.$store.state.user.data.tasksOrder[`${task.type}s`].unshift(task._id);
this.$store.state.tasks.data[`${task.type}s`].unshift(task); this.$store.state.tasks.data[`${task.type}s`].unshift(task);
@@ -926,7 +924,7 @@ export default {
let setOwnedByUser = false; let setOwnedByUser = false;
for (let key in set) { for (let key in set) {
let value = set[key]; const value = set[key];
if (type === 'background') key = value.key; if (type === 'background') key = value.key;
if (this.user.purchased[type][key]) setOwnedByUser = true; if (this.user.purchased[type][key]) setOwnedByUser = true;
} }
@@ -948,8 +946,8 @@ export default {
return isPinned(this.user, bg); return isPinned(this.user, bg);
}, },
togglePinned (bg) { togglePinned (bg) {
if (!this.$store.dispatch('user:togglePinnedItem', {type: bg.pinType, path: bg.path})) { if (!this.$store.dispatch('user:togglePinnedItem', { type: bg.pinType, path: bg.path })) {
this.text(this.$t('unpinnedItem', {item: bg.text})); this.text(this.$t('unpinnedItem', { item: bg.text }));
} }
}, },
backgroundSelected (bg) { backgroundSelected (bg) {

View File

@@ -1,4 +1,4 @@
<template lang="pug"> <template lang="pug">
// Container component used when you only need an empty view to support children routes // Container component used when you only need an empty view to support children routes
router-view router-view
</template> </template>

View File

@@ -62,7 +62,7 @@ export default {
} }
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
isLeader () { isLeader () {
return this.user._id === this.group.leader._id; return this.user._id === this.group.leader._id;
}, },
@@ -81,8 +81,8 @@ export default {
}, },
methods: { methods: {
async loadGroup () { async loadGroup () {
let group = await this.$store.dispatch('guilds:getGroup', {groupId: this.groupId}); const group = await this.$store.dispatch('guilds:getGroup', { groupId: this.groupId });
this.group = Object.assign({}, group); this.group = { ...group };
}, },
upgradeGroup () { upgradeGroup () {
this.$store.state.upgradingGroup = this.group; this.$store.state.upgradingGroup = this.group;

View File

@@ -23,7 +23,7 @@
.col-12(v-if='activePage === PAGES.PAY') .col-12(v-if='activePage === PAGES.PAY')
h2 {{ $t('choosePaymentMethod') }} h2 {{ $t('choosePaymentMethod') }}
.payments-column .payments-column
button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)') button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)')
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon") .svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }} | {{ $t('card') }}
amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)") amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)")
@@ -79,10 +79,10 @@ import amazonButton from '@/components/payments/amazonButton';
import creditCardIcon from '@/assets/svg/credit-card-icon.svg'; import creditCardIcon from '@/assets/svg/credit-card-icon.svg';
export default { export default {
mixins: [paymentsMixin],
components: { components: {
amazonButton, amazonButton,
}, },
mixins: [paymentsMixin],
data () { data () {
return { return {
amazonPayments: {}, amazonPayments: {},
@@ -110,7 +110,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
newGroupIsReady () { newGroupIsReady () {
return Boolean(this.newGroup.name); return Boolean(this.newGroup.name);
}, },
@@ -131,7 +131,7 @@ export default {
}, },
pay (paymentMethod) { pay (paymentMethod) {
const subscriptionKey = 'group_monthly'; const subscriptionKey = 'group_monthly';
let paymentData = { const paymentData = {
subscription: subscriptionKey, subscription: subscriptionKey,
coupon: null, coupon: null,
}; };

View File

@@ -213,7 +213,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
methods: { methods: {
shown () { shown () {

View File

@@ -22,20 +22,18 @@ import SecondaryMenu from '@/components/secondaryMenu';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
export default { export default {
props: ['groupId'],
components: { components: {
SecondaryMenu, SecondaryMenu,
groupFormModal, groupFormModal,
}, },
props: ['groupId'],
computed: { computed: {
...mapState({ ...mapState({
user: 'user.data', user: 'user.data',
groupPlans: 'groupPlans', groupPlans: 'groupPlans',
}), }),
currentGroup () { currentGroup () {
let groupFound = this.groupPlans.find(group => { const groupFound = this.groupPlans.find(group => group._id === this.groupId);
return group._id === this.groupId;
});
return groupFound; return groupFound;
}, },

View File

@@ -67,6 +67,10 @@
</style> </style>
<script> <script>
import Vue from 'vue';
import cloneDeep from 'lodash/cloneDeep';
import findIndex from 'lodash/findIndex';
import groupBy from 'lodash/groupBy';
import taskDefaults from '@/../../common/script/libs/taskDefaults'; import taskDefaults from '@/../../common/script/libs/taskDefaults';
import TaskColumn from '../tasks/column'; import TaskColumn from '../tasks/column';
import TaskModal from '../tasks/taskModal'; import TaskModal from '../tasks/taskModal';
@@ -80,19 +84,15 @@ import dailyIcon from '@/assets/svg/daily.svg';
import todoIcon from '@/assets/svg/todo.svg'; import todoIcon from '@/assets/svg/todo.svg';
import rewardIcon from '@/assets/svg/reward.svg'; import rewardIcon from '@/assets/svg/reward.svg';
import Vue from 'vue';
import cloneDeep from 'lodash/cloneDeep';
import findIndex from 'lodash/findIndex';
import groupBy from 'lodash/groupBy';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
export default { export default {
props: ['groupId'],
components: { components: {
TaskColumn, TaskColumn,
TaskModal, TaskModal,
GroupPlanOverviewModal, GroupPlanOverviewModal,
}, },
props: ['groupId'],
data () { data () {
return { return {
openCreateBtn: false, openCreateBtn: false,
@@ -143,7 +143,7 @@ export default {
} }
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
tagsByType () { tagsByType () {
const userTags = this.user.tags; const userTags = this.user.tags;
const tagsByType = { const tagsByType = {
@@ -194,22 +194,22 @@ export default {
groupId: this.searchId, groupId: this.searchId,
}); });
let members = await this.$store.dispatch('members:getGroupMembers', {groupId: this.searchId}); const members = await this.$store.dispatch('members:getGroupMembers', { groupId: this.searchId });
this.group.members = members; this.group.members = members;
let tasks = await this.$store.dispatch('tasks:getGroupTasks', { const tasks = await this.$store.dispatch('tasks:getGroupTasks', {
groupId: this.searchId, groupId: this.searchId,
}); });
let groupedApprovals = await this.loadApprovals(); const groupedApprovals = await this.loadApprovals();
tasks.forEach((task) => { tasks.forEach(task => {
if (groupedApprovals[task._id] && groupedApprovals[task._id].length > 0) task.approvals = groupedApprovals[task._id]; if (groupedApprovals[task._id] && groupedApprovals[task._id].length > 0) task.approvals = groupedApprovals[task._id];
this.tasksByType[task.type].push(task); this.tasksByType[task.type].push(task);
}); });
}, },
async loadApprovals () { async loadApprovals () {
let approvalRequests = await this.$store.dispatch('tasks:getGroupApprovals', { const approvalRequests = await this.$store.dispatch('tasks:getGroupApprovals', {
groupId: this.searchId, groupId: this.searchId,
}); });
@@ -229,10 +229,8 @@ export default {
groupId: this.searchId, groupId: this.searchId,
}); });
completedTodos.forEach((task) => { completedTodos.forEach(task => {
const existingTaskIndex = findIndex(this.tasksByType.todo, (todo) => { const existingTaskIndex = findIndex(this.tasksByType.todo, todo => todo._id === task._id);
return todo._id === task._id;
});
if (existingTaskIndex === -1) { if (existingTaskIndex === -1) {
this.tasksByType.todo.push(task); this.tasksByType.todo.push(task);
} }
@@ -241,7 +239,7 @@ export default {
createTask (type) { createTask (type) {
this.openCreateBtn = false; this.openCreateBtn = false;
this.taskFormPurpose = 'create'; this.taskFormPurpose = 'create';
this.creatingTask = taskDefaults({type, text: ''}, this.user); this.creatingTask = taskDefaults({ type, text: '' }, this.user);
this.workingTask = this.creatingTask; this.workingTask = this.creatingTask;
// Necessary otherwise the first time the modal is not rendered // Necessary otherwise the first time the modal is not rendered
Vue.nextTick(() => { Vue.nextTick(() => {
@@ -253,15 +251,11 @@ export default {
this.tasksByType[task.type].push(task); this.tasksByType[task.type].push(task);
}, },
taskEdited (task) { taskEdited (task) {
let index = findIndex(this.tasksByType[task.type], (taskItem) => { const index = findIndex(this.tasksByType[task.type], taskItem => taskItem._id === task._id);
return taskItem._id === task._id;
});
this.tasksByType[task.type].splice(index, 1, task); this.tasksByType[task.type].splice(index, 1, task);
}, },
taskDestroyed (task) { taskDestroyed (task) {
let index = findIndex(this.tasksByType[task.type], (taskItem) => { const index = findIndex(this.tasksByType[task.type], taskItem => taskItem._id === task._id);
return taskItem._id === task._id;
});
this.tasksByType[task.type].splice(index, 1); this.tasksByType[task.type].splice(index, 1);
}, },
cancelTaskModal () { cancelTaskModal () {
@@ -289,12 +283,12 @@ export default {
this.closeFilterPanel(); this.closeFilterPanel();
}, },
applyFilters () { applyFilters () {
const temporarilySelectedTags = this.temporarilySelectedTags; const { temporarilySelectedTags } = this;
this.selectedTags = temporarilySelectedTags.slice(); this.selectedTags = temporarilySelectedTags.slice();
this.closeFilterPanel(); this.closeFilterPanel();
}, },
toggleTag (tag) { toggleTag (tag) {
const temporarilySelectedTags = this.temporarilySelectedTags; const { temporarilySelectedTags } = this;
const tagI = temporarilySelectedTags.indexOf(tag.id); const tagI = temporarilySelectedTags.indexOf(tag.id);
if (tagI === -1) { if (tagI === -1) {
temporarilySelectedTags.push(tag.id); temporarilySelectedTags.push(tag.id);

View File

@@ -6,20 +6,20 @@
</template> </template>
<script> <script>
import { TAVERN_ID } from '@/../../common/script/constants'; import { TAVERN_ID } from '@/../../common/script/constants';
export default { export default {
props: ['group'], props: ['group'],
methods: { methods: {
goToGroup () { goToGroup () {
if (this.group.type === 'party') { if (this.group.type === 'party') {
this.$router.push({name: 'party'}); this.$router.push({ name: 'party' });
} else if (this.group._id === TAVERN_ID) { } else if (this.group._id === TAVERN_ID) {
this.$router.push({name: 'tavern'}); this.$router.push({ name: 'tavern' });
} else { } else {
this.$router.push({name: 'guild', params: {groupId: this.group._id}}); this.$router.push({ name: 'guild', params: { groupId: this.group._id } });
} }
},
}, },
}; },
</script> };
</script>

View File

@@ -48,190 +48,190 @@
</template> </template>
<script> <script>
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import autocomplete from '../chat/autoComplete'; import autocomplete from '../chat/autoComplete';
import communityGuidelines from './communityGuidelines'; import communityGuidelines from './communityGuidelines';
import chatMessage from '../chat/chatMessages'; import chatMessage from '../chat/chatMessages';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import markdownDirective from '@/directives/markdown'; import markdownDirective from '@/directives/markdown';
export default { export default {
props: ['label', 'group', 'placeholder'], directives: {
directives: { markdown: markdownDirective,
markdown: markdownDirective, },
}, components: {
components: { autocomplete,
autocomplete, communityGuidelines,
communityGuidelines, chatMessage,
chatMessage, },
}, props: ['label', 'group', 'placeholder'],
data () { data () {
return { return {
newMessage: '', newMessage: '',
sending: false, sending: false,
caretPosition: 0, caretPosition: 0,
chat: { chat: {
submitDisable: false, submitDisable: false,
submitTimeout: null, submitTimeout: null,
},
coords: {
TOP: 0,
LEFT: 0,
},
textbox: this.$refs,
};
},
computed: {
...mapState({user: 'user.data'}),
currentLength () {
return this.newMessage.length;
}, },
communityGuidelinesAccepted () { coords: {
return this.user.flags.communityGuidelinesAccepted;
},
},
methods: {
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
getCoord (e, text) {
this.caretPosition = text.selectionEnd;
let div = document.createElement('div');
let span = document.createElement('span');
let copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, (prop) => {
div.style[prop] = copyStyle[prop];
});
div.style.position = 'absolute';
document.body.appendChild(div);
div.textContent = text.value.substr(0, this.caretPosition);
span.textContent = text.value.substr(this.caretPosition) || '.';
div.appendChild(span);
this.coords = {
TOP: span.offsetTop,
LEFT: span.offsetLeft,
};
document.body.removeChild(div);
},
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
let text = eventUpdate.target;
this.getCoord(eventUpdate, text);
},
async sendMessageShortcut () {
// If the user recently pasted in the text field, don't submit
if (!this.chat.submitDisable) {
this.sendMessage();
}
},
async sendMessage () {
if (this.sending) return;
this.sending = true;
let response;
try {
response = await this.$store.dispatch('chat:postChat', {
group: this.group,
message: this.newMessage,
});
} catch (e) {
// catch exception to allow function to continue
}
if (response) {
this.group.chat.unshift(response.message);
this.newMessage = '';
}
this.sending = false;
// @TODO: I would like to not reload everytime we send. Why are we reloading?
// The response has all the necessary data...
let chat = await this.$store.dispatch('chat:getChat', {groupId: this.group._id});
this.group.chat = chat;
},
disableMessageSendShortcut () {
// Some users were experiencing accidental sending of messages after pasting
// So, after pasting, disable the shortcut for a second.
this.chat.submitDisable = true;
if (this.chat.submitTimeout) {
// If someone pastes during the disabled period, prevent early re-enable
clearTimeout(this.chat.submitTimeout);
this.chat.submitTimeout = null;
}
this.chat.submitTimeout = window.setTimeout(() => {
this.chat.submitTimeout = null;
this.chat.submitDisable = false;
}, 500);
},
handleTab (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
if (e.shiftKey) {
this.$refs.autocomplete.selectPrevious();
} else {
this.$refs.autocomplete.selectNext();
}
}
},
handleEscape (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.cancel();
}
},
selectNextAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectNext();
}
},
selectPreviousAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectPrevious();
}
},
selectAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.makeSelection();
}
},
selectedAutocomplete (newText) {
this.newMessage = newText;
},
fetchRecentMessages () {
this.$emit('fetchRecentMessages');
},
reverseChat () {
this.group.chat.reverse();
},
},
beforeRouteUpdate (to, from, next) {
// Reset chat
this.newMessage = '';
this.coords = {
TOP: 0, TOP: 0,
LEFT: 0, LEFT: 0,
}; },
textbox: this.$refs,
next(); };
},
computed: {
...mapState({ user: 'user.data' }),
currentLength () {
return this.newMessage.length;
}, },
}; communityGuidelinesAccepted () {
return this.user.flags.communityGuidelinesAccepted;
},
},
methods: {
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
getCoord (e, text) {
this.caretPosition = text.selectionEnd;
const div = document.createElement('div');
const span = document.createElement('span');
const copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, prop => {
div.style[prop] = copyStyle[prop];
});
div.style.position = 'absolute';
document.body.appendChild(div);
div.textContent = text.value.substr(0, this.caretPosition);
span.textContent = text.value.substr(this.caretPosition) || '.';
div.appendChild(span);
this.coords = {
TOP: span.offsetTop,
LEFT: span.offsetLeft,
};
document.body.removeChild(div);
},
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
this._updateCarretPosition(eventUpdate);
}, 250),
_updateCarretPosition (eventUpdate) {
const text = eventUpdate.target;
this.getCoord(eventUpdate, text);
},
async sendMessageShortcut () {
// If the user recently pasted in the text field, don't submit
if (!this.chat.submitDisable) {
this.sendMessage();
}
},
async sendMessage () {
if (this.sending) return;
this.sending = true;
let response;
try {
response = await this.$store.dispatch('chat:postChat', {
group: this.group,
message: this.newMessage,
});
} catch (e) {
// catch exception to allow function to continue
}
if (response) {
this.group.chat.unshift(response.message);
this.newMessage = '';
}
this.sending = false;
// @TODO: I would like to not reload everytime we send. Why are we reloading?
// The response has all the necessary data...
const chat = await this.$store.dispatch('chat:getChat', { groupId: this.group._id });
this.group.chat = chat;
},
disableMessageSendShortcut () {
// Some users were experiencing accidental sending of messages after pasting
// So, after pasting, disable the shortcut for a second.
this.chat.submitDisable = true;
if (this.chat.submitTimeout) {
// If someone pastes during the disabled period, prevent early re-enable
clearTimeout(this.chat.submitTimeout);
this.chat.submitTimeout = null;
}
this.chat.submitTimeout = window.setTimeout(() => {
this.chat.submitTimeout = null;
this.chat.submitDisable = false;
}, 500);
},
handleTab (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
if (e.shiftKey) {
this.$refs.autocomplete.selectPrevious();
} else {
this.$refs.autocomplete.selectNext();
}
}
},
handleEscape (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.cancel();
}
},
selectNextAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectNext();
}
},
selectPreviousAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.selectPrevious();
}
},
selectAutocomplete (e) {
if (this.$refs.autocomplete.searchActive) {
e.preventDefault();
this.$refs.autocomplete.makeSelection();
}
},
selectedAutocomplete (newText) {
this.newMessage = newText;
},
fetchRecentMessages () {
this.$emit('fetchRecentMessages');
},
reverseChat () {
this.group.chat.reverse();
},
},
beforeRouteUpdate (to, from, next) {
// Reset chat
this.newMessage = '';
this.coords = {
TOP: 0,
LEFT: 0,
};
next();
},
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -47,14 +47,14 @@ import { mapState } from '@/libs/store';
export default { export default {
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
communityGuidelinesAccepted () { communityGuidelinesAccepted () {
return this.user.flags.communityGuidelinesAccepted; return this.user.flags.communityGuidelinesAccepted;
}, },
}, },
methods: { methods: {
acceptCommunityGuidelines () { acceptCommunityGuidelines () {
this.$store.dispatch('user:set', {'flags.communityGuidelinesAccepted': true}); this.$store.dispatch('user:set', { 'flags.communityGuidelinesAccepted': true });
}, },
}, },
}; };

View File

@@ -140,55 +140,55 @@ b-modal#create-party-modal(size='lg', hide-footer=true)
</style> </style>
<script> <script>
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import * as Analytics from '@/libs/analytics'; import * as Analytics from '@/libs/analytics';
import notifications from '@/mixins/notifications'; import notifications from '@/mixins/notifications';
import copyIcon from '@/assets/svg/copy.svg'; import copyIcon from '@/assets/svg/copy.svg';
export default { export default {
data () { data () {
return { return {
icons: Object.freeze({ icons: Object.freeze({
copy: copyIcon, copy: copyIcon,
}), }),
};
},
computed: {
...mapState({ user: 'user.data' }),
},
methods: {
async createParty () {
const group = {
type: 'party',
}; };
}, group.name = this.$t('possessiveParty', { name: this.user.profile.name });
computed: { const party = await this.$store.dispatch('guilds:create', { group });
...mapState({user: 'user.data'}), this.$store.state.party.data = party;
}, this.user.party._id = party._id;
methods: {
async createParty () {
let group = {
type: 'party',
};
group.name = this.$t('possessiveParty', {name: this.user.profile.name});
let party = await this.$store.dispatch('guilds:create', {group});
this.$store.state.party.data = party;
this.user.party._id = party._id;
Analytics.updateUser({ Analytics.updateUser({
partyID: party._id, partyID: party._id,
partySize: 1, partySize: 1,
}); });
this.$root.$emit('bv::hide::modal', 'create-party-modal'); this.$root.$emit('bv::hide::modal', 'create-party-modal');
this.$router.push('/party'); this.$router.push('/party');
},
copyUsername () {
if (navigator.clipboard) {
navigator.clipboard.writeText(this.user.auth.local.username);
} else {
let copyText = document.createElement('textarea');
copyText.value = this.user.auth.local.username;
document.body.appendChild(copyText);
copyText.select();
document.execCommand('copy');
document.body.removeChild(copyText);
}
this.text(this.$t('usernameCopied'));
},
}, },
mixins: [notifications], copyUsername () {
}; if (navigator.clipboard) {
navigator.clipboard.writeText(this.user.auth.local.username);
} else {
const copyText = document.createElement('textarea');
copyText.value = this.user.auth.local.username;
document.body.appendChild(copyText);
copyText.select();
document.execCommand('copy');
document.body.removeChild(copyText);
}
this.text(this.$t('usernameCopied'));
},
},
mixins: [notifications],
};
</script> </script>

View File

@@ -64,7 +64,7 @@ import groupUtilities from '@/mixins/groupsUtilities';
import positiveIcon from '@/assets/svg/positive.svg'; import positiveIcon from '@/assets/svg/positive.svg';
function _mapCategories (guilds) { function _mapCategories (guilds) {
guilds.forEach((guild) => { guilds.forEach(guild => {
if (!guild.categories) return; if (!guild.categories) return;
guild.categorySlugs = guild.categories.map(cat => { guild.categorySlugs = guild.categories.map(cat => {
if (!cat) return; if (!cat) return;
@@ -74,8 +74,8 @@ function _mapCategories (guilds) {
} }
export default { export default {
mixins: [groupUtilities],
components: { PublicGuildItem, MugenScroll, Sidebar }, components: { PublicGuildItem, MugenScroll, Sidebar },
mixins: [groupUtilities],
data () { data () {
return { return {
icons: Object.freeze({ icons: Object.freeze({
@@ -113,14 +113,12 @@ export default {
}, },
computed: { computed: {
filteredGuilds () { filteredGuilds () {
let search = this.search; const { search } = this;
let filters = this.filters; const { filters } = this;
let user = this.$store.state.user.data; const user = this.$store.state.user.data;
let filterGuild = this.filterGuild; const { filterGuild } = this;
// @TODO: Move this to the server // @TODO: Move this to the server
return this.guilds.filter((guild) => { return this.guilds.filter(guild => filterGuild(guild, filters, search, user));
return filterGuild(guild, filters, search, user);
});
}, },
}, },
methods: { methods: {
@@ -133,7 +131,7 @@ export default {
this.queryFilters.search = eventData.searchTerm; this.queryFilters.search = eventData.searchTerm;
let guilds = await this.$store.dispatch('guilds:getPublicGuilds', this.queryFilters); const guilds = await this.$store.dispatch('guilds:getPublicGuilds', this.queryFilters);
_mapCategories(guilds); _mapCategories(guilds);
this.guilds = guilds; this.guilds = guilds;
}, },
@@ -157,7 +155,7 @@ export default {
this.queryFilters.categories = eventData.categories.join(','); this.queryFilters.categories = eventData.categories.join(',');
// Role filters // Role filters
let filteringRole = eventData.roles && eventData.roles.length > 0; const filteringRole = eventData.roles && eventData.roles.length > 0;
if (filteringRole && eventData.roles.indexOf('member') !== -1) { if (filteringRole && eventData.roles.indexOf('member') !== -1) {
this.queryFilters.member = true; this.queryFilters.member = true;
} }
@@ -182,7 +180,7 @@ export default {
this.queryFilters.maxMemberCount = 99; this.queryFilters.maxMemberCount = 99;
} }
let guilds = await this.$store.dispatch('guilds:getPublicGuilds', this.queryFilters); const guilds = await this.$store.dispatch('guilds:getPublicGuilds', this.queryFilters);
_mapCategories(guilds); _mapCategories(guilds);
this.guilds = guilds; this.guilds = guilds;
}, },
@@ -194,7 +192,7 @@ export default {
this.loading = true; this.loading = true;
this.queryFilters.page = this.lastPageLoaded; this.queryFilters.page = this.lastPageLoaded;
let guilds = await this.$store.dispatch('guilds:getPublicGuilds', this.queryFilters); const guilds = await this.$store.dispatch('guilds:getPublicGuilds', this.queryFilters);
if (guilds.length === 0) this.hasLoadedAllGuilds = true; if (guilds.length === 0) this.hasLoadedAllGuilds = true;
_mapCategories(guilds); _mapCategories(guilds);

View File

@@ -289,8 +289,6 @@ import silverGuildBadgeIcon from '@/assets/svg/silver-guild-badge-small.svg';
import bronzeGuildBadgeIcon from '@/assets/svg/bronze-guild-badge-small.svg'; import bronzeGuildBadgeIcon from '@/assets/svg/bronze-guild-badge-small.svg';
export default { export default {
mixins: [groupUtilities, styleHelper],
props: ['groupId'],
components: { components: {
membersModal, membersModal,
startQuestModal, startQuestModal,
@@ -307,6 +305,8 @@ export default {
directives: { directives: {
markdown: markdownDirective, markdown: markdownDirective,
}, },
mixins: [groupUtilities, styleHelper],
props: ['groupId'],
data () { data () {
return { return {
searchId: '', searchId: '',
@@ -333,7 +333,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
partyStore () { partyStore () {
return this.$store.state.party; return this.$store.state.party;
}, },
@@ -353,16 +353,6 @@ export default {
return this.group.memberCount > this.$store.state.constants.LARGE_GROUP_COUNT_MESSAGE_CUTOFF; return this.group.memberCount > this.$store.state.constants.LARGE_GROUP_COUNT_MESSAGE_CUTOFF;
}, },
}, },
mounted () {
if (this.isParty) this.searchId = 'party';
if (!this.searchId) this.searchId = this.groupId;
this.load();
},
beforeRouteUpdate (to, from, next) {
this.$set(this, 'searchId', to.params.groupId);
next();
},
watch: { watch: {
// call again the method if the route changes (when this route is already active) // call again the method if the route changes (when this route is already active)
$route: 'fetchGuild', $route: 'fetchGuild',
@@ -375,9 +365,19 @@ export default {
} }
}, },
}, },
mounted () {
if (this.isParty) this.searchId = 'party';
if (!this.searchId) this.searchId = this.groupId;
this.load();
},
beforeRouteUpdate (to, from, next) {
this.$set(this, 'searchId', to.params.groupId);
next();
},
methods: { methods: {
acceptCommunityGuidelines () { acceptCommunityGuidelines () {
this.$store.dispatch('user:set', {'flags.communityGuidelinesAccepted': true}); this.$store.dispatch('user:set', { 'flags.communityGuidelinesAccepted': true });
}, },
async load () { async load () {
if (this.isParty) { if (this.isParty) {
@@ -393,7 +393,7 @@ export default {
includeAllPublicFields: true, includeAllPublicFields: true,
}); });
this.$root.$on('updatedGroup', group => { this.$root.$on('updatedGroup', group => {
let updatedGroup = extend(this.group, group); const updatedGroup = extend(this.group, group);
this.$set(this.group, updatedGroup); this.$set(this.group, updatedGroup);
}); });
}, },
@@ -442,7 +442,7 @@ export default {
this.group = this.$store.state.party.data; this.group = this.$store.state.party.data;
this.checkForAchievements(); this.checkForAchievements();
} else { } else {
const group = await this.$store.dispatch('guilds:getGroup', {groupId: this.searchId}); const group = await this.$store.dispatch('guilds:getGroup', { groupId: this.searchId });
this.$set(this, 'group', group); this.$set(this, 'group', group);
} }
@@ -450,7 +450,7 @@ export default {
if (this.hasUnreadMessages(groupId)) { if (this.hasUnreadMessages(groupId)) {
// Delay by 1sec to make sure it returns after other requests that don't have the notification marked as read // Delay by 1sec to make sure it returns after other requests that don't have the notification marked as read
setTimeout(() => { setTimeout(() => {
this.$store.dispatch('chat:markChatSeen', {groupId}); this.$store.dispatch('chat:markChatSeen', { groupId });
this.$delete(this.user.newMessages, groupId); this.$delete(this.user.newMessages, groupId);
}, 1000); }, 1000);
} }
@@ -458,9 +458,7 @@ export default {
hasUnreadMessages (groupId) { hasUnreadMessages (groupId) {
if (this.user.newMessages[groupId]) return true; if (this.user.newMessages[groupId]) return true;
return this.user.notifications.some(n => { return this.user.notifications.some(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupId);
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupId;
});
}, },
checkForAchievements () { checkForAchievements () {
// Checks if user's party has reached 2 players for the first time. // Checks if user's party has reached 2 players for the first time.
@@ -481,7 +479,7 @@ export default {
if (this.group.cancelledPlan && !confirm(this.$t('aboutToJoinCancelledGroupPlan'))) { if (this.group.cancelledPlan && !confirm(this.$t('aboutToJoinCancelledGroupPlan'))) {
return; return;
} }
await this.$store.dispatch('guilds:join', {groupId: this.group._id, type: 'guild'}); await this.$store.dispatch('guilds:join', { groupId: this.group._id, type: 'guild' });
}, },
clickLeave () { clickLeave () {
Analytics.track({ Analytics.track({
@@ -493,13 +491,13 @@ export default {
// @TODO: Get challenges and ask to keep or remove // @TODO: Get challenges and ask to keep or remove
if (!confirm('Are you sure you want to leave?')) return; if (!confirm('Are you sure you want to leave?')) return;
let keep = true; const keep = true;
this.leave(keep); this.leave(keep);
}, },
async leave (keepTasks) { async leave (keepTasks) {
let keepChallenges = 'remain-in-challenges'; const keepChallenges = 'remain-in-challenges';
let data = { const data = {
groupId: this.group._id, groupId: this.group._id,
keep: keepTasks, keep: keepTasks,
keepChallenges, keepChallenges,
@@ -507,14 +505,14 @@ export default {
if (this.isParty) { if (this.isParty) {
data.type = 'party'; data.type = 'party';
Analytics.updateUser({partySize: null, partyID: null}); Analytics.updateUser({ partySize: null, partyID: null });
this.$store.state.partyMembers = []; this.$store.state.partyMembers = [];
} }
await this.$store.dispatch('guilds:leave', data); await this.$store.dispatch('guilds:leave', data);
if (this.isParty) { if (this.isParty) {
this.$router.push({name: 'tasks'}); this.$router.push({ name: 'tasks' });
} }
}, },
upgradeGroup () { upgradeGroup () {

View File

@@ -185,7 +185,7 @@ export default {
markdown: markdownDirective, markdown: markdownDirective,
}, },
data () { data () {
let data = { const data = {
workingGroup: { workingGroup: {
id: '', id: '',
name: '', name: '',
@@ -267,8 +267,8 @@ export default {
membersToInvite: [], membersToInvite: [],
}; };
let hashedCategories = {}; const hashedCategories = {};
data.categoryOptions.forEach((category) => { data.categoryOptions.forEach(category => {
hashedCategories[category.key] = category.label; hashedCategories[category.key] = category.label;
}); });
data.categoriesHashByKey = hashedCategories; data.categoriesHashByKey = hashedCategories;
@@ -281,12 +281,12 @@ export default {
return data; return data;
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
editingGroup () { editingGroup () {
return this.$store.state.editingGroup; return this.$store.state.editingGroup;
}, },
charactersRemaining () { charactersRemaining () {
let currentLength = this.workingGroup.summary ? this.workingGroup.summary.length : 0; const currentLength = this.workingGroup.summary ? this.workingGroup.summary.length : 0;
return MAX_SUMMARY_SIZE_FOR_GUILDS - currentLength; return MAX_SUMMARY_SIZE_FOR_GUILDS - currentLength;
}, },
title () { title () {
@@ -304,7 +304,7 @@ export default {
}, },
watch: { watch: {
editingGroup () { editingGroup () {
let editingGroup = this.editingGroup; const { editingGroup } = this;
if (!editingGroup._id) { if (!editingGroup._id) {
this.resetWorkingGroup(); this.resetWorkingGroup();
@@ -339,7 +339,7 @@ export default {
methods: { methods: {
async getMembers () { async getMembers () {
if (!this.workingGroup.id) return; if (!this.workingGroup.id) return;
let members = await this.$store.dispatch('members:getGroupMembers', { const members = await this.$store.dispatch('members:getGroupMembers', {
groupId: this.workingGroup.id, groupId: this.workingGroup.id,
includeAllPublicFields: true, includeAllPublicFields: true,
}); });
@@ -367,7 +367,7 @@ export default {
// @TODO return $rootScope.openModal('buyGems', {track:"Gems > Gems > Create Group"}); // @TODO return $rootScope.openModal('buyGems', {track:"Gems > Gems > Create Group"});
} }
let errors = []; const errors = [];
if (!this.workingGroup.name) errors.push(this.$t('nameRequired')); if (!this.workingGroup.name) errors.push(this.$t('nameRequired'));
if (!this.workingGroup.summary) errors.push(this.$t('summaryRequired')); if (!this.workingGroup.summary) errors.push(this.$t('summaryRequired'));
@@ -391,10 +391,10 @@ export default {
challenges: this.workingGroup.onlyLeaderCreatesChallenges, challenges: this.workingGroup.onlyLeaderCreatesChallenges,
}; };
let categoryKeys = this.workingGroup.categories; const categoryKeys = this.workingGroup.categories;
let serverCategories = []; const serverCategories = [];
categoryKeys.forEach(key => { categoryKeys.forEach(key => {
let catName = this.categoriesHashByKey[key]; const catName = this.categoriesHashByKey[key];
serverCategories.push({ serverCategories.push({
slug: key, slug: key,
name: catName, name: catName,
@@ -402,16 +402,16 @@ export default {
}); });
this.workingGroup.categories = serverCategories; this.workingGroup.categories = serverCategories;
let groupData = Object.assign({}, this.workingGroup); const groupData = { ...this.workingGroup };
let newgroup; let newgroup;
if (groupData.id) { if (groupData.id) {
await this.$store.dispatch('guilds:update', {group: groupData}); await this.$store.dispatch('guilds:update', { group: groupData });
this.$root.$emit('updatedGroup', this.workingGroup); this.$root.$emit('updatedGroup', this.workingGroup);
// @TODO: this doesn't work because of the async resource // @TODO: this doesn't work because of the async resource
// if (updatedGroup.type === 'party') this.$store.state.party = {data: updatedGroup}; // if (updatedGroup.type === 'party') this.$store.state.party = {data: updatedGroup};
} else { } else {
newgroup = await this.$store.dispatch('guilds:create', {group: groupData}); newgroup = await this.$store.dispatch('guilds:create', { group: groupData });
this.$store.state.user.data.balance -= 1; this.$store.state.user.data.balance -= 1;
} }

View File

@@ -49,7 +49,7 @@ div
.box.payment-providers .box.payment-providers
h3 Choose your payment method h3 Choose your payment method
.payments-column .payments-column
button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)') button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)')
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon") .svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }} | {{ $t('card') }}
amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)") amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)")
@@ -99,7 +99,7 @@ div
.text-center .text-center
h3 Choose your payment method h3 Choose your payment method
.payments-column.mx-auto .payments-column.mx-auto
button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)') button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)')
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon") .svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }} | {{ $t('card') }}
amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)") amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)")
@@ -297,10 +297,10 @@ import creditCardIcon from '@/assets/svg/credit-card-icon.svg';
import amazonButton from '@/components/payments/amazonButton'; import amazonButton from '@/components/payments/amazonButton';
export default { export default {
mixins: [paymentsMixin],
components: { components: {
amazonButton, amazonButton,
}, },
mixins: [paymentsMixin],
data () { data () {
return { return {
amazonPayments: {}, amazonPayments: {},
@@ -341,7 +341,7 @@ export default {
return this.$store.state.upgradingGroup; return this.$store.state.upgradingGroup;
}, },
// @TODO: can we move this to payment mixin? // @TODO: can we move this to payment mixin?
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
methods: { methods: {
launchModal () { launchModal () {

View File

@@ -90,111 +90,103 @@
</style> </style>
<script> <script>
import { mapState } from '@/libs/store'; import clone from 'lodash/clone';
import clone from 'lodash/clone'; import debounce from 'lodash/debounce';
import debounce from 'lodash/debounce'; import filter from 'lodash/filter';
import filter from 'lodash/filter'; import forEach from 'lodash/forEach';
import forEach from 'lodash/forEach'; import isEmail from 'validator/lib/isEmail';
import isEmail from 'validator/lib/isEmail'; import isUUID from 'validator/lib/isUUID';
import isUUID from 'validator/lib/isUUID'; import { mapState } from '@/libs/store';
import notifications from '@/mixins/notifications'; import notifications from '@/mixins/notifications';
import positiveIcon from '@/assets/svg/positive.svg'; import positiveIcon from '@/assets/svg/positive.svg';
const INVITE_DEFAULTS = {text: '', error: null, valid: null}; const INVITE_DEFAULTS = { text: '', error: null, valid: null };
export default { export default {
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
cannotSubmit () { cannotSubmit () {
const filteredInvites = filter(this.invites, (invite) => { const filteredInvites = filter(this.invites, invite => invite.text.length > 0 && !invite.valid);
return invite.text.length > 0 && !invite.valid; if (filteredInvites.length > 0) return true;
}); return false;
if (filteredInvites.length > 0) return true;
return false;
},
inviter () {
return this.user.profile.name;
},
}, },
data () { inviter () {
return { return this.user.profile.name;
invites: [clone(INVITE_DEFAULTS), clone(INVITE_DEFAULTS)],
icons: Object.freeze({
positiveIcon,
}),
};
}, },
methods: { },
checkInviteList: debounce(function checkList () { data () {
this.invites = filter(this.invites, (invite, index) => { return {
return invite.text.length > 0 || index === this.invites.length - 1; invites: [clone(INVITE_DEFAULTS), clone(INVITE_DEFAULTS)],
}); icons: Object.freeze({
while (this.invites.length < 2) this.invites.push(clone(INVITE_DEFAULTS)); positiveIcon,
forEach(this.invites, (value, index) => { }),
if (value.text.length < 1 || isEmail(value.text)) { };
return this.fillErrors(index); },
} methods: {
if (isUUID(value.text)) { checkInviteList: debounce(function checkList () {
this.$store.dispatch('user:userLookup', {uuid: value.text}) this.invites = filter(this.invites, (invite, index) => invite.text.length > 0 || index === this.invites.length - 1);
.then(res => { while (this.invites.length < 2) this.invites.push(clone(INVITE_DEFAULTS));
return this.fillErrors(index, res); forEach(this.invites, (value, index) => {
}); if (value.text.length < 1 || isEmail(value.text)) {
} else { return this.fillErrors(index);
let searchUsername = value.text;
if (searchUsername[0] === '@') searchUsername = searchUsername.slice(1, searchUsername.length);
this.$store.dispatch('user:userLookup', {username: searchUsername})
.then(res => {
return this.fillErrors(index, res);
});
}
});
}, 250),
expandInviteList () {
if (this.invites[this.invites.length - 1].text.length > 0) this.invites.push(clone(INVITE_DEFAULTS));
},
fillErrors (index, res) {
if (!res || res.status === 200) {
this.invites[index].error = null;
if (this.invites[index].text.length < 1) return this.invites[index].valid = null;
return this.invites[index].valid = true;
} }
this.invites[index].error = res.response.data.message; if (isUUID(value.text)) {
return this.invites[index].valid = false; this.$store.dispatch('user:userLookup', { uuid: value.text })
}, .then(res => this.fillErrors(index, res));
close () { } else {
this.invites = [clone(INVITE_DEFAULTS), clone(INVITE_DEFAULTS)]; let searchUsername = value.text;
this.$root.$emit('bv::hide::modal', 'invite-modal'); if (searchUsername[0] === '@') searchUsername = searchUsername.slice(1, searchUsername.length);
}, this.$store.dispatch('user:userLookup', { username: searchUsername })
async sendInvites () { .then(res => this.fillErrors(index, res));
let invitationDetails = { }
inviter: this.inviter, });
emails: [], }, 250),
uuids: [], expandInviteList () {
usernames: [], if (this.invites[this.invites.length - 1].text.length > 0) this.invites.push(clone(INVITE_DEFAULTS));
};
forEach(this.invites, (invite) => {
if (invite.text.length < 1) return;
if (isEmail(invite.text)) {
invitationDetails.emails.push({email: invite.text});
} else if (isUUID(invite.text)) {
invitationDetails.uuids.push(invite.text);
} else {
invitationDetails.usernames.push(invite.text);
}
});
await this.$store.dispatch('guilds:invite', {
invitationDetails,
groupId: this.group._id,
});
const invitesSent = invitationDetails.emails.length + invitationDetails.uuids.length + invitationDetails.usernames.length;
let invitationString = invitesSent > 1 ? 'invitationsSent' : 'invitationSent';
this.text(this.$t(invitationString));
this.close();
},
}, },
mixins: [notifications], fillErrors (index, res) {
props: ['group', 'groupType'], if (!res || res.status === 200) {
}; this.invites[index].error = null;
if (this.invites[index].text.length < 1) return this.invites[index].valid = null;
return this.invites[index].valid = true;
}
this.invites[index].error = res.response.data.message;
return this.invites[index].valid = false;
},
close () {
this.invites = [clone(INVITE_DEFAULTS), clone(INVITE_DEFAULTS)];
this.$root.$emit('bv::hide::modal', 'invite-modal');
},
async sendInvites () {
const invitationDetails = {
inviter: this.inviter,
emails: [],
uuids: [],
usernames: [],
};
forEach(this.invites, invite => {
if (invite.text.length < 1) return;
if (isEmail(invite.text)) {
invitationDetails.emails.push({ email: invite.text });
} else if (isUUID(invite.text)) {
invitationDetails.uuids.push(invite.text);
} else {
invitationDetails.usernames.push(invite.text);
}
});
await this.$store.dispatch('guilds:invite', {
invitationDetails,
groupId: this.group._id,
});
const invitesSent = invitationDetails.emails.length + invitationDetails.uuids.length + invitationDetails.usernames.length;
const invitationString = invitesSent > 1 ? 'invitationsSent' : 'invitationSent';
this.text(this.$t(invitationString));
this.close();
},
},
mixins: [notifications],
props: ['group', 'groupType'],
};
</script> </script>

View File

@@ -214,11 +214,11 @@ import starIcon from '@/assets/members/star.svg';
import dots from '@/assets/svg/dots.svg'; import dots from '@/assets/svg/dots.svg';
export default { export default {
props: ['hideBadge'],
components: { components: {
MemberDetails, MemberDetails,
removeMemberModal, removeMemberModal,
}, },
props: ['hideBadge'],
data () { data () {
return { return {
sortOption: {}, sortOption: {},
@@ -278,7 +278,7 @@ export default {
}; };
}, },
mounted () { mounted () {
this.$root.$on('habitica:show-member-modal', (data) => { this.$root.$on('habitica:show-member-modal', data => {
// @TODO: Remove store // @TODO: Remove store
this.$store.state.memberModalOptions.challengeId = data.challengeId; this.$store.state.memberModalOptions.challengeId = data.challengeId;
this.$store.state.memberModalOptions.groupId = data.groupId; this.$store.state.memberModalOptions.groupId = data.groupId;
@@ -294,7 +294,7 @@ export default {
this.$root.$off('habitica:show-member-modal'); this.$root.$off('habitica:show-member-modal');
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
isLeader () { isLeader () {
if (!this.group || !this.group.leader) return false; if (!this.group || !this.group.leader) return false;
return this.user._id === this.group.leader || this.user._id === this.group.leader._id; return this.user._id === this.group.leader || this.user._id === this.group.leader._id;
@@ -370,10 +370,10 @@ export default {
}); });
}, },
async getMembers () { async getMembers () {
let groupId = this.groupId; const { groupId } = this;
if (groupId && groupId !== 'challenge') { if (groupId && groupId !== 'challenge') {
let invites = await this.$store.dispatch('members:getGroupInvites', { const invites = await this.$store.dispatch('members:getGroupInvites', {
groupId, groupId,
includeAllPublicFields: true, includeAllPublicFields: true,
}); });
@@ -383,7 +383,7 @@ export default {
this.members = this.$store.state.memberModalOptions.viewingMembers; this.members = this.$store.state.memberModalOptions.viewingMembers;
}, },
async clickMember (uid, forceShow) { async clickMember (uid, forceShow) {
let user = this.$store.state.user.data; const user = this.$store.state.user.data;
if (user._id === uid && !forceShow) { if (user._id === uid && !forceShow) {
if (this.$route.name === 'tasks') { if (this.$route.name === 'tasks') {
@@ -409,7 +409,7 @@ export default {
memberRemoved () { memberRemoved () {
this.members.splice(this.memberToRemove.index, 1); this.members.splice(this.memberToRemove.index, 1);
this.group.memberCount -= 1; this.group.memberCount -= 1;
this.memberToRemove = {}; this.memberToRemove = {};
}, },
async quickReply (uid) { async quickReply (uid) {
this.memberToReply = uid; this.memberToReply = uid;
@@ -459,7 +459,7 @@ export default {
const lastMember = this.members[this.members.length - 1]; const lastMember = this.members[this.members.length - 1];
if (!lastMember) return; if (!lastMember) return;
let newMembers = await this.$store.state.memberModalOptions.fetchMoreMembers({ const newMembers = await this.$store.state.memberModalOptions.fetchMoreMembers({
challengeId: this.challengeId, challengeId: this.challengeId,
groupId: this.groupId, groupId: this.groupId,
lastMemberId: lastMember._id, lastMemberId: lastMember._id,
@@ -483,10 +483,10 @@ export default {
this.viewMembers(); this.viewMembers();
}, },
async promoteToLeader (member) { async promoteToLeader (member) {
let groupData = Object.assign({}, this.group); const groupData = { ...this.group };
groupData.leader = member._id; groupData.leader = member._id;
await this.$store.dispatch('guilds:update', {group: groupData}); await this.$store.dispatch('guilds:update', { group: groupData });
alert(this.$t('leaderChanged')); alert(this.$t('leaderChanged'));

View File

@@ -61,10 +61,10 @@
</style> </style>
<script> <script>
import MugenScroll from 'vue-mugen-scroll';
import { mapState } from '@/libs/store'; import { mapState } from '@/libs/store';
import groupUtilities from '@/mixins/groupsUtilities'; import groupUtilities from '@/mixins/groupsUtilities';
import MugenScroll from 'vue-mugen-scroll';
import PublicGuildItem from './publicGuildItem'; import PublicGuildItem from './publicGuildItem';
import Sidebar from './sidebar'; import Sidebar from './sidebar';
@@ -73,8 +73,8 @@ import greyBadgeIcon from '@/assets/svg/grey-badge.svg';
import positiveIcon from '@/assets/svg/positive.svg'; import positiveIcon from '@/assets/svg/positive.svg';
export default { export default {
mixins: [groupUtilities],
components: { PublicGuildItem, MugenScroll, Sidebar }, components: { PublicGuildItem, MugenScroll, Sidebar },
mixins: [groupUtilities],
data () { data () {
return { return {
icons: Object.freeze({ icons: Object.freeze({
@@ -109,15 +109,13 @@ export default {
guilds: 'myGuilds', guilds: 'myGuilds',
}), }),
filteredGuilds () { filteredGuilds () {
let search = this.search; const { search } = this;
let filters = this.filters; const { filters } = this;
let user = this.$store.state.user.data; const user = this.$store.state.user.data;
let filterGuild = this.filterGuild; const { filterGuild } = this;
return this.guilds.filter((guild) => { return this.guilds.filter(guild => {
if (guild.categories) { if (guild.categories) {
guild.categorySlugs = guild.categories.map(cat => { guild.categorySlugs = guild.categories.map(cat => cat.slug);
return cat.slug;
});
} }
return filterGuild(guild, filters, search, user); return filterGuild(guild, filters, search, user);
}); });

View File

@@ -71,16 +71,16 @@ import { mapGetters } from '@/libs/store';
import MemberDetails from '../memberDetails'; import MemberDetails from '../memberDetails';
export default { export default {
props: ['group'],
components: { components: {
MemberDetails, MemberDetails,
}, },
props: ['group'],
computed: { computed: {
...mapGetters({ ...mapGetters({
partyMembers: 'party:members', partyMembers: 'party:members',
}), }),
participants () { participants () {
let partyMembers = this.partyMembers || []; const partyMembers = this.partyMembers || [];
return partyMembers.filter(member => this.group.quest.members[member._id] === true); return partyMembers.filter(member => this.group.quest.members[member._id] === true);
}, },
}, },
@@ -90,4 +90,4 @@ export default {
}, },
}, },
}; };
</script> </script>

View File

@@ -144,16 +144,16 @@ import bronzeGuildBadgeIcon from '@/assets/svg/bronze-guild-badge-large.svg';
import { MAX_SUMMARY_SIZE_FOR_GUILDS } from '@/../../common/script/constants'; import { MAX_SUMMARY_SIZE_FOR_GUILDS } from '@/../../common/script/constants';
export default { export default {
mixins: [groupUtilities],
directives: { directives: {
markdown, markdown,
}, },
props: ['guild', 'displayLeave', 'displayGemBank'],
components: { components: {
categoryTags, categoryTags,
}, },
mixins: [groupUtilities],
props: ['guild', 'displayLeave', 'displayGemBank'],
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
isOwner () { isOwner () {
return this.guild.leader && this.guild.leader === this.user._id; return this.guild.leader && this.guild.leader === this.user._id;
}, },
@@ -174,9 +174,9 @@ export default {
}, },
methods: { methods: {
showSuggested (guildId) { showSuggested (guildId) {
let habiticaHelpingGuildId = '5481ccf3-5d2d-48a9-a871-70a7380cee5a'; const habiticaHelpingGuildId = '5481ccf3-5d2d-48a9-a871-70a7380cee5a';
let sixtyDaysAgoFromNow = moment().subtract(60, 'days'); const sixtyDaysAgoFromNow = moment().subtract(60, 'days');
let isUserNew = moment(this.user.auth.timestamps.created).isAfter(sixtyDaysAgoFromNow); const isUserNew = moment(this.user.auth.timestamps.created).isAfter(sixtyDaysAgoFromNow);
return guildId === habiticaHelpingGuildId && isUserNew; return guildId === habiticaHelpingGuildId && isUserNew;
}, },
async join () { async join () {
@@ -184,11 +184,11 @@ export default {
if (this.guild.cancelledPlan && !confirm(window.env.t('aboutToJoinCancelledGroupPlan'))) { if (this.guild.cancelledPlan && !confirm(window.env.t('aboutToJoinCancelledGroupPlan'))) {
return; return;
} }
await this.$store.dispatch('guilds:join', {groupId: this.guild._id, type: 'guild'}); await this.$store.dispatch('guilds:join', { groupId: this.guild._id, type: 'guild' });
}, },
async leave () { async leave () {
// @TODO: ask about challenges when we add challenges // @TODO: ask about challenges when we add challenges
await this.$store.dispatch('guilds:leave', {groupId: this.guild._id, type: 'myGuilds'}); await this.$store.dispatch('guilds:leave', { groupId: this.guild._id, type: 'myGuilds' });
}, },
}, },
}; };

View File

@@ -149,11 +149,11 @@ import questDialogDrops from '../shops/quests/questDialogDrops';
import questDialogContent from '../shops/quests/questDialogContent'; import questDialogContent from '../shops/quests/questDialogContent';
export default { export default {
props: ['group'],
components: { components: {
questDialogDrops, questDialogDrops,
questDialogContent, questDialogContent,
}, },
props: ['group'],
data () { data () {
return { return {
loading: false, loading: false,
@@ -181,38 +181,36 @@ export default {
return quests.quests[this.group.quest.key]; return quests.quests[this.group.quest.key];
}, },
members () { members () {
let partyMembers = this.partyMembers || []; const partyMembers = this.partyMembers || [];
return partyMembers.map(member => { return partyMembers.map(member => ({
return { name: member.profile.name,
name: member.profile.name, accepted: this.group.quest.members[member._id],
accepted: this.group.quest.members[member._id], }));
};
});
}, },
canEditQuest () { canEditQuest () {
if (!this.group.quest) return false; if (!this.group.quest) return false;
let isQuestLeader = this.group.quest.leader === this.user._id; const isQuestLeader = this.group.quest.leader === this.user._id;
let isPartyLeader = this.group.leader._id === this.user._id; const isPartyLeader = this.group.leader._id === this.user._id;
return isQuestLeader || isPartyLeader; return isQuestLeader || isPartyLeader;
}, },
}, },
methods: { methods: {
async questConfirm () { async questConfirm () {
let count = 0; let count = 0;
for (let uuid in this.group.quest.members) { for (const uuid in this.group.quest.members) {
if (this.group.quest.members[uuid]) count += 1; if (this.group.quest.members[uuid]) count += 1;
} }
if (!confirm(this.$t('questConfirm', { questmembers: count, totalmembers: this.group.memberCount}))) return; if (!confirm(this.$t('questConfirm', { questmembers: count, totalmembers: this.group.memberCount }))) return;
this.questForceStart(); this.questForceStart();
}, },
async questForceStart () { async questForceStart () {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/force-start'}); const quest = await this.$store.dispatch('quests:sendAction', { groupId: this.group._id, action: 'quests/force-start' });
this.group.quest = quest; this.group.quest = quest;
this.close(); this.close();
}, },
async questCancel () { async questCancel () {
if (!confirm(this.$t('sureCancel'))) return; if (!confirm(this.$t('sureCancel'))) return;
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/cancel'}); const quest = await this.$store.dispatch('quests:sendAction', { groupId: this.group._id, action: 'quests/cancel' });
this.group.quest = quest; this.group.quest = quest;
this.close(); this.close();
}, },

View File

@@ -198,10 +198,10 @@ import sidebarSection from '../sidebarSection';
import questIcon from '@/assets/svg/quest.svg'; import questIcon from '@/assets/svg/quest.svg';
export default { export default {
props: ['group'],
components: { components: {
sidebarSection, sidebarSection,
}, },
props: ['group'],
data () { data () {
return { return {
icons: Object.freeze({ icons: Object.freeze({
@@ -210,7 +210,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
userIsOnQuest () { userIsOnQuest () {
if (!this.group.quest || !this.group.quest.members) return false; if (!this.group.quest || !this.group.quest.members) return false;
return Boolean(this.group.quest.members[this.user._id]); return Boolean(this.group.quest.members[this.user._id]);
@@ -230,20 +230,20 @@ export default {
}, },
canEditQuest () { canEditQuest () {
if (!this.group.quest) return false; if (!this.group.quest) return false;
let isQuestLeader = this.group.quest.leader === this.user._id; const isQuestLeader = this.group.quest.leader === this.user._id;
let isPartyLeader = this.group.leader._id === this.user._id; const isPartyLeader = this.group.leader._id === this.user._id;
return isQuestLeader || isPartyLeader; return isQuestLeader || isPartyLeader;
}, },
isMemberOfPendingQuest () { isMemberOfPendingQuest () {
let userid = this.user._id; const userid = this.user._id;
let group = this.group; const { group } = this;
if (!group.quest || !group.quest.members) return false; if (!group.quest || !group.quest.members) return false;
if (group.quest.active) return false; // quest is started, not pending if (group.quest.active) return false; // quest is started, not pending
return userid in group.quest.members && group.quest.members[userid] !== false; return userid in group.quest.members && group.quest.members[userid] !== false;
}, },
isMemberOfRunningQuest () { isMemberOfRunningQuest () {
let userid = this.user._id; const userid = this.user._id;
let group = this.group; const { group } = this;
if (!group.quest || !group.quest.members) return false; if (!group.quest || !group.quest.members) return false;
if (!group.quest.active) return false; // quest is pending, not started if (!group.quest.active) return false; // quest is pending, not started
return group.quest.members[userid]; return group.quest.members[userid];
@@ -253,7 +253,7 @@ export default {
if (!this.group || !this.group.quest) return count; if (!this.group || !this.group.quest) return count;
for (let uuid in this.group.quest.members) { for (const uuid in this.group.quest.members) {
if (this.group.quest.members[uuid]) count += 1; if (this.group.quest.members[uuid]) count += 1;
} }
@@ -273,20 +273,20 @@ export default {
async questAbort () { async questAbort () {
if (!confirm(this.$t('sureAbort'))) return; if (!confirm(this.$t('sureAbort'))) return;
if (!confirm(this.$t('doubleSureAbort'))) return; if (!confirm(this.$t('doubleSureAbort'))) return;
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/abort'}); const quest = await this.$store.dispatch('quests:sendAction', { groupId: this.group._id, action: 'quests/abort' });
this.group.quest = quest; this.group.quest = quest;
}, },
async questLeave () { async questLeave () {
if (!confirm(this.$t('sureLeave'))) return; if (!confirm(this.$t('sureLeave'))) return;
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/leave'}); const quest = await this.$store.dispatch('quests:sendAction', { groupId: this.group._id, action: 'quests/leave' });
this.group.quest = quest; this.group.quest = quest;
}, },
async questAccept (partyId) { async questAccept (partyId) {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: partyId, action: 'quests/accept'}); const quest = await this.$store.dispatch('quests:sendAction', { groupId: partyId, action: 'quests/accept' });
this.user.party.quest = quest; this.user.party.quest = quest;
}, },
async questReject (partyId) { async questReject (partyId) {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: partyId, action: 'quests/reject'}); const quest = await this.$store.dispatch('quests:sendAction', { groupId: partyId, action: 'quests/reject' });
this.user.party.quest = quest; this.user.party.quest = quest;
}, },
}, },

View File

@@ -123,12 +123,12 @@ import questDialogContent from '../shops/quests/questDialogContent';
import QuestInfo from '../shops/quests/questInfo'; import QuestInfo from '../shops/quests/questInfo';
export default { export default {
props: ['group'],
components: { components: {
questDialogDrops, questDialogDrops,
questDialogContent, questDialogContent,
QuestInfo, QuestInfo,
}, },
props: ['group'],
data () { data () {
return { return {
loading: false, loading: false,
@@ -162,7 +162,7 @@ export default {
this.$root.$off('selectQuest', this.selectQuest); this.$root.$off('selectQuest', this.selectQuest);
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
questData () { questData () {
return quests.quests[this.selectedQuest]; return quests.quests[this.selectedQuest];
}, },
@@ -179,11 +179,11 @@ export default {
partySize: this.group.memberCount, partySize: this.group.memberCount,
}); });
let groupId = this.group._id || this.user.party._id; const groupId = this.group._id || this.user.party._id;
const key = this.selectedQuest; const key = this.selectedQuest;
try { try {
const response = await this.$store.dispatch('guilds:inviteToQuest', {groupId, key}); const response = await this.$store.dispatch('guilds:inviteToQuest', { groupId, key });
const quest = response.data.data; const quest = response.data.data;
if (this.$store.state.party.data) this.$store.state.party.data.quest = quest; if (this.$store.state.party.data) this.$store.state.party.data.quest = quest;

View File

@@ -501,14 +501,14 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
questData () { questData () {
if (!this.group.quest) return {}; if (!this.group.quest) return {};
return quests.quests[this.group.quest.key]; return quests.quests[this.group.quest.key];
}, },
}, },
async mounted () { async mounted () {
this.group = await this.$store.dispatch('guilds:getGroup', {groupId: TAVERN_ID}); this.group = await this.$store.dispatch('guilds:getGroup', { groupId: TAVERN_ID });
}, },
methods: { methods: {
modForm () { modForm () {
@@ -543,7 +543,7 @@ export default {
} }
}, },
async fetchRecentMessages () { async fetchRecentMessages () {
this.group = await this.$store.dispatch('guilds:getGroup', {groupId: TAVERN_ID}); this.group = await this.$store.dispatch('guilds:getGroup', { groupId: TAVERN_ID });
}, },
}, },
}; };

View File

@@ -119,10 +119,13 @@ import notifications from '@/mixins/notifications';
import userLink from '../userLink'; import userLink from '../userLink';
export default { export default {
mixins: [notifications, styleHelper],
components: { components: {
userLink, userLink,
}, },
directives: {
markdown: markdownDirective,
},
mixins: [notifications, styleHelper],
data () { data () {
return { return {
heroes: [], heroes: [],
@@ -141,14 +144,11 @@ export default {
expandAuth: false, expandAuth: false,
}; };
}, },
directives: {
markdown: markdownDirective,
},
async mounted () { async mounted () {
this.heroes = await this.$store.dispatch('hall:getHeroes'); this.heroes = await this.$store.dispatch('hall:getHeroes');
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
methods: { methods: {
getAllItemPaths () { getAllItemPaths () {
@@ -172,7 +172,7 @@ export default {
getFormattedItemReference (pathPrefix, itemKeys, values) { getFormattedItemReference (pathPrefix, itemKeys, values) {
let finishedString = '\n'.concat('path: ', pathPrefix, ', ', 'value: {', values, '}\n'); let finishedString = '\n'.concat('path: ', pathPrefix, ', ', 'value: {', values, '}\n');
each(itemKeys, (key) => { each(itemKeys, key => {
finishedString = finishedString.concat('\t', pathPrefix, '.', key, '\n'); finishedString = finishedString.concat('\t', pathPrefix, '.', key, '\n');
}); });
@@ -180,8 +180,8 @@ export default {
}, },
async loadHero (uuid, heroIndex) { async loadHero (uuid, heroIndex) {
this.currentHeroIndex = heroIndex; this.currentHeroIndex = heroIndex;
let hero = await this.$store.dispatch('hall:getHero', { uuid }); const hero = await this.$store.dispatch('hall:getHero', { uuid });
this.hero = Object.assign({}, hero); this.hero = { ...hero };
if (!this.hero.flags) { if (!this.hero.flags) {
this.hero.flags = { this.hero.flags = {
chatRevoked: false, chatRevoked: false,
@@ -192,8 +192,8 @@ export default {
this.expandAuth = false; this.expandAuth = false;
}, },
async saveHero () { async saveHero () {
this.hero.contributor.admin = this.hero.contributor.level > 7 ? true : false; this.hero.contributor.admin = this.hero.contributor.level > 7;
let heroUpdated = await this.$store.dispatch('hall:updateHero', { heroDetails: this.hero }); const heroUpdated = await this.$store.dispatch('hall:updateHero', { heroDetails: this.hero });
this.text('User updated'); this.text('User updated');
this.hero = {}; this.hero = {};
this.heroID = -1; this.heroID = -1;

View File

@@ -32,7 +32,7 @@ export default {
this.patrons = await this.$store.dispatch('hall:getPatrons', { page: 0 }); this.patrons = await this.$store.dispatch('hall:getPatrons', { page: 0 });
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
}, },
methods: { methods: {
// @TODO: Import member modal - clickMember() // @TODO: Import member modal - clickMember()

View File

@@ -178,7 +178,7 @@ export default {
} }
}, },
mounted () { mounted () {
this.$root.$on('inviteModal::inviteToGroup', (group) => { this.$root.$on('inviteModal::inviteToGroup', group => {
this.inviteModalGroup = group; this.inviteModalGroup = group;
this.inviteModalGroupType = group.type === 'guild' ? 'Guild' : 'Party'; this.inviteModalGroupType = group.type === 'guild' ? 'Guild' : 'Party';
this.$root.$emit('bv::show::modal', 'invite-modal'); this.$root.$emit('bv::show::modal', 'invite-modal');

View File

@@ -488,7 +488,7 @@ export default {
eventLabel: 'Gems > Toolbar', eventLabel: 'Gems > Toolbar',
}); });
this.$root.$emit('bv::show::modal', 'buy-gems', {alreadyTracked: true}); this.$root.$emit('bv::show::modal', 'buy-gems', { alreadyTracked: true });
}, },
dropdownDesktop (hover) { dropdownDesktop (hover) {
if (this.isDesktop() && hover.target.classList.contains('droppable')) { if (this.isDesktop() && hover.target.classList.contains('droppable')) {
@@ -499,7 +499,7 @@ export default {
this.dropdown(click.currentTarget.parentElement); this.dropdown(click.currentTarget.parentElement);
}, },
dropdown (element) { dropdown (element) {
let droppedElement = document.getElementsByClassName('down')[0]; const droppedElement = document.getElementsByClassName('down')[0];
if (droppedElement && droppedElement !== element) { if (droppedElement && droppedElement !== element) {
droppedElement.classList.remove('down'); droppedElement.classList.remove('down');
if (droppedElement.lastChild) { if (droppedElement.lastChild) {

View File

@@ -30,4 +30,4 @@ span.message-count(
.message-count.top-count-gray { .message-count.top-count-gray {
background-color: $gray-200; background-color: $gray-200;
} }
</style> </style>

View File

@@ -11,7 +11,7 @@
slot(name="content") slot(name="content")
.notification-remove(@click.stop="canRemove ? remove() : null",) .notification-remove(@click.stop="canRemove ? remove() : null",)
.svg-icon( .svg-icon(
v-if="canRemove", v-if="canRemove",
v-html="icons.close", v-html="icons.close",
) )
</template> </template>
@@ -130,7 +130,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
isNotBailey () { isNotBailey () {
return this.notification.type !== 'NEW_STUFF'; return this.notification.type !== 'NEW_STUFF';
}, },
@@ -141,7 +141,7 @@ export default {
}), }),
clicked () { clicked () {
if (this.readAfterClick === true) { if (this.readAfterClick === true) {
this.readNotification({notificationId: this.notification.id}); this.readNotification({ notificationId: this.notification.id });
} }
this.$emit('click'); this.$emit('click');
@@ -149,14 +149,14 @@ export default {
remove () { remove () {
if (this.notification.type === 'NEW_CHAT_MESSAGE') { if (this.notification.type === 'NEW_CHAT_MESSAGE') {
const groupId = this.notification.data.group.id; const groupId = this.notification.data.group.id;
this.$store.dispatch('chat:markChatSeen', {groupId}); this.$store.dispatch('chat:markChatSeen', { groupId });
if (this.user.newMessages[groupId]) { if (this.user.newMessages[groupId]) {
this.$delete(this.user.newMessages, groupId); this.$delete(this.user.newMessages, groupId);
} }
} else { } else {
this.readNotification({notificationId: this.notification.id}); this.readNotification({ notificationId: this.notification.id });
} }
}, },
}, },
}; };
</script> </script>

View File

@@ -14,10 +14,10 @@ base-notification(
import BaseNotification from './base'; import BaseNotification from './base';
export default { export default {
props: ['notification', 'canRemove'],
components: { components: {
BaseNotification, BaseNotification,
}, },
props: ['notification', 'canRemove'],
computed: { computed: {
cardString () { cardString () {
return this.$t(`${this.notification.data.card}Card`); return this.$t(`${this.notification.data.card}Card`);
@@ -28,8 +28,8 @@ export default {
}, },
methods: { methods: {
action () { action () {
this.$router.push({name: 'items'}); this.$router.push({ name: 'items' });
}, },
}, },
}; };
</script> </script>

View File

@@ -1,3 +1,3 @@
<template lang="pug" functional> <template lang="pug" functional>
div {{ props.notification }} div {{ props.notification }}
</template> </template>

View File

@@ -18,13 +18,13 @@ import { mapState } from '@/libs/store';
import sync from '@/mixins/sync'; import sync from '@/mixins/sync';
export default { export default {
mixins: [sync],
props: ['notification', 'canRemove'],
components: { components: {
BaseNotification, BaseNotification,
}, },
mixins: [sync],
props: ['notification', 'canRemove'],
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({ user: 'user.data' }),
// Check that the notification has all the necessary data (old ones are missing some fields) // Check that the notification has all the necessary data (old ones are missing some fields)
notificationHasData () { notificationHasData () {
return Boolean(this.notification.data.groupTaskId && this.notification.data.userId); return Boolean(this.notification.data.groupTaskId && this.notification.data.userId);
@@ -33,14 +33,17 @@ export default {
methods: { methods: {
action () { action () {
const groupId = this.notification.data.group.id; const groupId = this.notification.data.group.id;
this.$router.push({ name: 'groupPlanDetailTaskInformation', params: { groupId }}); this.$router.push({ name: 'groupPlanDetailTaskInformation', params: { groupId } });
}, },
async approve () { async approve () {
// Redirect users to the group tasks page if the notification doesn't have data // Redirect users to the group tasks page if the notification doesn't have data
if (!this.notificationHasData) { if (!this.notificationHasData) {
this.$router.push({ name: 'groupPlanDetailTaskInformation', params: { this.$router.push({
groupId: this.notification.data.groupId, name: 'groupPlanDetailTaskInformation',
}}); params: {
groupId: this.notification.data.groupId,
},
});
return; return;
} }
@@ -55,9 +58,12 @@ export default {
async needsWork () { async needsWork () {
// Redirect users to the group tasks page if the notification doesn't have data // Redirect users to the group tasks page if the notification doesn't have data
if (!this.notificationHasData) { if (!this.notificationHasData) {
this.$router.push({ name: 'groupPlanDetailTaskInformation', params: { this.$router.push({
groupId: this.notification.data.groupId, name: 'groupPlanDetailTaskInformation',
}}); params: {
groupId: this.notification.data.groupId,
},
});
return; return;
} }

View File

@@ -13,15 +13,15 @@ base-notification(
import BaseNotification from './base'; import BaseNotification from './base';
export default { export default {
props: ['notification', 'canRemove'],
components: { components: {
BaseNotification, BaseNotification,
}, },
props: ['notification', 'canRemove'],
methods: { methods: {
action () { action () {
const groupId = this.notification.data.groupId; const { groupId } = this.notification.data;
this.$router.push({ name: 'groupPlanDetailTaskInformation', params: { groupId }}); this.$router.push({ name: 'groupPlanDetailTaskInformation', params: { groupId } });
}, },
}, },
}; };
</script> </script>

View File

@@ -10,17 +10,17 @@
</template> </template>
<script> <script>
import BaseNotification from './base'; import BaseNotification from './base';
export default { export default {
props: ['notification', 'canRemove'], components: {
components: { BaseNotification,
BaseNotification, },
props: ['notification', 'canRemove'],
methods: {
action () {
this.$router.push({ name: 'tasks' });
}, },
methods: { },
action () { };
this.$router.push({ name: 'tasks'});
},
},
};
</script> </script>

View File

@@ -13,14 +13,14 @@ base-notification(
import BaseNotification from './base'; import BaseNotification from './base';
export default { export default {
props: ['notification', 'canRemove'],
components: { components: {
BaseNotification, BaseNotification,
}, },
props: ['notification', 'canRemove'],
methods: { methods: {
action () { action () {
const groupId = this.notification.data.groupId; const { groupId } = this.notification.data;
this.$router.push({ name: 'groupPlanDetailTaskInformation', params: { groupId }}); this.$router.push({ name: 'groupPlanDetailTaskInformation', params: { groupId } });
}, },
}, },
}; };

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