import * as Os from 'os' ; import * as Fs from 'fs' ; import * as Path from 'path' ; import * as Util from 'util' ; import * as Types from '@meetalva/types' ; import * as getPort from 'get-port' ; import * as Electron from 'electron' ; import { createWindow } from './create-window' ; import { AlvaApp } from '@meetalva/model' ; import * as Mobx from 'mobx' ; import * as Message from '@meetalva/message' ; import * as ElectronLog from 'electron-log' ; import Project from 'ts-simple-ast' ; ElectronLog . transports . console . level = 'info' ; ElectronLog . transports . file . level = 'silly' ; export interface ElectronHostInit { process : NodeJS . Process ; forced ? : Partial < Types . HostFlags > ; } export class ElectronHost implements Types . Host < AlvaApp < Message . Message > , Project , Message . Message > { public type = Types . HostType . Electron ; private forced ? : Partial < Types . HostFlags > ; private process : NodeJS . Process ; private sender ? : Types . Sender < Message . Message > ; private windows : Map < string | number , Electron . BrowserWindow > = new Map ( ) ; @ Mobx . observable private apps : Map < string , AlvaApp < Message . Message > > = new Map ( ) ; private constructor ( init ) { this . process = init . process ; this . forced = init . forced ; } public static async from ( init ) { return new ElectronHost ( init ) ; } public async createWindow ( options ) { const win = await createWindow ( options ) ; this . windows . set ( win . id , win ) ; return win ; } public async getFlags ( ) { const yargsParser = require ( 'yargs-parser' ) ; return { ... yargsParser ( this . process . argv . slice ( 2 ) , { number : [ 'port' ] , boolean : [ 'localhost' , 'force-updates' ] } ) , ... this . forced } ; } public async getPort ( requested ) { return getPort ( { port : requested } ) ; } public async log ( message ? , ... optionalParams ) { ElectronLog . log ( message , ... optionalParams ) ; } public async resolveFrom ( base , ... paths ) { const getBasePath = ( b ) : string => { switch ( b ) { case Types . HostBase . Source : return Path . resolve ( __dirname , '..' , '..' ) ; case Types . HostBase . AppData : return Path . resolve ( Os . tmpdir ( ) , 'alva' ) ; case Types . HostBase . AppPath : return Electron . app . getAppPath ( ) ; case Types . HostBase . UserData : return Electron . app . getPath ( 'userData' ) ; case Types . HostBase . UserHome : return Os . homedir ( ) ; } } ; return Path . resolve ( ... [ getBasePath ( base ) , ... paths ] ) ; } public async exists ( path ) { return Fs . existsSync ( path ) ; } public access ( path , mode ) { return new Promise ( resolve => { Fs . access ( path , mode , err => { if ( err ) { return resolve ( false ) ; } resolve ( true ) ; } ) ; } ) ; } public async stat ( path ) { return new Promise ( ( resolve , reject ) => { Fs . stat ( path , ( err , stats ) => { if ( err ) { return reject ( err ) ; } resolve ( stats ) ; } ) ; } ) ; } public async mkdir ( path ) { const mkdir = Util . promisify ( Fs . mkdir ) ; if ( ! Fs . existsSync ( path ) ) { await mkdir ( path ) ; } } public async readFile ( path ) { const readFile = Util . promisify ( Fs . readFile ) ; const buffer = await readFile ( path ) ; return { path , buffer , contents : buffer . toString ( 'utf-8' ) } ; } public async saveFile ( path , data ) { return ; } public async writeFile ( path , data ) { const writeFile = Util . promisify ( Fs . writeFile ) ; return writeFile ( path , data ) ; } public async open ( uri ) { Electron . shell . openExternal ( uri ) ; return ; } public selectFile ( opts ) { return new Promise ( resolve => { Electron . dialog . showOpenDialog ( opts , ( paths ) => { if ( ! paths ) { return resolve ( ) ; } resolve ( paths [ 0 ] ) ; } ) ; } ) ; } public selectSaveFile ( opts ) { return new Promise ( resolve => { Electron . dialog . showSaveDialog ( opts , ( path ) => { if ( ! path ) { return resolve ( ) ; } resolve ( path ) ; } ) ; } ) ; } public async showMessage ( opts ) { const cancelId = opts . buttons . findIndex ( b => typeof b . cancel !== 'undefined' && b . cancel ) ; const defaultId = opts . buttons . findIndex ( b => typeof b . selected !== 'undefined' && b . selected ) ; const result = Electron . dialog . showMessageBox ( { type : opts . type , message : opts . message , detail : opts . detail , cancelId , defaultId , buttons : opts . buttons . map ( button => button . label ) } ) ; if ( typeof result === 'undefined' ) { return ; } const button = opts . buttons [ result ] ; if ( ! button ) { return ; } if ( button . message && button && this . sender ) { this . sender . send ( button . message ) ; } return button ; } public async showContextMenu ( opts ) { const focusedWindow = Electron . BrowserWindow . getFocusedWindow ( ) ; if ( ! focusedWindow || focusedWindow === null ) { return ; } Electron . Menu . buildFromTemplate ( opts . items . map ( o => ( { ... o , click : ( ) => ( o as any ) . click ( this . sender ) } ) ) ) . popup ( { window : focusedWindow } ) ; } public async toggleDevTools ( ) { const window = Electron . BrowserWindow . getFocusedWindow ( ) ; if ( window ) { window . webContents . toggleDevTools ( ) ; } } public async writeClipboard ( input ) { Electron . clipboard . writeText ( input ) ; } public async readClipboard ( ) { return Electron . clipboard . readText ( ) ; } @ Mobx . action public async addApp ( app ) { this . apps . set ( app . getId ( ) , app ) ; return ; } public async getApp ( id ) { return this . apps . get ( id ) ; } public async getSender ( ) { return this . sender ! ; } public setSender ( sender ) { this . sender = sender ; this . sender . setLog ( ( m , message ) => { this . log ( m , message . type , message . id ) ; ElectronLog . silly ( message ) ; } ) ; } }