Skip to content
This repository has been archived by the owner on Nov 28, 2022. It is now read-only.

Commit

Permalink
🎨 ✨ implement night mode
Browse files Browse the repository at this point in the history
no issue
- add functionality for night mode feature flag using alternate
stylesheets
- modify lazy loader service to work with alternate stylesheets
- update feature service to use user accessibility property & add tests
  • Loading branch information
acburdine authored and kevinansfield committed Mar 3, 2017
1 parent e834c96 commit 02c7181
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 49 deletions.
2 changes: 1 addition & 1 deletion app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<meta name="msapplication-square310x310logo" content="{{asset "img/large.png" ghost="true"}}" />

<link rel="stylesheet" href="{{asset "vendor.css" ghost="true" minifyInProduction="true"}}" />
<link rel="stylesheet" href="{{asset "ghost.css" ghost="true" minifyInProduction="true"}}" />
<link rel="stylesheet" href="{{asset "ghost.css" ghost="true" minifyInProduction="true"}}" title="light" />

{{content-for "head-footer"}}
</head>
Expand Down
26 changes: 25 additions & 1 deletion app/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {htmlSafe} from 'ember-string';
import injectService from 'ember-service/inject';
import run from 'ember-runloop';
import {isEmberArray} from 'ember-array/utils';
import observer from 'ember-metal/observer';
import $ from 'jquery';

import AuthConfiguration from 'ember-simple-auth/configuration';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
Expand All @@ -27,6 +29,7 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
config: injectService(),
feature: injectService(),
dropdown: injectService(),
lazyLoader: injectService(),
notifications: injectService(),
upgradeNotification: injectService(),

Expand Down Expand Up @@ -54,7 +57,11 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {

// return the feature loading promise so that we block until settings
// are loaded in order for synchronous access everywhere
return this.get('feature').fetch();
return this.get('feature').fetch().then(() => {
if (this.get('feature.nightShift')) {
return this._setAdminTheme();
}
});
}
},

Expand Down Expand Up @@ -88,6 +95,19 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
}
},

_nightShift: observer('feature.nightShift', function () {
this._setAdminTheme();
}),

_setAdminTheme() {
let nightShift = this.get('feature.nightShift');

return this.get('lazyLoader').loadStyle('dark', 'assets/ghost-dark.css', true).then(() => {
$('link[title=dark]').prop('disabled', !nightShift);
$('link[title=light]').prop('disabled', nightShift);
});
},

actions: {
openMobileMenu() {
this.controller.set('showMobileMenu', true);
Expand All @@ -113,6 +133,10 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
signedIn() {
this.get('notifications').clearAll();
this.send('loadServerNotifications', true);

if (this.get('feature.nightShift')) {
this._setAdminTheme();
}
},

invalidateSession() {
Expand Down
65 changes: 46 additions & 19 deletions app/services/feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,45 @@ import Service from 'ember-service';
import computed from 'ember-computed';
import injectService from 'ember-service/inject';
import set from 'ember-metal/set';
import RSVP from 'rsvp';

// ember-cli-shims doesn't export Error
const {Error: EmberError} = Ember;

export function feature(name) {
return computed(`config.${name}`, `labs.${name}`, {
export function feature(name, user = false) {
let watchedProps = user ? [`accessibility.${name}`] : [`config.${name}`, `labs.${name}`];

return computed.apply(Ember, watchedProps.concat({
get() {
if (user) {
return this.get(`accessibility.${name}`);
}

if (this.get(`config.${name}`)) {
return this.get(`config.${name}`);
}

return this.get(`labs.${name}`) || false;
},
set(key, value) {
this.update(key, value);
this.update(key, value, user);
return value;
}
});
}));
}

export default Service.extend({
store: injectService(),
config: injectService(),
session: injectService(),
notifications: injectService(),

publicAPI: feature('publicAPI'),
subscribers: feature('subscribers'),
nightShift: feature('nightShift', true),

_settings: null,
_user: null,

labs: computed('_settings.labs', function () {
let labs = this.get('_settings.labs');
Expand All @@ -43,40 +53,57 @@ export default Service.extend({
}
}),

accessibility: computed('_user.accessibility', function () {
let accessibility = this.get('_user.accessibility');

try {
return JSON.parse(accessibility) || {};
} catch (e) {
return {};
}
}),

fetch() {
return this.get('store').queryRecord('setting', {type: 'blog,theme,private'}).then((settings) => {
return RSVP.hash({
settings: this.get('store').queryRecord('setting', {type: 'blog,theme,private'}),
user: this.get('session.user')
}).then(({settings, user}) => {
this.set('_settings', settings);
this.set('_user', user);

return true;
});
},

update(key, value) {
let settings = this.get('_settings');
let labs = this.get('labs');
update(key, value, user = false) {
let serviceProperty = user ? 'accessibility' : 'labs';
let model = this.get(user ? '_user' : '_settings');
let featureObject = this.get(serviceProperty);

// set the new key value for either the labs property or the accessibility property
set(featureObject, key, value);

// set the new labs key value
set(labs, key, value);
// update the 'labs' key of the settings model
settings.set('labs', JSON.stringify(labs));
// update the 'labs' or 'accessibility' key of the model
model.set(serviceProperty, JSON.stringify(featureObject));

return settings.save().then(() => {
return model.save().then(() => {
// return the labs key value that we get from the server
this.notifyPropertyChange('labs');
return this.get(`labs.${key}`);
this.notifyPropertyChange(serviceProperty);
return this.get(`${serviceProperty}.${key}`);

}).catch((error) => {
settings.rollbackAttributes();
this.notifyPropertyChange('labs');
model.rollbackAttributes();
this.notifyPropertyChange(serviceProperty);

// we'll always have an errors object unless we hit a
// validation error
if (!error) {
throw new EmberError('Validation of the feature service settings model failed when updating labs.');
throw new EmberError(`Validation of the feature service ${user ? 'user' : 'settings'} model failed when updating ${serviceProperty}.`);
}

this.get('notifications').showAPIError(error);

return this.get(`labs.${key}`);
return this.get(`${serviceProperty}.${key}`);
});
}
});
18 changes: 15 additions & 3 deletions app/services/lazy-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,30 @@ export default Service.extend({
return scriptPromise;
},

loadStyle(key, url) {
loadStyle(key, url, alternate = false) {
if (this.get('testing') || $(`#${key}-styles`).length) {
return RSVP.resolve();
}

return new RSVP.Promise((resolve, reject) => {
let link = document.createElement('link');
link.id = `${key}-styles`;
link.rel = 'stylesheet';
link.rel = alternate ? 'alternate stylesheet' : 'stylesheet';
link.href = `${this.get('ghostPaths.adminRoot')}${url}`;
link.onload = resolve;
link.onload = () => {
if (alternate) {
// If stylesheet is alternate and we disable the stylesheet before injecting into the DOM,
// the onload handler never gets called. Thus, we should disable the link after it has finished loading
link.disabled = true;
}
resolve();
};
link.onerror = reject;

if (alternate) {
link.title = key;
}

$('head').append($(link));
});
}
Expand Down
Loading

0 comments on commit 02c7181

Please sign in to comment.