import { ipcRenderer } from 'electron' ; import log from 'electron-log' ; import enpeem from 'enpeem' ; import { readFileSync , writeFileSync } from 'fs' ; import moment from 'moment' ; import { join } from 'path' ; import React , { CSSProperties } from 'react' ; import ReactGridLayout , { Layout , WidthProvider } from 'react-grid-layout' ; import { IPCMessageNames } from '../../ipc/IPCMessageNames' ; import { pluginInstalledLockFileName } from '../helpers/constants' ; import { getPlugins , IManipulateSettingsProps } from '../helpers/serialization' ; import { IInstalledPlugin , IInstallNeededPlugin } from '../plugin/IPlugin' ; import { Plugin } from './Plugin' ; const RGL = WidthProvider ( ReactGridLayout ) ; interface ISettings { layout : Layout [ ] ; editStyles : CSSProperties ; } interface IState extends ISettings { /**
     * The installed plugins that we're currently showing
     */ plugins : IInstalledPlugin [ ] ; } export interface IPluginGridProps extends IManipulateSettingsProps < ISettings > , ISettings { isEditMode : boolean ; } export class PluginGrid extends React . Component < IPluginGridProps , IState > { constructor ( props ) { super ( props ) ; this . state = { editStyles : this . props . editStyles || { backgroundColor : 'rgba(51, 138, 46, 0.6)' } , layout : this . props . layout || [ ] , plugins : [ ] } ; this . onLayoutChanged = this . onLayoutChanged . bind ( this ) ; } public componentDidMount ( ) { this . loadPlugins ( ) ; } public componentWillUnmount ( ) { this . props . updateSettings ( { editStyles : this . state . editStyles , layout : this . state . layout , } ) ; } public render ( ) { return ( < RGL compactType = { null } useCSSTransforms = { true } onLayoutChange = { this . onLayoutChanged } > { this . generatePluginComponents ( ) } < /RGL>); } private generatePluginComponents ( ) { const layoutMap = this . preprocessLayoutData ( ) ; return this . state . plugins . filter ( p => ! p . settings . hidden ) . map ( p => ( < div style = { this . props . isEditMode ? this . state . editStyles : { } } key = { `${ p . name }@${ p . version }` } data - grid = { layoutMap [ `${ p . name }@${ p . version }` ] } > < Plugin plugin = { p } isEditMode = { this . props . isEditMode } / > < /div>)); } private preprocessLayoutData ( ) { const layoutIndexMap = { } ; this . state . layout . forEach ( ( elem ) => { layoutIndexMap [ elem . i ] = elem ; } ) ; return layoutIndexMap ; } private onLayoutChanged ( layout ) { if ( layout . length > 0 ) { this . setState ( { layout } ) ; } } private loadPlugins ( ) { const plugins = getPlugins ( ) . filter ( p => typeof ( p as any ) . component !== 'undefined' ) ; const noInstallNeeded = plugins . filter ( p => ! p . requiresInstall ) as IInstalledPlugin [ ] ; const installNeeded = plugins . filter ( p => p . requiresInstall ) as IInstallNeededPlugin [ ] ; log . info ( `starting load for [${ noInstallNeeded . map ( e => e . name ) . join ( ',' ) }]` ) ; this . setState ( { plugins : noInstallNeeded } ) ; log . info ( `starting install for [${ installNeeded . map ( e => e . name ) . join ( ',' ) }]` ) ; Promise . all ( this . installPlugins ( installNeeded ) ) . then ( ( installedPlugins ) => { this . setState ( { plugins : this . state . plugins . concat ( installedPlugins ) } ) ; } , ( error ) => { log . error ( `failed installation, ${ error }` ) ; } ) ; } private installPlugins ( plugins ) { return plugins . map ( ( plugin ) => { const jsonData = readFileSync ( join ( plugin . diskPath , 'package.json' ) ) . toString ( ) ; const pkg = JSON . parse ( jsonData ) ; const deps = Object . keys ( pkg . dependencies ) . map ( k => `${ k }@${ pkg . dependencies [ k ] }` ) ; return new Promise < IInstalledPlugin > ( ( resolve , reject ) => { enpeem . install ( { dependencies : deps , dir : plugin . diskPath , } , ( err ) => { if ( err ) { reject ( err ) ; } try { resolve ( { ... plugin , ... { component : require ( plugin . component ) . default as React . ComponentType < any > } } as IInstalledPlugin ) ; } catch ( ex ) { const errorContents = `Failed to load ${ plugin . name } from ${ plugin . diskPath }` ; ipcRenderer . send ( IPCMessageNames . ShowTooltip , { content : errorContents , title : 'Plugin Load Error' , } ) ; log . error ( errorContents ) ; } } ) ; } ) . then ( ( installedPlugin ) => { writeFileSync ( join ( installedPlugin . diskPath , pluginInstalledLockFileName ) , moment ( ) . toISOString ( ) ) ; return installedPlugin ; } ) ; } ) ; } }