diff --git a/Composer/packages/electron-server/electron-builder-config.json b/Composer/packages/electron-server/electron-builder-config.json index bc27b1da44..dd065273b9 100644 --- a/Composer/packages/electron-server/electron-builder-config.json +++ b/Composer/packages/electron-server/electron-builder-config.json @@ -13,11 +13,13 @@ "asarUnpack": [ "build/assets", "build/templates", - "resources/composerIcon_1024x1024.png" + "resources/composerIcon_1024x1024.png", + "resources/ms_logo.svg" ], "files": [ "build", - "resources/composerIcon_1024x1024.png" + "resources/composerIcon_1024x1024.png", + "resources/ms_logo.svg" ], "extraResources": [ { diff --git a/Composer/packages/electron-server/resources/ms_logo.svg b/Composer/packages/electron-server/resources/ms_logo.svg new file mode 100644 index 0000000000..4451bcde70 --- /dev/null +++ b/Composer/packages/electron-server/resources/ms_logo.svg @@ -0,0 +1,39 @@ + + + diff --git a/Composer/packages/electron-server/src/main.ts b/Composer/packages/electron-server/src/main.ts index 851c5fb63a..875f8f555e 100644 --- a/Composer/packages/electron-server/src/main.ts +++ b/Composer/packages/electron-server/src/main.ts @@ -3,22 +3,25 @@ import { join, resolve } from 'path'; -import { mkdirp } from 'fs-extra'; +import { AppUpdaterSettings, UserSettings } from '@bfc/shared'; import { app, ipcMain } from 'electron'; -import fixPath from 'fix-path'; import { UpdateInfo } from 'electron-updater'; -import { AppUpdaterSettings, UserSettings } from '@bfc/shared'; +import fixPath from 'fix-path'; +import { mkdirp } from 'fs-extra'; +import { initAppMenu } from './appMenu'; +import { AppUpdater } from './appUpdater'; +import { composerProtocol } from './constants'; +import ElectronWindow from './electronWindow'; +import { initSplashScreen } from './splash/splashScreen'; import { isDevelopment } from './utility/env'; -import { isWindows, isMac } from './utility/platform'; import { getUnpackedAsarPath } from './utility/getUnpackedAsarPath'; -import ElectronWindow from './electronWindow'; import log from './utility/logger'; -import { AppUpdater } from './appUpdater'; -import { parseDeepLinkUrl } from './utility/url'; -import { composerProtocol } from './constants'; -import { initAppMenu } from './appMenu'; import { getAccessToken, loginAndGetIdToken, OAuthLoginOptions } from './utility/oauthImplicitFlowHelper'; +import { isMac, isWindows } from './utility/platform'; +import { parseDeepLinkUrl } from './utility/url'; + +const microsoftLogoPath = join(__dirname, '../resources/ms_logo.svg'); const error = log.extend('error'); let deeplinkUrl = ''; @@ -150,7 +153,7 @@ async function loadServer() { log(`Server started at port: ${serverPort}`); } -async function main() { +async function main(show = false) { log('Rendering application...'); const mainWindow = ElectronWindow.getInstance().browserWindow; initAppMenu(mainWindow); @@ -165,7 +168,9 @@ async function main() { } await mainWindow.webContents.loadURL(getBaseUrl() + deeplinkUrl); - mainWindow.show(); + if (show) { + mainWindow.show(); + } mainWindow.on('closed', () => { ElectronWindow.destroy(); @@ -200,13 +205,30 @@ async function run() { app.on('ready', async () => { log('App ready'); + const getMainWindow = () => ElectronWindow.getInstance().browserWindow; + const { startApp, updateStatus } = await initSplashScreen({ + getMainWindow, + color: 'rgb(0, 120, 212)', + logo: `file://${microsoftLogoPath}`, + productName: 'Bot Framework Composer', + productFamily: 'Microsoft Azure', + status: 'Initializing...', + website: 'www.botframework.com', + width: 500, + height: 300, + }); + + updateStatus('Starting server...'); + await loadServer(); + await main(); + + setTimeout(startApp, 500); + ipcMain.once('init-user-settings', (_ev, settings: UserSettings) => { // we can't synchronously call the main process (due to deadlocks) // so we wait for the initial settings to be loaded from the client initializeAppUpdater(settings.appUpdater); }); - await loadServer(); - await main(); }); // Quit when all windows are closed. @@ -222,7 +244,7 @@ async function run() { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (!ElectronWindow.isBrowserWindowCreated) { - main(); + main(true); } }); diff --git a/Composer/packages/electron-server/src/splash/splashScreen.ts b/Composer/packages/electron-server/src/splash/splashScreen.ts new file mode 100644 index 0000000000..3c0ad74c4a --- /dev/null +++ b/Composer/packages/electron-server/src/splash/splashScreen.ts @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { BrowserWindow, systemPreferences } from 'electron'; + +import { getSplashScreenContent, statusElmId } from './template'; + +export type SplashScreenProps = { + getMainWindow: () => BrowserWindow | undefined; + color?: string; + icon?: string; + width?: number; + height?: number; + productName?: string; + productFamily?: string; + logo?: string; + website?: string; + status?: string; +}; + +export const initSplashScreen = async ({ + getMainWindow, + color: initColor, + icon, + width = 600, + height = 400, + productName, + productFamily, + logo, + website, + status, +}: SplashScreenProps) => { + // If no color is provided, uses OS accent color + const color = initColor || (systemPreferences.getAccentColor && `#${systemPreferences.getAccentColor()}`); + + const splashScreenWindow = new BrowserWindow({ + parent: getMainWindow(), + show: false, + width, + height, + modal: true, + transparent: true, + skipTaskbar: true, + frame: false, + autoHideMenuBar: true, + alwaysOnTop: true, + resizable: false, + movable: false, + icon, + webPreferences: { + // This is necessary to enable loading local images in the url protocol (window.loadURL) + webSecurity: false, + }, + }); + + const args = { + productName, + productFamily, + logo, + website, + color, + status, + }; + + // This prevents window visual flash + splashScreenWindow.on('ready-to-show', () => { + splashScreenWindow.show(); + }); + + const file = 'data:text/html;charset=UTF-8,' + encodeURIComponent(getSplashScreenContent(args)); + await splashScreenWindow.loadURL(file); + + /** + * Displays the main windows of the app and destroys the splash screen. + */ + const startApp = () => { + setTimeout(() => splashScreenWindow.destroy(), 500); + getMainWindow()?.show(); + }; + + /** + * Updates the loading status on the splash screen. + * @param status New status text. + */ + const updateStatus = async (status: string) => { + await splashScreenWindow.webContents.executeJavaScript( + `document.querySelector('#${statusElmId}').textContent = '${status}';` + ); + }; + + return { startApp, updateStatus }; +}; diff --git a/Composer/packages/electron-server/src/splash/template.ts b/Composer/packages/electron-server/src/splash/template.ts new file mode 100644 index 0000000000..601b3af81c --- /dev/null +++ b/Composer/packages/electron-server/src/splash/template.ts @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export const statusElmId = 'status-txt'; + +export const getSplashScreenContent = ({ + logo = '', + productName = 'Product', + productFamily = 'Product Family', + text = 'Loading ...', + website = 'www.website.com', + color = '#666', +}) => ` + + + +
+ + + +