├── .gitignore ├── LICENSE ├── README.md ├── example ├── index.js └── modal.html ├── modal.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | sandbox.js 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # electron-modal-window 2 | 3 | Easily create and use electron modal windows 4 | 5 | ``` 6 | npm install electron-modal-window 7 | ``` 8 | 9 | ## Usage 10 | 11 | In your "main" renderer process where you want to spawn a modal from do 12 | 13 | ``` js 14 | const modal = require('electron-modal-window') 15 | 16 | const m = modal.createModal(`file://${__dirname}/modal.html`, { 17 | width: 300, 18 | height: 300 // and pass any other electron BrowserWindow opts you want 19 | }) 20 | 21 | m.window // this is the modal BrowserWindow 22 | 23 | m.on('hello', function (cb) { 24 | // emitted when the modal sends 'hello' 25 | cb(null, 'world') 26 | }) 27 | ``` 28 | 29 | In the js for modal.html 30 | 31 | ``` js 32 | const modal = require('electron-modal-window') 33 | 34 | m.send('hello', function (err, val) { 35 | console.log('they said', val) 36 | }) 37 | 38 | // m.window is the current window 39 | ``` 40 | 41 | ## API 42 | 43 | #### `m = modal.createModal([url, browserWindowOptions])` 44 | 45 | Make a new module. Set `url` to the url the modal should load. 46 | All `browserWindowOptions` are forwarded to the BrowserWindow constructor. 47 | 48 | #### `m.window` 49 | 50 | The attached BrowserWindow instance. 51 | 52 | #### `m.on(name, args..., callback)` 53 | 54 | Emitted when the modal sends a message. You can reply back by calling the calling cb. 55 | 56 | #### `m.send(name, args..., [callback])` 57 | 58 | Send a message to the modal. The optional callback is called with the reply. 59 | If an error occured (i.e. the modal sent an error or the modal closed) it will 60 | be passed to the callback. 61 | 62 | #### `modal.on(name, args..., callback)` 63 | 64 | Same as `m.on` but use this in the modal window to listen for messages from the modal creator. 65 | 66 | #### `modal.send(name, args..., [callback])` 67 | 68 | Same as `m.send` but use this in the modal window to message the modal creator. 69 | 70 | #### `modal.window` 71 | 72 | The modals own window instance. 73 | 74 | ## License 75 | 76 | MIT 77 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | const modal = require('../modal') 2 | 3 | let n = 0 4 | 5 | const m = modal.createModal(`file://${__dirname}/modal.html`, { 6 | height: 600, 7 | width: 300, 8 | webPreferences: { 9 | nodeIntegration: true 10 | }, 11 | frame: true 12 | }) 13 | 14 | // modal never answers 15 | m.send('void', function (err) { 16 | console.log('reply', err) 17 | }) 18 | 19 | m.on('increment', function (cb) { 20 | console.log('incrementing') 21 | cb(null, ++n) 22 | }) 23 | 24 | m.on('decrement', function (cb) { 25 | console.log('decrementing') 26 | cb(null, --n) 27 | }) 28 | 29 | m.window.on('closed', function () { 30 | console.log('modal closed') 31 | }) 32 | -------------------------------------------------------------------------------- /example/modal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

0

