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: add splash screen on top of ctx refactor #2548

Merged
merged 34 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7c02e34
chore: update ctx refactor
SgtPooki Jan 17, 2023
b947d61
fix: tests
SgtPooki Jan 17, 2023
042f858
feat: add context.spec.js tests
SgtPooki Jan 17, 2023
472219c
fix: startup issues
SgtPooki Jan 17, 2023
93a2bac
feat: app context supports lazy functions
SgtPooki Jan 17, 2023
94e752e
chore: self-PR-review cleanup
SgtPooki Jan 18, 2023
b6124fc
Merge branch 'main' into feat/ctx-refactor
SgtPooki May 4, 2023
221612d
Merge branch 'main' into feat/ctx-refactor
SgtPooki Jul 7, 2023
07ff889
test: fix e2e tests
SgtPooki Jul 8, 2023
af05f6d
feat: add splash screen
SgtPooki Jul 8, 2023
b3fb596
tmp: trying to fix e2e tests
SgtPooki Jul 8, 2023
118825c
chore(deps): upgrade playwright
SgtPooki Jul 8, 2023
c160aa0
fix: revert daemonReady function
SgtPooki Jul 8, 2023
5f444f0
Merge branch 'feat/ctx-refactor' into feat/ctx-refactor-splash-screen
SgtPooki Jul 8, 2023
6f05030
Update src/splash/splash.html
SgtPooki Jul 8, 2023
a3982ea
Update src/splash/splash.html
SgtPooki Jul 8, 2023
675166e
Update src/splash/splash.html
SgtPooki Jul 8, 2023
d7e4d83
Update src/webui/index.js
SgtPooki Jul 8, 2023
241a0aa
Update test/e2e/launch.e2e.test.js
SgtPooki Jul 8, 2023
5dfd14e
Update test/e2e/launch.e2e.test.js
SgtPooki Jul 8, 2023
d676ce1
Update test/e2e/launch.e2e.test.js
SgtPooki Jul 8, 2023
8e3d4e7
Update test/e2e/launch.e2e.test.js
SgtPooki Jul 8, 2023
aec2293
Update test/e2e/launch.e2e.test.js
SgtPooki Jul 8, 2023
7b8af6a
Merge branch 'main' into feat/ctx-refactor-splash-screen
SgtPooki Jul 31, 2023
4155efd
fix: do not prompt for new port when using CI
SgtPooki Jul 31, 2023
09d9d10
fix: handle errors that were breaking e2e tests
SgtPooki Jul 31, 2023
497bdc2
feat: splashscreen page background is transparent
SgtPooki Jul 31, 2023
dfcabe2
feat: splash screen animation polish
SgtPooki Aug 1, 2023
0d550bc
chore: removed webkit keyframes definition
SgtPooki Aug 1, 2023
6ede66f
chore: remove debugging code
SgtPooki Aug 1, 2023
bf58346
chore: remove extra line
SgtPooki Aug 1, 2023
4277da6
chore(splash-screen): repositioning awaits
SgtPooki Aug 1, 2023
b3b4e49
chore: fix after pr comment change
SgtPooki Aug 1, 2023
7819ed2
chore: move splash screen css to separate file
SgtPooki Aug 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions assets/pages/splash.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
body {
border-radius: 5vh;
}

