fepper-ui
Version:
The client-side package that powers the Fepper UI
363 lines (284 loc) • 9.84 kB
JavaScript
/**
* Limit this class to handling UI navigation, and the sizing and loading of the iframe.
*
* Copyright (c) 2013-2016 Dave Olsen, http://dmolsen.com
* Licensed under the MIT license.
*/
export default class PatternViewport {
/* CLASS FIELDS */
// The following function is declared as a class field to retain the Event function prototype while keeping the
// class constructor tidy. Exposed as a property on the instance so it can be unit tested.
receiveIframeMessage = (event) => {
const data = this.uiFns.receiveIframeMessageBoilerplate(event);
/* istanbul ignore if */
if (!data) {
return;
}
switch (data.event) {
case 'patternlab.bodyClick': {
this.uiFns.closeAllPanels();
break;
}
case 'patternlab.keyPress': {
switch (data.keyPress) {
case 'ctrl+alt+0':
case 'ctrl+shift+0':
this.goXXSmall();
break;
case 'ctrl+shift+x':
this.goXSmall();
break;
case 'ctrl+shift+s':
this.goSmall();
break;
case 'ctrl+shift+m':
this.goMedium();
break;
case 'ctrl+shift+l':
this.goLarge();
break;
case 'ctrl+alt+w':
case 'ctrl+shift+w':
this.goWhole();
break;
case 'ctrl+alt+r':
this.goRandom();
break;
case 'ctrl+alt+g':
this.uiFns.toggleGrow();
break;
case 'ctrl+shift+d':
this.uiFns.toggleDisco();
break;
}
break;
}
case 'patternlab.pageLoad': {
if (
!this.urlHandler.skipBack &&
(!this.#root.history.state || this.#root.history.state.pattern !== data.patternPartial)
) {
this.urlHandler.pushPattern(data.patternPartial);
}
// Reset default.
this.urlHandler.skipBack = false;
break;
}
case 'patternlab.updatePatternInfo': {
const addressReplacement = this.urlHandler.getAddressReplacement(data.patternPartial);
// The primary use-case for this function is replacing history state instead of pushing it.
this.#root.history.replaceState({pattern: data.patternPartial}, null, addressReplacement);
this.uiFns.updatePatternInfo(data.patternPartial, data.path);
break;
}
}
};
// Private class fields.
#fepperUi;
#root;
/* CONSTRUCTOR */
constructor(fepperUi, root) {
this.#fepperUi = fepperUi;
this.#root = root;
this.$orgs = fepperUi.requerio.$orgs;
this.requerio = fepperUi.requerio;
}
/* GETTERS for fepperUi instance props in case they are undefined at instantiation. */
get dataSaver() {
return this.#fepperUi.dataSaver;
}
get uiData() {
return this.#fepperUi.uiData;
}
get uiFns() {
return this.#fepperUi.uiFns;
}
get uiProps() {
return this.#fepperUi.uiProps;
}
get urlHandler() {
return this.#fepperUi.urlHandler;
}
/* METHODS */
// Declared before other methods because it must be unit tested before other methods. Be sure to e2e test .stoke().
stoke() {
const protocol = this.#root.location.protocol;
// Use CSS to hide scrape menu if using "file:" protocol.
this.$orgs['#patternlab-html'].dispatchAction('addClass', 'protocol-' + protocol.slice(0, -1));
/* istanbul ignore if */
if (this.uiProps.isMobile) {
this.$orgs['#patternlab-html'].dispatchAction('addClass', 'mobile');
}
// Set up viewport.
const searchParams = this.urlHandler.getSearchParams();
let vpWidth = 0;
if (searchParams.d || searchParams.disco) {
// Disco param.
this.uiFns.startDisco();
}
else if (searchParams.g || searchParams.grow) {
// Grow param.
this.uiFns.startGrow();
}
else {
if (searchParams.w || searchParams.width) {
// Width param.
const vpWidthParam = searchParams.w || searchParams.width;
if (vpWidthParam.indexOf('em') > -1) {
vpWidth = Math.round(parseFloat(vpWidthParam) * this.uiProps.bodyFontSize);
}
else {
vpWidth = parseInt(vpWidthParam);
}
if (!Number.isNaN(vpWidth)) {
this.uiProps.vpWidth = vpWidth;
this.dataSaver.updateValue('vpWidth', vpWidth);
}
}
else {
// Check for viewport width cookie.
vpWidth = Number(this.dataSaver.findValue('vpWidth'));
}
}
// Load patternPartial.
let patternPartial;
if (searchParams.p || searchParams.pattern) {
patternPartial = searchParams.p || searchParams.pattern;
}
else if (this.uiData.config.defaultPattern) {
patternPartial = this.uiData.config.defaultPattern;
}
else {
patternPartial = 'viewall';
}
try {
/* istanbul ignore if */
if (Object.keys(this.uiData.patternPaths).length <= 1) {
this.$orgs['#sg-nav-message'].dispatchAction('removeClass', 'visually-hidden');
// Returning instead of throwing because #sg-nav-message tells to check the console for errors.
// Throwing an explicit error here would likely be a red herring.
return;
}
}
catch (err) /* istanbul ignore next */ {
this.$orgs['#sg-nav-message'].dispatchAction('removeClass', 'visually-hidden');
throw err;
}
let iframePath = this.uiData.patternPaths[patternPartial];
/* istanbul ignore else */
if (iframePath) {
this.urlHandler.skipBack = true;
// Update DOM with pattern info.
this.uiFns.updatePatternInfo(patternPartial, iframePath);
// Update history. Need to do this so urlHandler.popPattern has an Event.state.pattern to work with.
this.#root.history.replaceState({pattern: patternPartial}, null, null);
}
else {
iframePath = `?p=${patternPartial}`;
}
// Render Feplet templates.
try {
// Render pattern navigation.
const templateRenderedNav = window.Feplet.render(
this.$orgs['#sg-nav-target'].html(),
{patternTypes: this.uiData.navItems.patternTypes, pathsPublic: this.uiData.config.pathsPublic}
);
this.$orgs['#sg-nav-target'].dispatchAction('html', templateRenderedNav);
this.$orgs['#sg-nav-target'].dispatchAction('removeClass', 'visually-hidden');
// Render UI controls. "Ish" is apparently the pre-patternlab name for the viewport resizer.
// http://bradfrost.com/blog/post/ish/
const templateRenderedIsh = window.Feplet.render(
this.$orgs['#sg-controls'].html(),
this.uiData.ishControls
);
this.$orgs['#sg-controls'].dispatchAction('html', templateRenderedIsh);
this.$orgs['#sg-controls'].dispatchAction('removeClass', 'visually-hidden');
// Erase preemptive warning message.
this.$orgs['#sg-nav-message'].dispatchAction('empty');
}
catch (err) /* istanbul ignore next */ {
this.$orgs['#sg-nav-message'].dispatchAction('removeClass', 'visually-hidden');
throw err;
}
// Render resize buttons.
this.renderResizeBtns();
// Load pattern in iframe.
this.$orgs['#sg-viewport'][0].contentWindow.location.replace(iframePath);
// For server-side testing.
if (typeof global === 'object') {
if (vpWidth) {
this.uiFns.updateViewportWidth(vpWidth);
}
else {
// If no search param or cookie, update the size readings with the innerWidth of the viewport.
// Must be innerWidth even though other #sg-viewport behaviors act on style.width.
// This is because style.width is undefined at this point.
this.uiFns.updateSizeReading(this.$orgs['#sg-viewport'].getState().innerWidth);
}
}
}
goResize(bp) {
this.uiFns.stopDisco();
this.uiFns.stopGrow();
this.uiFns.sizeIframe(this.uiProps.bpObj[bp]);
if (this.$orgs[`#sg-size-${bp}`] && typeof this.$orgs[`#sg-size-${bp}`].dispatchAction === 'function') {
this.$orgs[`#sg-size-${bp}`].dispatchAction('focus');
}
else {
this.#root.$(`#sg-size-${bp}`).focus();
}
}
// These "go" methods are listed as they appear in the UI.
// Handle extra extra small button.
goXXSmall() {
this.goResize('xx');
}
// Handle extra small button.
goXSmall() {
this.goResize('xs');
}
// Handle small button.
goSmall() {
this.goResize('sm');
}
// Handle medium button.
goMedium() {
this.goResize('md');
}
// Handle large button.
goLarge() {
this.goResize('lg');
}
// Handle whole button.
goWhole() {
this.uiFns.stopDisco();
this.uiFns.stopGrow();
this.uiFns.sizeIframe(this.uiProps.sw, true, true);
this.$orgs['#sg-size-w'].dispatchAction('focus');
}
// Handle random button.
goRandom() {
this.uiFns.stopDisco();
this.uiFns.stopGrow();
this.uiFns.sizeIframe(this.uiFns.getRandom(this.uiProps.minViewportWidth, this.uiProps.sw));
this.$orgs['#sg-size-random'].dispatchAction('focus');
}
renderResizeBtns() {
/* istanbul ignore if */
if (!this.$orgs['#sg-resize-btns'].length) {
return;
}
const bpObj = this.uiProps.bpObj;
const resizeBtns = [];
let html = '';
Object.keys(bpObj).reverse().forEach((bp) => {
// HTML for rendering.
html += `<li><a href="#" id="sg-size-${bp}">${bp.toUpperCase()}</a></li>`;
// Since breakpoint sizes are user-configurable, the resize buttons are not initially declared as organisms.
// The members of this array get incepted after this loop.
resizeBtns.push('#sg-size-' + bp);
});
this.$orgs['#sg-resize-btns'].dispatchAction('html', html);
this.requerio.incept(...resizeBtns);
}
}