8 | 9 | 10 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /modal.js: -------------------------------------------------------------------------------- 1 | const { remote, ipcRenderer } = require('electron') 2 | const { EventEmitter } = require('events') 3 | 4 | const { BrowserWindow } = remote 5 | const parent = remote.getCurrentWindow() 6 | 7 | // clean up lingering windows 8 | for (const win of parent.getChildWindows()) { 9 | win.removeAllListeners('closed') 10 | win.destroy() 11 | } 12 | 13 | let isSetup = false 14 | 15 | const client = module.exports = new EventEmitter() 16 | 17 | client.once('newListener', setupClient) 18 | 19 | client.send = function (name, ...values) { 20 | setupClient() 21 | const req = client.queue.request(name, ...values) 22 | if (req) ipcRenderer.send('--modal-window-request', req) 23 | } 24 | 25 | client.modals = [] 26 | client.createModal = createModal 27 | client.get = get 28 | client.id = 0 29 | client.window = null 30 | client.queue = null 31 | 32 | function get (id) { 33 | for (let i = 0; i < client.modals.length; i++) { 34 | if (client.modals[i].id === id) return client.modals[i] 35 | } 36 | return null 37 | } 38 | 39 | function setupClient () { 40 | if (isSetup) return 41 | isSetup = true 42 | 43 | client.window = remote.getCurrentWindow() 44 | client.id = client.window.id 45 | client.queue = new RequestQueue(client.id) 46 | 47 | ipcRenderer.on('--modal-window-response', function (e, data) { 48 | client.queue.callback(data) 49 | }) 50 | 51 | ipcRenderer.on('--modal-window-request', function (e, data) { 52 | const values = data.values.concat(reply) 53 | client.emit(data.name, ...values) 54 | 55 | function reply (err, ...values) { 56 | ipcRenderer.send('--modal-window-response', client.queue.response(data, err, ...values)) 57 | } 58 | }) 59 | } 60 | 61 | function setupMain () { 62 | if (isSetup) return 63 | isSetup = true 64 | 65 | const { ipcMain } = remote 66 | 67 | ipcMain.removeAllListeners('--modal-window-response') 68 | ipcMain.on('--modal-window-response', function (e, data) { 69 | const { queue } = client.get(data.windowId) 70 | queue.callback(data) 71 | }) 72 | 73 | ipcMain.removeAllListeners('--modal-window-request') 74 | ipcMain.on('--modal-window-request', function (e, data) { 75 | const modal = client.get(data.windowId) 76 | const values = data.values.concat(reply) 77 | modal.emit(data.name, ...values) 78 | 79 | function reply (err, ...values) { 80 | modal.window.webContents.send('--modal-window-response', modal.queue.response(data, err, ...values)) 81 | } 82 | }) 83 | } 84 | 85 | function noop () {} 86 | 87 | function createModal (url, opts) { 88 | setupMain() 89 | 90 | const parent = remote.getCurrentWindow() 91 | 92 | const win = new BrowserWindow({ 93 | parent, 94 | ...opts 95 | }) 96 | 97 | const queue = new RequestQueue(win.id) 98 | 99 | if (url) win.loadURL(url) 100 | 101 | const modal = new EventEmitter() 102 | client.modals.push(modal) 103 | 104 | modal.id = win.id 105 | modal.window = win 106 | modal.queue = queue 107 | 108 | modal.send = function (name, ...values) { 109 | const req = queue.request(name, ...values) 110 | if (req) win.webContents.send('--modal-window-request', req) 111 | } 112 | 113 | win.on('closed', function () { 114 | const i = client.modals.indexOf(modal) 115 | if (i > -1) client.modals.splice(i, 1) 116 | queue.destroy() 117 | }) 118 | 119 | return modal 120 | } 121 | 122 | class RequestQueue { 123 | constructor (windowId) { 124 | this.windowId = windowId 125 | this.inflight = [] 126 | } 127 | 128 | callback (response) { 129 | if (!this.inflight) return 130 | const req = this.inflight[response.requestId - 1] 131 | this.inflight[response.requestId - 1] = null 132 | while (this.inflight.length && !this.inflight[this.inflight.length - 1]) this.inflight.pop() 133 | req.callback(response.error ? new Error(response.error) : null, ...response.values) 134 | } 135 | 136 | response (request, err, ...values) { 137 | return { 138 | requestId: request.requestId, 139 | error: err ? err.message : null, 140 | values 141 | } 142 | } 143 | 144 | request (name, ...values) { 145 | const requestId = this.inflight.length + 1 146 | 147 | const callback = typeof values[values.length - 1] === 'function' 148 | ? values.pop() 149 | : noop 150 | 151 | if (!this.inflight) { 152 | process.nextTick(callback, new Error('Modal closed')) 153 | return null 154 | } 155 | 156 | this.inflight.push({ 157 | requestId, 158 | name, 159 | values, 160 | callback 161 | }) 162 | 163 | return { 164 | windowId: this.windowId, 165 | requestId, 166 | name, 167 | values 168 | } 169 | } 170 | 171 | destroy () { 172 | if (!this.inflight) return 173 | for (const req of this.inflight) { 174 | if (req) process.nextTick(req.callback, new Error('Modal closed')) 175 | } 176 | this.inflight = null 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-modal-window", 3 | "version": "1.0.0", 4 | "description": "Easily make electron modal windows", 5 | "main": "modal.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "nanotron": "^2.1.1", 9 | "standard": "^12.0.1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/hyperdivision/electron-modal-window.git" 14 | }, 15 | "scripts": { 16 | "example": "cd example && nanotron", 17 | "test": "standard" 18 | }, 19 | "author": "Mathias Buus (@mafintosh)", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/hyperdivision/electron-modal-window/issues" 23 | }, 24 | "homepage": "https://github.com/hyperdivision/electron-modal-window" 25 | } 26 | --------------------------------------------------------------------------------