@infect/frontend
Version:
infect frontend
186 lines (155 loc) • 6.92 kB
JSX
import React from 'react';
import { observer } from 'mobx-react';
import { reaction } from 'mobx';
import debug from 'debug';
import { severityLevels } from '@infect/frontend-logic';
import cssVars from 'css-vars-ponyfill';
const log = debug('infect:TenantRunner');
/**
* Executes tenant related functions that cannot be tied to a real React component:
* - loads Google Analytics
* - sets CSS variables
* - sets Favicon
*/
export default class InfoOverlay extends React.Component {
componentDidMount() {
// As soon as tenant data has been fetched from server, update DOM
reaction(
() => this.props.tenantConfig.config &&
this.props.tenantConfig.config.get('frontend'),
(frontendConfig, disposer) => {
log('frontend config is %o', frontendConfig);
if (typeof frontendConfig !== 'object') {
this.props.notifications.handle({
message: `Property frontendConfig on tenantConfig must be an object, is ${JSON.stringify(frontendConfig)} instead.`,
severity: severityLevels.warning,
});
return;
}
this.updateCSSVariables(frontendConfig);
this.initializeGoogleAnalytics(frontendConfig);
this.updateFavicon(frontendConfig);
// Let's assume that tenantConfig does not change once it has been fetched.
// Therefore we can and should dispose the reaction.
disposer.dispose();
},
);
}
/**
* @param {object} userInterfaceConfig frontend.userInterface config from tenantConfig
*/
updateCSSVariables(frontendConfig) {
if (
!frontendConfig.userInterface ||
typeof frontendConfig.userInterface !== 'object' ||
!frontendConfig.userInterface.darkColor ||
!frontendConfig.userInterface.lightColor
) {
this.props.notifications.handle({
message: `Expected userIterface config to be an object with properties darkColor and lightColor, got ${JSON.stringify(frontendConfig.userInterface)} instead.`,
severity: severityLevels.warning,
});
return;
}
const { lightColor, darkColor } = frontendConfig.userInterface;
/* global document */
// Use this code later when we can drop IE11 support 🙏
// const documentRoot = document.documentElement;
// documentRoot.style.setProperty('--color--accent-1', lightColor);
// documentRoot.style.setProperty('--color--accent-2', darkColor);
// IE11: https://jhildenbiddle.github.io/css-vars-ponyfill/#/
cssVars({
variables: {
'--color--accent-1': lightColor,
'--color--accent-2': darkColor,
}
});
log('Colors set to %o and %o', lightColor, darkColor);
}
updateFavicon(frontendConfig) {
if (
!frontendConfig.userInterface ||
typeof frontendConfig.userInterface !== 'object' ||
!frontendConfig.userInterface.darkColor
) {
this.props.notifications.handle({
message: `Expected userIterface config to be an object with property darkColor, got ${JSON.stringify(frontendConfig.userInterface)} instead.`,
severity: severityLevels.warning,
});
return;
}
// There are multiple favicons (16x16 and 32x32). Replace all of them.
const favicons = document.querySelectorAll('link[rel="icon"]');
if (!favicons.length) {
// Favicon is just a «nice to have». Handle errors very discreetly.
console.warn('Could not find favicon, is %o', favicon);
return;
}
const color = frontendConfig.userInterface.darkColor;
const faviconSize = 32;
const canvas = document.createElement('canvas');
canvas.width = faviconSize;
canvas.height = faviconSize;
// Create 3 levels of circles
const createCircle = (level, [x, y], radius, maxDepth) => {
if (maxDepth > 3) {
console.warn('TenantRunner createCircle has a high maxDepth which may slow down INFECT');
}
if (level > maxDepth - 1) return;
context.beginPath();
context.arc(x, y, radius, 0, Math.PI * 2, true);
context.closePath();
context.fill();
// Draw 8 child circles around parent circle. If level is 0, only 1 circle is needed.
for (let number = 0; number < 6; number++) {
// Angle of the current circle in relation to the previous circle; 0 is 3 o'clock,
// Math.PI is 9 o'clock (unit circle)
const angle = (Math.PI * 2) / 6 * number;
// Distance from center of parent to center of child circle
const centerDistance = radius * 2.4;
const newX = x + Math.cos(angle) * centerDistance;
const newY = y + Math.sin(angle) * centerDistance;
createCircle(level + 1, [newX, newY], radius / 2, maxDepth);
}
};
const context = canvas.getContext('2d');
context.fillStyle = color;
createCircle(0, [faviconSize / 2, faviconSize / 2], faviconSize / 8, 3);
// To test, use size of ~640
// document.body.prepend(canvas);
for (const favicon of favicons) {
favicon.href = canvas.toDataURL('image/png');
}
}
initializeGoogleAnalytics(frontendConfig) {
if (!frontendConfig.analytics ||
!frontendConfig.analytics.googleAnalyticsTag ||
typeof frontendConfig.analytics.googleAnalyticsTag !== 'string'
) {
this.props.notifications.handle({
message: `Expected analytics config to be an object with property googleAnalyticsTag (string), received ${JSON.stringify(frontendConfig.analytics)} instead.`,
severity: severityLevels.warning,
});
return;
}
/* global window */
const host = window.location.hostname;
// Don't track on local dev installation
if (host === 'localhost') {
console.log('Dev environment; don\'t add Analytics');
return;
}
// Start: Code from Google
window.dataLayer = window.dataLayer || [];
function gtag() {
window.dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', frontendConfig.analytics.googleAnalyticsTag);
// End: Code from Google
log('Google Analytics initialized');
}
render() {
return null;
}
}