diff --git a/src/main/application.ts b/src/main/application.ts index 65a266ad4..39acc1932 100644 --- a/src/main/application.ts +++ b/src/main/application.ts @@ -9,6 +9,7 @@ import { WindowsService } from './windows-service'; import { StorageService } from './services/storage'; import { getMainMenu } from './menus/main'; import { runAutoUpdaterService } from './services'; +import { DialogsService } from './services/dialogs-service'; export class Application { public static instance = new Application(); @@ -21,6 +22,8 @@ export class Application { public windows = new WindowsService(); + public dialogs = new DialogsService(); + public start() { const gotTheLock = app.requestSingleInstanceLock(); @@ -83,6 +86,7 @@ export class Application { checkFiles(); this.storage.run(); + this.dialogs.run(); this.windows.open(); diff --git a/src/main/dialogs/add-bookmark.ts b/src/main/dialogs/add-bookmark.ts index 8dd52e470..89048c83f 100644 --- a/src/main/dialogs/add-bookmark.ts +++ b/src/main/dialogs/add-bookmark.ts @@ -1,65 +1,46 @@ -import { AppWindow } from '../windows'; -import { Dialog } from '.'; -import { DIALOG_MARGIN, DIALOG_MARGIN_TOP } from '~/constants/design'; +import { BrowserWindow } from 'electron'; import { Application } from '../application'; +import { DIALOG_MARGIN_TOP, DIALOG_MARGIN } from '~/constants/design'; import { IBookmark } from '~/interfaces'; -const WIDTH = 366; - -export class AddBookmarkDialog extends Dialog { - public visible = false; - - public left = 0; - public top = 0; - - constructor(appWindow: AppWindow) { - super(appWindow, { - name: 'add-bookmark', - bounds: { - width: WIDTH, - height: 240, - }, - }); - } - - public rearrange() { - super.rearrange({ - x: Math.round(this.left - WIDTH + DIALOG_MARGIN), - y: Math.round(this.top - DIALOG_MARGIN_TOP), - }); - } - - public async show() { - await super.show(); +export const showAddBookmarkDialog = ( + browserWindow: BrowserWindow, + x: number, + y: number, + data?: { + url: string; + title: string; + bookmark?: IBookmark; + favicon?: string; + }, +) => { + if (!data) { const { url, title, bookmark, favicon, } = Application.instance.windows.current.viewManager.selected; - - this.send('visible', true, { + data = { url, title, bookmark, favicon, - }); + }; } - public async showForBookmark(data: { - url: string; - title: string; - bookmark?: IBookmark; - favicon?: string; - }) { - await super.show(); - const { url, title, bookmark, favicon } = data; - - this.send('visible', true, { - url, - title, - bookmark, - favicon, - }); - } -} + const dialog = Application.instance.dialogs.show({ + name: 'add-bookmark', + browserWindow, + getBounds: () => ({ + width: 366, + height: 240, + x: x - 366 + DIALOG_MARGIN, + y: y - DIALOG_MARGIN_TOP, + }), + }); + + dialog.on('loaded', (e) => { + e.reply('data', data); + }); +}; diff --git a/src/main/dialogs/dialog.ts b/src/main/dialogs/dialog.ts index db174baab..9b2a3f9b4 100644 --- a/src/main/dialogs/dialog.ts +++ b/src/main/dialogs/dialog.ts @@ -1,6 +1,5 @@ -import { BrowserView, app, ipcMain } from 'electron'; +import { BrowserView, app, ipcMain, BrowserWindow } from 'electron'; import { join } from 'path'; -import { AppWindow } from '../windows'; interface IOptions { name: string; @@ -18,8 +17,8 @@ interface IRectangle { height?: number; } -export class Dialog { - public appWindow: AppWindow; +export class PersistentDialog { + public browserWindow: BrowserWindow; public browserView: BrowserView; public visible = false; @@ -31,19 +30,21 @@ export class Dialog { height: 0, }; + public name: string; + private timeout: any; private hideTimeout: number; - private name: string; - - public tabIds: number[] = []; private loaded = false; private showCallback: any = null; - public constructor( - appWindow: AppWindow, - { bounds, name, devtools, hideTimeout, webPreferences }: IOptions, - ) { + public constructor({ + bounds, + name, + devtools, + hideTimeout, + webPreferences, + }: IOptions) { this.browserView = new BrowserView({ webPreferences: { nodeIntegration: true, @@ -53,7 +54,6 @@ export class Dialog { }, }); - this.appWindow = appWindow; this.bounds = { ...this.bounds, ...bounds }; this.hideTimeout = hideTimeout; this.name = name; @@ -62,9 +62,6 @@ export class Dialog { ipcMain.on(`hide-${webContents.id}`, () => { this.hide(false, false); - this.tabIds = this.tabIds.filter( - (x) => x !== appWindow.viewManager.selectedId, - ); }); webContents.once('dom-ready', () => { @@ -77,13 +74,10 @@ export class Dialog { }); if (process.env.NODE_ENV === 'development') { - webContents.loadURL(`http://localhost:4444/${name}.html`); - if (devtools) { - webContents.openDevTools({ mode: 'detach' }); - } + this.webContents.loadURL(`http://localhost:4444/${this.name}.html`); } else { - webContents.loadURL( - join('file://', app.getAppPath(), `build/${name}.html`), + this.webContents.loadURL( + join('file://', app.getAppPath(), `build/${this.name}.html`), ); } } @@ -109,15 +103,13 @@ export class Dialog { } } - public toggle() { - if (!this.visible) this.show(); - } - - public show(focus = true, waitForLoad = true) { + public show(browserWindow: BrowserWindow, focus = true, waitForLoad = true) { return new Promise((resolve) => { + this.browserWindow = browserWindow; + clearTimeout(this.timeout); - this.appWindow.webContents.send( + browserWindow.webContents.send( 'dialog-visibility-change', this.name, true, @@ -131,7 +123,7 @@ export class Dialog { this.visible = true; - this.appWindow.win.addBrowserView(this.browserView); + browserWindow.addBrowserView(this.browserView); this.rearrange(); if (focus) this.webContents.focus(); @@ -157,11 +149,13 @@ export class Dialog { } public hide(bringToTop = false, hideVisually = true) { + if (!this.browserWindow) return; + if (hideVisually) this.hideVisually(); if (!this.visible) return; - this.appWindow.webContents.send( + this.browserWindow.webContents.send( 'dialog-visibility-change', this.name, false, @@ -175,10 +169,10 @@ export class Dialog { if (this.hideTimeout) { this.timeout = setTimeout(() => { - this.appWindow.win.removeBrowserView(this.browserView); + this.browserWindow.removeBrowserView(this.browserView); }, this.hideTimeout); } else { - this.appWindow.win.removeBrowserView(this.browserView); + this.browserWindow.removeBrowserView(this.browserView); } this.visible = false; @@ -187,8 +181,8 @@ export class Dialog { } public bringToTop() { - this.appWindow.win.removeBrowserView(this.browserView); - this.appWindow.win.addBrowserView(this.browserView); + this.browserWindow.removeBrowserView(this.browserView); + this.browserWindow.addBrowserView(this.browserView); } public destroy() { diff --git a/src/main/dialogs/downloads.ts b/src/main/dialogs/downloads.ts index bf197668a..f050ad427 100644 --- a/src/main/dialogs/downloads.ts +++ b/src/main/dialogs/downloads.ts @@ -1,53 +1,45 @@ -import { AppWindow } from '../windows'; -import { Dialog } from '.'; -import { ipcMain } from 'electron'; +import { BrowserWindow } from 'electron'; +import { Application } from '../application'; import { + DIALOG_MARGIN_TOP, DIALOG_MARGIN, DIALOG_TOP, - DIALOG_MARGIN_TOP, } from '~/constants/design'; -const WIDTH = 350; - -export class DownloadsDialog extends Dialog { - public visible = false; - - private height = 0; - - public left = 0; - public top = 0; - - constructor(appWindow: AppWindow) { - super(appWindow, { - name: 'downloads-dialog', - bounds: { - width: WIDTH, - height: 0, - }, - }); - - ipcMain.on(`height-${this.id}`, (e, height) => { - this.height = height; - this.rearrange(); - }); - } - - public rearrange() { - const { height } = this.appWindow.win.getContentBounds(); - - const maxHeight = height - DIALOG_TOP - 16; - - super.rearrange({ - x: Math.round(this.left - WIDTH + DIALOG_MARGIN), - height: Math.round(Math.min(height, this.height + 28)), - y: Math.round(this.top - DIALOG_MARGIN_TOP), - }); - - this.send(`max-height`, Math.min(maxHeight, this.height)); - } - - public async show() { - await super.show(); - this.send('visible', true); - } -} +export const showDownloadsDialog = ( + browserWindow: BrowserWindow, + x: number, + y: number, +) => { + let height = 0; + + const dialog = Application.instance.dialogs.show({ + name: 'downloads-dialog', + browserWindow, + getBounds: () => { + const winBounds = browserWindow.getContentBounds(); + const maxHeight = winBounds.height - DIALOG_TOP - 16; + + height = Math.round(Math.min(winBounds.height, height + 28)); + + dialog.browserView.webContents.send( + `max-height`, + Math.min(maxHeight, height), + ); + + return { + x: x - 350 + DIALOG_MARGIN, + y: y - DIALOG_MARGIN_TOP, + width: 350, + height, + }; + }, + }); + + if (!dialog) return; + + dialog.on('height', (e, h) => { + height = h; + dialog.rearrange(); + }); +}; diff --git a/src/main/dialogs/extension-popup.ts b/src/main/dialogs/extension-popup.ts index 01259c19f..1453170a8 100644 --- a/src/main/dialogs/extension-popup.ts +++ b/src/main/dialogs/extension-popup.ts @@ -1,57 +1,50 @@ -import { AppWindow } from '../windows'; -import { Dialog } from '.'; -import { ipcMain } from 'electron'; -import { DIALOG_MARGIN, DIALOG_MARGIN_TOP } from '~/constants/design'; - -export class ExtensionPopup extends Dialog { - public visible = false; - - private height = 512; - - public left = 0; - public top = 0; - - private width = 512; - - public url = ''; - - constructor(appWindow: AppWindow) { - super(appWindow, { - name: 'extension-popup', - bounds: { - width: 512, - height: 512, - }, - devtools: false, - webPreferences: { - webviewTag: true, - }, - }); - - ipcMain.on(`bounds-${this.id}`, (e, width, height) => { - this.height = height; - this.width = width; - this.rearrange(); - }); - - this.webContents.on('will-attach-webview', (e, webPreferences, params) => { +import { BrowserWindow } from 'electron'; +import { Application } from '../application'; +import { DIALOG_MARGIN_TOP, DIALOG_MARGIN } from '~/constants/design'; + +export const showExtensionDialog = ( + browserWindow: BrowserWindow, + x: number, + y: number, + url: string, + inspect = false, +) => { + if (!process.env.ENABLE_EXTENSIONS) return; + + let height = 512; + let width = 512; + + const dialog = Application.instance.dialogs.show({ + name: 'extension-popup', + browserWindow, + getBounds: () => { + return { + x: x - width + DIALOG_MARGIN, + y: y - DIALOG_MARGIN_TOP, + height: Math.min(1024, height), + width: Math.min(1024, width), + }; + }, + }); + + if (!dialog) return; + + dialog.on('bounds', (e, w, h) => { + width = w; + height = h; + dialog.rearrange(); + }); + + dialog.browserView.webContents.on( + 'will-attach-webview', + (e, webPreferences, params) => { webPreferences.sandbox = true; webPreferences.nodeIntegration = false; webPreferences.contextIsolation = true; - }); - } - - public rearrange() { - super.rearrange({ - x: Math.round(this.left - this.width + DIALOG_MARGIN), - y: Math.round(this.top - DIALOG_MARGIN_TOP), - height: Math.round(Math.min(1024, this.height)), - width: Math.round(Math.min(1024, this.width)), - }); - } + }, + ); - public async show(inspect = false) { - await super.show(); - this.send('visible', true, { url: this.url, inspect }); - } -} + dialog.on('loaded', (e) => { + e.reply('data', { url, inspect }); + }); +}; diff --git a/src/main/dialogs/find.ts b/src/main/dialogs/find.ts index 15f8cceb1..7ef0a41fe 100644 --- a/src/main/dialogs/find.ts +++ b/src/main/dialogs/find.ts @@ -1,45 +1,32 @@ -import { AppWindow } from '../windows'; -import { DIALOG_MIN_HEIGHT, VIEW_Y_OFFSET } from '~/constants/design'; -import { Dialog } from '.'; -import { ipcMain } from 'electron'; +import { VIEW_Y_OFFSET } from '~/constants/design'; +import { BrowserWindow } from 'electron'; +import { Application } from '../application'; -const WIDTH = 416; -const HEIGHT = 70; +export const showFindDialog = (browserWindow: BrowserWindow) => { + const { width } = browserWindow.getContentBounds(); + const appWindow = Application.instance.windows.fromBrowserWindow( + browserWindow, + ); -export class FindDialog extends Dialog { - public constructor(appWindow: AppWindow) { - super(appWindow, { - name: 'find', - bounds: { - width: WIDTH, - height: HEIGHT, - y: VIEW_Y_OFFSET, + Application.instance.dialogs.show({ + name: 'find', + browserWindow, + getBounds: () => ({ + width: 416, + height: 70, + x: width - 416, + y: VIEW_Y_OFFSET, + }), + tabAssociation: { + tabId: appWindow.viewManager.selectedId, + getTabInfo: (tabId) => { + const tab = appWindow.viewManager.views.get(tabId); + return tab.findInfo; }, - }); - - ipcMain.on(`show-${this.id}`, () => { - this.show(); - }); - } - - public rearrangePreview(toggle: boolean) { - super.rearrange({ - height: toggle ? DIALOG_MIN_HEIGHT : HEIGHT, - }); - } - - public async show() { - super.show(); - - const tabId = this.appWindow.viewManager.selectedId; - this.tabIds.push(tabId); - this.send('visible', true, tabId); - } - - public rearrange() { - const { width } = this.appWindow.win.getContentBounds(); - super.rearrange({ - x: width - WIDTH, - }); - } -} + setTabInfo: (tabId, info) => { + const tab = appWindow.viewManager.views.get(tabId); + tab.findInfo = info; + }, + }, + }); +}; diff --git a/src/main/dialogs/index.ts b/src/main/dialogs/index.ts deleted file mode 100644 index bb8752254..000000000 --- a/src/main/dialogs/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export * from './dialog'; -export * from './menu'; -export * from './find'; -export * from './search'; -export * from './auth'; -export * from './permissions'; -export * from './form-fill'; -export * from './credentials'; -export * from './preview'; -export * from './downloads'; -export * from './tabgroup'; -export * from './add-bookmark'; -export * from './extension-popup'; -export * from './zoom'; diff --git a/src/main/dialogs/menu.ts b/src/main/dialogs/menu.ts index 0f6deba73..9bec84333 100644 --- a/src/main/dialogs/menu.ts +++ b/src/main/dialogs/menu.ts @@ -1,39 +1,21 @@ -import { AppWindow } from '../windows'; -import { - MENU_WIDTH, - DIALOG_MARGIN, - DIALOG_MARGIN_TOP, -} from '~/constants/design'; -import { Dialog } from '.'; +import { BrowserWindow } from 'electron'; +import { Application } from '../application'; +import { DIALOG_MARGIN_TOP, DIALOG_MARGIN } from '~/constants/design'; -const WIDTH = MENU_WIDTH; -const HEIGHT = 550; - -export class MenuDialog extends Dialog { - public visible = false; - public left = 0; - public top = 0; - - constructor(appWindow: AppWindow) { - super(appWindow, { - name: 'menu', - bounds: { - width: WIDTH, - height: HEIGHT, - }, - devtools: false, - }); - } - - public rearrange() { - super.rearrange({ - x: Math.round(this.left - WIDTH + DIALOG_MARGIN), - y: Math.round(this.top - DIALOG_MARGIN_TOP), - }); - } - - public async show() { - await super.show(); - this.send('visible', true); - } -} +export const showMenuDialog = ( + browserWindow: BrowserWindow, + x: number, + y: number, +) => { + const menuWidth = 330; + Application.instance.dialogs.show({ + name: 'menu', + browserWindow, + getBounds: () => ({ + width: menuWidth, + height: 470, + x: x - menuWidth + DIALOG_MARGIN, + y: y - DIALOG_MARGIN_TOP, + }), + }); +}; diff --git a/src/main/dialogs/permissions.ts b/src/main/dialogs/permissions.ts index eb06d1cdd..7625173d2 100644 --- a/src/main/dialogs/permissions.ts +++ b/src/main/dialogs/permissions.ts @@ -1,53 +1,50 @@ -import { ipcMain } from 'electron'; import { VIEW_Y_OFFSET } from '~/constants/design'; -import { AppWindow } from '../windows'; -import { Dialog } from '.'; +import { BrowserWindow } from 'electron'; +import { Application } from '../application'; -const HEIGHT = 165; -const WIDTH = 366; +export const requestPermission = ( + browserWindow: BrowserWindow, + name: string, + url: string, + details: any, + tabId: number, +): Promise => { + return new Promise((resolve, reject) => { + if ( + name === 'unknown' || + (name === 'media' && details.mediaTypes.length === 0) || + name === 'midiSysex' + ) { + return reject('Unknown permission'); + } -export class PermissionsDialog extends Dialog { - public constructor(appWindow: AppWindow) { - super(appWindow, { + const appWindow = Application.instance.windows.fromBrowserWindow( + browserWindow, + ); + + appWindow.viewManager.selected.requestedPermission = { name, url, details }; + + const dialog = Application.instance.dialogs.show({ name: 'permissions', - bounds: { - height: HEIGHT, - width: WIDTH, - y: VIEW_Y_OFFSET, + browserWindow, + getBounds: () => ({ + width: 366, + height: 165, x: 0, + y: VIEW_Y_OFFSET, + }), + tabAssociation: { + tabId, + getTabInfo: (tabId) => { + const tab = appWindow.viewManager.views.get(tabId); + return tab.requestedPermission; + }, }, }); - } - public async requestPermission( - name: string, - url: string, - details: any, - tabId: number, - ): Promise { - return new Promise((resolve, reject) => { - if ( - name === 'unknown' || - (name === 'media' && details.mediaTypes.length === 0) || - name === 'midiSysex' - ) { - return reject('Unknown permission'); - } - - this.tabIds.push(tabId); - - this.show(); - - this.send('request-permission', { name, url, details }); - - ipcMain.once( - `request-permission-result-${this.appWindow.id}`, - (e, r: boolean) => { - resolve(r); - this.tabIds = this.tabIds.filter((x) => x !== tabId); - this.hide(); - }, - ); + dialog.on('result', (e, result) => { + resolve(result); + dialog.hide(); }); - } -} + }); +}; diff --git a/src/main/dialogs/preview.ts b/src/main/dialogs/preview.ts index 63c337a8a..c7d3a99aa 100644 --- a/src/main/dialogs/preview.ts +++ b/src/main/dialogs/preview.ts @@ -1,21 +1,22 @@ -import { AppWindow } from '../windows'; -import { MENU_WIDTH, TITLEBAR_HEIGHT } from '~/constants/design'; -import { Dialog } from '.'; +import { BrowserWindow } from 'electron'; +import { Application } from '../application'; +import { + DIALOG_MARGIN_TOP, + DIALOG_MARGIN, + TITLEBAR_HEIGHT, +} from '~/constants/design'; +import { PersistentDialog } from './dialog'; -const WIDTH = MENU_WIDTH; const HEIGHT = 256; -export class PreviewDialog extends Dialog { +export class PreviewDialog extends PersistentDialog { public visible = false; public tab: { id?: number; x?: number } = {}; - private timeout1: any; - - constructor(appWindow: AppWindow) { - super(appWindow, { + constructor() { + super({ name: 'preview', bounds: { - width: appWindow.win.getBounds().width, height: HEIGHT, y: TITLEBAR_HEIGHT, }, @@ -24,24 +25,21 @@ export class PreviewDialog extends Dialog { } public rearrange() { - const { width } = this.appWindow.win.getContentBounds(); + const { width } = this.browserWindow.getContentBounds(); super.rearrange({ width }); } - public rearrangeDialogs(toggle: boolean) { - this.appWindow.dialogs.searchDialog.rearrangePreview(toggle); - this.appWindow.dialogs.findDialog.rearrangePreview(toggle); - } - - public async show() { - clearTimeout(this.timeout1); - this.rearrangeDialogs(true); - - super.show(false); + public async show(browserWindow: BrowserWindow) { + super.show(browserWindow, false); - const { id, url, title, errorURL } = this.appWindow.viewManager.views.get( - this.tab.id, - ); + const { + id, + url, + title, + errorURL, + } = Application.instance.windows + .fromBrowserWindow(browserWindow) + .viewManager.views.get(this.tab.id); this.send('visible', true, { id, @@ -52,11 +50,6 @@ export class PreviewDialog extends Dialog { } public hide(bringToTop = true) { - clearTimeout(this.timeout1); - this.timeout1 = setTimeout(() => { - this.rearrangeDialogs(false); - }, 210); - super.hide(bringToTop); } } diff --git a/src/main/dialogs/search.ts b/src/main/dialogs/search.ts index 028ea20d3..91c342be8 100644 --- a/src/main/dialogs/search.ts +++ b/src/main/dialogs/search.ts @@ -1,20 +1,17 @@ -import { AppWindow } from '../windows'; -import { ipcMain } from 'electron'; -import { Dialog } from '.'; +import { ipcMain, BrowserWindow } from 'electron'; import { DIALOG_MIN_HEIGHT, DIALOG_MARGIN_TOP, TITLEBAR_HEIGHT, DIALOG_MARGIN, } from '~/constants/design'; +import { PersistentDialog } from './dialog'; +import { Application } from '../application'; const WIDTH = 800; const HEIGHT = 80; -export class SearchDialog extends Dialog { - private queueShow = false; - - private lastHeight = 0; +export class SearchDialog extends PersistentDialog { private isPreviewVisible = false; public data = { @@ -23,8 +20,8 @@ export class SearchDialog extends Dialog { width: 200, }; - public constructor(appWindow: AppWindow) { - super(appWindow, { + public constructor() { + super({ name: 'search', bounds: { width: WIDTH, @@ -41,24 +38,13 @@ export class SearchDialog extends Dialog { ? Math.max(DIALOG_MIN_HEIGHT, HEIGHT + height) : HEIGHT + height, }); - - this.lastHeight = HEIGHT + height; }); ipcMain.on(`addressbar-update-input-${this.id}`, (e, data) => { - this.appWindow.send('addressbar-update-input', data); - }); - - ipcMain.on(`can-show-${this.id}`, () => { - if (this.queueShow) this.show(); + this.browserWindow.webContents.send('addressbar-update-input', data); }); } - public toggle() { - if (!this.visible) this.show(); - else this.hide(); - } - public rearrange() { super.rearrange({ x: Math.round(this.data.x - DIALOG_MARGIN), @@ -67,26 +53,11 @@ export class SearchDialog extends Dialog { }); } - public rearrangePreview(toggle: boolean) { - this.isPreviewVisible = toggle; - super.rearrange({ - height: toggle - ? Math.max(DIALOG_MIN_HEIGHT, this.bounds.height) - : this.lastHeight, - }); - } - - public async show() { - if (this.appWindow.dialogs.previewDialog.visible) { - this.appWindow.dialogs.previewDialog.hide(true); - } - - super.show(true, false); - - this.queueShow = true; + public async show(browserWindow: BrowserWindow) { + super.show(browserWindow, true, false); this.send('visible', true, { - id: this.appWindow.viewManager.selectedId, + id: Application.instance.windows.current.viewManager.selectedId, ...this.data, }); @@ -94,11 +65,10 @@ export class SearchDialog extends Dialog { this.send('search-tabs', tabs); }); - this.appWindow.send('get-search-tabs'); + browserWindow.webContents.send('get-search-tabs'); } public hide(bringToTop = false) { super.hide(bringToTop); - this.queueShow = false; } } diff --git a/src/main/dialogs/tabgroup.ts b/src/main/dialogs/tabgroup.ts index 85950182f..1933c8a11 100644 --- a/src/main/dialogs/tabgroup.ts +++ b/src/main/dialogs/tabgroup.ts @@ -1,31 +1,27 @@ -import { AppWindow } from '../windows'; -import { TITLEBAR_HEIGHT, DIALOG_MARGIN_TOP } from '~/constants/design'; -import { Dialog } from '.'; +import { BrowserWindow } from 'electron'; +import { Application } from '../application'; +import { + DIALOG_MARGIN_TOP, + DIALOG_MARGIN, + TITLEBAR_HEIGHT, +} from '~/constants/design'; -const WIDTH = 266; -const HEIGHT = 180; +export const showTabGroupDialog = ( + browserWindow: BrowserWindow, + tabGroup: any, +) => { + const dialog = Application.instance.dialogs.show({ + name: 'tabgroup', + browserWindow, + getBounds: () => ({ + width: 266, + height: 180, + x: tabGroup.x - DIALOG_MARGIN, + y: TITLEBAR_HEIGHT - DIALOG_MARGIN_TOP, + }), + }); -export class TabGroupDialog extends Dialog { - public visible = false; + if (!dialog) return; - constructor(appWindow: AppWindow) { - super(appWindow, { - name: 'tabgroup', - bounds: { - width: WIDTH, - height: HEIGHT, - y: TITLEBAR_HEIGHT - DIALOG_MARGIN_TOP, - }, - }); - } - - public rearrange() { - super.rearrange({ x: this.bounds.x - 20 }); - } - - public edit(tabGroup: any) { - this.bounds.x = Math.round(tabGroup.x); - super.show(); - this.send('visible', true, tabGroup); - } -} + dialog.handle('tabgroup', () => tabGroup); +}; diff --git a/src/main/dialogs/zoom.ts b/src/main/dialogs/zoom.ts index 73c973c15..625c23c4d 100644 --- a/src/main/dialogs/zoom.ts +++ b/src/main/dialogs/zoom.ts @@ -1,34 +1,27 @@ -import { AppWindow } from '../windows'; -import { Dialog } from '.'; -import { DIALOG_MARGIN, DIALOG_MARGIN_TOP } from '~/constants/design'; +import { BrowserWindow } from 'electron'; +import { Application } from '../application'; +import { DIALOG_MARGIN_TOP, DIALOG_MARGIN } from '~/constants/design'; -const WIDTH = 280; +export const showZoomDialog = ( + browserWindow: BrowserWindow, + x: number, + y: number, +) => { + const tabId = Application.instance.windows.fromBrowserWindow(browserWindow) + .viewManager.selectedId; -export class ZoomDialog extends Dialog { - public visible = false; + const dialog = Application.instance.dialogs.show({ + name: 'zoom', + browserWindow, + getBounds: () => ({ + width: 280, + height: 100, + x: x - 280 + DIALOG_MARGIN, + y: y - DIALOG_MARGIN_TOP, + }), + }); - public left = 0; - public top = 0; + if (!dialog) return; - constructor(appWindow: AppWindow) { - super(appWindow, { - name: 'zoom', - bounds: { - width: WIDTH, - height: 100, - }, - }); - } - - public rearrange() { - super.rearrange({ - x: Math.round(this.left - WIDTH + DIALOG_MARGIN), - y: Math.round(this.top - DIALOG_MARGIN_TOP), - }); - } - - public async show() { - await super.show(); - this.send('visible', true); - } -} + dialog.handle('tab-id', () => tabId); +}; diff --git a/src/main/menus/bookmarks.ts b/src/main/menus/bookmarks.ts index 806fa51b9..ba4f4b61c 100644 --- a/src/main/menus/bookmarks.ts +++ b/src/main/menus/bookmarks.ts @@ -9,6 +9,7 @@ import { join } from 'path'; import { IBookmark } from '~/interfaces'; import { Application } from '../application'; import { AppWindow } from '../windows/app'; +import { showAddBookmarkDialog } from '../dialogs/add-bookmark'; function getPath(file: string) { if (process.env.NODE_ENV === 'development') { @@ -110,9 +111,7 @@ export function createMenu(appWindow: AppWindow, item: IBookmark) { label: 'Edit', click: () => { const windowBounds = appWindow.win.getBounds(); - appWindow.dialogs.addBookmarkDialog.left = windowBounds.width - 20; - appWindow.dialogs.addBookmarkDialog.top = 72; - appWindow.dialogs.addBookmarkDialog.showForBookmark({ + showAddBookmarkDialog(appWindow.win, windowBounds.width - 20, 72, { url: item.url, title: item.title, bookmark: item, diff --git a/src/main/menus/main.ts b/src/main/menus/main.ts index a963d3d78..42a10ba8f 100644 --- a/src/main/menus/main.ts +++ b/src/main/menus/main.ts @@ -4,6 +4,7 @@ import { viewSource, saveAs, printPage } from './common-actions'; import { WEBUI_BASE_URL, WEBUI_URL_SUFFIX } from '~/constants/files'; import { AppWindow } from '../windows'; import { Application } from '../application'; +import { showMenuDialog } from '../dialogs/menu'; const isMac = process.platform === 'darwin'; @@ -126,12 +127,14 @@ export const getMainMenu = () => { // Focus address bar ...createMenuItem(['Ctrl+Space', 'CmdOrCtrl+L', 'Alt+D', 'F6'], () => { - Application.instance.windows.current.dialogs.searchDialog.show(); + Application.instance.dialogs + .getPersistent('search') + .show(Application.instance.windows.current.win); }), // Toggle menu ...createMenuItem(['Alt+F', 'Alt+E'], () => { - Application.instance.windows.current.dialogs.menuDialog.show(); + Application.instance.windows.current.send('show-menu-dialog'); }), ], }, diff --git a/src/main/models/settings.ts b/src/main/models/settings.ts index 9a079719f..9f9bad5f9 100644 --- a/src/main/models/settings.ts +++ b/src/main/models/settings.ts @@ -84,13 +84,11 @@ export class Settings extends EventEmitter { this.object.theme === 'wexond-dark' ? 'dark' : 'light'; } + Application.instance.dialogs.sendToAll('update-settings', this.object); + for (const window of Application.instance.windows.list) { window.send('update-settings', this.object); - Object.values(window.dialogs).forEach((dialog) => { - dialog.send('update-settings', this.object); - }); - window.viewManager.views.forEach(async (v) => { if (v.webContents.getURL().startsWith(WEBUI_BASE_URL)) { v.webContents.send('update-settings', this.object); diff --git a/src/main/services/auto-updater.ts b/src/main/services/auto-updater.ts index a5dc76c2c..3727c9161 100644 --- a/src/main/services/auto-updater.ts +++ b/src/main/services/auto-updater.ts @@ -24,7 +24,9 @@ export const runAutoUpdaterService = () => { for (const window of Application.instance.windows.list) { window.send('update-available'); - window.dialogs.menuDialog.send('update-available'); + Application.instance.dialogs + .getDynamic('menu') + ?.browserView?.webContents?.send('update-available'); } }); }; diff --git a/src/main/services/dialogs-service.ts b/src/main/services/dialogs-service.ts new file mode 100644 index 000000000..548a935a2 --- /dev/null +++ b/src/main/services/dialogs-service.ts @@ -0,0 +1,294 @@ +import { BrowserView, app, ipcMain } from 'electron'; +import { join } from 'path'; +import { SearchDialog } from '../dialogs/search'; +import { PreviewDialog } from '../dialogs/preview'; +import { PersistentDialog } from '../dialogs/dialog'; +import { Application } from '../application'; +import { IRectangle } from '~/interfaces'; + +interface IDialogTabAssociation { + tabId?: number; + getTabInfo?: (tabId: number) => any; + setTabInfo?: (tabId: number, ...args: any[]) => void; +} + +interface IDialogShowOptions { + name: string; + browserWindow: Electron.BrowserWindow; + hideTimeout?: number; + devtools?: boolean; + tabAssociation?: IDialogTabAssociation; + onHide?: (dialog: IDialog) => void; + getBounds: () => IRectangle; +} + +interface IDialog { + name: string; + browserView: BrowserView; + id: number; + tabIds: number[]; + _sendTabInfo: (tabId: number) => void; + hide: (tabId?: number) => void; + handle: (name: string, cb: (...args: any[]) => any) => void; + on: (name: string, cb: (...args: any[]) => any) => void; + rearrange: (bounds?: IRectangle) => void; +} + +const roundifyRectangle = (rect: IRectangle): IRectangle => { + const newRect: any = { ...rect }; + Object.keys(newRect).forEach((key) => { + if (!isNaN(newRect[key])) newRect[key] = Math.round(newRect[key]); + }); + return newRect; +}; + +export class DialogsService { + public browserViews: BrowserView[] = []; + public browserViewDetails = new Map(); + public dialogs: IDialog[] = []; + + public persistentDialogs: PersistentDialog[] = []; + + public run() { + this.createBrowserView(); + + this.persistentDialogs.push(new SearchDialog()); + this.persistentDialogs.push(new PreviewDialog()); + } + + private createBrowserView() { + const view = new BrowserView({ + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + enableRemoteModule: true, + webviewTag: true, + }, + }); + + view.webContents.loadURL(`about:blank`); + + this.browserViews.push(view); + + this.browserViewDetails.set(view.id, false); + + return view; + } + + public show(options: IDialogShowOptions): IDialog { + const { + name, + browserWindow, + getBounds, + devtools, + onHide, + hideTimeout, + tabAssociation, + } = options; + + const foundDialog = this.getDynamic(name); + + let browserView = foundDialog + ? foundDialog.browserView + : this.browserViews.find((x) => !this.browserViewDetails.get(x.id)); + + if (!browserView) { + browserView = this.createBrowserView(); + } + + const appWindow = Application.instance.windows.fromBrowserWindow( + browserWindow, + ); + + if (foundDialog && tabAssociation) { + foundDialog.tabIds.push(tabAssociation.tabId); + foundDialog._sendTabInfo(tabAssociation.tabId); + } + + browserWindow.webContents.send('dialog-visibility-change', name, true); + + this.browserViewDetails.set(browserView.id, true); + + if (foundDialog) { + browserWindow.addBrowserView(browserView); + foundDialog.rearrange(); + return null; + } + + browserWindow.addBrowserView(browserView); + browserView.setBounds({ x: 0, y: 0, width: 1, height: 1 }); + + if (devtools) { + browserView.webContents.openDevTools({ mode: 'detach' }); + } + + const channels: string[] = []; + + let activateHandler: any; + let closeHandler: any; + + const dialog: IDialog = { + browserView, + id: browserView.id, + name, + tabIds: [tabAssociation?.tabId], + _sendTabInfo: (tabId) => { + if (tabAssociation.getTabInfo) { + const data = tabAssociation.getTabInfo(tabId); + browserView.webContents.send('update-tab-info', tabId, data); + } + }, + hide: (tabId) => { + const { selectedId } = appWindow.viewManager; + + dialog.tabIds = dialog.tabIds.filter( + (x) => x !== (tabId || selectedId), + ); + + if (tabId && tabId !== selectedId) return; + + browserWindow.webContents.send('dialog-visibility-change', name, false); + + browserWindow.removeBrowserView(browserView); + + if (tabAssociation && dialog.tabIds.length > 0) return; + + ipcMain.removeAllListeners(`hide-${browserView.webContents.id}`); + channels.forEach((x) => { + ipcMain.removeHandler(x); + ipcMain.removeAllListeners(x); + }); + + this.dialogs = this.dialogs.filter((x) => x.id !== dialog.id); + + this.browserViewDetails.set(browserView.id, false); + + if (this.browserViews.length > 1) { + // TODO: garbage collect unused BrowserViews? + // this.browserViewDetails.delete(browserView.id); + // browserView.destroy(); + // this.browserViews.splice(1, 1); + } else { + browserView.webContents.loadURL('about:blank'); + } + + if (tabAssociation) { + appWindow.viewManager.off('activated', activateHandler); + appWindow.viewManager.off('activated', closeHandler); + } + + if (onHide) onHide(dialog); + }, + handle: (name, cb) => { + const channel = `${name}-${browserView.webContents.id}`; + ipcMain.handle(channel, (...args) => cb(...args)); + channels.push(channel); + }, + on: (name, cb) => { + const channel = `${name}-${browserView.webContents.id}`; + ipcMain.on(channel, (...args) => cb(...args)); + channels.push(channel); + }, + rearrange: (rect) => { + rect = rect || {}; + browserView.setBounds({ + x: 0, + y: 0, + width: 0, + height: 0, + ...roundifyRectangle(getBounds()), + ...roundifyRectangle(rect), + }); + }, + }; + + browserView.webContents.once('dom-ready', () => { + dialog.rearrange(); + browserView.webContents.focus(); + }); + + if (process.env.NODE_ENV === 'development') { + browserView.webContents.loadURL(`http://localhost:4444/${name}.html`); + } else { + browserView.webContents.loadURL( + join('file://', app.getAppPath(), `build/${name}.html`), + ); + } + + if (tabAssociation) { + activateHandler = (id: number) => { + const visible = dialog.tabIds.includes(id); + browserWindow.webContents.send( + 'dialog-visibility-change', + name, + visible, + ); + + if (visible) { + dialog._sendTabInfo(id); + browserWindow.removeBrowserView(browserView); + browserWindow.addBrowserView(browserView); + } else { + browserWindow.removeBrowserView(browserView); + } + }; + + closeHandler = (id: number) => { + dialog.hide(id); + }; + + appWindow.viewManager.on('removed', closeHandler); + appWindow.viewManager.on('activated', activateHandler); + } + + ipcMain.on(`hide-${browserView.webContents.id}`, () => { + dialog.hide(); + }); + + if (tabAssociation) { + dialog.on('loaded', () => { + dialog._sendTabInfo(tabAssociation.tabId); + }); + + if (tabAssociation.setTabInfo) { + dialog.on('update-tab-info', (e, tabId, ...args) => { + tabAssociation.setTabInfo(tabId, ...args); + }); + } + } + + this.dialogs.push(dialog); + + return dialog; + } + + public getBrowserViews = () => { + return this.browserViews.concat( + Array.from(this.persistentDialogs).map((x) => x.browserView), + ); + }; + + public destroy = () => { + this.getBrowserViews().forEach((x) => x.destroy()); + }; + + public sendToAll = (channel: string, ...args: any[]) => { + this.getBrowserViews().forEach((x) => x.webContents.send(channel, ...args)); + }; + + public get(name: string) { + return this.getDynamic(name) || this.getPersistent(name); + } + + public getDynamic(name: string) { + return this.dialogs.find((x) => x.name === name); + } + + public getPersistent(name: string) { + return this.persistentDialogs.find((x) => x.name === name); + } + + public isVisible = (name: string) => { + return this.getDynamic(name) || this.getPersistent(name)?.visible; + }; +} diff --git a/src/main/services/messaging.ts b/src/main/services/messaging.ts index 6c14eb776..27a8c1aca 100644 --- a/src/main/services/messaging.ts +++ b/src/main/services/messaging.ts @@ -1,12 +1,22 @@ -import { ipcMain, IpcMainInvokeEvent } from 'electron'; +import { ipcMain } from 'electron'; import { parse } from 'url'; -import { setPassword, deletePassword, getPassword } from 'keytar'; +import { getPassword, setPassword, deletePassword } from 'keytar'; -import { IFormFillData, IBookmark } from '~/interfaces'; import { AppWindow } from '../windows'; -import { getFormFillMenuItems } from '../utils'; import { Application } from '../application'; +import { showMenuDialog } from '../dialogs/menu'; +import { PreviewDialog } from '../dialogs/preview'; +import { IFormFillData, IBookmark } from '~/interfaces'; +import { SearchDialog } from '../dialogs/search'; + import * as bookmarkMenu from '../menus/bookmarks'; +import { showFindDialog } from '../dialogs/find'; +import { getFormFillMenuItems } from '../utils'; +import { showAddBookmarkDialog } from '../dialogs/add-bookmark'; +import { showExtensionDialog } from '../dialogs/extension-popup'; +import { showDownloadsDialog } from '../dialogs/downloads'; +import { showZoomDialog } from '../dialogs/zoom'; +import { showTabGroupDialog } from '../dialogs/tabgroup'; export const runMessagingService = (appWindow: AppWindow) => { const { id } = appWindow; @@ -36,73 +46,65 @@ export const runMessagingService = (appWindow: AppWindow) => { appWindow.fixDragging(); }); - ipcMain.on(`find-show-${id}`, () => { - appWindow.dialogs.findDialog.show(); - }); - - ipcMain.on(`find-in-page-${id}`, () => { - appWindow.send('find'); + ipcMain.on(`show-menu-dialog-${id}`, (e, x, y) => { + showMenuDialog(appWindow.win, x, y); }); - ipcMain.on(`show-menu-dialog-${id}`, (e, left, top) => { - appWindow.dialogs.menuDialog.left = left; - appWindow.dialogs.menuDialog.top = top; - appWindow.dialogs.menuDialog.show(); + ipcMain.on(`search-show-${id}`, (e, data) => { + const dialog = Application.instance.dialogs.getPersistent( + 'search', + ) as SearchDialog; + dialog.data = data; + dialog.show(appWindow.win); }); ipcMain.handle(`is-dialog-visible-${id}`, (e, dialog) => { - return appWindow.dialogs[dialog].visible; - }); - - ipcMain.on(`search-show-${id}`, (e, data) => { - appWindow.dialogs.searchDialog.data = data; - appWindow.dialogs.searchDialog.show(); + return Application.instance.dialogs.isVisible(dialog); }); ipcMain.on(`show-tab-preview-${id}`, (e, tab) => { - appWindow.dialogs.previewDialog.tab = tab; - appWindow.dialogs.previewDialog.show(); + const dialog = Application.instance.dialogs.getPersistent( + 'preview', + ) as PreviewDialog; + dialog.tab = tab; + dialog.show(appWindow.win); }); ipcMain.on(`hide-tab-preview-${id}`, (e, tab) => { - appWindow.dialogs.previewDialog.hide( - appWindow.dialogs.previewDialog.visible, - ); + const dialog = Application.instance.dialogs.getPersistent( + 'preview', + ) as PreviewDialog; + dialog.hide(); }); - ipcMain.on(`show-tabgroup-dialog-${id}`, (e, tabGroup) => { - appWindow.dialogs.tabGroupDialog.edit(tabGroup); + ipcMain.on(`find-show-${id}`, () => { + showFindDialog(appWindow.win); }); - ipcMain.on(`show-downloads-dialog-${id}`, (e, left, top) => { - appWindow.dialogs.downloadsDialog.left = left; - appWindow.dialogs.downloadsDialog.top = top; - appWindow.dialogs.downloadsDialog.show(); + ipcMain.on(`find-in-page-${id}`, () => { + appWindow.send('find'); }); - ipcMain.on(`show-extension-popup-${id}`, (e, left, top, url, inspect) => { - appWindow.dialogs.extensionPopup.left = left; - appWindow.dialogs.extensionPopup.top = top; - appWindow.dialogs.extensionPopup.url = url; - appWindow.dialogs.extensionPopup.show(inspect); + ipcMain.on(`show-add-bookmark-dialog-${id}`, (e, left, top) => { + showAddBookmarkDialog(appWindow.win, left, top); }); - ipcMain.on(`hide-extension-popup-${id}`, (e) => { - if (appWindow.dialogs.extensionPopup.visible) { - appWindow.dialogs.extensionPopup.hideVisually(); - } - }); + if (process.env.ENABLE_EXTENSIONS) { + ipcMain.on(`show-extension-popup-${id}`, (e, left, top, url, inspect) => { + showExtensionDialog(appWindow.win, left, top, url, inspect); + }); + } - ipcMain.on(`show-add-bookmark-dialog-${id}`, (e, left, top) => { - appWindow.dialogs.addBookmarkDialog.left = left; - appWindow.dialogs.addBookmarkDialog.top = top; - appWindow.dialogs.addBookmarkDialog.show(); + ipcMain.on(`show-downloads-dialog-${id}`, (e, left, top) => { + showDownloadsDialog(appWindow.win, left, top); }); ipcMain.on(`show-zoom-dialog-${id}`, (e, left, top) => { - appWindow.dialogs.zoomDialog.left = left; - appWindow.dialogs.zoomDialog.top = top; - appWindow.dialogs.zoomDialog.show(); + showZoomDialog(appWindow.win, left, top); + }); + + ipcMain.on(`show-tabgroup-dialog-${id}`, (e, tabGroup) => { + showTabGroupDialog(appWindow.win, tabGroup); }); ipcMain.on(`edit-tabgroup-${id}`, (e, tabGroup) => { @@ -114,27 +116,28 @@ export const runMessagingService = (appWindow: AppWindow) => { }); if (process.env.ENABLE_AUTOFILL) { - ipcMain.on(`form-fill-show-${id}`, async (e, rect, name, value) => { - const items = await getFormFillMenuItems(name, value); - - if (items.length) { - appWindow.dialogs.formFillDialog.send(`formfill-get-items`, items); - appWindow.dialogs.formFillDialog.inputRect = rect; - - appWindow.dialogs.formFillDialog.resize( - items.length, - items.find((r) => r.subtext) != null, - ); - appWindow.dialogs.formFillDialog.rearrange(); - appWindow.dialogs.formFillDialog.show(false); - } else { - appWindow.dialogs.formFillDialog.hide(); - } - }); - - ipcMain.on(`form-fill-hide-${id}`, () => { - appWindow.dialogs.formFillDialog.hide(); - }); + // TODO: autofill + // ipcMain.on(`form-fill-show-${id}`, async (e, rect, name, value) => { + // const items = await getFormFillMenuItems(name, value); + + // if (items.length) { + // appWindow.dialogs.formFillDialog.send(`formfill-get-items`, items); + // appWindow.dialogs.formFillDialog.inputRect = rect; + + // appWindow.dialogs.formFillDialog.resize( + // items.length, + // items.find((r) => r.subtext) != null, + // ); + // appWindow.dialogs.formFillDialog.rearrange(); + // appWindow.dialogs.formFillDialog.show(false); + // } else { + // appWindow.dialogs.formFillDialog.hide(); + // } + // }); + + // ipcMain.on(`form-fill-hide-${id}`, () => { + // appWindow.dialogs.formFillDialog.hide(); + // }); ipcMain.on( `form-fill-update-${id}`, @@ -164,15 +167,15 @@ export const runMessagingService = (appWindow: AppWindow) => { }, ); - ipcMain.on(`credentials-show-${id}`, (e, data) => { - appWindow.dialogs.credentialsDialog.send('credentials-update', data); - appWindow.dialogs.credentialsDialog.rearrange(); - appWindow.dialogs.credentialsDialog.show(); - }); + // ipcMain.on(`credentials-show-${id}`, (e, data) => { + // appWindow.dialogs.credentialsDialog.send('credentials-update', data); + // appWindow.dialogs.credentialsDialog.rearrange(); + // appWindow.dialogs.credentialsDialog.show(); + // }); - ipcMain.on(`credentials-hide-${id}`, () => { - appWindow.dialogs.credentialsDialog.hide(); - }); + // ipcMain.on(`credentials-hide-${id}`, () => { + // appWindow.dialogs.credentialsDialog.hide(); + // }); ipcMain.on(`credentials-save-${id}`, async (e, data) => { const { username, password, update, oldUsername } = data; @@ -253,7 +256,7 @@ export const runMessagingService = (appWindow: AppWindow) => { ipcMain.handle( `show-bookmarks-bar-dropdown-${id}`, ( - event: IpcMainInvokeEvent, + event, folderId: string, bookmarks: IBookmark[], { x, y }: { x: number; y: number }, @@ -265,7 +268,7 @@ export const runMessagingService = (appWindow: AppWindow) => { ); ipcMain.handle( `show-bookmarks-bar-context-menu-${id}`, - (event: IpcMainInvokeEvent, item: IBookmark) => { + (event, item: IBookmark) => { bookmarkMenu.createMenu(appWindow, item).popup({ window: appWindow.win }); }, ); diff --git a/src/main/sessions-service.ts b/src/main/sessions-service.ts index 16f171217..b145f6ee1 100644 --- a/src/main/sessions-service.ts +++ b/src/main/sessions-service.ts @@ -10,6 +10,7 @@ import { parseCrx } from '~/utils/crx'; import { pathExists } from '~/utils/files'; import { extractZip } from '~/utils/zip'; import { extensions, _setFallbackSession } from 'electron-extensions'; +import { requestPermission } from './dialogs/permissions'; // TODO: move windows list to the corresponding sessions export class SessionsService { @@ -76,7 +77,8 @@ export class SessionsService { }); if (!perm) { - const response = await window.dialogs.permissionsDialog.requestPermission( + const response = await requestPermission( + window.win, permission, webContents.getURL(), details, @@ -115,6 +117,16 @@ export class SessionsService { id, }); + const downloadsDialog = () => + Application.instance.dialogs.getDynamic('downloads-dialog')?.browserView + ?.webContents; + + const downloads: IDownloadItem[] = []; + + ipcMain.handle('get-downloads', () => { + return downloads; + }); + // TODO(sentialx): clean up the download listeners this.view.on('will-download', (event, item, webContents) => { const fileName = item.getFilename(); @@ -139,8 +151,9 @@ export class SessionsService { } const downloadItem = getDownloadItem(item, id); + downloads.push(downloadItem); - window.dialogs.downloadsDialog.send('download-started', downloadItem); + downloadsDialog()?.send('download-started', downloadItem); window.send('download-started', downloadItem); item.on('updated', (event, state) => { @@ -154,17 +167,18 @@ export class SessionsService { const data = getDownloadItem(item, id); - window.dialogs.downloadsDialog.send('download-progress', data); + downloadsDialog()?.send('download-progress', data); window.send('download-progress', data); + + Object.assign(downloadItem, data); }); item.once('done', async (event, state) => { if (state === 'completed') { - window.dialogs.downloadsDialog.send('download-completed', id); - window.send( - 'download-completed', - id, - !window.dialogs.downloadsDialog.visible, - ); + const dialog = downloadsDialog(); + dialog?.send('download-completed', id); + window.send('download-completed', id, !!dialog); + + downloadItem.completed = true; if (process.env.ENABLE_EXTENSIONS && extname(fileName) === '.crx') { const crxBuf = await promises.readFile(item.savePath); @@ -215,8 +229,9 @@ export class SessionsService { ); const downloadItem = getDownloadItem(item, id); + downloads.push(downloadItem); - window.dialogs.downloadsDialog.send('download-started', downloadItem); + downloadsDialog()?.send('download-started', downloadItem); window.send('download-started', downloadItem); item.on('updated', (event, state) => { @@ -230,17 +245,18 @@ export class SessionsService { const data = getDownloadItem(item, id); - window.dialogs.downloadsDialog.send('download-progress', data); + Object.assign(downloadItem, data); + + downloadsDialog()?.send('download-progress', data); window.send('download-progress', data); }); item.once('done', async (event, state) => { + const dialog = downloadsDialog(); if (state === 'completed') { - window.dialogs.downloadsDialog.send('download-completed', id); - window.send( - 'download-completed', - id, - !window.dialogs.downloadsDialog.visible, - ); + dialog?.send('download-completed', id); + window.send('download-completed', id, !!dialog); + + downloadItem.completed = true; } else { console.log(`Download failed: ${state}`); } diff --git a/src/main/view-manager.ts b/src/main/view-manager.ts index 33424d8f2..75abc1e32 100644 --- a/src/main/view-manager.ts +++ b/src/main/view-manager.ts @@ -10,8 +10,10 @@ import { ZOOM_FACTOR_INCREMENT, } from '~/constants/web-contents'; import { extensions } from 'electron-extensions'; +import { EventEmitter } from 'events'; +import { Application } from './application'; -export class ViewManager { +export class ViewManager extends EventEmitter { public views = new Map(); public selectedId = 0; public _fullscreen = false; @@ -30,6 +32,8 @@ export class ViewManager { } public constructor(window: AppWindow, incognito: boolean) { + super(); + this.window = window; this.incognito = incognito; @@ -176,24 +180,6 @@ export class ViewManager { this.window.webContents.focus(); } - this.window.dialogs.previewDialog.hide(true); - - [ - 'findDialog', - 'authDialog', - 'permissionsDialog', - ...(process.env.ENABLE_AUTOFILL - ? ['formFillDialog', 'credentialsDialog'] - : []), - ].forEach((dialog) => { - if (this.window.dialogs[dialog].tabIds.includes(id)) { - this.window.dialogs[dialog].show(); - this.window.dialogs[dialog].bringToTop(); - } else { - this.window.dialogs[dialog].hide(); - } - }); - this.window.updateTitle(); view.updateBookmark(); @@ -201,7 +187,9 @@ export class ViewManager { view.updateNavigationState(); - this.emitZoomUpdate(false); + this.emit('activated', id); + + // TODO: this.emitZoomUpdate(false); } public async fixBounds() { @@ -256,14 +244,18 @@ export class ViewManager { if (view && !view.browserView.isDestroyed()) { this.window.win.removeBrowserView(view.browserView); view.destroy(); + this.emit('removed', id); } } public emitZoomUpdate(showDialog = true) { - this.window.dialogs.zoomDialog.send( - 'zoom-factor-updated', - this.selected.webContents.zoomFactor, - ); + Application.instance.dialogs + .getDynamic('zoom') + ?.browserView?.webContents?.send( + 'zoom-factor-updated', + this.selected.webContents.zoomFactor, + ); + this.window.webContents.send( 'zoom-factor-updated', this.selected.webContents.zoomFactor, diff --git a/src/main/view.ts b/src/main/view.ts index 086fe7828..e33d18a5e 100644 --- a/src/main/view.ts +++ b/src/main/view.ts @@ -32,6 +32,13 @@ export class View { public bookmark: IBookmark; + public findInfo = { + occurrences: '0/0', + text: '', + }; + + public requestedPermission: any; + private historyQueue = new Queue(); private lastUrl = ''; @@ -83,7 +90,9 @@ export class View { }); this.webContents.addListener('found-in-page', (e, result) => { - this.window.dialogs.findDialog.send('found-in-page', result); + Application.instance.dialogs + .getDynamic('find') + .browserView.webContents.send('found-in-page', result); }); this.webContents.addListener('page-title-updated', (e, title) => { diff --git a/src/main/windows-service.ts b/src/main/windows-service.ts index e17e8b1f3..9f648860d 100644 --- a/src/main/windows-service.ts +++ b/src/main/windows-service.ts @@ -1,5 +1,6 @@ import { AppWindow } from './windows/app'; import { extensions } from 'electron-extensions'; +import { BrowserWindow, ipcMain } from 'electron'; export class WindowsService { public list: AppWindow[] = []; @@ -34,6 +35,11 @@ export class WindowsService { return view.id; }; } + + ipcMain.handle('get-tab-zoom', (e, tabId) => { + return this.findByBrowserView(tabId).viewManager.views.get(tabId) + .webContents.zoomFactor; + }); } public open(incognito = false) { @@ -55,6 +61,10 @@ export class WindowsService { return this.list.find((x) => !!x.viewManager.views.get(webContentsId)); } + public fromBrowserWindow(browserWindow: BrowserWindow) { + return this.list.find((x) => x.id === browserWindow.id); + } + public broadcast(channel: string, ...args: unknown[]) { this.list.forEach((appWindow) => appWindow.win.webContents.send(channel, ...args), diff --git a/src/main/windows/app.ts b/src/main/windows/app.ts index 3c04bcd21..6ddd1a629 100644 --- a/src/main/windows/app.ts +++ b/src/main/windows/app.ts @@ -5,45 +5,9 @@ import { resolve, join } from 'path'; import { getPath } from '~/utils'; import { runMessagingService } from '../services'; import { Application } from '../application'; -import { - MenuDialog, - SearchDialog, - FindDialog, - PermissionsDialog, - AuthDialog, - FormFillDialog, - CredentialsDialog, - PreviewDialog, - TabGroupDialog, - DownloadsDialog, - AddBookmarkDialog, - ZoomDialog, - Dialog, - ExtensionPopup, -} from '../dialogs'; import { isNightly } from '..'; import { ViewManager } from '../view-manager'; -interface IDialogs { - searchDialog?: SearchDialog; - previewDialog?: PreviewDialog; - - tabGroupDialog?: TabGroupDialog; - menuDialog?: MenuDialog; - findDialog?: FindDialog; - downloadsDialog?: DownloadsDialog; - addBookmarkDialog?: AddBookmarkDialog; - zoomDialog?: ZoomDialog; - - permissionsDialog?: PermissionsDialog; - authDialog?: AuthDialog; - formFillDialog?: FormFillDialog; - credentialsDialog?: CredentialsDialog; - extensionPopup?: ExtensionPopup; - - [key: string]: Dialog; -} - export class AppWindow { public win: BrowserWindow; @@ -51,10 +15,6 @@ export class AppWindow { public incognito: boolean; - public dialogs: IDialogs = { - searchDialog: new SearchDialog(this), - }; - public constructor(incognito: boolean) { this.win = new BrowserWindow({ frame: false, @@ -82,29 +42,6 @@ export class AppWindow { this.viewManager = new ViewManager(this, incognito); - this.webContents.once('dom-ready', () => { - this.dialogs.previewDialog = new PreviewDialog(this); - - this.dialogs.tabGroupDialog = new TabGroupDialog(this); - this.dialogs.menuDialog = new MenuDialog(this); - this.dialogs.findDialog = new FindDialog(this); - this.dialogs.downloadsDialog = new DownloadsDialog(this); - this.dialogs.addBookmarkDialog = new AddBookmarkDialog(this); - this.dialogs.zoomDialog = new ZoomDialog(this); - - this.dialogs.permissionsDialog = new PermissionsDialog(this); - this.dialogs.authDialog = new AuthDialog(this); - - if (process.env.ENABLE_AUTOFILL) { - this.dialogs.formFillDialog = new FormFillDialog(this); - this.dialogs.credentialsDialog = new CredentialsDialog(this); - } - - if (process.env.ENABLE_EXTENSIONS) { - this.dialogs.extensionPopup = new ExtensionPopup(this); - } - }); - runMessagingService(this); const windowDataPath = getPath('window-data.json'); @@ -144,11 +81,11 @@ export class AppWindow { windowState.bounds = this.win.getBounds(); } - Object.values(this.dialogs).forEach((dialog) => { + /*Object.values(this.dialogs).forEach((dialog) => { if (dialog.visible) { dialog.rearrange(); } - }); + });*/ }); this.win.on('move', () => { @@ -202,12 +139,7 @@ export class AppWindow { this.win.setBrowserView(null); - Object.keys(this.dialogs).forEach((key) => { - if (this.dialogs[key]) { - this.dialogs[key].destroy(); - } - this.dialogs[key] = null; - }); + Application.instance.dialogs.destroy(); this.viewManager.clear(); diff --git a/src/models/dialog-store.ts b/src/models/dialog-store.ts index e3673e055..f7ca52108 100644 --- a/src/models/dialog-store.ts +++ b/src/models/dialog-store.ts @@ -4,6 +4,12 @@ import { getTheme } from '~/utils/themes'; import { ISettings } from '~/interfaces'; import { DEFAULT_SETTINGS } from '~/constants'; +export declare interface DialogStore { + onVisibilityChange: (visible: boolean, ...args: any[]) => void; + onUpdateTabInfo: (tabId: number, data: any) => void; + onHide: (data: any) => void; +} + export class DialogStore { @observable public settings: ISettings = DEFAULT_SETTINGS; @@ -15,6 +21,8 @@ export class DialogStore { private _windowId = -1; + private persistent = false; + @observable public visible = false; @@ -24,31 +32,23 @@ export class DialogStore { options: { hideOnBlur?: boolean; visibilityWrapper?: boolean; + persistent?: boolean; } = {}, ) { - const { visibilityWrapper, hideOnBlur } = { + const { visibilityWrapper, hideOnBlur, persistent } = { hideOnBlur: true, visibilityWrapper: true, + persistent: false, ...options, }; - if (visibilityWrapper) { + + if (!persistent) this.visible = true; + + this.persistent = persistent; + + if (visibilityWrapper && persistent) { ipcRenderer.on('visible', async (e, flag, ...args) => { - if (!this.firstTime) { - requestAnimationFrame(() => { - this.visible = true; - - setTimeout(() => { - this.visible = false; - - setTimeout(() => { - this.onVisibilityChange(flag, ...args); - }, 20); - }, 20); - }); - this.firstTime = true; - } else { - this.onVisibilityChange(flag, ...args); - } + this.onVisibilityChange(flag, ...args); }); } @@ -58,11 +58,32 @@ export class DialogStore { }); } - ipcRenderer.send('get-settings'); + this.settings = { + ...this.settings, + ...ipcRenderer.sendSync('get-settings-sync'), + }; ipcRenderer.on('update-settings', (e, settings: ISettings) => { this.settings = { ...this.settings, ...settings }; }); + + ipcRenderer.on('update-tab-info', (e, tabId, data) => + this.onUpdateTabInfo(tabId, data), + ); + + this.onHide = () => {}; + this.onUpdateTabInfo = () => {}; + this.onVisibilityChange = () => {}; + + this.send('loaded'); + } + + public async invoke(channel: string, ...args: any[]) { + return await ipcRenderer.invoke(`${channel}-${this.id}`, ...args); + } + + public async send(channel: string, ...args: any[]) { + ipcRenderer.send(`${channel}-${this.id}`, ...args); } public get id() { @@ -78,17 +99,14 @@ export class DialogStore { return this._windowId; } - public onVisibilityChange(visible: boolean, ...args: any[]) {} - public hide(data: any = null) { - if (this.visible) { - this.visible = false; - this.onHide(data); - setTimeout(() => { - ipcRenderer.send(`hide-${this.id}`); - }); - } - } + if (this.persistent && !this.visible) return; + + this.visible = false; + this.onHide(data); - public onHide(data: any = null) {} + setTimeout(() => { + this.send('hide'); + }); + } } diff --git a/src/preloads/popup-preload.ts b/src/preloads/popup-preload.ts index a7f8de360..449855ae8 100644 --- a/src/preloads/popup-preload.ts +++ b/src/preloads/popup-preload.ts @@ -3,10 +3,8 @@ import { ipcRenderer } from 'electron'; const updateBounds = () => { ipcRenderer.sendToHost( 'webview-size', - document.documentElement.offsetWidth || - document.documentElement.scrollWidth, - document.documentElement.offsetHeight || - document.documentElement.scrollHeight, + document.body.offsetWidth || document.body.scrollWidth, + document.body.offsetHeight || document.body.scrollHeight, ); }; diff --git a/src/renderer/mixins/dialogs.ts b/src/renderer/mixins/dialogs.ts index 05bf23094..965d01bc0 100644 --- a/src/renderer/mixins/dialogs.ts +++ b/src/renderer/mixins/dialogs.ts @@ -8,7 +8,7 @@ export const DIALOG_BOX_SHADOW = export const DIALOG_BORDER_RADIUS = '4'; -export const DialogStyle = styled.div` +export const DialogBaseStyle = styled.div` margin: 16px; margin-top: 3px; box-shadow: ${DIALOG_BOX_SHADOW}; @@ -16,17 +16,33 @@ export const DialogStyle = styled.div` overflow: hidden; position: relative; + ${({ theme }: { theme?: ITheme }) => css` + background-color: ${theme['dialog.backgroundColor']}; + `} +`; + +export const DialogStyle = styled(DialogBaseStyle)` + @keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + + animation: 0.15s ease-out 0s 1 fadeIn; +`; + +export const PersistentDialogStyle = styled(DialogBaseStyle)` ${({ visible, - theme, hideTransition, }: { visible: boolean; - theme?: ITheme; hideTransition?: boolean; }) => css` transition: ${!visible && !hideTransition ? 'none' : DIALOG_TRANSITION}; opacity: ${visible ? 1 : 0}; - background-color: ${theme['dialog.backgroundColor']}; `} `; diff --git a/src/renderer/views/add-bookmark/store/index.ts b/src/renderer/views/add-bookmark/store/index.ts index 954baa8ea..79bb6d6df 100644 --- a/src/renderer/views/add-bookmark/store/index.ts +++ b/src/renderer/views/add-bookmark/store/index.ts @@ -25,14 +25,14 @@ export class Store extends DialogStore { this.folders = await ipcRenderer.invoke('bookmarks-get-folders'); this.currentFolder = this.folders.find((x) => x.static === 'main'); })(); - } - - public async onVisibilityChange(visible: boolean, data: any) { - this.visible = visible; - if (visible) { + ipcRenderer.on('data', async (e, data) => { const { bookmark, title, url, favicon } = data; + if (!bookmark) { + this.dialogTitle = !bookmark ? 'Bookmark added' : 'Edit bookmark'; + } + this.bookmark = bookmark; this.folders = await ipcRenderer.invoke('bookmarks-get-folders'); @@ -43,9 +43,6 @@ export class Store extends DialogStore { favicon, parent: this.folders.find((x) => x.static === 'main')._id, }); - this.dialogTitle = 'Bookmark added'; - } else { - this.dialogTitle = 'Edit bookmark'; } this.currentFolder = this.folders.find( @@ -57,7 +54,7 @@ export class Store extends DialogStore { this.titleRef.current.focus(); this.titleRef.current.select(); } - } + }); } } diff --git a/src/renderer/views/app/components/BrowserAction/index.tsx b/src/renderer/views/app/components/BrowserAction/index.tsx index 42226e921..ca26a16f0 100644 --- a/src/renderer/views/app/components/BrowserAction/index.tsx +++ b/src/renderer/views/app/components/BrowserAction/index.tsx @@ -72,7 +72,7 @@ const onMouseDown = (data: IBrowserAction) => async (e: any) => { canOpenPopup = !store.dialogsVisibility['extension-popup'] || data.extensionId !== store.extensions.currentlyToggledPopup; - ipcRenderer.send(`hide-extension-popup-${store.windowId}`); + // ipcRenderer.send(`hide-extension-popup-${store.windowId}`); }; export const BrowserAction = observer(({ data }: Props) => { diff --git a/src/renderer/views/app/components/Toolbar/index.tsx b/src/renderer/views/app/components/Toolbar/index.tsx index ed8220d62..37c4d0128 100644 --- a/src/renderer/views/app/components/Toolbar/index.tsx +++ b/src/renderer/views/app/components/Toolbar/index.tsx @@ -25,7 +25,6 @@ import { ICON_INCOGNITO, ICON_MORE, ICON_SEARCH, - ICON_DASHBOARD, ICON_MAGNIFY_PLUS, ICON_MAGNIFY_MINUS, } from '~/renderer/constants/icons'; @@ -58,28 +57,20 @@ let menuRef: HTMLDivElement = null; let zoomRef: HTMLDivElement = null; const showAddBookmarkDialog = async () => { - if (!(await isDialogVisible('addBookmarkDialog'))) { - const { right, bottom } = starRef.getBoundingClientRect(); - ipcRenderer.send( - `show-add-bookmark-dialog-${store.windowId}`, - right, - bottom, - ); - } + const { right, bottom } = starRef.getBoundingClientRect(); + ipcRenderer.send(`show-add-bookmark-dialog-${store.windowId}`, right, bottom); }; const showZoomDialog = async () => { - if (!(await isDialogVisible('zoomDialog')) && store.zoomFactor != 1) { + if (store.zoomFactor != 1) { const { right, bottom } = zoomRef.getBoundingClientRect(); ipcRenderer.send(`show-zoom-dialog-${store.windowId}`, right, bottom); } }; const showMenuDialog = async () => { - if (!(await isDialogVisible('menuDialog'))) { - const { right, bottom } = menuRef.getBoundingClientRect(); - ipcRenderer.send(`show-menu-dialog-${store.windowId}`, right, bottom); - } + const { right, bottom } = menuRef.getBoundingClientRect(); + ipcRenderer.send(`show-menu-dialog-${store.windowId}`, right, bottom); }; ipcRenderer.on('show-add-bookmark-dialog', () => { diff --git a/src/renderer/views/downloads-dialog/store/index.ts b/src/renderer/views/downloads-dialog/store/index.ts index 0ed0cf43a..46a115e76 100644 --- a/src/renderer/views/downloads-dialog/store/index.ts +++ b/src/renderer/views/downloads-dialog/store/index.ts @@ -13,6 +13,8 @@ export class Store extends DialogStore { public constructor() { super(); + this.init(); + ipcRenderer.on('download-started', (e, item) => { this.downloads.push(item); }); @@ -38,8 +40,8 @@ export class Store extends DialogStore { }); } - public onVisibilityChange(visible: boolean) { - this.visible = visible; + public async init() { + this.downloads = await ipcRenderer.invoke('get-downloads'); } } diff --git a/src/renderer/views/extension-popup/index.tsx b/src/renderer/views/extension-popup/index.tsx index e3f7d4049..92e0f143f 100644 --- a/src/renderer/views/extension-popup/index.tsx +++ b/src/renderer/views/extension-popup/index.tsx @@ -98,12 +98,9 @@ const createWebview = (url: string, inspect: boolean) => { container.appendChild(webview); }; -ipcRenderer.on('visible', (e, flag, data) => { - if (flag) { - const { url, inspect } = data; - createWebview(url, inspect); - } else { - visible = false; - hide(); - } +ipcRenderer.on('data', (e, data) => { + const { url, inspect } = data; + createWebview(url, inspect); }); + +ipcRenderer.send(`loaded-${getWebContentsId()}`); diff --git a/src/renderer/views/find/store/index.ts b/src/renderer/views/find/store/index.ts index aeaac7987..7d66433b2 100644 --- a/src/renderer/views/find/store/index.ts +++ b/src/renderer/views/find/store/index.ts @@ -1,6 +1,6 @@ import * as React from 'react'; -import { observable, computed } from 'mobx'; +import { observable } from 'mobx'; import { ipcRenderer } from 'electron'; import { DialogStore } from '~/models/dialog-store'; import { callViewMethod } from '~/utils/view'; @@ -10,6 +10,11 @@ interface IFindInfo { text: string; } +const defaultFindInfo = { + occurrences: '0/0', + text: '', +}; + export class Store extends DialogStore { @observable public tabId = -1; @@ -19,52 +24,43 @@ export class Store extends DialogStore { public findInputRef = React.createRef(); - @computed - public get findInfo() { - const findInfo = this.tabsFindInfo.get(this.tabId); + @observable + public findInfo = defaultFindInfo; - if (findInfo) { - return findInfo; - } + public constructor() { + super({ hideOnBlur: false }); - return { - occurrences: '0/0', - text: '', - }; - } + this.init(); - public constructor() { - super({ hideOnBlur: false, visibilityWrapper: false }); + this.onUpdateTabInfo = (tabId, info) => { + this.tabId = tabId; + this.findInfo = info; + }; - ipcRenderer.on('visible', (e, flag, tabId) => { - this.visible = flag; + this.onHide = () => { + callViewMethod(this.tabId, 'stopFindInPage', 'clearSelection'); + this.findInfo = defaultFindInfo; + this.sendInfo(); + ipcRenderer.send(`window-focus-${this.windowId}`); + }; + } - if (flag && tabId) { - if (!this.tabsFindInfo.get(tabId)) { - this.tabsFindInfo.set(tabId, { - occurrences: '0/0', - text: '', - }); - } - this.tabId = tabId; - } - if (this.findInputRef && this.findInputRef.current) { - this.findInputRef.current.focus(); - } - }); + public async init() { + if (this.findInputRef && this.findInputRef.current) { + this.findInputRef.current.focus(); + } ipcRenderer.on( 'found-in-page', (e, { activeMatchOrdinal, matches }: Electron.FoundInPageResult) => { this.findInfo.occurrences = `${activeMatchOrdinal}/${matches}`; + this.sendInfo(); }, ); } - public onHide() { - callViewMethod(this.tabId, 'stopFindInPage', 'clearSelection'); - this.tabsFindInfo.delete(this.tabId); - ipcRenderer.send(`window-focus-${this.windowId}`); + public sendInfo() { + this.send('update-tab-info', this.tabId, { ...this.findInfo }); } } diff --git a/src/renderer/views/menu/components/App/index.tsx b/src/renderer/views/menu/components/App/index.tsx index af6ba1f5e..7d138202d 100644 --- a/src/renderer/views/menu/components/App/index.tsx +++ b/src/renderer/views/menu/components/App/index.tsx @@ -14,7 +14,7 @@ export const App = hot( - + diff --git a/src/renderer/views/menu/store/index.ts b/src/renderer/views/menu/store/index.ts index 997bfacba..eb33cdac8 100644 --- a/src/renderer/views/menu/store/index.ts +++ b/src/renderer/views/menu/store/index.ts @@ -17,16 +17,12 @@ export class Store extends DialogStore { }); } - public async onVisibilityChange(visible: boolean) { - this.visible = visible; - - if (visible) { - if (remote.getCurrentWindow()) { - this.alwaysOnTop = remote.getCurrentWindow().isAlwaysOnTop(); - } - - this.updateAvailable = await ipcRenderer.invoke('is-update-available'); + public async init() { + if (remote.getCurrentWindow()) { + this.alwaysOnTop = remote.getCurrentWindow().isAlwaysOnTop(); } + + this.updateAvailable = await ipcRenderer.invoke('is-update-available'); } public async save() { diff --git a/src/renderer/views/permissions/components/App/index.tsx b/src/renderer/views/permissions/components/App/index.tsx index 4b9490c4f..1baed6779 100644 --- a/src/renderer/views/permissions/components/App/index.tsx +++ b/src/renderer/views/permissions/components/App/index.tsx @@ -3,14 +3,13 @@ import { observer } from 'mobx-react-lite'; import { ThemeProvider } from 'styled-components'; import { hot } from 'react-hot-loader/root'; -import { ipcRenderer } from 'electron'; import { StyledApp, Title, Permissions, Permission, Buttons } from './style'; import store from '../../store'; import { Button } from '~/renderer/components/Button'; import { UIStyle } from '~/renderer/mixins/default-styles'; const sendResult = (r: boolean) => { - ipcRenderer.send(`request-permission-result-${store.windowId}`, r); + store.send('result', r); }; const getText = (permission: string) => { diff --git a/src/renderer/views/permissions/store/index.ts b/src/renderer/views/permissions/store/index.ts index 0413c4773..f4bd09d01 100644 --- a/src/renderer/views/permissions/store/index.ts +++ b/src/renderer/views/permissions/store/index.ts @@ -12,9 +12,9 @@ export class Store extends DialogStore { public domain: string; public constructor() { - super(); + super({ hideOnBlur: false }); - ipcRenderer.on('request-permission', (e, { url, name, details }) => { + ipcRenderer.on('update-tab-info', (e, tabId, { url, name, details }) => { this.domain = getDomain(url); this.permissions = []; diff --git a/src/renderer/views/preview/components/App/style.ts b/src/renderer/views/preview/components/App/style.ts index fce2f3137..d7781e90f 100644 --- a/src/renderer/views/preview/components/App/style.ts +++ b/src/renderer/views/preview/components/App/style.ts @@ -2,9 +2,9 @@ import styled, { css } from 'styled-components'; import { ITheme } from '~/interfaces'; import { maxLines } from '~/renderer/mixins'; import { TAB_MAX_WIDTH } from '~/renderer/views/app/constants/tabs'; -import { DialogStyle } from '~/renderer/mixins/dialogs'; +import { PersistentDialogStyle } from '~/renderer/mixins/dialogs'; -export const StyledApp = styled(DialogStyle)` +export const StyledApp = styled(PersistentDialogStyle)` margin: 0; padding: 12px; font-size: 13px; diff --git a/src/renderer/views/preview/store/index.ts b/src/renderer/views/preview/store/index.ts index eaccf3852..8241a8ebe 100644 --- a/src/renderer/views/preview/store/index.ts +++ b/src/renderer/views/preview/store/index.ts @@ -38,7 +38,7 @@ export class Store extends DialogStore { } constructor() { - super({ visibilityWrapper: false }); + super({ visibilityWrapper: false, persistent: true }); ipcRenderer.on('visible', (e, visible, tab) => { clearTimeout(this.timeout); diff --git a/src/renderer/views/search/components/App/index.tsx b/src/renderer/views/search/components/App/index.tsx index 699adbe14..d1b4fff88 100644 --- a/src/renderer/views/search/components/App/index.tsx +++ b/src/renderer/views/search/components/App/index.tsx @@ -119,7 +119,7 @@ export const App = hot( return ( - + diff --git a/src/renderer/views/search/store/index.ts b/src/renderer/views/search/store/index.ts index 7ec40391a..c68e5b7d0 100644 --- a/src/renderer/views/search/store/index.ts +++ b/src/renderer/views/search/store/index.ts @@ -4,7 +4,6 @@ import { ipcRenderer } from 'electron'; import { observable, computed } from 'mobx'; import { ISuggestion, IVisitedItem } from '~/interfaces'; import { SuggestionsStore } from './suggestions'; -import { NEWTAB_URL } from '~/constants/tabs'; import { DialogStore } from '~/models/dialog-store'; let lastSuggestion: string; @@ -67,6 +66,7 @@ export class Store extends DialogStore { public constructor() { super({ visibilityWrapper: false, + persistent: true, }); ipcRenderer.on('visible', (e, visible, data) => { @@ -74,7 +74,6 @@ export class Store extends DialogStore { if (visible) { this.tabs = []; - this.suggestions.list = []; this.tabId = data.id; this.canSuggest = this.inputText.length <= data.text.length; @@ -96,6 +95,20 @@ export class Store extends DialogStore { this.loadHistory(); ipcRenderer.send(`can-show-${this.id}`); + + this.onHide = (data) => { + ipcRenderer.send(`addressbar-update-input-${this.id}`, { + id: this.tabId, + text: this.inputRef.current.value, + selectionStart: this.inputRef.current.selectionStart, + selectionEnd: this.inputRef.current.selectionEnd, + ...data, + }); + + this.tabs = []; + this.inputRef.current.value = ''; + this.suggestions.list = []; + }; } public getCanSuggest(key: number) { @@ -116,20 +129,6 @@ export class Store extends DialogStore { return false; } - public onHide(data: any = null) { - ipcRenderer.send(`addressbar-update-input-${this.id}`, { - id: this.tabId, - text: this.inputRef.current.value, - selectionStart: this.inputRef.current.selectionStart, - selectionEnd: this.inputRef.current.selectionEnd, - ...data, - }); - - this.tabs = []; - this.inputRef.current.value = ''; - this.suggestions.list = []; - } - public async loadHistory() { this.visitedItems = await ipcRenderer.invoke('topsites-get'); } diff --git a/src/renderer/views/tabgroup/components/App/index.tsx b/src/renderer/views/tabgroup/components/App/index.tsx index 19fdef308..df8398878 100644 --- a/src/renderer/views/tabgroup/components/App/index.tsx +++ b/src/renderer/views/tabgroup/components/App/index.tsx @@ -45,7 +45,7 @@ export const App = hot( observer(() => { return ( - + this.zoomFactor, + () => this.resetHideTimer(), + ); ipcRenderer.on('zoom-factor-updated', (e, zoomFactor) => { this.zoomFactor = zoomFactor; }); - const zoomFactorChange = reaction( - () => this.zoomFactor, - () => this.resetHideTimer() - ) - } + const tabId = await this.invoke('tab-id'); + this.zoomFactor = await ipcRenderer.invoke('get-tab-zoom', tabId); - public async onVisibilityChange(visible: boolean) { - this.visible = visible; - if (visible) { - this.resetHideTimer(); - } + this.resetHideTimer(); } public resetHideTimer() { clearTimeout(this.timer); - var context = this; - this.timer = setTimeout(function () { - context.hide(); + this.timer = setTimeout(() => { + this.hide(); }, 1500); } diff --git a/webpack.config.base.js b/webpack.config.base.js index 7389b9756..ce4bc989f 100644 --- a/webpack.config.base.js +++ b/webpack.config.base.js @@ -77,7 +77,7 @@ const config = { loader: 'ts-loader', options: { experimentalWatchApi: dev, - transpileOnly: dev, + transpileOnly: true, // TODO: dev getCustomTransformers: () => ({ before: [styledComponentsTransformer], }),