UNPKG

@infect/frontend

Version:
186 lines (155 loc) 6.92 kB
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 @observer 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; } }