diff --git a/client/less/main.less b/client/less/main.less
index 60320c5d6a..0bd7729021 100644
--- a/client/less/main.less
+++ b/client/less/main.less
@@ -64,7 +64,6 @@ body.top-and-bottom-margins {
}
body.no-top-and-bottom-margins {
- display: none;
margin: 75px 20px 0px 20px;
}
diff --git a/client/main.js b/client/main.js
index 05fd4dc69f..4ac008e5da 100644
--- a/client/main.js
+++ b/client/main.js
@@ -165,6 +165,7 @@ main.setMapShare = function setMapShare(id) {
$(document).ready(function() {
+ const { Observable } = window.Rx;
var CSRF_HEADER = 'X-CSRF-Token';
var setCSRFToken = function(securityToken) {
@@ -548,51 +549,68 @@ $(document).ready(function() {
// keyboard shortcuts: open map
window.Mousetrap.bind('g m', toggleMap);
- // Night Mode
- function changeMode() {
- var newValue = false;
- try {
- newValue = !JSON.parse(localStorage.getItem('nightMode'));
- } catch (e) {
- console.error('Error parsing value form local storage:', 'nightMode', e);
- }
- localStorage.setItem('nightMode', String(newValue));
- toggleNightMode(newValue);
+ function addAlert(message = '', type = 'alert-info') {
+ return $('.flashMessage').append($(`
+
+ `));
}
- function toggleNightMode(nightModeEnabled) {
- var iframe = document.getElementById('map-aside-frame');
- if (iframe) {
- iframe.src = iframe.src;
+ function toggleNightMode() {
+ if (!main.userId) {
+ return addAlert('Must be logged in to use themes');
}
- var body = $('body');
- body.hide();
- if (nightModeEnabled) {
- body.addClass('night');
+ const iframe$ = document.getElementById('map-aside-frame');
+ const body$ = $('body');
+ if (iframe$) {
+ iframe$.src = iframe$.src;
+ }
+ body$.hide();
+ let updateThemeTo;
+ if (body$.hasClass('night')) {
+ body$.removeClass('night');
+ updateThemeTo = 'default';
} else {
- body.removeClass('night');
+ body$.addClass('night');
+ updateThemeTo = 'night';
}
- body.fadeIn('100');
+ body$.fadeIn('100');
+ const options = {
+ url: `/api/users/${main.userId}/update-theme`,
+ type: 'POST',
+ data: { theme: updateThemeTo },
+ dataType: 'json'
+ };
+ return $.ajax(options)
+ .success(() => console.log('theme updated successfully'))
+ .fail(err => {
+ let message;
+ try {
+ message = JSON.parse(err.responseText).error.message;
+ } catch (error) {
+ return null;
+ }
+ if (!message) {
+ return null;
+ }
+ return addAlert(message);
+ });
}
- if (typeof localStorage.getItem('nightMode') !== 'undefined') {
- var oldVal = false;
- try {
- oldVal = JSON.parse(localStorage.getItem('nightMode'));
- } catch (e) {
- console.error('Error parsing value form local storage:', 'nightMode', e);
- }
- toggleNightMode(oldVal);
- $('.nightMode-btn').on('click', function() {
- changeMode();
- });
- } else {
- localStorage.setItem('nightMode', 'false');
- toggleNightMode('false');
- }
+ Observable.merge(
+ Observable.fromEvent($('#night-mode'), 'click'),
+ Observable.create(observer => {
+ window.Mousetrap.bind('g t n', () => observer.onNext());
+ })
+ )
+ .debounce(500)
+ .subscribe(toggleNightMode, err => console.error(err));
// Hot Keys
- window.Mousetrap.bind('g t n', changeMode);
window.Mousetrap.bind('g n n', () => {
// Next Challenge
window.location = '/challenges/next-challenge';
diff --git a/common/models/user.js b/common/models/user.js
index 0ced33a968..b26b5cb13d 100644
--- a/common/models/user.js
+++ b/common/models/user.js
@@ -307,49 +307,6 @@ module.exports = function(User) {
}
}
);
-
- User.prototype.updateEmail = function updateEmail(email) {
- if (this.email && this.email === email) {
- return Promise.reject(new Error(
- `${email} is already associated with this account.`
- ));
- }
- return User.doesExist(null, email)
- .then(exists => {
- if (exists) {
- return Promise.reject(
- new Error(`${email} is already associated with another account.`)
- );
- }
- return this.update$({ email }).toPromise();
- });
- };
-
- User.remoteMethod(
- 'updateEmail',
- {
- isStatic: false,
- description: 'updates the email of the user object',
- accepts: [
- {
- arg: 'email',
- type: 'string',
- required: true
- }
- ],
- returns: [
- {
- arg: 'status',
- type: 'object'
- }
- ],
- http: {
- path: '/update-email',
- verb: 'POST'
- }
- }
- );
-
User.giveBrowniePoints =
function giveBrowniePoints(receiver, giver, data = {}, dev = false, cb) {
const findUser = observeMethod(User, 'findOne');
@@ -467,6 +424,91 @@ module.exports = function(User) {
}
);
+ User.prototype.updateEmail = function updateEmail(email) {
+ if (this.email && this.email === email) {
+ return Promise.reject(new Error(
+ `${email} is already associated with this account.`
+ ));
+ }
+ return User.doesExist(null, email)
+ .then(exists => {
+ if (exists) {
+ return Promise.reject(
+ new Error(`${email} is already associated with another account.`)
+ );
+ }
+ return this.update$({ email }).toPromise();
+ });
+ };
+
+ User.remoteMethod(
+ 'updateEmail',
+ {
+ isStatic: false,
+ description: 'updates the email of the user object',
+ accepts: [
+ {
+ arg: 'email',
+ type: 'string',
+ required: true
+ }
+ ],
+ returns: [
+ {
+ arg: 'status',
+ type: 'object'
+ }
+ ],
+ http: {
+ path: '/update-email',
+ verb: 'POST'
+ }
+ }
+ );
+
+ User.themes = {
+ night: true,
+ default: true
+ };
+ User.prototype.updateTheme = function updateTheme(theme) {
+ if (!this.constructor.themes[theme]) {
+ const err = new Error(
+ 'Theme is not valid.'
+ );
+ err.messageType = 'info';
+ err.userMessage = err.message;
+ return Promise.reject(err);
+ }
+ return this.update$({ theme })
+ .map({ updatedTo: theme })
+ .toPromise();
+ };
+
+ User.remoteMethod(
+ 'updateTheme',
+ {
+ isStatic: false,
+ description: 'updates the users chosen theme',
+ accepts: [
+ {
+ arg: 'theme',
+ type: 'string',
+ required: true
+ }
+ ],
+ returns: [
+ {
+ arg: 'status',
+ type: 'object'
+ }
+ ],
+ http: {
+ path: '/update-theme',
+ verb: 'POST'
+ }
+ }
+ );
+
// user.updateTo$(updateData: Object) => Observable[Number]
User.prototype.update$ = function update$(updateData) {
const id = this.getId();
diff --git a/common/models/user.json b/common/models/user.json
index ad7294688d..cdbff24c58 100644
--- a/common/models/user.json
+++ b/common/models/user.json
@@ -188,6 +188,10 @@
},
"timezone": {
"type": "string"
+ },
+ "theme": {
+ "type": "string",
+ "default": "default"
}
},
"validations": [],
@@ -242,6 +246,13 @@
"principalId": "$owner",
"permission": "ALLOW",
"property": "updateEmail"
+ },
+ {
+ "accessType": "EXECUTE",
+ "principalType": "ROLE",
+ "principalId": "$owner",
+ "permission": "ALLOW",
+ "property": "updateTheme"
}
],
"methods": []
diff --git a/server/middlewares/global-locals.js b/server/middlewares/global-locals.js
index 1871eb95ff..aa2d7e32d0 100644
--- a/server/middlewares/global-locals.js
+++ b/server/middlewares/global-locals.js
@@ -6,6 +6,10 @@ export default function globalLocals() {
if (req.csrfToken) {
res.expose({ token: res.locals._csrf }, 'csrf');
}
+ res.locals.theme = req.user && req.user.theme ||
+ req.cookies.theme ||
+ 'default';
+
next();
};
}
diff --git a/server/views/account/settings.jade b/server/views/account/settings.jade
index c992ece48e..e904dbc413 100644
--- a/server/views/account/settings.jade
+++ b/server/views/account/settings.jade
@@ -5,7 +5,7 @@ block content
h2.text-center Actions
.row
.col-xs-12
- a.btn.btn-lg.btn-block.btn-primary.btn-link-social(class = "nightMode-btn") Night Mode
+ a#night-mode.btn.btn-lg.btn-block.btn-primary.btn-link-social Night Mode
.row
.col-xs-12
if (!user.isGithubCool)
@@ -133,4 +133,4 @@ block content
form(action='/account/delete', method='POST')
input(type='hidden', name='_csrf', value=_csrf)
button.btn.btn-danger.btn-block(type='submit')
- | I am 100% sure I want to delete my account and all of my progress
\ No newline at end of file
+ | I am 100% sure I want to delete my account and all of my progress
diff --git a/server/views/layout-wide.jade b/server/views/layout-wide.jade
index 4ffc168436..cf548c3edc 100644
--- a/server/views/layout-wide.jade
+++ b/server/views/layout-wide.jade
@@ -8,9 +8,9 @@ html(lang='en')
include partials/scripts
block content
else
- body.no-top-and-bottom-margins
- include partials/scripts
- include partials/navbar
- include partials/flash
- block content
- include partials/footer
+ body.no-top-and-bottom-margins(class=theme !== 'default' ? theme : '')
+ include partials/scripts
+ include partials/navbar
+ include partials/flash
+ block content
+ include partials/footer
diff --git a/server/views/partials/scripts.jade b/server/views/partials/scripts.jade
index a002f935eb..988b26bd9f 100644
--- a/server/views/partials/scripts.jade
+++ b/server/views/partials/scripts.jade
@@ -7,5 +7,11 @@ script.
ga('require', 'displayfeatures');
ga('send', 'pageview');
// Leave the below lines alone!
+script.
+ (function(global) {
+ global.main = global.main || {};
+ global.main.isLoggedIn = !{JSON.stringify(!!user)};
+ global.main.userId = !{JSON.stringify(user && user.id || false)};
+ }(window))
script(src=rev('/js', 'vendor-main.js'))
script(src=rev('/js', 'main.js'))