From b093595f1d0b60dbe506427b69fe1cecc8d26a8e Mon Sep 17 00:00:00 2001 From: Robbie Antenesse Date: Wed, 25 Sep 2019 12:32:52 -0600 Subject: [PATCH] Restructure app initiation - Split initialization steps into their own files. - Use Choo routes instead of viewManager to properly set up 404 - Add styling for different colors of Picnic cards --- app/appListeners.js | 27 ++++++ app/appRoutes.js | 18 ++++ app/appState.js | 16 ++++ app/appUtilities.js | 32 +++++++ app/i18n/locales/en.json | 4 + app/index.js | 87 +++---------------- app/styles/picnic-customizations/_custom.scss | 36 ++++++-- app/views/404.js | 12 +++ app/views/{manager.js => global.js} | 33 +------ 9 files changed, 153 insertions(+), 112 deletions(-) create mode 100644 app/appListeners.js create mode 100644 app/appRoutes.js create mode 100644 app/appState.js create mode 100644 app/appUtilities.js create mode 100644 app/views/404.js rename app/views/{manager.js => global.js} (64%) diff --git a/app/appListeners.js b/app/appListeners.js new file mode 100644 index 0000000..eb8ba20 --- /dev/null +++ b/app/appListeners.js @@ -0,0 +1,27 @@ +export const appListeners = (app, state, emitter) => { + emitter.on('DOMContentLoaded', () => { + document.title = app.siteConfig.siteName; + // Emitter listeners + emitter.on('render', callback => { + app.setSessionState(); + // This is a dirty hack to get the callback to call *after* re-rendering. + if (callback && typeof callback === "function") { + setTimeout(() => { + callback(); + }, 50); + } + }); + + emitter.on('change-view', newView => { + // Change the view and call render. Makes it easier to call within views. + state.currentView = newView; + emitter.emit('render', () => { }); + }); + + emitter.on('set-language', newLanguage => { + app.setSettingsItem('lang', newLanguage); + state.language = newLanguage; + emitter.emit('render', () => { }); + }); + }); +} \ No newline at end of file diff --git a/app/appRoutes.js b/app/appRoutes.js new file mode 100644 index 0000000..2c6c6b8 --- /dev/null +++ b/app/appRoutes.js @@ -0,0 +1,18 @@ +import { I18n } from './i18n'; +import { globalView } from './views/global'; +import { homeView } from './views/home'; +import { loginView } from './views/login'; +import { searchView } from './views/search'; +import { errorView } from './views/404'; + +export const appRoutes = (app) => { + const i18n = new I18n(app.state); // Global I18n class passed to all views + + app.route('/', (state, emit) => globalView(state, emit, i18n, homeView)); + + app.route('/login', (state, emit) => globalView(state, emit, i18n, loginView)); + + app.route('/search', (state, emit) => globalView(state, emit, i18n, searchView)); + + app.route('/404', (state, emit) => globalView(state, emit, i18n, errorView)); +} diff --git a/app/appState.js b/app/appState.js new file mode 100644 index 0000000..609fc22 --- /dev/null +++ b/app/appState.js @@ -0,0 +1,16 @@ +export const appState = (app, state, emitter) => { + const sessionState = app.getSessionState(); + if (sessionState) { + Object.keys(sessionState).forEach(key => { + if (typeof state[key] === 'undefined') { + state[key] = sessionState[key]; + } + }); + } else { + // Default state variables + state.currentView = 'home'; + state.language = app.getSettingsItem('lang') ? app.getSettingsItem('lang') : (navigator.language || navigator.userLanguage).split('-')[0]; + state.viewStates = {}; + state.isLoggedIn = false; + } +} \ No newline at end of file diff --git a/app/appUtilities.js b/app/appUtilities.js new file mode 100644 index 0000000..c134332 --- /dev/null +++ b/app/appUtilities.js @@ -0,0 +1,32 @@ +export const appUtilities = (app) => { + app.getSettingsItem = settingsKey => { + let savedSettings = window.localStorage.getItem('settings'); + if (savedSettings) { + savedSettings = JSON.parse(savedSettings); + if (typeof savedSettings[settingsKey] !== 'undefined') { + return savedSettings[settingsKey]; + } + } + return null; + } + app.setSettingsItem = (settingsKey, value) => { + let savedSettings = window.localStorage.getItem('settings'); + if (savedSettings) { + savedSettings = JSON.parse(savedSettings); + } else { + savedSettings = {}; + } + savedSettings[settingsKey] = value; + return window.localStorage.setItem('settings', JSON.stringify(savedSettings)); + } + app.getSessionState = () => { + let sessionState = window.sessionStorage.getItem('sessionState'); + if (sessionState) { + return JSON.parse(sessionState); + } + return null; + } + app.setSessionState = () => { + return window.sessionStorage.setItem('sessionState', JSON.stringify(app.state)); + } +} \ No newline at end of file diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 403b3f2..6a33581 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -18,6 +18,10 @@ "logged_out_recent_updates": "Recent Updates", "logged_out_join_now": "Join Now!" }, + "404": { + "header": "Oops!", + "subheader": "It looks like the page you requested doesn't exist. Please try a different one!" + }, "login": { "log_in": "Log In", "email": "Email", diff --git a/app/index.js b/app/index.js index 83fae20..d82d77a 100644 --- a/app/index.js +++ b/app/index.js @@ -3,7 +3,10 @@ import 'babel-polyfill'; import choo from 'choo'; import config from './config.json'; -import { viewManager } from './views/manager'; +import { appRoutes } from './appRoutes'; +import { appListeners } from './appListeners'; +import { appState } from './appState.js'; +import { appUtilities } from './appUtilities.js'; const app = choo(); @@ -13,88 +16,18 @@ if (process.env.NODE_ENV !== 'production') { } app.use((state, emitter) => { - app.getSettingsItem = settingsKey => { - let savedSettings = window.localStorage.getItem('settings'); - if (savedSettings) { - savedSettings = JSON.parse(savedSettings); - if (typeof savedSettings[settingsKey] !== 'undefined') { - return savedSettings[settingsKey]; - } - } - return null; - } - app.setSettingsItem = (settingsKey, value) => { - let savedSettings = window.localStorage.getItem('settings'); - if (savedSettings) { - savedSettings = JSON.parse(savedSettings); - } else { - savedSettings = {}; - } - savedSettings[settingsKey] = value; - return window.localStorage.setItem('settings', JSON.stringify(savedSettings)); - } - app.getSessionState = () => { - let sessionState = window.sessionStorage.getItem('sessionState'); - if (sessionState) { - return JSON.parse(sessionState); - } - return null; - } - app.setSessionState = () => { - return window.sessionStorage.setItem('sessionState', JSON.stringify(app.state)); - } + app.siteConfig = config; + appUtilities(app); }); -// App state and emitters app.use((state, emitter) => { - const sessionState = app.getSessionState(); - if (sessionState) { - Object.keys(sessionState).forEach(key => { - if (typeof state[key] === 'undefined') { - state[key] = sessionState[key]; - } - }); - } else { - // Default state variables - state.currentView = 'home'; - state.language = app.getSettingsItem('lang') ? app.getSettingsItem('lang') : (navigator.language || navigator.userLanguage).split('-')[0]; - state.viewStates = {}; - state.isLoggedIn = false; - } + appState(app, state); // Listeners - emitter.on('DOMContentLoaded', () => { - document.title = config.siteName; - // Emitter listeners - emitter.on('render', callback => { - app.setSessionState(); - // This is a dirty hack to get the callback to call *after* re-rendering. - if (callback && typeof callback === "function") { - setTimeout(() => { - callback(); - }, 50); - } - }); - - emitter.on('change-view', newView => { - // Change the view and call render. Makes it easier to call within views. - state.currentView = newView; - emitter.emit('render', () => {}); - }); - - emitter.on('set-language', newLanguage => { - app.setSettingsItem('lang', newLanguage); - state.language = newLanguage; - emitter.emit('render', () => {}); - }); - }); + appListeners(app, state, emitter); }); -// For the main screen, pass the viewManager function in viewManager.js, -// which is given the app's state from above and the emitter.emit method that -// triggers the app's emitter listeners. -app.route('/', viewManager); -app.route('/:page', viewManager); -app.route('/404', viewManager); +// Routes +appRoutes(app); app.mount('body'); // Overwrite the `` tag with the content of the Choo app diff --git a/app/styles/picnic-customizations/_custom.scss b/app/styles/picnic-customizations/_custom.scss index 3490cc0..1fdcb1e 100644 --- a/app/styles/picnic-customizations/_custom.scss +++ b/app/styles/picnic-customizations/_custom.scss @@ -107,12 +107,38 @@ footer nav { margin: 0 0 0 auto; } -.card.info { - background: $picnic-info; - color: $picnic-white; - - * { +.card { + &.info { + background: $picnic-info; color: $picnic-white; + + * { + color: $picnic-white; + } + } + &.success { + background: $picnic-success; + color: $picnic-white; + + * { + color: $picnic-white; + } + } + &.warning { + background: $picnic-warning; + color: $picnic-white; + + * { + color: $picnic-white; + } + } + &.error { + background: $picnic-error; + color: $picnic-white; + + * { + color: $picnic-white; + } } } diff --git a/app/views/404.js b/app/views/404.js new file mode 100644 index 0000000..a089588 --- /dev/null +++ b/app/views/404.js @@ -0,0 +1,12 @@ +import html from 'choo/html'; + +export const errorView = (state, emit, i18n) => { + return html`
+
+

${i18n.__('404.header')}

+
+
+

${i18n.__('404.subheader')}

+
+
`; +} \ No newline at end of file diff --git a/app/views/manager.js b/app/views/global.js similarity index 64% rename from app/views/manager.js rename to app/views/global.js index 9d32768..36bfe59 100644 --- a/app/views/manager.js +++ b/app/views/global.js @@ -2,34 +2,9 @@ import html from 'choo/html'; import headerImage from '../../dev/images/header.png'; -import { I18n } from '../i18n'; -import { homeView } from './home'; -import { loginView } from './login'; -import { searchView } from './search'; - -export const viewManager = (state, emit) => { - const i18n = new I18n(state); // Global I18n class passed to all views - // In viewManager all we are doing is checking the app's state - // and passing the state and emit to the relevant view. - let htmlContent = html`
loading
`; - switch (state.params.page) { - case 'home': - default: { - htmlContent = homeView(state, emit, i18n); - break; - } - case 'login': { - htmlContent = loginView(state, emit, i18n); - break; - } - case 'search': { - htmlContent = searchView(state, emit, i18n); - break; - } - } - +export const globalView = (state, emit, i18n, view) => { // Create a wrapper for view content that includes global header/footer - let view = html` + return html`
- ${htmlContent} + ${view(state, emit, i18n)}
`; - - return view; } \ No newline at end of file