.loader {
background-image: url('../../assets/icons/tray/others/on-large.png');
width: 200px;
height: 200px;
-webkit-animation: heartbeat 1.5s ease-in-out 0s infinite normal both;
animation: heartbeat 1.5s ease-in-out 0s infinite normal both;
-webkit-transform-origin: center center;
transform-origin: center center;
margin: auto;
left: 0;
right: 0;
top: 0px;
bottom: 0;
position: fixed;
background-size: cover;
filter: drop-shadow(0 0 0 #378085);
}

@keyframes heartbeat {
0% {
animation-timing-function: ease-out;
transform: scale(1);
transform-origin: center center;
filter: drop-shadow(0 0 0 #378085);
}

45% {
animation-timing-function: ease-in;
transform: scale(1);
filter: drop-shadow(0 0 0rem #378085);
}

50% {
animation-timing-function: ease-in;
transform: scale(1);
filter: drop-shadow(0 0 0.5rem #378085);
}

55% {
animation-timing-function: ease-out;
transform: scale(1);
/* Size should be increased on drop-shadow so when scaling down, drop-shadow compensates for size reduction, and we don't get any visual artifacts */
filter: drop-shadow(0 0 1rem #378085);
}

67% {
animation-timing-function: ease-in;
transform: scale(1.13);
filter: drop-shadow(0 0 0.75rem #378085);
}

83% {
animation-timing-function: ease-out;
transform: scale(1.02);
filter: drop-shadow(0 0 0.50rem #378085);
}

90% {
animation-timing-function: ease-in;
transform: scale(1.09);
filter: drop-shadow(0 0 0.75rem #378085);
}
}
12 changes: 12 additions & 0 deletions assets/pages/splash.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<html>

<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="splash.css">
</head>

<body id="e2e-splashScreen">
<div class="loader"></div>
</body>

</html>
10 changes: 8 additions & 2 deletions src/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const pDefer = require('p-defer')
const logger = require('./common/logger')

/**
* @typedef {'tray-menu' | 'tray' | 'tray-menu-state' | 'tray.update-menu' | 'countlyDeviceId' | 'manualCheckForUpdates' | 'startIpfs' | 'stopIpfs' | 'restartIpfs' | 'getIpfsd' | 'launchWebUI' | 'webui'} ContextProperties
* @typedef {'tray-menu' | 'tray' | 'tray-menu-state' | 'tray.update-menu' | 'countlyDeviceId' | 'manualCheckForUpdates' | 'startIpfs' | 'stopIpfs' | 'restartIpfs' | 'getIpfsd' | 'launchWebUI' | 'webui' | 'splashScreen'} ContextProperties
*/

/**
Expand Down Expand Up @@ -101,7 +101,13 @@ class Context {

return async (...args) => {
const originalFn = await originalFnPromise
return originalFn(...args)
try {
return await originalFn(...args)
} catch (err) {
logger.error(`[ctx] Error calling ${String(propertyName)}`)
logger.error(err)
throw err
}
SgtPooki marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
7 changes: 6 additions & 1 deletion src/daemon/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,12 @@ async function checkPorts (ipfsd) {
}

// two "0" in config mean "pick free ports without any prompt"
const promptUser = (apiPort !== 0 || gatewayPort !== 0)
let promptUser = (apiPort !== 0 || gatewayPort !== 0)

if (process.env.NODE_ENV === 'test' || process.env.CI != null) {
logger.info('[daemon] CI or TEST mode, skipping busyPortDialog')
promptUser = false
}
Comment on lines +374 to +377
Copy link
Member Author

Choose a reason for hiding this comment

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

tests fail locally or timeout because of waiting on this prompt.


if (promptUser) {
let useAlternativePorts = null
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const setupAnalytics = require('./analytics')
const setupSecondInstance = require('./second-instance')
const { analyticsKeys } = require('./analytics/keys')
const handleError = require('./handleError')
const createSplashScreen = require('./splash/create-splash-screen')

// Hide Dock
if (app.dock) app.dock.hide()
Expand Down Expand Up @@ -65,6 +66,7 @@ async function run () {

try {
await Promise.all([
createSplashScreen(),
setupDaemon(), // ctx.getIpfsd, startIpfs, stopIpfs, restartIpfs
setupAnalytics(), // ctx.countlyDeviceId
setupI18n(),
Expand Down
35 changes: 35 additions & 0 deletions src/splash/create-splash-screen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* A splash screen for the application that is shown while the webui is loading.
*
* This is to prevent the user from seeing the `Could not connect to the IPFS API` error
* while we're still booting up the daemon.
*/
const { BrowserWindow } = require('electron')
const getCtx = require('../context')
const logger = require('../common/logger')
const path = require('node:path')

module.exports = async function createSplashScreen () {
const ctx = getCtx()
const splashScreen = new BrowserWindow({
title: 'IPFS Desktop splash screen',
width: 250,
height: 275,
Comment on lines +16 to +17
Copy link
Member Author

Choose a reason for hiding this comment

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

reduced size due to background page being gone, but it needs to be bigger than the 200x200 px icon

transparent: true,
frame: false,
alwaysOnTop: true,
show: false
})

try {
await splashScreen.loadFile(path.join(__dirname, '../../assets/pages/splash.html'))
} catch (err) {
logger.error('[splashScreen] loadFile failed')
logger.error(err)
return
}

splashScreen.center()

ctx.setProp('splashScreen', splashScreen)
}
1 change: 1 addition & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type CONFIG_KEYS = 'AUTO_LAUNCH' | 'AUTO_GARBAGE_COLLECTOR' | 'SCREENSHOT_SHORTCUT' | 'OPEN_WEBUI_LAUNCH' | 'MONOCHROME_TRAY_ICON' | 'EXPERIMENT_PUBSUB' | 'EXPERIMENT_PUBSUB_NAMESYS'
Copy link
Contributor

Choose a reason for hiding this comment

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

sort?

59 changes: 50 additions & 9 deletions src/webui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ const Countly = require('countly-sdk-nodejs')
const { analyticsKeys } = require('../analytics/keys')
const ipcMainEvents = require('../common/ipc-main-events')
const getCtx = require('../context')
const { STATUS } = require('../daemon/consts')

serve({ scheme: 'webui', directory: join(__dirname, '../../assets/webui') })

/**
*
* @returns {BrowserWindow}
*/
const createWindow = () => {
logger.info('[webui] creating window')
const dimensions = screen.getPrimaryDisplay()
Expand Down Expand Up @@ -107,6 +112,7 @@ const createWindow = () => {
})

app.on('before-quit', () => {
logger.info('[web ui] app-quit requested')
Copy link
Member Author

Choose a reason for hiding this comment

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

tests have some race condition where playwright will shut down electron but then use the same instance to try to load webui pages... this helped me debug

// Makes sure the app quits even though we prevent
// the closing of this window.
window.removeAllListeners('close')
Expand Down Expand Up @@ -143,6 +149,10 @@ module.exports = async function () {
url.searchParams.set('deviceId', await ctx.getProp('countlyDeviceId'))

ctx.setProp('launchWebUI', async (path, { focus = true, forceRefresh = false } = {}) => {
if (window.isDestroyed()) {
logger.error(`[web ui] window is destroyed, not launching web ui with ${path}`)
return
}
Comment on lines +152 to +155
Copy link
Member Author

Choose a reason for hiding this comment

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

e2e tests trying to launch webui even though it already killed electron and is done with the tests.

if (forceRefresh) window.webContents.reload()
if (!path) {
logger.info('[web ui] launching web ui', { withAnalytics: analyticsKeys.FN_LAUNCH_WEB_UI })
Expand All @@ -165,8 +175,10 @@ module.exports = async function () {
}

const getIpfsd = ctx.getFn('getIpfsd')
ipcMain.on(ipcMainEvents.IPFSD, async () => {
let ipfsdStatus = null
ipcMain.on(ipcMainEvents.IPFSD, async (status) => {
const ipfsd = await getIpfsd(true)
ipfsdStatus = status

if (ipfsd && ipfsd.apiAddr !== apiAddress) {
apiAddress = ipfsd.apiAddr
Expand All @@ -183,18 +195,47 @@ module.exports = async function () {
})

const launchWebUI = ctx.getFn('launchWebUI')
const splashScreen = await ctx.getProp('splashScreen')
if (store.get(CONFIG_KEY)) {
// we're supposed to show the window on startup, display the splash screen
splashScreen.show()
} else {
// we don't need the splash screen, ignore it.
splashScreen.destroy()
}
let splashScreenTimeoutId = null
window.on('close', () => {
if (splashScreenTimeoutId) {
clearTimeout(splashScreenTimeoutId)
splashScreenTimeoutId = null
}
})
Comment on lines +207 to +212
Copy link
Member Author

Choose a reason for hiding this comment

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

when app is closed, clear the timeout.. we don't need to handle splashscreen

Copy link
Contributor

Choose a reason for hiding this comment

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

won't it be auto GCed? I'm trying to understand the benefit of cleaning this up prior to close.

Copy link
Member Author

Choose a reason for hiding this comment

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

it should be, but it still causes issues in e2e due to some race condition in how playwright runs electron in our e2e tests.

const handleSplashScreen = async () => {
if ([null, STATUS.STARTING_STARTED].includes(ipfsdStatus)) {
splashScreenTimeoutId = setTimeout(handleSplashScreen, 500)
return
}

await launchWebUI('/')
try {
splashScreen.destroy()
} catch (err) {
logger.error('[web ui] failed to hide splash screen')
logger.error(err)
}
}

return /** @type {Promise<void>} */(new Promise(resolve => {
window.once('ready-to-show', async () => {
logger.info('[web ui] window ready')
// the electron window is ready, but the webui may not have successfully attempted to connect to the daemon yet.
if (store.get(CONFIG_KEY)) {
logger.info('[web ui] waiting for ipfsd to start')
window.once('ready-to-show', async () => {
logger.info('[web ui] window ready')

if (store.get(CONFIG_KEY)) {
await launchWebUI('/')
}
handleSplashScreen()

resolve()
})
resolve()
})
}

updateLanguage()
window.loadURL(url.toString())
Expand Down
11 changes: 11 additions & 0 deletions test/e2e/launch.e2e.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ test.describe.serial('Application launch', async () => {
}
})

/**
*
* @param {Object} [param0]
* @param {string} param0.repoPath
* @returns {Promise<{ app: Awaited<ReturnType<import('playwright')._electron['launch']>>, repoPath: string, home: string }>
*/
async function startApp ({ repoPath } = {}) {
const home = tmp.dirSync({ prefix: 'tmp_home_', unsafeCleanup: true }).name
if (!repoPath) {
Expand All @@ -43,6 +49,11 @@ test.describe.serial('Application launch', async () => {
return { app, repoPath, home }
}

/**
*
* @param {Awaited<ReturnType<import('playwright')._electron['launch']>>} app
* @returns {Promise<{ peerId: string }>
*/
async function daemonReady (app) {
const peerId = await app.evaluate(async ({ ipcMain }) => new Promise((resolve, reject) => {
ipcMain.on('ipfsd', (status, peerId) => {
Expand Down