diff --git a/CHANGELOG.md b/CHANGELOG.md index d88e8685..b136d806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.36.0] - 2025-05-14 + +### Added + +- Added `package-lock` file, bumped dev dependencies and bumped up `NODEJS` to `v20` for the `GH Actions`. PR: [bfx-facs-db-better-sqlite#12](https://github.com/bitfinexcom/bfx-facs-db-better-sqlite/pull/12) +- Implemented user notifications about inaccurate `Tax Report` calculations due to `delisted` tokens. PR: [bfx-report-ui#926](https://github.com/bitfinexcom/bfx-report-ui/pull/926) +- Implemented the possibility to `Deduct Fees` in the `Tax Report`. PR: [bfx-report-ui#928](https://github.com/bitfinexcom/bfx-report-ui/pull/928) + +### Changed + +- Made two loading windows for a startup without a parent window independently and for common purposes as a modal window with a parent window to prevent the main window interaction when showing the loading window for some sensitive cases such as import/export DB. PR: [bfx-report-electron#535](https://github.com/bitfinexcom/bfx-report-electron/pull/535) +- Disabled `Loan Report` refresh button during initial synchronization to prevent report generation errors. Added a corresponding notice to communicate this to the user. PR: [bfx-report-ui#927](https://github.com/bitfinexcom/bfx-report-ui/pull/927) + +### Fixed + +- Fixed the sync requested by the user via the UI button in case the sync was added by the scheduler and the app was closed before completing earlier. PR: [bfx-reports-framework#454](https://github.com/bitfinexcom/bfx-reports-framework/pull/454) + ## [4.35.0] - 2025-04-23 ### Added diff --git a/bfx-report-ui b/bfx-report-ui index 67f2bb34..4c63f6a8 160000 --- a/bfx-report-ui +++ b/bfx-report-ui @@ -1 +1 @@ -Subproject commit 67f2bb34189a70b236cbf0c413c48688d4652e49 +Subproject commit 4c63f6a812423e8ef302960d5929ee7b97d5da59 diff --git a/bfx-reports-framework b/bfx-reports-framework index ef7abebc..7db14630 160000 --- a/bfx-reports-framework +++ b/bfx-reports-framework @@ -1 +1 @@ -Subproject commit ef7abebc09341df199c0323eddb2db73625858a8 +Subproject commit 7db146304d2d62d3eb67a9f20d5e3420b7f5025b diff --git a/package-lock.json b/package-lock.json index a3807237..f03fae41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bfx-report-electron", - "version": "4.35.0", + "version": "4.36.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bfx-report-electron", - "version": "4.35.0", + "version": "4.36.0", "license": "Apache-2.0", "dependencies": { "archiver": "7.0.1", diff --git a/package.json b/package.json index 46c4db9c..f6c9037c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bfx-report-electron", - "version": "4.35.0", + "version": "4.36.0", "repository": "https://github.com/bitfinexcom/bfx-report-electron", "description": "Reporting tool", "author": "bitfinex.com", diff --git a/src/auto-updater/index.js b/src/auto-updater/index.js index a1632b3d..7b518671 100644 --- a/src/auto-updater/index.js +++ b/src/auto-updater/index.js @@ -18,6 +18,7 @@ const isMac = process.platform === 'darwin' const log = require('../error-manager/log') const BfxMacUpdater = require('./bfx.mac.updater') const wins = require('../window-creators/windows') +const WINDOW_NAMES = require('../window-creators/window.names') const { showLoadingWindow, hideLoadingWindow @@ -291,6 +292,7 @@ const _autoUpdaterFactory = () => { autoUpdater.addInstallingUpdateEventHandler(() => { return showLoadingWindow({ + windowName: WINDOW_NAMES.LOADING_WINDOW, description: i18next.t('autoUpdater.loadingWindow.description'), isRequiredToCloseAllWins: true }) @@ -330,7 +332,10 @@ const _autoUpdaterFactory = () => { isProgressToastEnabled = false - await hideLoadingWindow({ isRequiredToShowMainWin: false }) + await hideLoadingWindow({ + windowName: WINDOW_NAMES.LOADING_WINDOW, + isRequiredToShowMainWin: false + }) _switchMenuItem({ isCheckMenuItemDisabled: false, diff --git a/src/enforce-macos-app-location.js b/src/enforce-macos-app-location.js index 807fd992..335c85d7 100644 --- a/src/enforce-macos-app-location.js +++ b/src/enforce-macos-app-location.js @@ -8,6 +8,7 @@ const { showLoadingWindow, hideLoadingWindow } = require('./window-creators/change-loading-win-visibility-state') +const WINDOW_NAMES = require('./window-creators/window.names') module.exports = async () => { if ( @@ -47,12 +48,11 @@ module.exports = async () => { } await showLoadingWindow({ + windowName: WINDOW_NAMES.STARTUP_LOADING_WINDOW, description: i18next .t('enforceMacOSAppLocation.loadingWindow.description'), isRequiredToCloseAllWins: true, - isIndeterminateMode: true, - shouldCloseBtnBeShown: true, - shouldMinimizeBtnBeShown: true + isIndeterminateMode: true }) app.moveToApplicationsFolder({ @@ -76,5 +76,7 @@ module.exports = async () => { } }) - await hideLoadingWindow() + await hideLoadingWindow({ + windowName: WINDOW_NAMES.STARTUP_LOADING_WINDOW + }) } diff --git a/src/export-db.js b/src/export-db.js index 0f9652d2..63df4091 100644 --- a/src/export-db.js +++ b/src/export-db.js @@ -14,6 +14,7 @@ const { setLoadingDescription } = require('./window-creators/change-loading-win-visibility-state') const wins = require('./window-creators/windows') +const WINDOW_NAMES = require('./window-creators/window.names') const isMainWinAvailable = require('./helpers/is-main-win-available') const { DEFAULT_ARCHIVE_DB_FILE_NAME, @@ -39,8 +40,8 @@ module.exports = ({ const secretKeyPath = path.join(pathToUserData, SECRET_KEY_FILE_NAME) return async () => { - const win = isMainWinAvailable(wins.mainWindow) - ? wins.mainWindow + const win = isMainWinAvailable(wins[WINDOW_NAMES.MAIN_WINDOW]) + ? wins[WINDOW_NAMES.MAIN_WINDOW] : BrowserWindow.getFocusedWindow() try { @@ -69,6 +70,7 @@ module.exports = ({ } await showLoadingWindow({ + windowName: WINDOW_NAMES.LOADING_WINDOW, description: i18next .t('exportDB.loadingWindow.description') }) @@ -90,7 +92,11 @@ module.exports = ({ : '' const description = `${_description}${archived}` - await setLoadingDescription({ progress, description }) + await setLoadingDescription({ + windowName: WINDOW_NAMES.LOADING_WINDOW, + progress, + description + }) } await zip(filePath, [ @@ -99,7 +105,9 @@ module.exports = ({ dbWalPath, secretKeyPath ], { progressHandler }) - await hideLoadingWindow() + await hideLoadingWindow({ + windowName: WINDOW_NAMES.LOADING_WINDOW + }) await showMessageModalDialog(win, { buttons: [ @@ -111,7 +119,9 @@ module.exports = ({ }) } catch (err) { try { - await hideLoadingWindow() + await hideLoadingWindow({ + windowName: WINDOW_NAMES.LOADING_WINDOW + }) await showErrorModalDialog( win, i18next.t('exportDB.modalDialog.title'), diff --git a/src/import-db.js b/src/import-db.js index b6634802..d0fd3f93 100644 --- a/src/import-db.js +++ b/src/import-db.js @@ -13,6 +13,7 @@ const pauseApp = require('./pause-app') const relaunch = require('./relaunch') const { rm, isMainWinAvailable } = require('./helpers') const wins = require('./window-creators/windows') +const WINDOW_NAMES = require('./window-creators/window.names') const { setLoadingDescription } = require('./window-creators/change-loading-win-visibility-state') @@ -44,8 +45,8 @@ module.exports = ({ pathToUserDocuments }) => { return async () => { - const win = isMainWinAvailable(wins.mainWindow) - ? wins.mainWindow + const win = isMainWinAvailable(wins[WINDOW_NAMES.MAIN_WINDOW]) + ? wins[WINDOW_NAMES.MAIN_WINDOW] : BrowserWindow.getFocusedWindow() try { @@ -99,7 +100,11 @@ module.exports = ({ : '' const description = `${_description}${unzipped}` - await setLoadingDescription({ progress, description }) + await setLoadingDescription({ + windowName: WINDOW_NAMES.LOADING_WINDOW, + progress, + description + }) } await pauseApp({ @@ -130,8 +135,8 @@ module.exports = ({ relaunch() } catch (err) { try { - const _win = isMainWinAvailable(wins.loadingWindow) - ? wins.loadingWindow + const _win = isMainWinAvailable(wins[WINDOW_NAMES.LOADING_WINDOW]) + ? wins[WINDOW_NAMES.LOADING_WINDOW] : win await showErrorModalDialog( _win, diff --git a/src/initialize-app.js b/src/initialize-app.js index 42cd9160..b02020e0 100644 --- a/src/initialize-app.js +++ b/src/initialize-app.js @@ -28,6 +28,7 @@ const { const { hideLoadingWindow } = require('./window-creators/change-loading-win-visibility-state') +const WINDOW_NAMES = require('./window-creators/window.names') const makeOrReadSecretKey = require('./make-or-read-secret-key') const { configsKeeperFactory @@ -53,10 +54,6 @@ const manageWorkerMessages = require( ) const printToPDF = require('./print-to-pdf') -const pathToLayouts = path.join(__dirname, 'window-creators/layouts') -const pathToLayoutAppInitErr = path - .join(pathToLayouts, 'app-init-error.html') - const { rule: schedulerRule } = require( '../bfx-reports-framework/config/schedule.json' ) @@ -218,14 +215,17 @@ module.exports = async () => { manageWorkerMessages(ipc) await isServerReadyPromise await triggerSyncAfterUpdates() - await hideLoadingWindow({ isRequiredToShowMainWin: true }) + await hideLoadingWindow({ + windowName: WINDOW_NAMES.STARTUP_LOADING_WINDOW, + isRequiredToShowMainWin: true + }) await triggerElectronLoad(portsMap) await checkForUpdatesAndNotify() printToPDF() } catch (err) { await app.whenReady() - await createErrorWindow(pathToLayoutAppInitErr) + await createErrorWindow() throw err } diff --git a/src/pause-app.js b/src/pause-app.js index 96d513b2..6df5831b 100644 --- a/src/pause-app.js +++ b/src/pause-app.js @@ -4,6 +4,7 @@ const ipcs = require('./ipcs') const { showLoadingWindow } = require('./window-creators/change-loading-win-visibility-state') +const WINDOW_NAMES = require('./window-creators/window.names') const _closeServer = () => { return new Promise((resolve, reject) => { @@ -35,6 +36,7 @@ module.exports = async (opts) => { } = opts ?? {} await showLoadingWindow({ + windowName: WINDOW_NAMES.LOADING_WINDOW, isRequiredToCloseAllWins: true, ...loadingWinParams }) diff --git a/src/window-creators/change-loading-win-visibility-state.js b/src/window-creators/change-loading-win-visibility-state.js index d686fb77..cf02d74a 100644 --- a/src/window-creators/change-loading-win-visibility-state.js +++ b/src/window-creators/change-loading-win-visibility-state.js @@ -12,19 +12,27 @@ const { const GeneralIpcChannelHandlers = require( './main-renderer-ipc-bridge/general-ipc-channel-handlers' ) +const WINDOW_NAMES = require('./window.names') -let intervalMarker +const intervalMap = new Map() -const _closeAllWindows = () => { +const _closeAllWindows = (opts) => { + const excepedWindowName = opts?.excepedWindowName ?? WINDOW_NAMES.STARTUP_LOADING_WINDOW + const excepedWin = wins[excepedWindowName] const _wins = BrowserWindow.getAllWindows() - .filter((win) => win !== wins.loadingWindow) + .filter((win) => win !== excepedWin) const promises = _wins.map((win) => hideWindow(win)) return Promise.all(promises) } -const setParentToLoadingWindow = (noParent) => { +const setParentToLoadingWindow = (opts) => { + const { + windowName = WINDOW_NAMES.STARTUP_LOADING_WINDOW, + noParent + } = opts ?? {} + // TODO: The reason for it related to the electronjs issue: // `[Bug]: Wrong main window hidden state on macOS when using 'parent' option` // https://github.com/electron/electron/issues/29732 @@ -32,39 +40,41 @@ const setParentToLoadingWindow = (noParent) => { return } - if (wins.loadingWindow.isFocused()) { + if (wins[windowName].isFocused()) { return } const win = BrowserWindow.getFocusedWindow() if (noParent) { - wins.loadingWindow.setParentWindow(null) + wins[windowName].setParentWindow(null) return } if ( Object.values(wins).every((w) => w !== win) || - win === wins.loadingWindow + win === wins[windowName] ) { - const mainWindow = !wins.mainWindow?.isDestroyed() - ? wins.mainWindow + const mainWindow = !wins[WINDOW_NAMES.MAIN_WINDOW]?.isDestroyed() + ? wins[WINDOW_NAMES.MAIN_WINDOW] : null - wins.loadingWindow.setParentWindow(mainWindow) + wins[windowName] + .setParentWindow(mainWindow) return } - wins.loadingWindow.setParentWindow(win) + wins[windowName].setParentWindow(win) } const _runProgressLoader = (opts) => { const { - win = wins.loadingWindow, + windowName = WINDOW_NAMES.STARTUP_LOADING_WINDOW, isIndeterminateMode = false, progress } = opts ?? {} + const win = wins[windowName] if ( !win || @@ -100,7 +110,7 @@ const _runProgressLoader = (opts) => { const step = 1 / (duration / interval) let _progress = 0 - intervalMarker = setInterval(() => { + const intervalMarker = setInterval(() => { if (_progress >= 1) { _progress = 0 } @@ -112,6 +122,7 @@ const _runProgressLoader = (opts) => { typeof win !== 'object' || win.isDestroyed() ) { + const intervalMarker = intervalMap.get(windowName) clearInterval(intervalMarker) return @@ -119,11 +130,17 @@ const _runProgressLoader = (opts) => { win.setProgressBar(_progress) }, interval).unref() + + intervalMap.set(windowName, intervalMarker) } -const _stopProgressLoader = ( - win = wins.loadingWindow -) => { +const _stopProgressLoader = (opts) => { + const { + windowName = WINDOW_NAMES.STARTUP_LOADING_WINDOW + } = opts ?? {} + const win = wins[windowName] + const intervalMarker = intervalMap.get(windowName) + clearInterval(intervalMarker) if ( @@ -141,10 +158,12 @@ const _stopProgressLoader = ( const setLoadingDescription = async (params) => { try { const { - win = wins.loadingWindow, + windowName = WINDOW_NAMES.STARTUP_LOADING_WINDOW, progress, - description = '' + description = '', + isIndeterminateMode = false } = params ?? {} + const win = wins[windowName] if ( !win || @@ -175,17 +194,41 @@ const setLoadingDescription = async (params) => { : '

