Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: electron splash screen #4119

Merged
merged 12 commits into from
Sep 15, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down
39 changes: 39 additions & 0 deletions Composer/packages/electron-server/resources/ms_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 36 additions & 14 deletions Composer/packages/electron-server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand Down Expand Up @@ -150,7 +153,7 @@ async function loadServer() {
log(`Server started at port: ${serverPort}`);
}

async function main() {
async function main(show = false) {
hatpick marked this conversation as resolved.
Show resolved Hide resolved
log('Rendering application...');
const mainWindow = ElectronWindow.getInstance().browserWindow;
initAppMenu(mainWindow);
Expand All @@ -165,7 +168,9 @@ async function main() {
}
await mainWindow.webContents.loadURL(getBaseUrl() + deeplinkUrl);

mainWindow.show();
if (show) {
mainWindow.show();
}

mainWindow.on('closed', () => {
ElectronWindow.destroy();
Expand Down Expand Up @@ -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...');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Messages like this should be localized, since the app menu was not localized, I assumed the effort is still going on, please feel free to let me know if things are in place for electron localization and how can I use it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beyackle can you comment?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we're not localizing anything in the electron-server directory yet, but we ought to be, with our standard formatMessage calls. Let's open another issue for that and handle this string along with all the others.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beyackle sounds good, thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a side note, the l10n script does look in electron-server already, so no change is needed there. Just get formatMessage around all your strings and you'll be good to go.

Copy link
Contributor Author

@hatpick hatpick Sep 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#4120 Here's the issue. I'll take on it myself.

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.
Expand All @@ -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);
}
});

Expand Down
92 changes: 92 additions & 0 deletions Composer/packages/electron-server/src/splash/splashScreen.ts
Original file line number Diff line number Diff line change
@@ -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);
hatpick marked this conversation as resolved.
Show resolved Hide resolved
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 };
};
91 changes: 91 additions & 0 deletions Composer/packages/electron-server/src/splash/template.ts
Original file line number Diff line number Diff line change
@@ -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',
}) => `
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<style>
html,
body
{
margin: 0;
overflow: hidden;
}
#box {
position: absolute;
user-select: none;
width: 100%;
height: 100%;
overflow: hidden;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column
}
#logo {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 0;
left: 0;
}
#logo img {
height: 60px
}
#box .text {
color: white;
font-weight: 400;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
#box h1 {
color: white;
font-size: 36px;
}
#box h2 {
color: white;
font-size: 18px;
}
#box h4 {
font-size: 12px;
font-weight: 400;
opacity: 50%;
}
#${statusElmId} {
position: absolute;
left: 20px;
bottom: 16px;
}
#website-url {
position: absolute;
right: 20px;
bottom: 16px;
}
</style>
</head>
<body style="background-color:${color}">
<div id="box" style="background-color:${color}">
<span id="logo">
<img id="logo-img" src="${logo}" />
</span>
<h1 id="product" class="text">${productName}</h1>
${productFamily ? `<h2 id="product-family" class="text">${productFamily}</h2>` : ''}
<h4 class="text" id="${statusElmId}">${text}</h4>
<h4 class="text" id="website-url">${website}</h4>
</div>
</body>
</html>
`;