Skip to content
This repository has been archived by the owner on May 24, 2021. It is now read-only.

Commit

Permalink
feat(loader): Adds loading indicator
Browse files Browse the repository at this point in the history
- refactors embed javascript
  • Loading branch information
alexander-heimbuch committed Feb 13, 2018
1 parent a1abe8a commit f117dc6
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 144 deletions.
Binary file modified design.sketch
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@
"@podlove/html5-audio-driver": "0.7.1",
"babel-polyfill": "6.26.0",
"binary-search": "1.3.3",
"bluebird": "3.5.1",
"clipboard": "1.7.1",
"color": "3.0.0",
"data.task": "^3.1.1",
"detect-browser": "2.0.0",
"hashcode": "1.0.3",
"iframe-resizer": "3.5.16",
Expand Down Expand Up @@ -97,6 +97,7 @@
"cross-env": "5.1.3",
"css-loader": "0.28.9",
"cz-conventional-changelog": "2.1.0",
"eslint": "4.17.0",
"eslint-plugin-html": "4.0.2",
"extract-text-webpack-plugin": "3.0.2",
"file-loader": "1.1.6",
Expand Down
159 changes: 39 additions & 120 deletions src/embed/embed.js
Original file line number Diff line number Diff line change
@@ -1,135 +1,54 @@
/* globals BASE */

import 'babel-polyfill'
import { get, head, isString } from 'lodash'
import Bluebird from 'bluebird'

import { findNode, createNode, appendNode, tag } from 'utils/dom'
import requestConfig from 'utils/request'
import { urlParameters } from 'utils/url'

import { get, compose } from 'lodash'
import { iframeResizer } from 'iframe-resizer'
// eslint-disable-next-line
import iframeResizerContentWindow from 'raw-loader!iframe-resizer/js/iframeResizer.contentWindow.js'

// Player renderer
const playerSandbox = anchor => {
const frame = createNode('iframe')

frame.setAttribute('width', anchor.offsetWidth)

frame.setAttribute('min-width', '100%')
frame.setAttribute('seamless', '')
frame.setAttribute('scrolling', 'no')
frame.setAttribute('frameborder', '0')

// Reset the width on viewport resize
window.addEventListener('resize', () => {
frame.setAttribute('width', anchor.offsetWidth)
})

appendNode(anchor, frame)
return frame
}

const injectPlayer = (sandbox, player) => new Bluebird(resolve => {
const sandboxDoc = get(sandbox, ['contentWindow', 'document'])

const documentLoaded = () => {
if (sandboxDoc.readyState === 'complete') {
return resolve(sandbox)
}

return setTimeout(documentLoaded, 150)
}

sandboxDoc.open()
sandboxDoc.write('<!DOCTYPE html>')
sandboxDoc.write('<html>')
sandboxDoc.write('<head><meta charset="utf-8" /></head>')
sandboxDoc.write(player)
sandboxDoc.close()

return documentLoaded()
})

const getPodloveStore = sandbox =>
get(sandbox, ['contentWindow', 'PODLOVE_STORE', 'store'])
import { findNode, tag } from 'utils/dom'
import requestConfig from 'utils/request'
import { sandbox, sandboxWindow } from 'utils/sandbox'

const preloader = sandbox => ({
init: () => {
sandbox.style.opacity = 0
// maximum width player
sandbox.style['max-width'] = '768px'
sandbox.style.transition = 'all 500ms'
},
done: () => {
sandbox.style.opacity = 1
sandbox.style.height = 'auto'
}
})
import loader from './loader'

const renderPlayer = anchor => player => {
const sandbox = playerSandbox(anchor)
const loader = preloader(sandbox)
const player = config => [
// Config
tag('script', `window.PODLOVE = ${JSON.stringify(config)}`),

loader.init()
// Loader
loader(config),

return injectPlayer(sandbox, player)
.then(sandbox => {
iframeResizer({
checkOrigin: false,
log: false
}, sandbox)
// Entry
tag('PodlovePlayer'),

loader.done()
})
.return(sandbox)
.then(getPodloveStore)
}
// Bundles
tag('link', '', {rel: 'stylesheet', href: `${get(config.reference, 'base', BASE)}/style.css`}),
tag('script', '', {type: 'text/javascript', src: `${get(config.reference, 'base', BASE)}/vendor.js`}),
tag('script', '', {type: 'text/javascript', src: `${get(config.reference, 'base', BASE)}/window.js`}),

const getConfig = (episode) =>
Bluebird.resolve(episode)
// If the config is a string, lets assume that this will point to the remote config json
.then(config => isString(config) ? requestConfig(config) : config)
// iFrameResizer
tag('script', iframeResizerContentWindow)
].join('')

