diff --git a/CHANGELOG.md b/CHANGELOG.md index a156051d..70e042df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.40.1] - 2025-12-19 + +### Changed + +- Used and adapted the implemented modal window for the `show-docs` module instead of the `electron-alert` library. This module is used for showing the `changelog` and the `user manual`. PR: [bfx-report-electron#575](https://github.com/bitfinexcom/bfx-report-electron/pull/575) +- Reworked and optimized `BalancePrecisionSelector` in a more performant way. PR: [bfx-report-ui#1001](https://github.com/bitfinexcom/bfx-report-ui/pull/1001) + +### Fixed + +- Reverted [(improvements) USDT0 support and currencies mapping flow](https://github.com/bitfinexcom/bfx-report-ui/pull/992) as a quick fix for the `USDt` mapping flow issues noted after this update. PR: [bfx-report-ui#1002](https://github.com/bitfinexcom/bfx-report-ui/pull/1002) + ## [4.40.0] - 2025-12-17 ### Added diff --git a/bfx-report-ui b/bfx-report-ui index 51fcd392..1f316c66 160000 --- a/bfx-report-ui +++ b/bfx-report-ui @@ -1 +1 @@ -Subproject commit 51fcd392ecec5752c654ee03e0d505109606436e +Subproject commit 1f316c66075625f579a5448d39cde7f96144a3ca diff --git a/build/locales/en/translations.json b/build/locales/en/translations.json index 1939090e..4dec1675 100644 --- a/build/locales/en/translations.json +++ b/build/locales/en/translations.json @@ -154,6 +154,13 @@ "cancelButtonText": "Close" } }, + "changelog": { + "modalDialog": { + "title": "Changelog v{{version}}", + "fullChangelogTitle": "Full Changelog", + "confirmButtonText": "Show Full Changelog" + } + }, "printToPDF": { "defaultTemplate": "No data", "pagination": { diff --git a/build/locales/es-EM/translations.json b/build/locales/es-EM/translations.json index d75d80bf..8fb8b9bb 100644 --- a/build/locales/es-EM/translations.json +++ b/build/locales/es-EM/translations.json @@ -154,6 +154,13 @@ "cancelButtonText": "Cerrar" } }, + "changelog": { + "modalDialog": { + "title": "Registro de cambios v{{version}}", + "fullChangelogTitle": "Registro de cambios completo", + "confirmButtonText": "Mostrar registro de cambios completo" + } + }, "printToPDF": { "defaultTemplate": "Sin datos", "pagination": { diff --git a/build/locales/pt-BR/translations.json b/build/locales/pt-BR/translations.json index 6b36ec45..2a334dbe 100644 --- a/build/locales/pt-BR/translations.json +++ b/build/locales/pt-BR/translations.json @@ -154,6 +154,13 @@ "cancelButtonText": "Fechar" } }, + "changelog": { + "modalDialog": { + "title": "Registro de alterações v{{version}}", + "fullChangelogTitle": "Registro de alterações completo", + "confirmButtonText": "Mostrar registro de alterações completo" + } + }, "printToPDF": { "defaultTemplate": "Sem dados", "pagination": { diff --git a/build/locales/ru/translations.json b/build/locales/ru/translations.json index 3df34d17..8c1046b9 100644 --- a/build/locales/ru/translations.json +++ b/build/locales/ru/translations.json @@ -154,6 +154,13 @@ "cancelButtonText": "Закрыть" } }, + "changelog": { + "modalDialog": { + "title": "Журнал Изменений v{{version}}", + "fullChangelogTitle": "Полный Журнал Изменений", + "confirmButtonText": "Показать Полный Журнал" + } + }, "printToPDF": { "defaultTemplate": "Нет данных", "pagination": { diff --git a/build/locales/tr/translations.json b/build/locales/tr/translations.json index d56e625d..2def0bfc 100644 --- a/build/locales/tr/translations.json +++ b/build/locales/tr/translations.json @@ -154,6 +154,13 @@ "cancelButtonText": "Kapat" } }, + "changelog": { + "modalDialog": { + "title": "Değişiklik günlüğü v{{version}}", + "fullChangelogTitle": "Tam değişiklik günlüğü", + "confirmButtonText": "Tam değişiklik günlüğünü göster" + } + }, "printToPDF": { "defaultTemplate": "Veri yok", "pagination": { diff --git a/build/locales/vi/translations.json b/build/locales/vi/translations.json index b94d76f6..cabee3f5 100644 --- a/build/locales/vi/translations.json +++ b/build/locales/vi/translations.json @@ -154,6 +154,13 @@ "cancelButtonText": "Đóng" } }, + "changelog": { + "modalDialog": { + "title": "Nhật ký thay đổi v{{version}}", + "fullChangelogTitle": "Nhật ký thay đổi đầy đủ", + "confirmButtonText": "Hiển thị nhật ký thay đổi đầy đủ" + } + }, "printToPDF": { "defaultTemplate": "Không có dữ liệu", "pagination": { diff --git a/build/locales/zh-CN/translations.json b/build/locales/zh-CN/translations.json index 1c6332f5..8b89f01d 100644 --- a/build/locales/zh-CN/translations.json +++ b/build/locales/zh-CN/translations.json @@ -154,6 +154,13 @@ "cancelButtonText": "关闭" } }, + "changelog": { + "modalDialog": { + "title": "更新日志 v{{version}}", + "fullChangelogTitle": "完整更新日志", + "confirmButtonText": "显示完整更新日志" + } + }, "printToPDF": { "defaultTemplate": "无数据", "pagination": { diff --git a/build/locales/zh-TW/translations.json b/build/locales/zh-TW/translations.json index 0b806731..bbd69846 100644 --- a/build/locales/zh-TW/translations.json +++ b/build/locales/zh-TW/translations.json @@ -154,6 +154,13 @@ "cancelButtonText": "關閉" } }, + "changelog": { + "modalDialog": { + "title": "更新日誌 v{{version}}", + "fullChangelogTitle": "完整更新日誌", + "confirmButtonText": "顯示完整更新日誌" + } + }, "printToPDF": { "defaultTemplate": "無資料", "pagination": { diff --git a/package-lock.json b/package-lock.json index f8ea00d1..1ba5dfc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bfx-report-electron", - "version": "4.40.0", + "version": "4.40.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bfx-report-electron", - "version": "4.40.0", + "version": "4.40.1", "license": "Apache-2.0", "dependencies": { "archiver": "7.0.1", diff --git a/package.json b/package.json index c19e0f40..6b46235d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bfx-report-electron", - "version": "4.40.0", + "version": "4.40.1", "repository": "https://github.com/bitfinexcom/bfx-report-electron", "description": "Reporting tool", "author": "bitfinex.com", diff --git a/src/changelog-manager/show-changelog.js b/src/changelog-manager/show-changelog.js index 106df12c..e863acc3 100644 --- a/src/changelog-manager/show-changelog.js +++ b/src/changelog-manager/show-changelog.js @@ -1,8 +1,10 @@ 'use strict' const path = require('path') +const fs = require('fs') const parseChangelog = require('changelog-parser') const { rootPath } = require('electron-root-path') +const i18next = require('i18next') const getDebugInfo = require('../helpers/get-debug-info') const showDocs = require('../show-docs') @@ -11,6 +13,7 @@ const MENU_ITEM_IDS = require('../create-menu/menu.item.ids') const { changeMenuItemStatesById } = require('../create-menu/utils') const changelogPath = path.join(rootPath, 'CHANGELOG.md') +const changelog = fs.readFileSync(changelogPath, 'utf8') const disableShowChangelogMenuItem = () => { changeMenuItemStatesById( @@ -24,7 +27,7 @@ module.exports = async (params = {}) => { const version = params?.version ?? getDebugInfo()?.version const mdEntries = await parseChangelog({ - filePath: changelogPath, + text: changelog, removeMarkdown: false }) @@ -60,11 +63,23 @@ module.exports = async (params = {}) => { const versionTitle = `## ${mdEntry.title}` const mdDoc = `${mdTitle}\n\n${versionTitle}\n\n${mdEntry.body}` - await showDocs({ - title: mdEntries.title, - mdDoc + const res = await showDocs({ + title: i18next.t('changelog.modalDialog.title', { version }), + mdDoc, + showConfirmButton: true, + confirmButtonText: i18next + .t('changelog.modalDialog.confirmButtonText') }) + if (res?.dismiss === 'confirm') { + await showDocs({ + title: i18next + .t('changelog.modalDialog.fullChangelogTitle'), + showWinCloseButton: true, + mdDoc: changelog + }) + } + return { error: null, isShown: true diff --git a/src/show-docs/index.js b/src/show-docs/index.js index 5a3c9435..a32a4532 100644 --- a/src/show-docs/index.js +++ b/src/show-docs/index.js @@ -1,56 +1,16 @@ 'use strict' -const { app, screen, remote } = require('electron') -const fs = require('fs') -const path = require('path') +const { app } = require('electron') const { Converter } = require('showdown') -const Alert = require('electron-alert') -const { rootPath } = require('electron-root-path') const i18next = require('i18next') -const wins = require('../window-creators/windows') +const { + createModalWindow +} = require('../window-creators') const isMainWinAvailable = require( '../helpers/is-main-win-available' ) -const getAlertCustomClassObj = require( - '../helpers/get-alert-custom-class-obj' -) -const { - closeAlert -} = require('../modal-dialog-src/utils') const { UserManualShowingError } = require('../errors') -const getUIFontsAsCSSString = require( - '../helpers/get-ui-fonts-as-css-string' -) - -const { - WINDOW_EVENT_NAMES, - addOnceProcEventHandler -} = require('../window-creators/window-event-manager') -const ThemeIpcChannelHandlers = require( - '../window-creators/main-renderer-ipc-bridge/theme-ipc-channel-handlers' -) - -const mdStyle = fs.readFileSync(path.join( - rootPath, 'node_modules', 'github-markdown-css/github-markdown.css' -)) -const fontsStyle = getUIFontsAsCSSString() -const themesStyle = fs.readFileSync(path.join( - __dirname, '../window-creators/layouts/themes.css' -)) -const alertStyle = fs.readFileSync(path.join( - __dirname, '../modal-dialog-src/modal-dialog.css' -)) -const alertScript = fs.readFileSync(path.join( - __dirname, '../modal-dialog-src/modal-dialog.js' -)) - -const fonts = `` -const themes = `` -const mdS = `` -const style = `` -const script = `` -const sound = { freq: 'F2', type: 'triange', duration: 1.5 } const converter = new Converter({ tables: true, @@ -60,119 +20,47 @@ const converter = new Converter({ requireSpaceBeforeHeadingText: true }) -const _fireAlert = (params) => { +const _fireAlert = async (params) => { const { - type = 'info', title = i18next.t('showDocs.modalDialog.title'), - html - } = params - const win = wins.mainWindow - - if (!isMainWinAvailable()) { - return { value: false } - } - - const _screen = screen || remote.screen - const { - getCursorScreenPoint, - getDisplayNearestPoint - } = _screen - const { - workArea - } = getDisplayNearestPoint(getCursorScreenPoint()) - const { height: screenHeight } = workArea - const maxHeight = Math.floor(screenHeight * 0.90) - - const alert = new Alert([mdS, fonts, themes, style, script]) - - const eventHandlerCtx = addOnceProcEventHandler( - WINDOW_EVENT_NAMES.CLOSED, - () => closeAlert(alert) - ) - - const bwOptions = { - frame: false, - transparent: false, - thickFrame: false, - closable: false, - hasShadow: false, - backgroundColor: ThemeIpcChannelHandlers.getWindowBackgroundColor(), - darkTheme: false, - parent: win, - modal: true, - width: 1000 - } - const swalOptions = { - position: 'center', - allowOutsideClick: false, - customClass: getAlertCustomClassObj({ - htmlContainer: 'markdown-body' - }), - - type, - title, - html, - showConfirmButton: false, - focusCancel: true, - showCancelButton: true, - cancelButtonText: i18next.t('showDocs.modalDialog.cancelButtonText'), - timerProgressBar: false, - - willOpen: () => { - if ( - !alert || - !alert.browserWindow - ) return - - alert.browserWindow.hide() - }, - didOpen: () => { - if ( - !alert || - !alert.browserWindow - ) return - - alert.browserWindow.show() - const { height } = alert.browserWindow - .getContentBounds() - alert.browserWindow.setBounds({ - height: height > maxHeight - ? maxHeight - : height - }) + html = '', + showConfirmButton = false, + showWinCloseButton = false, + confirmButtonText = i18next.t('common.confirmButtonText') + } = params ?? {} + + const res = await createModalWindow( + { + icon: 'info', + title, + text: html, + textClassName: 'markdown-body', + showConfirmButton, + confirmButtonText, + showCancelButton: true, + showWinCloseButton, + cancelButtonText: i18next + .t('showDocs.modalDialog.cancelButtonText') }, - willClose: () => { - if ( - !alert || - !alert.browserWindow - ) return + { + width: 1000, + height: 600, + shouldWinBeClosedIfClickingOutside: true + }) - alert.browserWindow.hide() - }, - didClose: () => { - eventHandlerCtx.removeListener() - } - } - - const res = alert.fire( - swalOptions, - bwOptions, - null, - true, - false, - sound - ) - - return res + return res?.modalRes ?? {} } -module.exports = async (params = {}) => { +module.exports = async (params) => { try { const { - type, + icon, title, - mdDoc = i18next.t('mdDocs:userManual') - } = params + mdDoc = i18next.t('mdDocs:userManual'), + showConfirmButton, + confirmButtonText, + showWinCloseButton + } = params ?? {} if ( !app.isReady() || @@ -183,7 +71,14 @@ module.exports = async (params = {}) => { const html = converter.makeHtml(mdDoc) - await _fireAlert({ type, title, html }) + return await _fireAlert({ + icon, + title, + html, + showConfirmButton, + confirmButtonText, + showWinCloseButton + }) } catch (err) { console.error(err) } diff --git a/src/window-creators/index.js b/src/window-creators/index.js index 8b80438e..b6173be6 100644 --- a/src/window-creators/index.js +++ b/src/window-creators/index.js @@ -413,6 +413,7 @@ const createStartupLoadingWindow = async () => { } const createModalWindow = async (args, opts) => { + const shouldDevToolsBeShown = opts?.shouldDevToolsBeShown const parentWin = ( opts?.hasNoParentWin || !wins?.[WINDOW_NAMES.MAIN_WINDOW] || @@ -426,13 +427,25 @@ const createModalWindow = async (args, opts) => { const { height: screenHeight } = workArea const maxHeight = Math.floor(screenHeight * 0.90) const width = opts?.width ?? 600 + const shouldWinBeClosedIfClickingOutside = ( + parentWin && + opts?.shouldWinBeClosedIfClickingOutside && + !shouldDevToolsBeShown + ) let closedEventPromise = {} const winProps = await _createChildWindow( { + shouldDevToolsBeShown, pathname: pathToModalLayout, winName: WINDOW_NAMES.MODAL_WINDOW, didFinishLoadHook: async (win) => { + if (shouldWinBeClosedIfClickingOutside) { + win.once('blur', () => { + ModalIpcChannelHandlers.sendCloseModalEvent(win) + }) + } + closedEventPromise = ModalIpcChannelHandlers .sendFireModalEvent(win, args) await ModalIpcChannelHandlers @@ -446,6 +459,7 @@ const createModalWindow = async (args, opts) => { maxHeight, maximizable: false, fullscreenable: false, + minimizable: false, parent: parentWin, modal: !!parentWin } @@ -456,8 +470,12 @@ const createModalWindow = async (args, opts) => { winProps.win && !winProps.win.isDestroyed() ) { + const closedWinPromise = new Promise((resolve) => { + winProps.win.once('closed', resolve) + }) winProps.win.hide() - winProps.win.close() + winProps.win.destroy() + await closedWinPromise } return { diff --git a/src/window-creators/layouts/modal-window.css b/src/window-creators/layouts/modal-window.css index 301d79d3..80e8361d 100644 --- a/src/window-creators/layouts/modal-window.css +++ b/src/window-creators/layouts/modal-window.css @@ -16,6 +16,58 @@ body { font-size: 16px; } +.window-control-btns { + z-index: 9; + position: fixed; + top: 0; + right: 0; + display: block; + margin: 0; + padding: 5px; + font-size: 0px; +} + +.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__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; +} + body::-webkit-scrollbar, .markdown-body pre::-webkit-scrollbar { width: 8px; diff --git a/src/window-creators/layouts/modal-window.html b/src/window-creators/layouts/modal-window.html index ca34b18d..9570d15d 100644 --- a/src/window-creators/layouts/modal-window.html +++ b/src/window-creators/layouts/modal-window.html @@ -13,6 +13,10 @@ +
+
+
+ diff --git a/src/window-creators/layouts/modal-window.js b/src/window-creators/layouts/modal-window.js index cfa0c793..a96a08db 100644 --- a/src/window-creators/layouts/modal-window.js +++ b/src/window-creators/layouts/modal-window.js @@ -47,6 +47,7 @@ window.addEventListener('load', async () => { try { const modalElem = document.getElementById('modal') + const closeBtnElem = document.getElementById('closeBtn') let toastId = null let timeout = null @@ -63,8 +64,8 @@ window.addEventListener('load', async () => { const showModal = () => { modalElem.style.display = 'flex' } - const finalizeModalWindow = (e, args) => { - e.preventDefault() + const finalizeModalWindow = (args, e) => { + e?.preventDefault?.() clearTimeout(timeout) hideModal() @@ -83,7 +84,8 @@ window.addEventListener('load', async () => { cancelButtonText = 'Cancel', confirmHotkey = 'Enter', containerClassName = '', - textClassName = '' + textClassName = '', + showWinCloseButton = false } = args ?? {} const elems = [] const btnElems = [] @@ -102,6 +104,18 @@ window.addEventListener('load', async () => { ) { modalElem.classList.add(containerClassName) } + if (showWinCloseButton) { + closeBtnElem.addEventListener('click', (e) => { + finalizeModalWindow({ + dismiss: DISMISS_REASONS.CANCEL, + toastId: args?.toastId + }, e) + }) + + closeBtnElem.classList.remove('window-btn--disabled') + } else { + closeBtnElem.classList.add('window-btn--disabled') + } const iconHTML = iconMap[icon] @@ -144,18 +158,18 @@ class="modal__btn modal__btn--cancel">${cancelButtonText}`) if (showConfirmButton) { confirmBtnElem.addEventListener('click', (e) => { - finalizeModalWindow(e, { + finalizeModalWindow({ dismiss: DISMISS_REASONS.CONFIRM, toastId: args?.toastId - }) + }, e) }) } if (showCancelButton) { cancelBtnElem.addEventListener('click', (e) => { - finalizeModalWindow(e, { + finalizeModalWindow({ dismiss: DISMISS_REASONS.CANCEL, toastId: args?.toastId - }) + }, e) }) } @@ -180,13 +194,23 @@ class="modal__btn modal__btn--cancel">${cancelButtonText}`) ? DISMISS_REASONS.CONFIRM : DISMISS_REASONS.CANCEL - finalizeModalWindow(e, { + finalizeModalWindow({ dismiss, toastId: args?.toastId - }) + }, e) }) } + window.bfxReportElectronApi?.onCloseModalEvent(() => { + if (!toastId) { + return + } + + finalizeModalWindow({ + dismiss: DISMISS_REASONS.CLOSE, + toastId + }) + }) window.bfxReportElectronApi?.onFireModalEvent((args) => { if (toastId) { sendModalClosedEvent({ diff --git a/src/window-creators/main-renderer-ipc-bridge/modal-ipc-channel-handlers.js b/src/window-creators/main-renderer-ipc-bridge/modal-ipc-channel-handlers.js index 884bebd3..7409455b 100644 --- a/src/window-creators/main-renderer-ipc-bridge/modal-ipc-channel-handlers.js +++ b/src/window-creators/main-renderer-ipc-bridge/modal-ipc-channel-handlers.js @@ -22,6 +22,10 @@ class ModalIpcChannelHandlers extends IpcChannelHandlers { return this.handleListener(this.onModalClosedEvent, cb) } + static sendCloseModalEvent (win, args) { + return this.sendToRenderer(this.sendCloseModalEvent, win, args) + } + static async sendFireModalEvent (win, args) { if (!args?.preventSettingHeightToContent) { this.isModalReadyToBeShownControlObj diff --git a/src/window-creators/main-renderer-ipc-bridge/preload.js b/src/window-creators/main-renderer-ipc-bridge/preload.js index a539fad9..c7c15cfd 100644 --- a/src/window-creators/main-renderer-ipc-bridge/preload.js +++ b/src/window-creators/main-renderer-ipc-bridge/preload.js @@ -52,10 +52,11 @@ const AUTO_UPDATE_EVENT_METHOD_NAMES = { SEND_TOAST_CLOSED_EVENT: 'sendToastClosedEvent' } const MODAL_INVOKE_METHOD_NAMES = { - ON_FIRE_TOAST_EVENT: 'setWindowHeight' + SET_WINDOW_HEIGHT: 'setWindowHeight' } const MODAL_EVENT_METHOD_NAMES = { ON_FIRE_TOAST_EVENT: 'onFireModalEvent', + ON_CLOSE_MODAL_EVENT: 'onCloseModalEvent', SEND_TOAST_CLOSED_EVENT: 'sendModalClosedEvent' }