Skip to content
This repository has been archived by the owner on Dec 27, 2022. It is now read-only.

Commit

Permalink
Added shell-window modals
Browse files Browse the repository at this point in the history
  • Loading branch information
pfrazee committed May 2, 2018
1 parent 1714c89 commit d29e79b
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 19 deletions.
2 changes: 2 additions & 0 deletions app/background-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as adblocker from './background-process/adblocker'
import * as analytics from './background-process/analytics'

import * as windows from './background-process/ui/windows'
import * as modals from './background-process/ui/modals'
import * as windowMenu from './background-process/ui/window-menu'
import registerContextMenu from './background-process/ui/context-menu'
import * as downloads from './background-process/ui/downloads'
Expand Down Expand Up @@ -82,6 +83,7 @@ app.on('ready', async function () {
windowMenu.setup()
registerContextMenu()
windows.setup()
modals.setup()
downloads.setup()
permissions.setup()
basicAuth.setup()
Expand Down
9 changes: 8 additions & 1 deletion app/background-process/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const exec = require('util').promisify(require('child_process').exec)
var debug = require('debug')('beaker')
import * as settingsDb from './dbs/settings'
import {open as openUrl} from './open-url'
import {showModal, closeModal} from './ui/modals'
import {showModal, showShellModal, closeModal} from './ui/modals'
import {
INVALID_SAVE_FOLDER_CHAR_REGEX
} from '../lib/const'
Expand Down Expand Up @@ -117,6 +117,7 @@ export const WEBAPI = {
openUrl: url => { openUrl(url) }, // dont return anything
openFolder,
doWebcontentsCmd,
doTest,
closeModal
}

Expand Down Expand Up @@ -398,6 +399,12 @@ async function doWebcontentsCmd (method, wcId, ...args) {
return wc[method](...args)
}

async function doTest (test) {
if (test === 'modal') {
return showShellModal(this.sender, 'example', {i: 5})
}
}

// internal methods
// =

Expand Down
45 changes: 43 additions & 2 deletions app/background-process/ui/modals.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {app, BrowserWindow} from 'electron'
import {app, BrowserWindow, ipcMain} from 'electron'
import {ModalActiveError} from 'beaker-error-constants'
import path from 'path'

Expand All @@ -11,14 +11,21 @@ const SIZES = {
install: {width: 500, height: 250}
}

// state
// globals
// =

var modalWindow
var reqIdCounter = 0
var activeRequests = []

// exported apis
// =

export function setup () {
// wire up handlers
ipcMain.on('modal-response', onModalResponse)
}

export function showModal (parentWindow, modalName, opts = {}) {
if (modalWindow) {
return Promise.reject(new ModalActiveError())
Expand Down Expand Up @@ -81,3 +88,37 @@ export function closeModal (err, res) {
w.close()
return true
}

export function showShellModal (webContents, modalName, opts = {}) {
return new Promise((resolve, reject) => {
// sanity check
if (!webContents.hostWebContents) {
// abort, must be given the webContents of a page
console.error('Warning: showShellModal() was passed the webContents of a non page')
return reject('Invalid shell modal target')
}

// track the new request
var req = {id: ++reqIdCounter, resolve, reject}
activeRequests.push(req)

// send message to create the UI
webContents.hostWebContents.send('command', 'show-modal', req.id, webContents.id, modalName, opts)
})
}

// internal methods
// =

async function onModalResponse (e, reqId, err, res) {
// lookup the request
var req = activeRequests.find(req => req.id == reqId)
if (!req) { return console.error('Warning: failed to find modal request for response #' + reqId) }

// untrack
activeRequests.splice(activeRequests.indexOf(req), 1)

// finish
if (err) req.reject(new Error(err))
else req.resolve(res)
}
1 change: 1 addition & 0 deletions app/lib/api-manifests/internal/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ export default {
openUrl: 'promise',
openFolder: 'promise',
doWebcontentsCmd: 'promise',
doTest: 'promise',
closeModal: 'sync'
}
1 change: 1 addition & 0 deletions app/lib/web-apis/beaker.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ if (window.location.protocol === 'beaker:') {
beaker.browser.openUrl = beakerBrowserRPC.openUrl
beaker.browser.openFolder = beakerBrowserRPC.openFolder
beaker.browser.doWebcontentsCmd = beakerBrowserRPC.doWebcontentsCmd
beaker.browser.doTest = beakerBrowserRPC.doTest
beaker.browser.closeModal = beakerBrowserRPC.closeModal
}

Expand Down
1 change: 1 addition & 0 deletions app/shell-window.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</header>
<div id="prompts"></div>
<div id="toasts"></div>
<div id="modals"></div>
<div id="win32-titlebar"></div>
<div id="left-swipe-arrow"><span class="icon icon-left-bold"></span></div>
<div id="right-swipe-arrow"><span class="icon icon-right-bold"></span></div>
Expand Down
2 changes: 2 additions & 0 deletions app/shell-window/command-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ipcRenderer } from 'electron'
import * as pages from './pages'
import * as zoom from './pages/zoom'
import * as navbar from './ui/navbar'
import * as modal from './ui/modal'
import permsPrompt from './ui/prompts/permission'

export function setup () {
Expand Down Expand Up @@ -35,6 +36,7 @@ export function setup () {
case 'set-tab': return pages.changeActiveTo(arg1)
case 'load-pinned-tabs': return pages.loadPinnedFromDB()
case 'perms:prompt': return permsPrompt(arg1, arg2, arg3, arg4)
case 'show-modal': return modal.createFromBackgroundProcess(arg1, arg2, arg3, arg4)
}
})
}
16 changes: 12 additions & 4 deletions app/shell-window/pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import parseDatURL from 'parse-dat-url'
import * as zoom from './pages/zoom'
import * as navbar from './ui/navbar'
import * as prompt from './ui/prompt'
import * as modal from './ui/modal'
import * as statusBar from './ui/statusbar'
import * as toast from './ui/toast.js'
import { SiteInfoNavbarBtn } from './ui/navbar/site-info'
Expand Down Expand Up @@ -109,7 +110,8 @@ export function create (opts) {
wcID: null, // the id of the webcontents
webviewEl: createWebviewEl(id, url),
navbarEl: navbar.createEl(id),
promptEl: prompt.createEl(id),
promptEl: prompt.createContainer(id),
modalEl: modal.createContainer(id),
siteInfoNavbarBtn: null, // set after object is created

// page state
Expand Down Expand Up @@ -151,8 +153,9 @@ export function create (opts) {
_canGoBack: false, // cached to avoid sync calls to the main process
_canGoForward: false, // cached to avoid sync calls to the main process

// prompts
// UI elements
prompts: [], // list of active prompts (used with perms)
modals: [], // list of active modals

// tab state
isPinned: opts.isPinned, // is this page pinned?
Expand Down Expand Up @@ -377,7 +380,8 @@ export async function remove (page) {
pages.splice(i, 1)
webviewsDiv.removeChild(page.webviewEl)
navbar.destroyEl(page.id)
prompt.destroyEl(page.id)
prompt.destroyContainer(page.id)
modal.destroyContainer(page.id)

// persist pins w/o this one, if that was
if (page.isPinned) { savePinnedToDB() }
Expand Down Expand Up @@ -418,6 +422,7 @@ export function setActive (page) {
navbar.closeMenus()
navbar.update()
prompt.update()
modal.update()

events.emit('set-active', page)
ipcRenderer.send('shell-window:set-current-location', page.getIntendedURL())
Expand Down Expand Up @@ -626,8 +631,9 @@ function onLoadCommit (e) {
zoom.setZoomFromSitedata(page, parseURL(page.getIntendedURL()).origin)
// stop autocompleting
navbar.clearAutocomplete()
// close any prompts
// close any prompts and modals
prompt.forceRemoveAll(page)
modal.forceRemoveAll(page)
// set title in tabs
page.title = e.target.getTitle() // NOTE sync operation
navbar.update(page)
Expand Down Expand Up @@ -1026,13 +1032,15 @@ function show (page) {
page.webviewEl.classList.remove('hidden')
page.navbarEl.classList.remove('hidden')
page.promptEl.classList.remove('hidden')
page.modalEl.classList.remove('hidden')
events.emit('show', page)
}

function hide (page) {
page.webviewEl.classList.add('hidden')
page.navbarEl.classList.add('hidden')
page.promptEl.classList.add('hidden')
page.modalEl.classList.add('hidden')
events.emit('hide', page)
}

Expand Down
2 changes: 2 additions & 0 deletions app/shell-window/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as navbar from './ui/navbar'
import * as statusbar from './ui/statusbar'
import * as win32Titlebar from './ui/win32-titlebar'
import * as pages from './pages'
import * as modal from './ui/modal'
import * as commandHandlers from './command-handlers'
import * as swipeHandlers from './swipe-handlers'

Expand Down Expand Up @@ -45,6 +46,7 @@ export function setup (cb) {
commandHandlers.setup()
swipeHandlers.setup()
pages.setup()
modal.setup()
ipcRenderer.send('shell-window:pages-ready')
pages.on('first-page', cb)
}
Expand Down
116 changes: 116 additions & 0 deletions app/shell-window/ui/modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import {ipcRenderer} from 'electron'
import yo from 'yo-yo'
import * as pages from '../pages'
import modalFns from './modals/index'

// globals
// =

var modalsDiv = document.querySelector('#modals')

// exported functions
// =

export function setup () {
document.body.addEventListener('keydown', onGlobalKeydown)
}

export function createContainer (id) {
// render
var el = render(id, null)
modalsDiv.append(el)
return el
}

export function destroyContainer (id) {
var el = document.querySelector(`#modals [data-id="${id}"]`)
if (el) el.remove()
}

export async function createFromBackgroundProcess (reqId, webContentsId, modalId, opts) {
var cb = (err, res) => ipcRenderer.send('modal-response', reqId, err, res)

// look up the page
var page = pages.getByWebContentsID(webContentsId)
if (!page) return cb('Page not available')

// lookup the modal
var modalFn = modalFns[modalId]
if (!modalFn) return cb('Modal not available')

// run the modal
try {
var res = await modalFn(page, opts)
cb(null, res)
} catch (err) {
cb(err.toString())
}
}

export function add (page, { render, onForceClose }) {
// add the modal
var modal = {
render,
onForceClose
}
page.modals.push(modal)
update(page)

return true
}

export function remove (page, modal) {
if (!page || !page.modals) { return } // page no longer exists

// find and remove
var i = page.modals.indexOf(modal)
if (i !== -1) {
page.modals.splice(i, 1)
update(page)
}
}

export function forceRemoveAll (page) {
if (!page || !page.modals) { return } // page no longer exists

// find and remove
page.modals.forEach(p => {
if (typeof p.onForceClose == 'function') { p.onForceClose() }
})
page.modals = []
update(page)
}

export function update (page) {
// fetch current page, if not given
page = page || pages.getActive()
if (!page.webviewEl) return

// render
yo.update(page.modalEl, render(page.id, page))
}

// internal methods
// =

function render (id, page) {
if (!page) { return yo`<div data-id=${id} class="hidden"></div>` }

return yo`<div data-id=${id} class=${page.isActive ? '' : 'hidden'}>
${page.modals.map(modal => {
return yo`<div class="modal">
${modal.render({
rerender: () => update(page),
remove: () => remove(page, modal)
})}
</div>`
})}
</div>`
}

function onGlobalKeydown (e) {
if (e.key === 'Escape') {
// close any active modals
forceRemoveAll(pages.getActive())
}
}
36 changes: 36 additions & 0 deletions app/shell-window/ui/modals/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as yo from 'yo-yo'
import * as modal from '../modal'

// exported api
// =

export default function (page, opts = {}) {
return new Promise((resolve, reject) => {
// create the modal
let i = opts.i ? opts.i : 0
modal.add(page, {
render: ({rerender, remove}) => {
function oninc () {
i++
rerender()
}

return yo`
<div style="padding: 10px 20px 6px; width: 500px;">
<h3>Test Modal</h3>
<p>
Hello world! <a class="link" onclick=${oninc}>Click me (${i} clicks)</a>
</p>
<p>
<a class="btn primary" onclick=${() => { remove(); resolve(i) }}>OK</a>
<a class="btn" onclick=${() => { remove(); reject(new Error('Canceled')) }}>Cancel</a>
</p>
</div>
`
},
onForceClose: () => {
reject(new Error('Closed'))
}
})
})
}
Loading

0 comments on commit d29e79b

Please sign in to comment.