Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Developing desktop apps with React & Electron

Developing desktop apps with React & Electron

Code walkthrough to a desktop tool, I wrote for my self called Focused Task. It is built with React, Redux, and Electron.

App can be found at 👉 https://github.com/RStankov/FocusedTask
Video can be found at 👉 https://www.youtube.com/watch?v=nGgaak8MEmg

Radoslav Stankov

June 02, 2020
Tweet

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

  1. const { menubar } = require('menubar'); const electron = require('electron');

    const mb = menubar({ index: process.env.ELECTRON_START_URL || url.format({ pathname: path.join(__dirname, './build/index.html'), protocol: 'file:', slashes: true, }), icon: path.join(__dirname, 'assets/MenuBarIconTemplate.png'), browserWindow: { width: 500, height: 600, minWidth: 300, maxHeight: 900, minHeight: 600, webPreferences: { nodeIntegration: true, scrollBounce: true, }, }, }); mb.on('after-create-window', () => { // ... });
  2. const { menubar } = require('menubar'); const electron = require('electron');

    const mb = menubar({ index: process.env.ELECTRON_START_URL || url.format({ pathname: path.join(__dirname, './build/index.html'), protocol: 'file:', slashes: true, }), icon: path.join(__dirname, 'assets/MenuBarIconTemplate.png'), browserWindow: { width: 500, height: 600, minWidth: 300, maxHeight: 900, minHeight: 600, webPreferences: { nodeIntegration: true, scrollBounce: true, }, }, }); mb.on('after-create-window', () => { // ... });
  3. Open external links mb.app.on('web-contents-created', (e, contents) => { const openExternal

    = (e, url) => { e.preventDefault(); electron.shell.openExternal(url); }; contents.on('new-window', openExternal); contents.on('will-navigate', (e, url) => { if (url !== contents.getURL()) { openExternal(e, url); } }); });
  4. Open external links export function openURI(uri: string) { if (!isElectron)

    { return; } if (!isURI(uri)) { return; } if (isFilePathUri(uri)) { electron.remote.shell.openItem(uri); } else { electron.remote.shell.openExternal(uri); } }
  5. Open external links import * as React from 'react'; import

    { openURI } from 'utils/electron'; interface IProps { children: React.ReactNode; href: string; className?: string; } export default function ExternalLink(props: IProps) { return ( <a target="_blank" rel="noopener noreferrer" onClick={e => { e.preventDefault(); openURI(props.href); }} {...props} /> ); }
  6. Resize window to fit content export function resizeBasedOnContent() { if

    (!isElectron) { return; } const bodyStyle = window.getComputedStyle(document.body) as any; const padding = parseInt(bodyStyle['margin-top'], 10) + parseInt(bodyStyle['margin-bottom'], 10) + parseInt(bodyStyle['padding-top'], 10) + parseInt(bodyStyle['padding-bottom'], 10); const observer = new ResizeObserver(() => { const height = Math.min(900, document.body.offsetHeight + padding); const bounds = electron.remote .getCurrentWindow() .webContents.getOwnerBrowserWindow() .getBounds(); if (bounds.height === height) { return; } electron.ipcRenderer.send('resize', bounds.width, height); }); observer.observe(document.body); }
  7. Store window position const settings = require('electron-settings'); const electron =

    require('electron'); const _ = require('lodash'); const BOUNDS_KEY = 'windowBounds'; module.exports = { setWindowBounds(menubar) { menubar.setOption('browserWindow', { ...menubar.getOption('browserWindow'), ...(settings.get(BOUNDS_KEY) || {}), }); }, trackWindowBounds(menubar) { const win = menubar.window; const handler = _.debounce(() => { menubar.setOption('browserWindow', { ...menubar.getOption('browserWindow'), ...win.getBounds(), }); settings.set(BOUNDS_KEY, win.getBounds()); }, 500); win.on('resize', handler); win.on('move', handler); }, };
  8. Global shortcut export function getGlobalShortcutKey() { if (!isElectron) { return;

    } return electron.remote.getGlobal('globalShortcutKey'); } export function updateGlobalShortcutKey(key: string) { if (!isElectron) { return; } if (key.length !== 1) { return; } electron.ipcRenderer.send('updateGlobalShortcutKey', key); }
  9. Global shortcut mb.app.on('ready', () => { settings.setGlobalShortcut(mb); }); mb.app.on('will-quit', ()

    => { electron.globalShortcut.unregisterAll(); }); electron.ipcMain.on('updateGlobalShortcutKey', (_e, key) => { settings.updateGlobalShortcutKey(mb, key); });
  10. Global shortcut const settings = require('electron-settings'); const electron = require('electron');

    const _ = require('lodash'); module.exports = { setGlobalShortcut(menubar) { const key = settings.get(SHORTCUT_KEY) || "'"; global.globalShortcutKey = key; electron.globalShortcut.register(`CommandOrControl+${key}`, () => { if (menubar.window && menubar.window.isVisible()) { menubar.hideWindow(); } else { menubar.showWindow(); } }); }, updateGlobalShortcutKey(menubar, key) { settings.set(SHORTCUT_KEY, key); electron.globalShortcut.unregisterAll(); this.setGlobalShortcut(menubar); }, };
  11. Changelog import React from 'react'; import Stack from 'components/Stack'; import

    BackButton from 'components/BackButton'; import Title from 'components/Title'; import ReactMarkdown from 'react-markdown'; import styles from './styles.module.css'; import raw from 'raw.macro'; const markdown = raw('../../../../CHANGELOG.md').replace(/^# .*\n\n/, ''); export default function Shortcuts() { return ( <> <BackButton /> <Stack.Column> <Title emoji="#" title="Changelog" /> <div className={styles.markdown}> <ReactMarkdown source={markdown} escapeHtml={true} /> </div> </Stack.Column> </> ); }
  12. const packager = require('electron-packager'); const setLanguages = require('electron-packager-languages'); const createDMG

    = require('electron-installer-dmg'); const version = '0.1.0'; const distPath = './dist'; const name = 'FocusedTask'; packager({ dir: './shell', overwrite: true, out: distPath, afterCopy: [setLanguages(['en', 'en_GB'])], name, productName: 'Focused Task', appBundleId: 'com.rstankov.focused-task', appCopyright: 'Copyright (C) 2020 Radoslav Stankov', appVersion: version, appCategoryType: 'public.app-category.productivity', // osxSign: { // identity: '[?]', // 'hardened-runtime': true, // entitlements: 'entitlements.plist', // 'entitlements-inherit': 'entitlements.plist', // 'signature-flags': 'library', // }, // osxNotarize: { // appleId: '[?]', // appleIdPassword: '[?]', // }, }).then(() => {
  13. %