' const _description = `${progressChunk}${descriptionChunk}` - const loadingDescReadyPromise = GeneralIpcChannelHandlers - .onLoadingDescriptionReady() + const loadingDescReadyPromise = ( + !isIndeterminateMode && + windowName === WINDOW_NAMES.LOADING_WINDOW + ) + ? GeneralIpcChannelHandlers.onLoadingDescriptionReady() + : null + const startupLoadingDescReadyPromise = ( + !isIndeterminateMode && + windowName === WINDOW_NAMES.STARTUP_LOADING_WINDOW + ) + ? GeneralIpcChannelHandlers.onStartupLoadingDescriptionReady() + : null - GeneralIpcChannelHandlers - .sendLoadingDescription(win, { description: _description }) + if (windowName === WINDOW_NAMES.LOADING_WINDOW) { + GeneralIpcChannelHandlers.sendLoadingDescription(win, { + description: _description, + isIndeterminateMode + }) + } + if (windowName === WINDOW_NAMES.STARTUP_LOADING_WINDOW) { + GeneralIpcChannelHandlers.sendStartupLoadingDescription(win, { + description: _description, + isIndeterminateMode + }) + } const loadingRes = await loadingDescReadyPromise + const startupLoadingRes = await startupLoadingDescReadyPromise if (loadingRes?.err) { console.error(loadingRes?.err) } + if (startupLoadingRes?.err) { + console.error(loadingRes?.err) + } } catch (err) { console.error(err) } @@ -200,20 +243,30 @@ const showLoadingWindow = async (opts) => { isIndeterminateMode = false, noParent = false, shouldCloseBtnBeShown, - shouldMinimizeBtnBeShown + shouldMinimizeBtnBeShown, + windowName = WINDOW_NAMES.STARTUP_LOADING_WINDOW } = opts ?? {} if ( - !wins.loadingWindow || - typeof wins.loadingWindow !== 'object' || - wins.loadingWindow.isDestroyed() + !wins[windowName] || + typeof wins[windowName] !== 'object' || + wins[windowName].isDestroyed() ) { - await require('.') - .createLoadingWindow() + if (windowName === WINDOW_NAMES.LOADING_WINDOW) { + await require('.').createLoadingWindow() + } + if (windowName === WINDOW_NAMES.STARTUP_LOADING_WINDOW) { + await require('.').createStartupLoadingWindow() + } + } + if (windowName === WINDOW_NAMES.STARTUP_LOADING_WINDOW) { + setParentToLoadingWindow({ + windowName: WINDOW_NAMES.STARTUP_LOADING_WINDOW, + noParent: isRequiredToCloseAllWins || noParent + }) } - setParentToLoadingWindow(isRequiredToCloseAllWins || noParent) - + const win = wins[windowName] const _progress = Number.isFinite(progress) ? Math.floor(progress * 100) / 100 : progress @@ -222,53 +275,71 @@ const showLoadingWindow = async (opts) => { !isNotRunProgressLoaderRequired || Number.isFinite(progress) ) { - _runProgressLoader({ progress: _progress, isIndeterminateMode }) + _runProgressLoader({ + windowName, + progress: _progress, + isIndeterminateMode + }) } - GeneralIpcChannelHandlers - .sendLoadingBtnStates(wins.loadingWindow, { - shouldCloseBtnBeShown: shouldCloseBtnBeShown ?? false, - shouldMinimizeBtnBeShown: shouldMinimizeBtnBeShown ?? false - }) - await setLoadingDescription({ progress: _progress, description }) + const btnOpts = { + shouldCloseBtnBeShown: shouldCloseBtnBeShown ?? false, + shouldMinimizeBtnBeShown: shouldMinimizeBtnBeShown ?? false + } + + if (windowName === WINDOW_NAMES.LOADING_WINDOW) { + GeneralIpcChannelHandlers.sendLoadingBtnStates(win, btnOpts) + } + if (windowName === WINDOW_NAMES.STARTUP_LOADING_WINDOW) { + GeneralIpcChannelHandlers.sendStartupLoadingBtnStates(win, btnOpts) + } + + await setLoadingDescription({ + windowName, + progress: _progress, + description, + isIndeterminateMode + }) - if (!wins.loadingWindow.isVisible()) { - centerWindow(wins.loadingWindow) + if (!win.isVisible()) { + centerWindow(win) - await showWindow(wins.loadingWindow) + await showWindow(win) } if (!isRequiredToCloseAllWins) { return } - await _closeAllWindows() + await _closeAllWindows({ excepedWindowName: windowName }) } const hideLoadingWindow = async (opts) => { const { + windowName = WINDOW_NAMES.STARTUP_LOADING_WINDOW, isRequiredToShowMainWin = false } = opts ?? {} + const win = wins[windowName] // need to empty description - await setLoadingDescription({ description: '' }) - _stopProgressLoader() + await setLoadingDescription({ windowName, description: '' }) + _stopProgressLoader({ windowName }) if (isRequiredToShowMainWin) { await showWindow( - wins.mainWindow, + wins[WINDOW_NAMES.MAIN_WINDOW], { shouldWinBeFocused: true } ) if (appStates.isMainWinMaximized) { - wins.mainWindow.maximize() + wins[WINDOW_NAMES.MAIN_WINDOW].maximize() } if (appStates.isMainWinFullScreen) { - wins.mainWindow.setFullScreen(true) + wins[WINDOW_NAMES.MAIN_WINDOW].setFullScreen(true) } } return hideWindow( - wins.loadingWindow, + win, { shouldWinBeBlurred: true } ) } diff --git a/src/window-creators/index.js b/src/window-creators/index.js index 835bc188..08353445 100644 --- a/src/window-creators/index.js +++ b/src/window-creators/index.js @@ -8,6 +8,7 @@ const { BrowserWindow } = electron const isDevEnv = process.env.NODE_ENV === 'development' const isMac = process.platform === 'darwin' +const WINDOW_NAMES = require('./window.names') const wins = require('./windows') const ipcs = require('../ipcs') const serve = require('../serve') @@ -48,7 +49,12 @@ const publicDir = path.join(__dirname, '../../bfx-report-ui/build') const loadURL = serve({ directory: publicDir }) const pathToLayouts = path.join(__dirname, 'layouts') -const pathToLayoutAppInit = path.join(pathToLayouts, 'app-init.html') +const pathToLoadingLayout = path + .join(pathToLayouts, 'loading-window.html') +const pathToStartupLoadingLayout = path + .join(pathToLayouts, 'startup-loading-window.html') +const pathToAppInitErrorLayout = path + .join(pathToLayouts, 'app-init-error.html') const _getFileURL = (params) => { const { @@ -95,7 +101,7 @@ const _createWindow = async ( ) => { const { pathname = null, - winName = 'mainWindow' + winName = WINDOW_NAMES.MAIN_WINDOW } = params ?? {} const point = electron.screen.getCursorScreenPoint() @@ -107,7 +113,7 @@ const _createWindow = async ( width: defaultWidth, height: defaultHeight } = workAreaSize - const isMainWindow = winName === 'mainWindow' + const isMainWindow = winName === WINDOW_NAMES.MAIN_WINDOW const { width = defaultWidth, height = defaultHeight, @@ -176,21 +182,26 @@ const _createWindow = async ( win: wins[winName] } - if (!pathname) { + if (isMainWindow) { await showLoadingWindow({ shouldCloseBtnBeShown: true, shouldMinimizeBtnBeShown: true, - noParent: true + noParent: true, + windowName: WINDOW_NAMES.STARTUP_LOADING_WINDOW }) - wins.loadingWindow.setAlwaysOnTop(true) + wins[WINDOW_NAMES.STARTUP_LOADING_WINDOW].setAlwaysOnTop(true) return res } if (props.center) { centerWindow(wins[winName]) } + if (winName === WINDOW_NAMES.STARTUP_LOADING_WINDOW) { + setParentToLoadingWindow({ + windowName: WINDOW_NAMES.STARTUP_LOADING_WINDOW + }) + } - setParentToLoadingWindow() await showWindow(wins[winName]) return res @@ -236,7 +247,7 @@ const _createChildWindow = async ( noParent ) ? null - : wins.mainWindow, + : wins[WINDOW_NAMES.MAIN_WINDOW], alwaysOnTop: isMac, ...opts @@ -245,10 +256,10 @@ const _createChildWindow = async ( winProps.win.on('closed', () => { if (wins.mainWindow) { - wins.mainWindow.close() + wins[WINDOW_NAMES.MAIN_WINDOW].close() } - wins.mainWindow = null + wins[WINDOW_NAMES.MAIN_WINDOW] = null }) return winProps @@ -285,14 +296,22 @@ const createMainWindow = async ({ win.on('closed', () => { if ( - wins.loadingWindow && - typeof wins.loadingWindow === 'object' && - !wins.loadingWindow.isDestroyed() + wins[WINDOW_NAMES.LOADING_WINDOW] && + typeof wins[WINDOW_NAMES.LOADING_WINDOW] === 'object' && + !wins[WINDOW_NAMES.LOADING_WINDOW].isDestroyed() ) { - wins.loadingWindow.close() + wins[WINDOW_NAMES.LOADING_WINDOW].close() + } + if ( + wins[WINDOW_NAMES.STARTUP_LOADING_WINDOW] && + typeof wins[WINDOW_NAMES.STARTUP_LOADING_WINDOW] === 'object' && + !wins[WINDOW_NAMES.STARTUP_LOADING_WINDOW].isDestroyed() + ) { + wins[WINDOW_NAMES.STARTUP_LOADING_WINDOW].close() } - wins.loadingWindow = null + wins[WINDOW_NAMES.LOADING_WINDOW] = null + wins[WINDOW_NAMES.STARTUP_LOADING_WINDOW] = null }) if ( @@ -310,12 +329,14 @@ const createMainWindow = async ({ } if (isDevEnv) { - wins.mainWindow.webContents.openDevTools({ mode: 'right' }) + wins[WINDOW_NAMES.MAIN_WINDOW].webContents + .openDevTools({ mode: 'right' }) } if (isBfxApiStaging()) { - const title = wins.mainWindow.getTitle() + const title = wins[WINDOW_NAMES.MAIN_WINDOW].getTitle() - wins.mainWindow.setTitle(`${title} - BFX API STAGING USED`) + wins[WINDOW_NAMES.MAIN_WINDOW] + .setTitle(`${title} - BFX API STAGING USED`) } createMenu({ pathToUserData, pathToUserDocuments }) @@ -330,8 +351,25 @@ const createMainWindow = async ({ const createLoadingWindow = async () => { const winProps = await _createChildWindow( - pathToLayoutAppInit, - 'loadingWindow', + pathToLoadingLayout, + WINDOW_NAMES.LOADING_WINDOW, + { + width: 350, + height: 350, + maximizable: false, + fullscreenable: false, + parent: wins[WINDOW_NAMES.MAIN_WINDOW], + modal: true + } + ) + + return winProps +} + +const createStartupLoadingWindow = async () => { + const winProps = await _createChildWindow( + pathToStartupLoadingLayout, + WINDOW_NAMES.STARTUP_LOADING_WINDOW, { width: 350, height: 350, @@ -344,18 +382,19 @@ const createLoadingWindow = async () => { return winProps } -const createErrorWindow = async (pathname) => { +const createErrorWindow = async () => { const winProps = await _createChildWindow( - pathname, - 'errorWindow', + pathToAppInitErrorLayout, + WINDOW_NAMES.ERROR_WINDOW, { height: 300, frame: false } ) - await hideLoadingWindow() - await hideWindow(wins.mainWindow) + await hideLoadingWindow({ windowName: WINDOW_NAMES.LOADING_WINDOW }) + await hideLoadingWindow({ windowName: WINDOW_NAMES.STARTUP_LOADING_WINDOW }) + await hideWindow(wins[WINDOW_NAMES.MAIN_WINDOW]) return winProps } @@ -363,5 +402,6 @@ const createErrorWindow = async (pathname) => { module.exports = { createMainWindow, createErrorWindow, - createLoadingWindow + createLoadingWindow, + createStartupLoadingWindow } diff --git a/src/window-creators/layouts/app-init.html b/src/window-creators/layouts/app-init.html deleted file mode 100644 index 16134eb6..00000000 --- a/src/window-creators/layouts/app-init.html +++ /dev/null @@ -1,348 +0,0 @@ - - - - - - - - - - Bitfinex Reports - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - - diff --git a/src/window-creators/layouts/loading-window.css b/src/window-creators/layouts/loading-window.css new file mode 100644 index 00000000..e3a3b2f6 --- /dev/null +++ b/src/window-creators/layouts/loading-window.css @@ -0,0 +1,250 @@ +html { + height: 100%; + background-color: var(--bgColor, #172d3e); +} + +body { + position: relative; + height: 100%; + padding: 0; + margin: 0; + text-align: center; + background-color: var(--bgColor, #172d3e); + font-family: "Inter", sans-serif; + font-feature-settings: 'tnum'; +} + +.win-control-btn { + position: absolute; + top: 0; + right: 0; + display: block; + min-height: 30px; + margin: 0; + padding: 5px; +} + +.window-btn { + position: relative; + display: inline-block; + width: 30px; + height: 30px; + border-radius: 50%; + background-color: var(--bgColor, #172d3e); + transition: background-color .3s; + cursor: pointer; +} + +.window-btn__minimize::after { + content: ""; + display: block; + position: absolute; + width: 12px; + height: 1px; + background-color: var(--btnWindowAfterBg, #9b9a9a); + bottom: 9px; + left: calc((30px - 12px) / 2); +} + +.window-btn__close::before, .window-btn__close::after { + content: ""; + display: block; + position: absolute; + width: 17px; /* sqrt(12^2 + 12^2) where 12px width */ + height: 1px; + background-color: var(--btnWindowAfterBg, #9b9a9a); + bottom: calc((11px / 2) + 9px); + left: calc((30px - 17px) / 2); +} + +.window-btn__close::before { + transform: rotate(45deg); +} + +.window-btn__close::after { + transform: rotate(-45deg); +} + +.window-btn:hover, +.window-btn:active, +.window-btn:focus { + background-color: var(--btnWindowHoverBg, #9b9a9a); +} + +.window-btn--disabled { + display: none; +} + +.lds-roller { + display: inline-block; + position: absolute; + width: 128px; + height: 128px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + opacity: 0; + transition: opacity .5s ease-in-out; +} + +.show-roller { + opacity: 1; +} + +.lds-roller div { + animation: lds-roller 2.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + transform-origin: 64px 64px; +} + +.lds-roller div:after { + content: " "; + display: block; + position: absolute; + width: 6px; + height: 6px; + border-radius: 50%; + background-color: var(--color, #ffffff); + margin: -3px 0 0 -3px; +} + +.lds-roller div:nth-child(1) { + animation-delay: -0.036s; +} + +.lds-roller div:nth-child(1):after { + top: 100px; + left: 100px; +} + +.lds-roller div:nth-child(2) { + animation-delay: -0.072s; +} + +.lds-roller div:nth-child(2):after { + top: 108px; + left: 90px; +} + +.lds-roller div:nth-child(3) { + animation-delay: -0.108s; +} + +.lds-roller div:nth-child(3):after { + top: 113px; + left: 77px; +} + +.lds-roller div:nth-child(4) { + animation-delay: -0.144s; +} + +.lds-roller div:nth-child(4):after { + top: 115px; + left: 64px; +} + +.lds-roller div:nth-child(5) { + animation-delay: -0.18s; +} + +.lds-roller div:nth-child(5):after { + top: 113px; + left: 51px; +} + +.lds-roller div:nth-child(6) { + animation-delay: -0.216s; +} + +.lds-roller div:nth-child(6):after { + top: 108px; + left: 38px; +} + +.lds-roller div:nth-child(7) { + animation-delay: -0.252s; +} + +.lds-roller div:nth-child(7):after { + top: 100px; + left: 28px; +} + +.lds-roller div:nth-child(8) { + animation-delay: -0.288s; +} + +.lds-roller div:nth-child(8):after { + top: 90px; + left: 20px; +} + +.logo-img { + width: 40px; + height: 40px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.logo { + display: inline-block; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + opacity: 0; + animation: logo-shower 3s ease-in-out; + width: 200px; + height: 200px; + background-color: var(--bgColor, #172d3e); +} + +.logo--show-constantly { + animation: none; + opacity: 1; +} + +.description { + display: none; + color: var(--color, #172d3e); + position: fixed; + text-align: center; + margin: 0; + width: 100%; + padding: 20px 0; + bottom: 0; +} + +.description small { + color: var(--color2, #808B93); +} + +@keyframes lds-roller { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes logo-shower { + 0% { + opacity: 0; + } + + 40% { + opacity: 1; + } + + 60% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} diff --git a/src/window-creators/layouts/loading-window.html b/src/window-creators/layouts/loading-window.html new file mode 100644 index 00000000..6b81256a --- /dev/null +++ b/src/window-creators/layouts/loading-window.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + Bitfinex Reports + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + diff --git a/src/window-creators/layouts/loading-window.js b/src/window-creators/layouts/loading-window.js new file mode 100644 index 00000000..9d082ebe --- /dev/null +++ b/src/window-creators/layouts/loading-window.js @@ -0,0 +1,80 @@ +window.initLoadingWindow = (opts) => { + const apiMethodPrefix = opts?.apiMethodPrefix ?? '' + + const rollers = document.getElementsByClassName('lds-roller') + const logoElem = document.getElementById('logo') + const descriptionElem = document.getElementById('description') + const minBtnElem = document.getElementById('minBtn') + const closeBtnElem = document.getElementById('closeBtn') + let timeout = null + + minBtnElem.onclick = async () => { + try { + await window.bfxReportElectronApi?.[`minimize${apiMethodPrefix}LoadingWindow`]() + } catch (err) { + console.error(err) + } + } + closeBtnElem.onclick = async () => { + try { + await window.bfxReportElectronApi?.[`close${apiMethodPrefix}LoadingWindow`]() + } catch (err) { + console.error(err) + } + } + + timeout = setTimeout(() => { + for (const roller of rollers) { + roller.classList.add('show-roller') + } + }, 2500) + + window.bfxReportElectronApi?.[`on${apiMethodPrefix}LoadingBtnStates`]((args) => { + try { + if (args?.shouldMinimizeBtnBeShown) { + minBtnElem.classList.remove('window-btn--disabled') + } else { + minBtnElem.classList.add('window-btn--disabled') + } + if (args?.shouldCloseBtnBeShown) { + closeBtnElem.classList.remove('window-btn--disabled') + } else { + closeBtnElem.classList.add('window-btn--disabled') + } + } catch (err) { + console.debug(err) + } + }) + window.bfxReportElectronApi?.[`on${apiMethodPrefix}LoadingDescription`]((args) => { + try { + if (typeof args?.description !== 'string') { + window.bfxReportElectronApi?.[`send${apiMethodPrefix}LoadingDescriptionReady`]() + + return + } + if (args?.isIndeterminateMode) { + clearTimeout(timeout) + + for (const roller of rollers) { + roller.classList.remove('show-roller') + } + + logoElem.classList.add('logo--show-constantly') + } else { + logoElem.classList.remove('logo--show-constantly') + } + + descriptionElem.innerHTML = args.description + + descriptionElem.style.display = args.description + ? 'block' + : 'none' + + window.bfxReportElectronApi?.[`send${apiMethodPrefix}LoadingDescriptionReady`]() + } catch (err) { + console.error(err) + + window.bfxReportElectronApi?.[`send${apiMethodPrefix}LoadingDescriptionReady`]({ err }) + } + }) +} diff --git a/src/window-creators/layouts/startup-loading-window.html b/src/window-creators/layouts/startup-loading-window.html new file mode 100644 index 00000000..a8328ca6 --- /dev/null +++ b/src/window-creators/layouts/startup-loading-window.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + Bitfinex Reports + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + diff --git a/src/window-creators/main-renderer-ipc-bridge/general-ipc-channel-handlers.js b/src/window-creators/main-renderer-ipc-bridge/general-ipc-channel-handlers.js index 9fed2eeb..23f0e524 100644 --- a/src/window-creators/main-renderer-ipc-bridge/general-ipc-channel-handlers.js +++ b/src/window-creators/main-renderer-ipc-bridge/general-ipc-channel-handlers.js @@ -3,6 +3,7 @@ const { app } = require('electron') const wins = require('../windows') +const WINDOW_NAMES = require('../window.names') const IpcChannelHandlers = require('./ipc.channel.handlers') class GeneralIpcChannelHandlers extends IpcChannelHandlers { @@ -10,16 +11,16 @@ class GeneralIpcChannelHandlers extends IpcChannelHandlers { return app.exit(args?.code ?? 0) } - async minimizeLoadingWindowHandler (event, args) { - await wins.loadingWindow?.minimize() + async getTitleHandler (event, args) { + return wins[WINDOW_NAMES.MAIN_WINDOW].getTitle() } - async closeLoadingWindowHandler (event, args) { - await wins.loadingWindow?.close() + async minimizeLoadingWindowHandler (event, args) { + await wins[WINDOW_NAMES.LOADING_WINDOW]?.minimize() } - async getTitleHandler (event, args) { - return wins.mainWindow.getTitle() + async closeLoadingWindowHandler (event, args) { + await wins[WINDOW_NAMES.LOADING_WINDOW]?.close() } static onLoadingDescriptionReady (cb) { @@ -33,6 +34,26 @@ class GeneralIpcChannelHandlers extends IpcChannelHandlers { static sendLoadingBtnStates (win, args) { return this.sendToRenderer(this.sendLoadingBtnStates, win, args) } + + async minimizeStartupLoadingWindowHandler (event, args) { + await wins[WINDOW_NAMES.STARTUP_LOADING_WINDOW]?.minimize() + } + + async closeStartupLoadingWindowHandler (event, args) { + await wins[WINDOW_NAMES.STARTUP_LOADING_WINDOW]?.close() + } + + static onStartupLoadingDescriptionReady (cb) { + return this.handleListener(this.onStartupLoadingDescriptionReady, cb) + } + + static sendStartupLoadingDescription (win, args) { + return this.sendToRenderer(this.sendStartupLoadingDescription, win, args) + } + + static sendStartupLoadingBtnStates (win, args) { + return this.sendToRenderer(this.sendStartupLoadingBtnStates, win, args) + } } module.exports = GeneralIpcChannelHandlers diff --git a/src/window-creators/main-renderer-ipc-bridge/preload.js b/src/window-creators/main-renderer-ipc-bridge/preload.js index ae4b010b..aeaa83db 100644 --- a/src/window-creators/main-renderer-ipc-bridge/preload.js +++ b/src/window-creators/main-renderer-ipc-bridge/preload.js @@ -12,14 +12,19 @@ const CHANNEL_NAMES = { const GENERAL_INVOKE_METHOD_NAMES = { EXIT: 'exit', + GET_TITLE: 'getTitle', MINIMIZE_LOADING_WINDOW: 'minimizeLoadingWindow', CLOSE_LOADING_WINDOW: 'closeLoadingWindow', - GET_TITLE: 'getTitle' + MINIMIZE_STARTUP_LOADING_WINDOW: 'minimizeStartupLoadingWindow', + CLOSE_STARTUP_LOADING_WINDOW: 'closeStartupLoadingWindow' } const GENERAL_EVENT_METHOD_NAMES = { ON_LOADING_DESCRIPTION: 'onLoadingDescription', ON_LOADING_BTN_STATES: 'onLoadingBtnStates', - SEND_LOADING_DESCRIPTION_READY: 'sendLoadingDescriptionReady' + SEND_LOADING_DESCRIPTION_READY: 'sendLoadingDescriptionReady', + ON_STARTUP_LOADING_DESCRIPTION: 'onStartupLoadingDescription', + ON_STARTUP_LOADING_BTN_STATES: 'onStartupLoadingBtnStates', + SEND_STARTUP_LOADING_DESCRIPTION_READY: 'sendStartupLoadingDescriptionReady' } const TRANSLATIONS_INVOKE_METHOD_NAMES = { SET_LANGUAGE: 'setLanguage', diff --git a/src/window-creators/window.names.js b/src/window-creators/window.names.js new file mode 100644 index 00000000..c0b26659 --- /dev/null +++ b/src/window-creators/window.names.js @@ -0,0 +1,8 @@ +'use strict' + +module.exports = { + MAIN_WINDOW: 'mainWindow', + STARTUP_LOADING_WINDOW: 'startupLoadingWindow', + LOADING_WINDOW: 'loadingWindow', + ERROR_WINDOW: 'errorWindow' +} diff --git a/src/window-creators/windows.js b/src/window-creators/windows.js index 4a1de391..a6e21258 100644 --- a/src/window-creators/windows.js +++ b/src/window-creators/windows.js @@ -1,7 +1,10 @@ 'use strict' +const WINDOW_NAMES = require('./window.names') + module.exports = { - mainWindow: null, - loadingWindow: null, - errorWindow: null + [WINDOW_NAMES.MAIN_WINDOW]: null, + [WINDOW_NAMES.STARTUP_LOADING_WINDOW]: null, + [WINDOW_NAMES.LOADING_WINDOW]: null, + [WINDOW_NAMES.ERROR_WINDOW]: null }