const dispatchUrlParameters = store => {
store.dispatch({
type: 'SET_URL_PARAMS',
payload: urlParameters
})
const resizer = sandbox => {
iframeResizer({
checkOrigin: false,
log: false
}, sandbox)

return store
return sandbox
}

// Config Node
const configNode = (config = {}) => tag('script', `window.PODLOVE = ${JSON.stringify(config)}`)

// Player Logic
const styleBundle = config => tag('link', '', {rel: 'stylesheet', href: `${get(config.reference, 'base', BASE)}/style.css`})
const vendorBundle = config => tag('script', '', {type: 'text/javascript', src: `${get(config.reference, 'base', BASE)}/vendor.js`})
const appBundle = config => tag('script', '', {type: 'text/javascript', src: `${get(config.reference, 'base', BASE)}/window.js`})

// Dynamic resizer
const dynamicResizer = tag('script', iframeResizerContentWindow)

// Transclusion point
const playerEntry = tag('PodlovePlayer')

// Bootstrap
window.podlovePlayer = (selector, episode) => {
const anchor = typeof selector === 'string' ? head(findNode(selector)) : selector

return getConfig(episode)
.then(config => Bluebird.all([
playerEntry,
configNode(config),
styleBundle(config),
vendorBundle(config),
appBundle(config),
dynamicResizer
]))
.then(result => result.join(''))
.then(renderPlayer(anchor))
.then(dispatchUrlParameters)
}
const sandboxFromSelector = compose(sandbox, findNode)

window.podlovePlayer = (selector, episode) =>
requestConfig(episode)
.then(player)
.then(sandboxFromSelector(selector))
.then(resizer)
.then(sandboxWindow(['PODLOVE_STORE', 'store']))
.catch(() => {
console.group(`Can't load player with config`)
console.error('selector', selector)
console.error('config', episode)
console.groupEnd()
})
35 changes: 35 additions & 0 deletions src/embed/loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import color from 'color'
import { get } from 'lodash'

import { tag } from 'utils/dom'
import css from 'css-loader!autoprefixer-loader!sass-loader!../styles/_loader.scss'

const style = tag('style', css.toString())

const dom = ({ theme }) => {
const light = '#fff'
const dark = '#000'

const main = get(theme, 'main', '#2B8AC6')
const luminosity = color(main).luminosity()

const highlight = get(theme, 'highlight', luminosity < 0.25 ? light : dark)

return `<div class="loader" id="loader" style="background: ${main}">
<div class="dot bounce1" style="background: ${highlight}"></div>
<div class="dot bounce2" style="background: ${highlight}"></div>
<div class="dot bounce3" style="background: ${highlight}"></div>
</div>`
}

const script = tag('script', `
var loader = document.getElementById('loader')
window.addEventListener('load', function() {
loader.className += ' done'
setTimeout(loader.remove, 300)
})
`)

export default config => ([style, dom(config), script].join(''))
56 changes: 56 additions & 0 deletions src/styles/_loader.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
.loader {
width: 100%;
height: 100%;
position: fixed;
left: 0;
top: 0;
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
opacity: 1;
transition: opacity linear 300ms;

.dot {
width: 20px;
height: 20px;
margin: 3px;
border-radius: 100%;
display: inline-block;
animation: loader 1.4s ease-in-out 0s infinite both;
}

.bounce1 {
animation-delay: -0.32s;
}

.bounce2 {
animation-delay: -0.16s;
}

&.done {
opacity: 0;
}
}

@-webkit-keyframes loader {
0%,
80%,
100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}

@keyframes loader {
0%,
80%,
100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}
20 changes: 17 additions & 3 deletions src/utils/dom.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { curry, compose, uniq, concat, join, filter } from 'lodash/fp'
import { curry, compose, uniq, concat, join, filter, head } from 'lodash/fp'

export const findNode = selector => document.querySelectorAll(selector)
export const findNode = selector => typeof selector === 'string' ? head(document.querySelectorAll(selector)) : selector
export const createNode = tag => document.createElement(tag)
export const appendNode = curry((node, child) => node.appendChild(child))
export const appendNode = curry((node, child) => {
node.appendChild(child)

return child
})

export const tag = curry((tag, value = '', attributes = {}) => {
let attr = Object.keys(attributes).map(attribute => ` ${attribute}="${attributes[attribute]}"`)
Expand All @@ -15,6 +19,8 @@ export const setStyles = (attrs = {}) => el => {
Object.keys(attrs).forEach(property => {
el.style[property] = attrs[property]
})

return el
}

export const getClasses = el => el.className.split(' ')
Expand All @@ -32,3 +38,11 @@ export const removeClasses = (...classes) => el => {

return el
}

export const setAttributes = (attrs = {}) => el => {
Object.keys(attrs).forEach(property => {
el.setAttribute(property, attrs[property])
})

return el
}
12 changes: 7 additions & 5 deletions src/utils/request.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import request from 'superagent'

export default url =>
request
.get(url)
.query({ format: 'json' })
.set('Accept', 'application/json')
.then(res => res.body)
typeof url === 'string'
? request
.get(url)
.query({ format: 'json' })
.set('Accept', 'application/json')
.then(res => res.body)
: new Promise(resolve => resolve(url))
Loading

0 comments on commit f117dc6

Please sign in to comment.