Repository: electron/electron-api-demos Branch: master Commit: 26b3d1d57adc Files: 115 Total size: 144.0 KB Directory structure: gitextract_8wwg7s1f/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── sentinel.yml ├── .gitignore ├── .node-version ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── README.md ├── assets/ │ ├── app-icon/ │ │ └── mac/ │ │ └── app.icns │ ├── code-blocks.js │ ├── css/ │ │ ├── about.css │ │ ├── demo.css │ │ ├── fonts/ │ │ │ ├── SourceSansPro-Black.otf │ │ │ ├── SourceSansPro-BlackIt.otf │ │ │ ├── SourceSansPro-Bold.otf │ │ │ ├── SourceSansPro-BoldIt.otf │ │ │ ├── SourceSansPro-ExtraLight.otf │ │ │ ├── SourceSansPro-ExtraLightIt.otf │ │ │ ├── SourceSansPro-It.otf │ │ │ ├── SourceSansPro-Light.otf │ │ │ ├── SourceSansPro-LightIt.otf │ │ │ ├── SourceSansPro-Regular.otf │ │ │ ├── SourceSansPro-Semibold.otf │ │ │ └── SourceSansPro-SemiboldIt.otf │ │ ├── github.css │ │ ├── global.css │ │ ├── nativize.css │ │ ├── nav.css │ │ ├── print.css │ │ ├── section.css │ │ └── variables.css │ ├── demo-btns.js │ ├── ex-links.js │ ├── imports.js │ ├── mac/ │ │ ├── child.plist │ │ ├── info.plist │ │ └── parent.plist │ ├── nav.js │ └── normalize-shortcuts.js ├── cli.js ├── docs.md ├── index.html ├── license.md ├── main-process/ │ ├── communication/ │ │ ├── async-msg.js │ │ └── sync-msg.js │ ├── menus/ │ │ ├── application-menu.js │ │ ├── context-menu.js │ │ └── shortcuts.js │ ├── native-ui/ │ │ ├── dialogs/ │ │ │ ├── error.js │ │ │ ├── information.js │ │ │ ├── open-file.js │ │ │ └── save.js │ │ ├── drag/ │ │ │ └── drag.js │ │ └── tray/ │ │ └── tray.js │ └── system/ │ ├── app-information.js │ └── protocol-handler.js ├── main.js ├── package.json ├── renderer-process/ │ ├── communication/ │ │ ├── async-msg.js │ │ ├── invisible-msg.js │ │ └── sync-msg.js │ ├── media/ │ │ └── desktop-capturer.js │ ├── menus/ │ │ └── context-menu.js │ ├── native-ui/ │ │ ├── dialogs/ │ │ │ ├── error.js │ │ │ ├── information.js │ │ │ ├── open-file.js │ │ │ └── save.js │ │ ├── drag/ │ │ │ └── drag.js │ │ ├── ex-links-file-manager/ │ │ │ ├── ex-links.js │ │ │ └── file-manager.js │ │ ├── notifications/ │ │ │ ├── advanced-notification.js │ │ │ └── basic-notification.js │ │ └── tray/ │ │ └── tray.js │ ├── system/ │ │ ├── app-information.js │ │ ├── copy.js │ │ ├── paste.js │ │ ├── protocol-handler.js │ │ ├── screen-information.js │ │ ├── sys-information.js │ │ └── version-information.js │ └── windows/ │ ├── create-window.js │ ├── frameless-window.js │ ├── manage-window.js │ ├── process-crash.js │ ├── process-hang.js │ └── using-window-events.js ├── script/ │ ├── installer.js │ ├── mas.sh │ ├── release.js │ └── windows-store.js ├── sections/ │ ├── about.html │ ├── communication/ │ │ ├── invisible.html │ │ └── ipc.html │ ├── media/ │ │ └── desktop-capturer.html │ ├── menus/ │ │ ├── menus.html │ │ └── shortcuts.html │ ├── native-ui/ │ │ ├── dialogs.html │ │ ├── drag.html │ │ ├── ex-links-file-manager.html │ │ ├── notifications.html │ │ └── tray.html │ ├── system/ │ │ ├── app-sys-information.html │ │ ├── clipboard.html │ │ ├── protocol-handler.html │ │ └── protocol-link.html │ └── windows/ │ ├── crash-hang.html │ ├── manage-modal.html │ ├── modal-toggle-visibility.html │ ├── modal.html │ ├── process-crash.html │ ├── process-hang.html │ └── windows.html └── test/ ├── index.js └── setup.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .github/workflows/sentinel.yml ================================================ name: Generate Sentinel Report on: repository_dispatch: types: generate-sentinel-report jobs: build-and-test: name: Build Project runs-on: ubuntu-latest steps: - name: Check Out Repository uses: actions/checkout@v2 - name: Install Dependencies run: npm ci - name: Run Test Suite run: npm run generate-test-report - name: Upload Test Report uses: actions/upload-artifact@v1 with: name: report path: report.json send-data: name: Generate Report needs: build-and-test runs-on: ubuntu-latest steps: - name: Download Report uses: actions/download-artifact@v1 with: name: report - name: Send Data to Sentinel uses: codebytere/sentinel-client@v1 ================================================ FILE: .gitignore ================================================ node_modules out npm-debug.log ================================================ FILE: .node-version ================================================ v8.9.3 ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "node" - "lts/*" before_script: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start - sleep 3 script: - npm test - npm run package:linux branches: only: - master cache: directories: - node_modules notifications: email: on_success: never on_failure: change ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery - Personal attacks - Trolling or insulting/derogatory comments - Public or private harassment - Publishing other's private information, such as physical or electronic addresses, without explicit permission - Other unethical or unprofessional conduct Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at [coc@electronjs.org](mailto:coc@electronjs.org). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. This Code of Conduct is adapted from the Contributor Covenant, version 1.3.0, available from http://contributor-covenant.org/version/1/3/0/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Electron API Demos [![JavaScript Standard Style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com) :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [coc@electronjs.org](mailto:coc@electronjs.org). **See the [documentation](docs.md) for information on how this project works.** ## Code Style & ES6 This project uses the [JavaScript Standard](http://standardjs.com) style and limited ES6 syntax. Because this project is intended for beginners we stick to mostly vanilla JavaScript. One of the features we want illustrate about Electron, however, is that you can use most of ES6 out of the box. To that end, in this project we use these parts of ES6: - [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) - [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) - [string templates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) ## Pull Requests and Issues Tips for making an easier-to-review contribution: - Please provide a description. - Include screenshots and animated GIFs whenever possible. - Use short, present tense commit messages. ================================================ FILE: ISSUE_TEMPLATE.md ================================================ * What operating system are you using? * What version of Node.js is on your system? ================================================ FILE: README.md ================================================ # Electron API Demos icon Electron API Demos [![Build Status](https://travis-ci.org/electron/electron-api-demos.svg?branch=master)](https://travis-ci.org/electron/electron-api-demos) [![JavaScript Standard Style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com) This is a desktop app that interactively and with sample code demonstrates core features of the [Electron](http://electronjs.org) API. It's built with Electron, too, of course. This app works on Windows, macOS and Linux operating systems. Use this app to see what you can do with Electron and use the source code to learn how to create a basic Electron app. ![Electron API Demos Screenshots](https://cloud.githubusercontent.com/assets/378023/15016148/ae06cc80-124a-11e6-80dd-076d83e492f6.png) --- This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [coc@electronjs.org](mailto:coc@electronjs.org). Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important announcements. Visit the [electron website](http://electronjs.org). ## Using You can [download the latest release](https://github.com/electron/electron-api-demos/releases) for your operating system or build it yourself (see below). ## Building You'll need [Node.js](https://nodejs.org) installed on your computer in order to build this app. ```bash $ git clone https://github.com/electron/electron-api-demos $ cd electron-api-demos $ npm install $ npm start ``` If you don't wish to clone, you can [download the source code](https://github.com/electron/electron-api-demos/archive/master.zip). For easier developing you can launch the app in fullscreen with DevTools open: ```bash $ npm run dev ``` ## Extending [Read the docs](docs.md) to learn more about how this app is built or how to [add a new demo](docs.md#add-a-section-or-demo). ## Translations * Simplified Chinese translation of this app is available at [`demopark/electron-api-demos-Zh_CN`](https://github.com/demopark/electron-api-demos-Zh_CN). * Traditional Chinese translation of this app is available at [`CalvertYang/electron-api-demos-zh-Hant`](https://github.com/CalvertYang/electron-api-demos-zh-Hant). * Japanese translation of this app is available at [`LeeDDHH/electron-api-demos-ja`](https://github.com/LeeDDHH/electron-api-demos-ja). Note: these versions are maintained by outside contributors and may not always be in sync with this version. ================================================ FILE: assets/code-blocks.js ================================================ const fs = require('fs') const path = require('path') const codeBlocksWithPaths = document.querySelectorAll('code[data-path]') Array.prototype.forEach.call(codeBlocksWithPaths, (code) => { const codePath = path.join(__dirname, '..', code.dataset.path) const extension = path.extname(codePath) code.classList.add(`language-${extension.substring(1)}`) code.textContent = fs.readFileSync(codePath) }) document.addEventListener('DOMContentLoaded', () => { const highlight = require('highlight.js') const codeBlocks = document.querySelectorAll('pre code') Array.prototype.forEach.call(codeBlocks, (code) => { highlight.highlightBlock(code) }) }) ================================================ FILE: assets/css/about.css ================================================ /* Welcome ------------------------ */ .about { --about-space: 4rem; position: absolute; display: flex; top: 0; left: 0; right: 0; bottom: 0; z-index: 1; overflow-x: hidden; overflow-y: auto; padding: 0; background-color: hsl(0,0%,98%); pointer-events: none; visibility: hidden; opacity: 0; transform: scale(1.1); transition: visibility 0s .12s linear , opacity .12s ease-in, transform .12s ease-in; } .about.is-shown { pointer-events: auto; visibility: visible; opacity: 1; transform: scale(1); transition: visibility 0s 0s linear , opacity .24s ease-out, transform .24s ease-out; } .about-wrapper { margin: auto; } .about-header { padding: var(--about-space) 0; border-bottom: 1px solid hsl(0,0%,88%); } .about-logo { display: block; margin: 0 auto; width: 320px; /* TODO: Adjust asset to this size */ max-width: 100%; } .about-sections { max-width: 680px; padding: 0 var(--about-space); } .about-section { margin: var(--about-space) 0; } .about h2 { text-align: center; margin: 0 0 1em 0; font-size: 1.5em; color: hsl(0, 0%, 55%); } .about .about-code h2 { color: hsl(330, 65%, 55%); } .about .play-along h2 { color: hsl(222, 53%, 50%); } .about-button { display: block; margin: 0 auto; padding: .4em 1.2em; font: inherit; font-size: 1.6em; color: inherit; border: 2px solid; border-radius: 4px; background-color: transparent; } .about-button:focus { outline: none; border-color: hsl(0,0%,88%); } footer.about-section { text-align: center; } .rainbow-button-wrapper { --rainbow-button-width: 170px; --rainbow-button-height: 50px; --rainbow-button-width-inner: 164px; --rainbow-button-height-inner: 44px; --rainbow-color-1: hsl(116, 30%, 36%); --rainbow-color-2: hsl(194, 60%, 36%); --rainbow-color-3: hsl(222, 53%, 50%); --rainbow-color-4: hsl(285, 47%, 46%); --rainbow-color-5: hsl(330, 65%, 48%); --rainbow-color-6: hsl(32, 79%, 49%); --rainbow-color-7: hsl(53, 84%, 50%); display: inline-block; width: var(--rainbow-button-width); height: var(--rainbow-button-height); position: relative; overflow: hidden; border-radius: 5px; } .rainbow-button-wrapper:before { display: block; position: absolute; z-index: 2; top: 0; left: 0; width: 600px; height: var(--rainbow-button-height); background: #CCC; background: linear-gradient(to right, var(--rainbow-color-1) 0%, var(--rainbow-color-2) 14%, var(--rainbow-color-3) 28%, var(--rainbow-color-4) 42%, var(--rainbow-color-5) 56%, var(--rainbow-color-6) 70%, var(--rainbow-color-7) 84%, var(--rainbow-color-1) 100%); background-position: -200px 0; transition: all 0.5s; content: ""; } .rainbow-button-wrapper button { display: block; width: var(--rainbow-button-width-inner); height: var(--rainbow-button-height-inner); position: absolute; z-index: 3; top: 3px; left: 3px; border: none; background: white; color: black; font-size: 1.3rem; } .rainbow-button-wrapper:hover:before { background-position: 200px 0; } @media (min-width: 940px) { .about-header { align-self: center; padding: var(--about-space); border-right: 1px solid hsl(0,0%,88%); border-bottom: none; } .about-wrapper { display: flex; } } ================================================ FILE: assets/css/demo.css ================================================ /* Demo */ .demo:first-of-type { margin-top: 2rem; } .demo:last-of-type { margin-bottom: 2rem; } @media (min-width: 940px) { .demo:last-of-type { margin-bottom: 4rem; } } .demo-wrapper { position: relative; max-width: 740px; margin: 0 auto; padding: 0 2rem; } /* Toggle Button ----------------------------- */ .demo-toggle-button { position: relative; display: block; margin: 0; padding: .5em 1.5em; line-height: 1.5; font: inherit; font-weight: 600; font-size: 1.2em; text-align: left; border: none; color: inherit; background-color: transparent; transition: border-color .12s; outline: none; } .demo-toggle-button:before, .demo-toggle-button:after { content: ""; position: absolute; left: 0; width: 2px; height: 50%; background-color: hsl(0,0%,88%); transition: transform .2s cubic-bezier(.4,.1,0,1); } .demo-toggle-button:before { top: 0; transform-origin: bottom center; transform: translateX(.7em) rotate(-30deg) scale(.75); } .demo-toggle-button:after { bottom: 0; transform-origin: top center; transform: translateX(.7em) rotate(30deg) scale(.75); } .is-open .demo-toggle-button:before, .is-open .demo-toggle-button:after { transform: rotate(0deg); } .demo-toggle-button:focus:before, .demo-toggle-button:focus:after { background-color: currentColor; } /* Meta info */ .demo-meta { margin-top: .2em; font-size: 11px; font-weight: 300; text-transform: uppercase; color: var(--color-subtle); } .demo-meta-divider { margin: 0 .5em; } /* Demo Box ----------------------------- */ .demo-box { display: none; position: relative; padding: 2em; margin-top: 1em; margin-bottom: 2em; border-radius: 6px; border: 1px solid var(--color-border); background-color: var(--color-bg); } .demo-box:before { content: ""; position: absolute; top: -11px; width: 20px; height: 20px; background-color: inherit; border-top: inherit; border-right: inherit; border-top-right-radius: 3px; transform: rotate(-45deg); } .is-open .demo-box { display: block; animation: demo-box-fade-in .2s cubic-bezier(0, .20, .20, .96); } @keyframes demo-box-fade-in { 0% { opacity: 0; transform: translateY(-20px); } 100% { opacity: 1; transform: translateY(0); } } .demo-box > p:first-child { margin-top: 0; } .demo-box h5 { font-size: 1em; margin-bottom: .6em; } /* Demo Controls ----------------------------- */ .demo-controls { display: flex; align-items: center; } .demo-button { align-self: flex-start; margin-right: 1em; border: 2px solid; border-radius: 4px; font: inherit; font-size: 1.2em; padding: .4em 1.2em; color: inherit; background-color: transparent; } .demo-button:focus { outline: none; background-color: white; } .demo-button:active { border-color: var(--color-border); } .demo-input { flex: 1; border: 2px solid var(--color-border); border-radius: 4px; font: inherit; font-size: 1.2em; padding: .4em .8em; color: inherit; background-color: transparent; } .demo-input:focus { outline: none; border-color: hsl(0,0%,80%); background-color: white; } .demo-response { flex: 1; word-break: break-word; } .smooth-appear { opacity: 1; transition: opacity .5s ease-in-out; } .disappear { opacity: 0; } .demo-button.smooth-disappear:focus { outline: inherit; border-color: inherit; background-color: inherit; } /* ProTip ----------------------------- */ .demo-protip { margin-top: 2rem; padding: 1.5rem 2rem 2rem 2rem; border: 1px solid hsla(0,0%,0%,.06); border-radius: 6px; background: var(--color-accent) linear-gradient(hsla(0,0%,100%,.85), hsla(0,0%,100%,.85)); } .demo-protip h2 { margin: 0 0 .5rem 0; } .demo-protip strong { font-weight: 600; } ================================================ FILE: assets/css/github.css ================================================ /* github.com style (c) Vasily Polovnyov */ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hljs-comment, .hljs-quote { color: #998; font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-subst { color: #333; font-weight: bold; } .hljs-number, .hljs-literal, .hljs-variable, .hljs-template-variable, .hljs-tag .hljs-attr { color: #008080; } .hljs-string, .hljs-doctag { color: #d14; } .hljs-title, .hljs-section, .hljs-selector-id { color: #900; font-weight: bold; } .hljs-subst { font-weight: normal; } .hljs-type, .hljs-class .hljs-title { color: #458; font-weight: bold; } .hljs-tag, .hljs-name, .hljs-attribute { color: #000080; font-weight: normal; } .hljs-regexp, .hljs-link { color: #009926; } .hljs-symbol, .hljs-bullet { color: #990073; } .hljs-built_in, .hljs-builtin-name { color: #0086b3; } .hljs-meta { color: #999; font-weight: bold; } .hljs-deletion { background: #fdd; } .hljs-addition { background: #dfd; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } ================================================ FILE: assets/css/global.css ================================================ /* Fonts ---------------------------- */ @font-face { font-family: 'Source Code Pro'; font-style: normal; font-weight: 400; src: local('Source Code Pro'), local('SourceCodePro'), url(fonts/SourceCodePro-Regular.ttf) format('truetype'); } /* Global ---------------------------- */ * { box-sizing: border-box; } html { height: 100%; font-family: 'BlinkMacSystemFont', 'Lucida Grande', 'Segoe UI', Ubuntu, Cantarell, sans-serif; font-size: 14px; line-height: 1.5; overflow: hidden; /* Prevents rubber-band scrolling of the whole "page" */ color: var(--color); background-color: #fff; /* To cover OSes with no default background color */ } body { margin: 0; height: 100%; display: flex; } a { color: var(--color-link); } h1, h2, h3 { margin-top: 0; line-height: 1.5; } h1 { font-size: 1.5em; font-weight: 600; } h2 { font-size: 1.3em; font-weight: normal; } h3 { font-size: 1.12em; font-weight: 600; } table { width: 100%; border-spacing: 0; border: 1px solid hsla(0,0%,0%,.08); border-width: 0 1px 1px 0; } th { background-color: hsla(0,0%,50%,.06); } th, td { text-align: center; border: 1px solid hsla(0,0%,0%,.08); border-width: 1px 0 0 1px; } svg { fill: currentColor; } /* Code */ code, kbd { font-family: 'Source Code Pro', monospace; border-radius: 4px; padding: 1px 4px; white-space: nowrap; color: hsl(0,0%,36%); background-color: hsla(0,0%,60%,.15); } pre, kbd { font-size: 13px; overflow: auto; padding: 1em; margin: 0; border-radius: 4px; border: 1px solid; border-color: var(--color-border); background-color: white; } pre code { white-space: pre; } pre > .hljs { color: var(--color-subtle); background-color: white; } kbd { padding: 0.5em; } /* Utilities ---------------------------- */ .u-avoid-clicks { pointer-events: none; } /* Visually hidden, but will be read by screen readers */ .u-visible-to-screen-reader { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; } .no-display { display: none; } /* Content ------------------ */ .content { flex: 1; position: relative; overflow: hidden; visibility: hidden; opacity: 0; } .content.is-shown { visibility: visible;; opacity: 1; } /* Hacks ---------------------------- */ /* Fixes horizontal scrolling in code blocks on OS X El Cap (10.11.3), retina screen * * By adding an invisible outline property, it will force a repaint * which enables the scrolling. */ .hljs:hover, .hljs:active { outline: 1px solid transparent; } ================================================ FILE: assets/css/nativize.css ================================================ /* ** nativize.css ** Makes the UI feel more native */ html { font-family: sans-serif; user-select: none; /* disable selection */ -webkit-user-drag: none; /* disable dragging */ cursor: default; /* use default cursor */ } body { margin: 0; /* remove default margin */ } /* enable text selection */ .is-selectable, pre, code { user-select: text; cursor: auto; } /* Buttons and links */ button{ cursor: default; } /* Internal links */ a { cursor: pointer; text-decoration: none; border-bottom: 1px dashed; outline: none; } /* New window (target) + external links */ a[target], a[href^="https://"], a[href^="http://"] { border-bottom: 1px solid; } a:hover, a:focus { border-bottom: none; } /* Images */ img { -webkit-user-drag: none; /* disable dragging */ } ================================================ FILE: assets/css/nav.css ================================================ /* Nav */ .nav { width: 340px; overflow-x: hidden; overflow-y: auto; color: var(--color-subtle); border-right: 1px solid var(--color-border); background-color: var(--color-bg); visibility: hidden; opacity: 0; } .nav.is-shown { visibility: visible;; opacity: 1; } .nav-header { position: relative; padding: 2rem; margin-bottom: 1rem; border-bottom: 1px solid var(--color-border); } .nav-title { text-transform: uppercase; font-weight: 300; line-height: 1; margin: 0; } .nav-title strong { font-weight: 600; color: var(--color-strong); } .nav-header-icon { position: absolute; width: 36px; height: 36px; top: 1.5rem; /* magic */ right: 1.75rem; /* magic */ } .nav-item { padding: .5em 0; } .nav-icon { width: 16px; height: 16px; vertical-align: top; margin-right: .25rem; } .nav-category { margin: .2em 0; padding-left: 2rem; font-size: 11px; font-weight: normal; text-transform: uppercase; } .nav-button { display: block; width: 100%; padding: .3rem; padding-left: calc(2rem + 16px + .5rem); /* padding + icon + magic */ line-height: 2; text-align: left; font: inherit; font-size: 13px; color: inherit; border: none; background-color: transparent; cursor: default; outline: none; } .nav-button:hover, .nav-button:focus:not(.is-selected) { background-color: hsla(0,0%,0%,.1); } .nav-button.is-selected { background-color: var(--color-accent); } .nav-button.is-selected, .nav-button.is-selected em { color: #fff; } .nav-button.is-selected:focus { opacity: .8; } .nav-button em { font-style: normal; font-weight: 600; color: var(--color-strong); pointer-events: none; /* makes it invisible to clicks */ } .nav-footer { margin-top: 1rem; padding: 2rem; border-top: 1px solid var(--color-border); text-align: center; } .nav-footer-icon { width: calc(770px / 6.5); height: calc(88px / 6.5); } .nav-footer a { outline: none; } .nav-footer-button { display: block; width: 100%; padding: 0; margin-bottom: .75rem; line-height: 2; text-align: left; font: inherit; font-size: 13px; color: inherit; border: none; background-color: transparent; cursor: default; outline: none; text-align: center; } .nav-footer-button:focus { color: var(--color-strong); } .nav-footer-logo { color: hsl(0,0%,66%); } .nav-footer-logo:focus { color: hsl(0,0%,33%); } /* Remove border on the logo */ .nav-footer-logo.nav-footer-logo { border-bottom: none; } ================================================ FILE: assets/css/print.css ================================================ @media print { body { background: none; color: black !important; font-size: 70%; margin: 0; padding: 0; } h1 { font-size: 22px; } .nav, button, .demo-box:before, header p { display: none; } .demo-box, h2, pre, code { padding: 0 !important; margin: 0 !important; } header { padding: 0 0 10px 0; } code, .support { font-size: 10px; } } ================================================ FILE: assets/css/section.css ================================================ /* Section ------------------ */ .section { position: absolute; top: 0; left: 0; right: 0; bottom: 0; overflow-x: hidden; overflow-y: auto; color: var(--color-accent); /* Hide */ pointer-events: none; visibility: hidden; opacity: 0; transform: translateX(-20px); transition: visibility 0s .12s linear , opacity .12s ease-in, transform .12s ease-in; } .section.is-shown { pointer-events: auto; visibility: visible; opacity: 1; transform: translateX(0); transition: visibility 0s 0s linear , opacity .36s ease-out, transform .36s ease-out; } .section h3, .section p { color: var(--color); } .section-wrapper { position: relative; max-width: 740px; margin: 0 auto; padding: 2rem 2rem 1rem 2rem; border-bottom: 1px solid var(--color-border); } @media (min-width: 940px) { .section-wrapper { padding-top: 4rem; } } .section-icon { width: 32px; height: 32px; vertical-align: middle; margin-right: .5em; } ================================================ FILE: assets/css/variables.css ================================================ /* Custom Properties */ :root { --color: hsl(0,0%,22%); --color-subtle: hsl(0,0%,44%); --color-strong: hsl(0,0%,11%); --color-link: hsl(0,0%,22%); --color-border: hsl(0,0%,88%); --color-bg: hsl(0,0%,96%); --color-accent: black; /* Fallback */ } /* Category Colors */ .u-category-windows { --color-accent: hsl(116, 30%, 36%); } .u-category-menu { --color-accent: hsl(194, 60%, 36%); } .u-category-native-ui { --color-accent: hsl(222, 53%, 50%); } .u-category-communication { --color-accent: hsl(285, 47%, 46%); } .u-category-system { --color-accent: hsl(330, 65%, 48%); } .u-category-media { --color-accent: hsl( 36, 77%, 34%); } ================================================ FILE: assets/demo-btns.js ================================================ const settings = require('electron-settings') const demoBtns = document.querySelectorAll('.js-container-target') // Listen for demo button clicks Array.prototype.forEach.call(demoBtns, (btn) => { btn.addEventListener('click', (event) => { const parent = event.target.parentElement // Toggles the "is-open" class on the demo's parent element. parent.classList.toggle('is-open') // Saves the active demo if it is open, or clears it if the demo was user // collapsed by the user if (parent.classList.contains('is-open')) { settings.set('activeDemoButtonId', event.target.getAttribute('id')) } else { settings.delete('activeDemoButtonId') } }) }) // Default to the demo that was active the last time the app was open const buttonId = settings.get('activeDemoButtonId') if (buttonId) { document.getElementById(buttonId).click() } ================================================ FILE: assets/ex-links.js ================================================ const shell = require('electron').shell const links = document.querySelectorAll('a[href]') Array.prototype.forEach.call(links, (link) => { const url = link.getAttribute('href') if (url.indexOf('http') === 0) { link.addEventListener('click', (e) => { e.preventDefault() shell.openExternal(url) }) } }) ================================================ FILE: assets/imports.js ================================================ const links = document.querySelectorAll('link[rel="import"]') // Import and add each page to the DOM Array.prototype.forEach.call(links, (link) => { let template = link.import.querySelector('.task-template') let clone = document.importNode(template.content, true) if (link.href.match('about.html')) { document.querySelector('body').appendChild(clone) } else { document.querySelector('.content').appendChild(clone) } }) ================================================ FILE: assets/mac/child.plist ================================================ com.apple.security.app-sandbox com.apple.security.inherit ================================================ FILE: assets/mac/info.plist ================================================ CFBundleURLTypes CFBundleURLSchemes electron-api-demos CFBundleURLName Electron API Demos Protocol ElectronTeamID VEKTX9H2N7 ================================================ FILE: assets/mac/parent.plist ================================================ com.apple.security.app-sandbox com.apple.security.application-groups VEKTX9H2N7.com.github.electron-api-demos com.apple.security.files.user-selected.read-write ================================================ FILE: assets/nav.js ================================================ const settings = require('electron-settings') document.body.addEventListener('click', (event) => { if (event.target.dataset.section) { handleSectionTrigger(event) } else if (event.target.dataset.modal) { handleModalTrigger(event) } else if (event.target.classList.contains('modal-hide')) { hideAllModals() } }) function handleSectionTrigger (event) { hideAllSectionsAndDeselectButtons() // Highlight clicked button and show view event.target.classList.add('is-selected') // Display the current section const sectionId = `${event.target.dataset.section}-section` document.getElementById(sectionId).classList.add('is-shown') // Save currently active button in localStorage const buttonId = event.target.getAttribute('id') settings.set('activeSectionButtonId', buttonId) } function activateDefaultSection () { document.getElementById('button-windows').click() } function showMainContent () { document.querySelector('.js-nav').classList.add('is-shown') document.querySelector('.js-content').classList.add('is-shown') } function handleModalTrigger (event) { hideAllModals() const modalId = `${event.target.dataset.modal}-modal` document.getElementById(modalId).classList.add('is-shown') } function hideAllModals () { const modals = document.querySelectorAll('.modal.is-shown') Array.prototype.forEach.call(modals, (modal) => { modal.classList.remove('is-shown') }) showMainContent() } function hideAllSectionsAndDeselectButtons () { const sections = document.querySelectorAll('.js-section.is-shown') Array.prototype.forEach.call(sections, (section) => { section.classList.remove('is-shown') }) const buttons = document.querySelectorAll('.nav-button.is-selected') Array.prototype.forEach.call(buttons, (button) => { button.classList.remove('is-selected') }) } function displayAbout () { document.querySelector('#about-modal').classList.add('is-shown') } // Default to the view that was active the last time the app was open const sectionId = settings.get('activeSectionButtonId') if (sectionId) { showMainContent() const section = document.getElementById(sectionId) if (section) section.click() } else { activateDefaultSection() displayAbout() } ================================================ FILE: assets/normalize-shortcuts.js ================================================ const normalize = require('electron-shortcut-normalizer') let shortcuts = document.querySelectorAll('kbd.normalize-to-platform') Array.prototype.forEach.call(shortcuts, (shortcut) => { shortcut.innerText = normalize(shortcut.innerText, process.platform) }) ================================================ FILE: cli.js ================================================ #!/usr/bin/env node const {spawn} = require('child_process') const electron = require('electron') const path = require('path') const appPath = path.join(__dirname, 'main.js') const args = [appPath].concat(process.argv.slice(2)) const proc = spawn(electron, args, {stdio: 'inherit'}) proc.on('close', (code) => process.exit(code)) ================================================ FILE: docs.md ================================================ # Documentation This app has been developed to be a lightweight Electron app, demonstrating how to create a basic Electron app with a few exceptions that have been made for the sake of code organization in regards to the demos themselves. All of the sample code shown in the app _is the actual code used in the app_. These JavaScript bits have been pulled out into their own file and organized by process (main or renderer) and then by section (communication, menus, native UI, media, system, windows). This was done for maintainability—code updates only have to be made in one place—and organization—it's easy to find the sample code you're looking for. All of the pages (or views) are separate `.html` files which are appended onto the `index.html` using [HTML imports](http://www.html5rocks.com/en/tutorials/webcomponents/imports/). Are you looking to add a demo? Jump to the [add a new demo section](#add-a-section-or-demo). ## Folder Structure ![Diagram of App Structure and Operations](/assets/img/diagram.png) #### `assets` This directory contains assets for the app itself: CSS, fonts, images and shared JavaScript libraries or helpers. #### `main-process` This directory contains sub folders for each demo section that requires JavaScript in the main process. This structure is mirrored in the `renderer-process` directory. The `main.js` file, located in the root, takes each `.js` file in these directories and executes them. #### `renderer-process` This directory contains sub folders for each demo section that requires JavaScript in the renderer process. This structure is mirrored in the `main-process` directory. Each of the HTML page views requires the corresponding renderer-process `.js` files that it needs for its demo. Each page view reads the content of its relevant main and renderer process files and adds them to the page for the snippets. #### `sections` This directory contains sub folders for each demo section. These subfolders contain the HTML files for each of the demo pages. Each of these files is appended to `index.html`, located at the root. #### `index.html` This is the main view in the app. It contains the sidebar with navigation and uses [HTML imports](http://www.html5rocks.com/en/tutorials/webcomponents/imports/) to append each section HTML page to the `body`. #### `main.js` This file contains the lifecycle instructions for the app like how to start and quit, it is the app's main process. It grabs every `.js` file in the `main-process` directory and executes. The `package.json` sets this file as the `main` file. #### `package.json` This file is required when using `npm` and Electron.js. It contains details on the app: the author, dependencies, repository and points to `main.js` as the application's main process file. #### Docs The files: `CODE_OF_CONDUCT`, `README`, `docs` and `CONTRIBUTING` files make up the documentation for the project. ## UI Terminology ![UI Terminology](/assets/img/ui-terminology.png) ## CSS Naming Convention Nothing too strict and used more as a guide: - Styling elements directly should be avoided, but ok in some cases. Like `

` or ``. - Elements that belong together are prefixed with their parent class. `.section`, `.section-header`, `.section-icon`. - States use `is-` prefix - Utilities use `u-` prefix ## Add a Section or Demo Here are tips for covering the bases when adding a new section or demo. General tip—for some of these just copy the line or file of a similar existing item to get started! ### New Section A whole new page with one or more demos. #### index.html This page contains the sidebar list of sections as well as each section template that is imported with HTML imports. - Add demo to sidebar in the appropriate category in `index.html` - update `id` i.e. `id="button-dialogs"` - update `data-section` i.e. `data-section="dialogs"` - Add demo template path to the import links in the `head` of `index.html` - i.e. `` #### Template This template is added to the `index.html` in the app. - In the `sections` directory, copy an existing template `html` file from the category you're adding a section to. - Update these tags `id` - i.e. `id="dialogs-section"` - Update all the text in the `header` tag with text relevant to your new section. - Remove the demos and pro-tips as needed. ### Demo Any code that you create for your demo should be added to the 'main-process' or 'renderer-process' directories depending on where it runs. All JavaScript files within the 'main-process' directory are run when the app starts but you'll link to the file so that it is displayed within your demo (see below). The renderer process code you add will be read and displayed within the demo and then required on the template page so that it runs in that process (see below). - Start by copying and pasting an existing `

` blocks from the template page. - Update the demo button `id` - i.e `` - If demo includes a response written to the DOM, update that `id`, otherwise delete: - i.e. `` - Update the text describing your demo. - If you are displaying main or renderer process sample code, include or remove that markup accordingly. - Sample code is read and added to the DOM by adding the path to the code in the `data-path` - i.e. `
` - Require your render process code in the script tag at the bottom of the template - i.e `require('./renderer-process/native-ui/dialogs/information')` #### Try it out At this point you should be able to run the app, `npm start`, and see your section and/or demo. :tada: ================================================ FILE: index.html ================================================
================================================ FILE: license.md ================================================ MIT License Copyright (c) 2016-2018 GitHub Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: main-process/communication/async-msg.js ================================================ const {ipcMain} = require('electron') ipcMain.on('asynchronous-message', (event, arg) => { event.sender.send('asynchronous-reply', 'pong') }) ================================================ FILE: main-process/communication/sync-msg.js ================================================ const {ipcMain} = require('electron') ipcMain.on('synchronous-message', (event, arg) => { event.returnValue = 'pong' }) ================================================ FILE: main-process/menus/application-menu.js ================================================ const {BrowserWindow, Menu, app, shell, dialog} = require('electron') let template = [{ label: 'Edit', submenu: [{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' }, { type: 'separator' }, { label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' }, { label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' }, { label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' }, { label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectall' }] }, { label: 'View', submenu: [{ label: 'Reload', accelerator: 'CmdOrCtrl+R', click: (item, focusedWindow) => { if (focusedWindow) { // on reload, start fresh and close any old // open secondary windows if (focusedWindow.id === 1) { BrowserWindow.getAllWindows().forEach(win => { if (win.id > 1) win.close() }) } focusedWindow.reload() } } }, { label: 'Toggle Full Screen', accelerator: (() => { if (process.platform === 'darwin') { return 'Ctrl+Command+F' } else { return 'F11' } })(), click: (item, focusedWindow) => { if (focusedWindow) { focusedWindow.setFullScreen(!focusedWindow.isFullScreen()) } } }, { label: 'Toggle Developer Tools', accelerator: (() => { if (process.platform === 'darwin') { return 'Alt+Command+I' } else { return 'Ctrl+Shift+I' } })(), click: (item, focusedWindow) => { if (focusedWindow) { focusedWindow.toggleDevTools() } } }, { type: 'separator' }, { label: 'App Menu Demo', click: function (item, focusedWindow) { if (focusedWindow) { const options = { type: 'info', title: 'Application Menu Demo', buttons: ['Ok'], message: 'This demo is for the Menu section, showing how to create a clickable menu item in the application menu.' } dialog.showMessageBox(focusedWindow, options, function () {}) } } }] }, { label: 'Window', role: 'window', submenu: [{ label: 'Minimize', accelerator: 'CmdOrCtrl+M', role: 'minimize' }, { label: 'Close', accelerator: 'CmdOrCtrl+W', role: 'close' }, { type: 'separator' }, { label: 'Reopen Window', accelerator: 'CmdOrCtrl+Shift+T', enabled: false, key: 'reopenMenuItem', click: () => { app.emit('activate') } }] }, { label: 'Help', role: 'help', submenu: [{ label: 'Learn More', click: () => { shell.openExternal('http://electron.atom.io') } }] }] function addUpdateMenuItems (items, position) { if (process.mas) return const version = app.getVersion() let updateItems = [{ label: `Version ${version}`, enabled: false }, { label: 'Checking for Update', enabled: false, key: 'checkingForUpdate' }, { label: 'Check for Update', visible: false, key: 'checkForUpdate', click: () => { require('electron').autoUpdater.checkForUpdates() } }, { label: 'Restart and Install Update', enabled: true, visible: false, key: 'restartToUpdate', click: () => { require('electron').autoUpdater.quitAndInstall() } }] items.splice.apply(items, [position, 0].concat(updateItems)) } function findReopenMenuItem () { const menu = Menu.getApplicationMenu() if (!menu) return let reopenMenuItem menu.items.forEach(item => { if (item.submenu) { item.submenu.items.forEach(item => { if (item.key === 'reopenMenuItem') { reopenMenuItem = item } }) } }) return reopenMenuItem } if (process.platform === 'darwin') { const name = app.getName() template.unshift({ label: name, submenu: [{ label: `About ${name}`, role: 'about' }, { type: 'separator' }, { label: 'Services', role: 'services', submenu: [] }, { type: 'separator' }, { label: `Hide ${name}`, accelerator: 'Command+H', role: 'hide' }, { label: 'Hide Others', accelerator: 'Command+Alt+H', role: 'hideothers' }, { label: 'Show All', role: 'unhide' }, { type: 'separator' }, { label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit() } }] }) // Window menu. template[3].submenu.push({ type: 'separator' }, { label: 'Bring All to Front', role: 'front' }) addUpdateMenuItems(template[0].submenu, 1) } if (process.platform === 'win32') { const helpMenu = template[template.length - 1].submenu addUpdateMenuItems(helpMenu, 0) } app.on('ready', () => { const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu) }) app.on('browser-window-created', () => { let reopenMenuItem = findReopenMenuItem() if (reopenMenuItem) reopenMenuItem.enabled = false }) app.on('window-all-closed', () => { let reopenMenuItem = findReopenMenuItem() if (reopenMenuItem) reopenMenuItem.enabled = true }) ================================================ FILE: main-process/menus/context-menu.js ================================================ const { BrowserWindow, Menu, MenuItem, ipcMain, app } = require('electron') const menu = new Menu() menu.append(new MenuItem({ label: 'Hello' })) menu.append(new MenuItem({ type: 'separator' })) menu.append(new MenuItem({ label: 'Electron', type: 'checkbox', checked: true })) app.on('browser-window-created', (event, win) => { win.webContents.on('context-menu', (e, params) => { menu.popup(win, params.x, params.y) }) }) ipcMain.on('show-context-menu', (event) => { const win = BrowserWindow.fromWebContents(event.sender) menu.popup(win) }) ================================================ FILE: main-process/menus/shortcuts.js ================================================ const {app, dialog, globalShortcut} = require('electron') app.on('ready', () => { globalShortcut.register('CommandOrControl+Alt+K', () => { dialog.showMessageBox({ type: 'info', message: 'Success!', detail: 'You pressed the registered global shortcut keybinding.', buttons: ['OK'] }) }) }) app.on('will-quit', () => { globalShortcut.unregisterAll() }) ================================================ FILE: main-process/native-ui/dialogs/error.js ================================================ const {ipcMain, dialog} = require('electron') ipcMain.on('open-error-dialog', (event) => { dialog.showErrorBox('An Error Message', 'Demonstrating an error message.') }) ================================================ FILE: main-process/native-ui/dialogs/information.js ================================================ const {ipcMain, dialog} = require('electron') ipcMain.on('open-information-dialog', (event) => { const options = { type: 'info', title: 'Information', message: "This is an information dialog. Isn't it nice?", buttons: ['Yes', 'No'] } dialog.showMessageBox(options, (index) => { event.sender.send('information-dialog-selection', index) }) }) ================================================ FILE: main-process/native-ui/dialogs/open-file.js ================================================ const {ipcMain, dialog} = require('electron') ipcMain.on('open-file-dialog', (event) => { dialog.showOpenDialog({ properties: ['openFile', 'openDirectory'] }, (files) => { if (files) { event.sender.send('selected-directory', files) } }) }) ================================================ FILE: main-process/native-ui/dialogs/save.js ================================================ const {ipcMain, dialog} = require('electron') ipcMain.on('save-dialog', (event) => { const options = { title: 'Save an Image', filters: [ { name: 'Images', extensions: ['jpg', 'png', 'gif'] } ] } dialog.showSaveDialog(options, (filename) => { event.sender.send('saved-file', filename) }) }) ================================================ FILE: main-process/native-ui/drag/drag.js ================================================ const {ipcMain} = require('electron') const path = require('path') ipcMain.on('ondragstart', (event, filepath) => { const iconName = 'codeIcon.png' event.sender.startDrag({ file: filepath, icon: path.join(__dirname, iconName) }) }) ================================================ FILE: main-process/native-ui/tray/tray.js ================================================ const path = require('path') const {ipcMain, app, Menu, Tray} = require('electron') let appIcon = null ipcMain.on('put-in-tray', (event) => { const iconName = process.platform === 'win32' ? 'windows-icon.png' : 'iconTemplate.png' const iconPath = path.join(__dirname, iconName) appIcon = new Tray(iconPath) const contextMenu = Menu.buildFromTemplate([{ label: 'Remove', click: () => { event.sender.send('tray-removed') } }]) appIcon.setToolTip('Electron Demo in the tray.') appIcon.setContextMenu(contextMenu) }) ipcMain.on('remove-tray', () => { appIcon.destroy() }) app.on('window-all-closed', () => { if (appIcon) appIcon.destroy() }) ================================================ FILE: main-process/system/app-information.js ================================================ const {app, ipcMain} = require('electron') ipcMain.on('get-app-path', (event) => { event.sender.send('got-app-path', app.getAppPath()) }) ================================================ FILE: main-process/system/protocol-handler.js ================================================ const {app, dialog} = require('electron') const path = require('path') if (process.defaultApp) { if (process.argv.length >= 2) { app.setAsDefaultProtocolClient('electron-api-demos', process.execPath, [path.resolve(process.argv[1])]) } } else { app.setAsDefaultProtocolClient('electron-api-demos') } app.on('open-url', (event, url) => { dialog.showErrorBox('Welcome Back', `You arrived from: ${url}`) }) ================================================ FILE: main.js ================================================ require('update-electron-app')({ logger: require('electron-log') }) const path = require('path') const glob = require('glob') const {app, BrowserWindow} = require('electron') const debug = /--debug/.test(process.argv[2]) if (process.mas) app.setName('Electron APIs') let mainWindow = null function initialize () { makeSingleInstance() loadDemos() function createWindow () { const windowOptions = { width: 1080, minWidth: 680, height: 840, title: app.getName(), webPreferences: { nodeIntegration: true } } if (process.platform === 'linux') { windowOptions.icon = path.join(__dirname, '/assets/app-icon/png/512.png') } mainWindow = new BrowserWindow(windowOptions) mainWindow.loadURL(path.join('file://', __dirname, '/index.html')) // Launch fullscreen with DevTools open, usage: npm run debug if (debug) { mainWindow.webContents.openDevTools() mainWindow.maximize() require('devtron').install() } mainWindow.on('closed', () => { mainWindow = null }) } app.on('ready', () => { createWindow() }) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } }) app.on('activate', () => { if (mainWindow === null) { createWindow() } }) } // Make this app a single instance app. // // The main window will be restored and focused instead of a second window // opened when a person attempts to launch a second instance. // // Returns true if the current version of the app should quit instead of // launching. function makeSingleInstance () { if (process.mas) return app.requestSingleInstanceLock() app.on('second-instance', () => { if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore() mainWindow.focus() } }) } // Require each JS file in the main-process dir function loadDemos () { const files = glob.sync(path.join(__dirname, 'main-process/**/*.js')) files.forEach((file) => { require(file) }) } initialize() ================================================ FILE: package.json ================================================ { "name": "electron-api-demos", "productName": "Electron API Demos", "version": "2.0.2", "description": "Electron interactive API demos", "main": "main.js", "bin": "cli.js", "scripts": { "start": "electron .", "dev": "electron . --debug", "test": "mocha && standard", "generate-test-report": "mocha --reporter=json > report.json", "package": "npm-run-all package:*", "package:mac": "electron-packager . --overwrite --platform=darwin --arch=x64 --out=out --icon=assets/app-icon/mac/app.icns --osx-sign.identity='Developer ID Application: GitHub' --extend-info=assets/mac/info.plist", "package:win": "electron-packager . --overwrite --platform=win32 --arch=ia32 --out=out --icon=assets/app-icon/win/app.ico", "package:linux": "electron-packager . --overwrite --platform=linux --arch=x64 --out=out", "package:sign-exe": "signcode './out/Electron API Demos-win32-ia32/Electron API Demos.exe' --cert ~/electron-api-demos.p12 --prompt --name 'Electron API Demos' --url 'http://electron.atom.io'", "package:installer": "node ./script/installer.js", "package:sign-installer": "signcode './out/windows-installer/ElectronAPIDemosSetup.exe' --cert ~/electron-api-demos.p12 --prompt --name 'Electron API Demos' --url 'http://electron.atom.io'", "xpackage:mas": "./script/mas.sh", "windows-store": "node ./script/windows-store.js", "release": "node ./script/release.js", "prepack": "check-for-leaks", "prepush": "check-for-leaks" }, "repository": "https://github.com/electron/electron-api-demos", "keywords": [ "Electron", "API", "demo" ], "author": "GitHub", "license": "MIT", "devDependencies": { "@octokit/rest": "^16.3.2", "chai": "^3.4.1", "chai-as-promised": "^6.0.0", "check-for-leaks": "^1.2.1", "devtron": "^1.3.0", "electron-packager": "^16.0.0", "electron-winstaller": "^2.2.0", "husky": "^0.14.3", "mocha": "^5.2.0", "npm-run-all": "^4.0.2", "request": "^2.70.0", "rimraf": "^2.5.2", "signcode": "^1.0.0", "spectron": "^5.0.0", "standard": "^8.2.0", "tap": "^14.10.6" }, "dependencies": { "electron": "^15.5.5", "electron-log": "^2.2.14", "electron-settings": "^3.0.7", "electron-shortcut-normalizer": "^1.0.0", "glob": "^7.1.0", "highlight.js": "^10.4.1", "update-electron-app": "^1.1.1" }, "standard": { "env": { "mocha": true } } } ================================================ FILE: renderer-process/communication/async-msg.js ================================================ const {ipcRenderer} = require('electron') const asyncMsgBtn = document.getElementById('async-msg') asyncMsgBtn.addEventListener('click', () => { ipcRenderer.send('asynchronous-message', 'ping') }) ipcRenderer.on('asynchronous-reply', (event, arg) => { const message = `Asynchronous message reply: ${arg}` document.getElementById('async-reply').innerHTML = message }) ================================================ FILE: renderer-process/communication/invisible-msg.js ================================================ const {BrowserWindow} = require('electron').remote const ipcRenderer = require('electron').ipcRenderer const path = require('path') const invisMsgBtn = document.getElementById('invis-msg') const invisReply = document.getElementById('invis-reply') invisMsgBtn.addEventListener('click', (clickEvent) => { const windowID = BrowserWindow.getFocusedWindow().id const invisPath = `file://${path.join(__dirname, '../../sections/communication/invisible.html')}` let win = new BrowserWindow({ width: 400, height: 400, show: false, webPreferences: { nodeIntegration: true } }) win.loadURL(invisPath) win.webContents.on('did-finish-load', () => { const input = 100 win.webContents.send('compute-factorial', input, windowID) }) }) ipcRenderer.on('factorial-computed', (event, input, output) => { const message = `The factorial of ${input} is ${output}` invisReply.textContent = message }) ================================================ FILE: renderer-process/communication/sync-msg.js ================================================ const {ipcRenderer} = require('electron') const syncMsgBtn = document.getElementById('sync-msg') syncMsgBtn.addEventListener('click', () => { const reply = ipcRenderer.sendSync('synchronous-message', 'ping') const message = `Synchronous message reply: ${reply}` document.getElementById('sync-reply').innerHTML = message }) ================================================ FILE: renderer-process/media/desktop-capturer.js ================================================ const {desktopCapturer, shell} = require('electron') const {screen} = require('electron').remote const fs = require('fs') const os = require('os') const path = require('path') const screenshot = document.getElementById('screen-shot') const screenshotMsg = document.getElementById('screenshot-path') screenshot.addEventListener('click', (event) => { screenshotMsg.textContent = 'Gathering screens...' const thumbSize = determineScreenShotSize() let options = { types: ['screen'], thumbnailSize: thumbSize } desktopCapturer.getSources(options, (error, sources) => { if (error) return console.log(error) sources.forEach((source) => { if (source.name === 'Entire Screen' || source.name === 'Screen 1') { const screenshotPath = path.join(os.tmpdir(), 'screenshot.png') fs.writeFile(screenshotPath, source.thumbnail.toPNG(), (error) => { if (error) return console.log(error) shell.openExternal(`file://${screenshotPath}`) const message = `Saved screenshot to: ${screenshotPath}` screenshotMsg.textContent = message }) } }) }) }) function determineScreenShotSize () { const screenSize = screen.getPrimaryDisplay().workAreaSize const maxDimension = Math.max(screenSize.width, screenSize.height) return { width: maxDimension * window.devicePixelRatio, height: maxDimension * window.devicePixelRatio } } ================================================ FILE: renderer-process/menus/context-menu.js ================================================ const {ipcRenderer} = require('electron') // Tell main process to show the menu when demo button is clicked const contextMenuBtn = document.getElementById('context-menu') contextMenuBtn.addEventListener('click', () => { ipcRenderer.send('show-context-menu') }) ================================================ FILE: renderer-process/native-ui/dialogs/error.js ================================================ const {ipcRenderer} = require('electron') const errorBtn = document.getElementById('error-dialog') errorBtn.addEventListener('click', (event) => { ipcRenderer.send('open-error-dialog') }) ================================================ FILE: renderer-process/native-ui/dialogs/information.js ================================================ const {ipcRenderer} = require('electron') const informationBtn = document.getElementById('information-dialog') informationBtn.addEventListener('click', (event) => { ipcRenderer.send('open-information-dialog') }) ipcRenderer.on('information-dialog-selection', (event, index) => { let message = 'You selected ' if (index === 0) message += 'yes.' else message += 'no.' document.getElementById('info-selection').innerHTML = message }) ================================================ FILE: renderer-process/native-ui/dialogs/open-file.js ================================================ const {ipcRenderer} = require('electron') const selectDirBtn = document.getElementById('select-directory') selectDirBtn.addEventListener('click', (event) => { ipcRenderer.send('open-file-dialog') }) ipcRenderer.on('selected-directory', (event, path) => { document.getElementById('selected-file').innerHTML = `You selected: ${path}` }) ================================================ FILE: renderer-process/native-ui/dialogs/save.js ================================================ const {ipcRenderer} = require('electron') const saveBtn = document.getElementById('save-dialog') saveBtn.addEventListener('click', (event) => { ipcRenderer.send('save-dialog') }) ipcRenderer.on('saved-file', (event, path) => { if (!path) path = 'No path' document.getElementById('file-saved').innerHTML = `Path selected: ${path}` }) ================================================ FILE: renderer-process/native-ui/drag/drag.js ================================================ const {ipcRenderer} = require('electron') const dragFileLink = document.getElementById('drag-file-link') dragFileLink.addEventListener('dragstart', (event) => { event.preventDefault() ipcRenderer.send('ondragstart', __filename) }) ================================================ FILE: renderer-process/native-ui/ex-links-file-manager/ex-links.js ================================================ const {shell} = require('electron') const exLinksBtn = document.getElementById('open-ex-links') exLinksBtn.addEventListener('click', (event) => { shell.openExternal('http://electron.atom.io') }) ================================================ FILE: renderer-process/native-ui/ex-links-file-manager/file-manager.js ================================================ const {shell} = require('electron') const os = require('os') const fileManagerBtn = document.getElementById('open-file-manager') fileManagerBtn.addEventListener('click', (event) => { shell.showItemInFolder(os.homedir()) }) ================================================ FILE: renderer-process/native-ui/notifications/advanced-notification.js ================================================ const path = require('path') const notification = { title: 'Notification with image', body: 'Short message plus a custom image', icon: path.join(__dirname, '../../../assets/img/programming.png') } const notificationButton = document.getElementById('advanced-noti') notificationButton.addEventListener('click', () => { const myNotification = new window.Notification(notification.title, notification) myNotification.onclick = () => { console.log('Notification clicked') } }) ================================================ FILE: renderer-process/native-ui/notifications/basic-notification.js ================================================ const notification = { title: 'Basic Notification', body: 'Short message part' } const notificationButton = document.getElementById('basic-noti') notificationButton.addEventListener('click', () => { const myNotification = new window.Notification(notification.title, notification) myNotification.onclick = () => { console.log('Notification clicked') } }) ================================================ FILE: renderer-process/native-ui/tray/tray.js ================================================ const ipc = require('electron').ipcRenderer const trayBtn = document.getElementById('put-in-tray') let trayOn = false trayBtn.addEventListener('click', function (event) { if (trayOn) { trayOn = false document.getElementById('tray-countdown').innerHTML = '' ipc.send('remove-tray') } else { trayOn = true const message = 'Click demo again to remove.' document.getElementById('tray-countdown').innerHTML = message ipc.send('put-in-tray') } }) // Tray removed from context menu on icon ipc.on('tray-removed', function () { ipc.send('remove-tray') trayOn = false document.getElementById('tray-countdown').innerHTML = '' }) ================================================ FILE: renderer-process/system/app-information.js ================================================ const {ipcRenderer} = require('electron') const appInfoBtn = document.getElementById('app-info') appInfoBtn.addEventListener('click', () => { ipcRenderer.send('get-app-path') }) ipcRenderer.on('got-app-path', (event, path) => { const message = `This app is located at: ${path}` document.getElementById('got-app-info').innerHTML = message }) ================================================ FILE: renderer-process/system/copy.js ================================================ const {clipboard} = require('electron') const copyBtn = document.getElementById('copy-to') const copyInput = document.getElementById('copy-to-input') copyBtn.addEventListener('click', () => { if (copyInput.value !== '') copyInput.value = '' copyInput.placeholder = 'Copied! Paste here to see.' clipboard.writeText('Electron Demo!') }) ================================================ FILE: renderer-process/system/paste.js ================================================ const {clipboard} = require('electron') const pasteBtn = document.getElementById('paste-to') pasteBtn.addEventListener('click', () => { clipboard.writeText('What a demo!') const message = `Clipboard contents: ${clipboard.readText()}` document.getElementById('paste-from').innerHTML = message }) ================================================ FILE: renderer-process/system/protocol-handler.js ================================================ const {shell} = require('electron') const path = require('path') const protocolHandlerBtn = document.getElementById('protocol-handler') protocolHandlerBtn.addEventListener('click', () => { const pageDirectory = __dirname.replace('app.asar', 'app.asar.unpacked') const pagePath = path.join('file://', pageDirectory, '../../sections/system/protocol-link.html') shell.openExternal(pagePath) }) ================================================ FILE: renderer-process/system/screen-information.js ================================================ const {screen} = require('electron').remote const screenInfoBtn = document.getElementById('screen-info') const size = screen.getPrimaryDisplay().size screenInfoBtn.addEventListener('click', () => { const message = `Your screen is: ${size.width}px x ${size.height}px` document.getElementById('got-screen-info').innerHTML = message }) ================================================ FILE: renderer-process/system/sys-information.js ================================================ const os = require('os') const homeDir = os.homedir() const sysInfoBtn = document.getElementById('sys-info') sysInfoBtn.addEventListener('click', () => { const message = `Your system home directory is: ${homeDir}` document.getElementById('got-sys-info').innerHTML = message }) ================================================ FILE: renderer-process/system/version-information.js ================================================ const versionInfoBtn = document.getElementById('version-info') const electronVersion = process.versions.electron versionInfoBtn.addEventListener('click', () => { const message = `This app is using Electron version: ${electronVersion}` document.getElementById('got-version-info').innerHTML = message }) ================================================ FILE: renderer-process/windows/create-window.js ================================================ const {BrowserWindow} = require('electron').remote const path = require('path') const newWindowBtn = document.getElementById('new-window') newWindowBtn.addEventListener('click', (event) => { const modalPath = path.join('file://', __dirname, '../../sections/windows/modal.html') let win = new BrowserWindow({ width: 400, height: 320 }) win.on('close', () => { win = null }) win.loadURL(modalPath) win.show() }) ================================================ FILE: renderer-process/windows/frameless-window.js ================================================ const {BrowserWindow} = require('electron').remote const newWindowBtn = document.getElementById('frameless-window') const path = require('path') newWindowBtn.addEventListener('click', (event) => { const modalPath = path.join('file://', __dirname, '../../sections/windows/modal.html') let win = new BrowserWindow({ frame: false }) win.on('close', () => { win = null }) win.loadURL(modalPath) win.show() }) ================================================ FILE: renderer-process/windows/manage-window.js ================================================ const {BrowserWindow} = require('electron').remote const path = require('path') const manageWindowBtn = document.getElementById('manage-window') let win manageWindowBtn.addEventListener('click', (event) => { const modalPath = path.join('file://', __dirname, '../../sections/windows/manage-modal.html') win = new BrowserWindow({ width: 400, height: 275 }) win.on('resize', updateReply) win.on('move', updateReply) win.on('close', () => { win = null }) win.loadURL(modalPath) win.show() function updateReply () { const manageWindowReply = document.getElementById('manage-window-reply') const message = `Size: ${win.getSize()} Position: ${win.getPosition()}` manageWindowReply.innerText = message } }) ================================================ FILE: renderer-process/windows/process-crash.js ================================================ const {BrowserWindow, dialog} = require('electron').remote const path = require('path') const processCrashBtn = document.getElementById('process-crash') processCrashBtn.addEventListener('click', (event) => { const crashWinPath = path.join('file://', __dirname, '../../sections/windows/process-crash.html') let win = new BrowserWindow({ width: 400, height: 320, webPreferences: { nodeIntegration: true } }); win.webContents.on('crashed', () => { const options = { type: 'info', title: 'Renderer Process Crashed', message: 'This process has crashed.', buttons: ['Reload', 'Close'] } dialog.showMessageBox(options, (index) => { if (index === 0) win.reload() else win.close() }) }) win.on('close', () => { win = null }) win.loadURL(crashWinPath) win.show() }) ================================================ FILE: renderer-process/windows/process-hang.js ================================================ const {BrowserWindow, dialog} = require('electron').remote const path = require('path') const processHangBtn = document.getElementById('process-hang') processHangBtn.addEventListener('click', (event) => { const hangWinPath = path.join('file://', __dirname, '../../sections/windows/process-hang.html') let win = new BrowserWindow({ width: 400, height: 320, webPreferences: { nodeIntegration: true } }); win.on('unresponsive', () => { const options = { type: 'info', title: 'Renderer Process Hanging', message: 'This process is hanging.', buttons: ['Reload', 'Close'] } dialog.showMessageBox(options, (index) => { if (index === 0) win.reload() else win.close() }) }) win.on('close', () => { win = null }) win.loadURL(hangWinPath) win.show() }) ================================================ FILE: renderer-process/windows/using-window-events.js ================================================ const {BrowserWindow} = require('electron').remote const path = require('path') const manageWindowBtn = document.getElementById('listen-to-window') const focusModalBtn = document.getElementById('focus-on-modal-window') let win manageWindowBtn.addEventListener('click', () => { const modalPath = path.join('file://', __dirname, '../../sections/windows/modal-toggle-visibility.html') win = new BrowserWindow({ width: 600, height: 400 }) const hideFocusBtn = () => { focusModalBtn.classList.add('disappear') focusModalBtn.classList.remove('smooth-appear') focusModalBtn.removeEventListener('click', clickHandler) } const showFocusBtn = (btn) => { if (!win) return focusModalBtn.classList.add('smooth-appear') focusModalBtn.classList.remove('disappear') focusModalBtn.addEventListener('click', clickHandler) } win.on('focus', hideFocusBtn) win.on('blur', showFocusBtn) win.on('close', () => { hideFocusBtn() win = null }) win.loadURL(modalPath) win.show() const clickHandler = () => { win.focus() } }) ================================================ FILE: script/installer.js ================================================ #!/usr/bin/env node const createWindowsInstaller = require('electron-winstaller').createWindowsInstaller const path = require('path') const rimraf = require('rimraf') deleteOutputFolder() .then(getInstallerConfig) .then(createWindowsInstaller) .catch((error) => { console.error(error.message || error) process.exit(1) }) function getInstallerConfig () { const rootPath = path.join(__dirname, '..') const outPath = path.join(rootPath, 'out') return Promise.resolve({ appDirectory: path.join(outPath, 'Electron API Demos-win32-ia32'), exe: 'Electron API Demos.exe', iconUrl: 'https://raw.githubusercontent.com/electron/electron-api-demos/master/assets/app-icon/win/app.ico', loadingGif: path.join(rootPath, 'assets', 'img', 'loading.gif'), noMsi: true, outputDirectory: path.join(outPath, 'windows-installer'), setupExe: 'ElectronAPIDemosSetup.exe', setupIcon: path.join(rootPath, 'assets', 'app-icon', 'win', 'app.ico'), skipUpdateIcon: true }) } function deleteOutputFolder () { return new Promise((resolve, reject) => { rimraf(path.join(__dirname, '..', 'out', 'windows-installer'), (error) => { error ? reject(error) : resolve() }) }) } ================================================ FILE: script/mas.sh ================================================ #!/bin/bash set -ex # App Store does not allow the word "demos" in the app's name APP="Electron APIs" electron-packager . \ "$APP" \ --asar \ --asar-unpack=protocol-link.html \ --overwrite \ --platform=mas \ --app-bundle-id=com.github.electron-api-demos \ --app-version="$npm_package_version" \ --build-version="1.1.0" \ --arch=x64 \ --icon=assets/app-icon/mac/app.icns \ --prune=true \ --out=out \ --extend-info=assets/mac/info.plist APP_PATH="./out/$APP-mas-x64/$APP.app" RESULT_PATH="./out/$APP.pkg" APP_KEY="3rd Party Mac Developer Application: GitHub (VEKTX9H2N7)" INSTALLER_KEY="3rd Party Mac Developer Installer: GitHub (VEKTX9H2N7)" FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" CHILD_PLIST="./assets/mac/child.plist" PARENT_PLIST="./assets/mac/parent.plist" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libnode.dylib" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/Contents/MacOS/$APP Helper" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/Contents/MacOS/$APP Helper EH" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper EH.app/" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/Contents/MacOS/$APP Helper NP" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper NP.app/" codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$APP_PATH/Contents/MacOS/$APP" codesign -s "$APP_KEY" -f --entitlements "$PARENT_PLIST" "$APP_PATH" productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" ================================================ FILE: script/release.js ================================================ #!/usr/bin/env node const childProcess = require('child_process') const fs = require('fs') const path = require('path') const octokit = require('@octokit/rest') const token = process.env.ELECTRON_API_DEMO_GITHUB_TOKEN const version = require('../package').version const github = octokit({ timeout: 30 * 1000, 'user-agent': `node/${process.versions.node}` }) if (!token) { console.error('ELECTRON_API_DEMO_GITHUB_TOKEN environment variable not set\nSet it to a token with repo scope created from https://github.com/settings/tokens/new') process.exit(1) } github.authenticate({ type: 'token', token: token }) async function doRelease () { const release = await getOrCreateRelease() const assets = await prepareAssets() await uploadAssets(release, assets) await publishRelease(release) console.log('Done!') } doRelease().catch(err => { console.error(err.message || err) process.exit(1) }) function prepareAssets () { const outPath = path.join(__dirname, '..', 'out') const zipAssets = [{ name: 'electron-api-demos-mac.zip', path: path.join(outPath, 'Electron API Demos-darwin-x64', 'Electron API Demos.app') }, { name: 'electron-api-demos-windows.zip', path: path.join(outPath, 'Electron API Demos-win32-ia32') }, { name: 'electron-api-demos-linux.zip', path: path.join(outPath, 'Electron API Demos-linux-x64') }] return Promise.all(zipAssets.map(zipAsset)).then((zipAssets) => { return zipAssets.concat([{ name: 'RELEASES', path: path.join(outPath, 'windows-installer', 'RELEASES') }, { name: 'ElectronAPIDemosSetup.exe', path: path.join(outPath, 'windows-installer', 'ElectronAPIDemosSetup.exe') }, { name: `electron-api-demos-${version}-full.nupkg`, path: path.join(outPath, 'windows-installer', `electron-api-demos-${version}-full.nupkg`) }]) }) } function zipAsset (asset) { return new Promise((resolve, reject) => { const assetBase = path.basename(asset.path) const assetDirectory = path.dirname(asset.path) console.log(`Zipping ${assetBase} to ${asset.name}`) if (!fs.existsSync(asset.path)) { return reject(new Error(`${asset.path} does not exist`)) } const zipCommand = `zip --recurse-paths --symlinks '${asset.name}' '${assetBase}'` const options = {cwd: assetDirectory, maxBuffer: Infinity} childProcess.exec(zipCommand, options, (error) => { if (error) { reject(error) } else { asset.path = path.join(assetDirectory, asset.name) resolve(asset) } }) }) } async function getOrCreateRelease () { const { data: releases } = await github.repos.listReleases({ owner: 'electron', repo: 'electron-api-demos', per_page: 100, page: 1 }) const existingRelease = releases.find(release => release.tag_name === `v${version}` && release.draft === true) if (existingRelease) { console.log(`Using existing draft release for v${version}`) return existingRelease } console.log('Creating new draft release') const { data: release } = await github.repos.createRelease({ owner: 'electron', repo: 'electron-api-demos', tag_name: `v${version}`, target_commitish: 'master', name: version, body: 'An awesome new release :tada:', draft: true, prerelease: false }) return release } async function uploadAssets (release, assets) { for (const asset of assets) { if (release.assets.some(ghAsset => ghAsset.name === asset.name)) { console.log(`Skipping already uploaded asset ${asset.name}`) } else { process.stdout.write(`Uploading ${asset.name}... `) try { await uploadAsset(release, asset) } catch (err) { if (err.name === 'HttpError' && err.message.startsWith('network timeout')) { console.error('\n') console.error(` There was a network timeout while uploading ${asset.name}.`) console.error(' This likely resulted in a bad asset; please visit the release at') console.error(` ${release.html_url} and manually remove the bad asset,`) console.error(` then run this script again to continue where you left off.`) console.error('') process.exit(2) } else { throw err } } process.stdout.write('Success!\n') // [mkt] Waiting a bit between uploads seems to increase success rate await new Promise(resolve => setTimeout(resolve, 5000)) } } } function uploadAsset (release, asset) { return github.repos.uploadReleaseAsset({ headers: { 'content-type': 'application/octet-stream', 'content-length': fs.statSync(asset.path).size }, url: release.upload_url, file: fs.createReadStream(asset.path), name: asset.name }) } function publishRelease (release) { console.log('Publishing release') return github.repos.updateRelease({ owner: 'electron', repo: 'electron-api-demos', release_id: release.id, draft: false }) } ================================================ FILE: script/windows-store.js ================================================ const ChildProcess = require('child_process') const path = require('path') const metadata = require('../package') const command = path.join(__dirname, '..', 'node_modules', '.bin', 'electron-windows-store.cmd') const args = [ '--input-directory', path.join(__dirname, '..', 'out', 'ElectronAPIDemos-win32-ia32'), '--output-directory', path.join(__dirname, '..', 'out', 'windows-store'), '--flatten', true, '--package-version', metadata.version + '.0', '--package-name', metadata.name, '--package-display-name', metadata.productName, '--assets', path.join(__dirname, '..', 'assets', 'tiles'), '--package-description', metadata.description ] const windowsStore = ChildProcess.spawn(command, args, {stdio: 'inherit'}) windowsStore.on('close', (code) => { process.exit(code) }) ================================================ FILE: sections/about.html ================================================ ================================================ FILE: sections/communication/invisible.html ================================================ ================================================ FILE: sections/communication/ipc.html ================================================ ================================================ FILE: sections/media/desktop-capturer.html ================================================ ================================================ FILE: sections/menus/menus.html ================================================ ================================================ FILE: sections/menus/shortcuts.html ================================================ ================================================ FILE: sections/native-ui/dialogs.html ================================================ ================================================ FILE: sections/native-ui/drag.html ================================================ ================================================ FILE: sections/native-ui/ex-links-file-manager.html ================================================ ================================================ FILE: sections/native-ui/notifications.html ================================================ ================================================ FILE: sections/native-ui/tray.html ================================================ ================================================ FILE: sections/system/app-sys-information.html ================================================ ================================================ FILE: sections/system/clipboard.html ================================================ ================================================ FILE: sections/system/protocol-handler.html ================================================ ================================================ FILE: sections/system/protocol-link.html ================================================

electron-api-demos://open

Click the link above to return to Electron API Demos.

================================================ FILE: sections/windows/crash-hang.html ================================================ ================================================ FILE: sections/windows/manage-modal.html ================================================

Resize or move the window. See the size and position in the main window.

Close this Window ================================================ FILE: sections/windows/modal-toggle-visibility.html ================================================

Click on the parent window to see how the "focus on demo" button appears.

Close this Window ================================================ FILE: sections/windows/modal.html ================================================

Hello World!

Close this Window ================================================ FILE: sections/windows/process-crash.html ================================================

Click the text below to crash and then reload this process.

Crash this process ================================================ FILE: sections/windows/process-hang.html ================================================

Click the text below to hang and then reload this process.

(This will take up to 30 seconds.) Hang this process ================================================ FILE: sections/windows/windows.html ================================================ ================================================ FILE: test/index.js ================================================ 'use strict' const Application = require('spectron').Application const electron = require('electron') const chai = require('chai') const chaiAsPromised = require('chai-as-promised') const path = require('path') const setup = require('./setup') chai.should() chai.use(chaiAsPromised) const timeout = process.env.CI ? 30000 : 10000 describe('demo app', function () { this.timeout(timeout) let app const startApp = () => { app = new Application({ path: electron, args: [ path.join(__dirname, '..') ], waitTimeout: timeout }) return app.start().then((ret) => { setup.setupApp(ret) }) } const restartApp = () => { return app.restart().then((ret) => { setup.setupApp(ret) }) } before(() => { setup.removeStoredPreferences() return startApp() }) after(() => { if (app && app.isRunning()) { return app.stop() } }) it('checks hardcoded path for userData is correct', function () { return app.client.execute(() => { return require('electron').remote.app.getPath('userData') }).then((result) => { return result.value }).should.eventually.equal(setup.getUserDataPath()) }) it('opens a window displaying the about page', function () { return app.client.getWindowCount().should.eventually.equal(1) .browserWindow.isMinimized().should.eventually.be.false .browserWindow.isDevToolsOpened().should.eventually.be.false .browserWindow.isVisible().should.eventually.be.true .browserWindow.isFocused().should.eventually.be.true .browserWindow.getBounds().should.eventually.have.property('width').and.be.above(0) .browserWindow.getBounds().should.eventually.have.property('height').and.be.above(0) .browserWindow.getTitle().should.eventually.equal('Electron API Demos') .waitForVisible('#about-modal').should.eventually.be.true .isVisible('.js-nav').should.eventually.be.false .click('button[id="get-started"]').pause(500) .isVisible('#about-modal').should.eventually.be.false .isVisible('.js-nav').should.eventually.be.true }) it('does not contain any accessibility warnings or errors', function () { return app.client.dismissAboutPage() .auditSectionAccessibility('windows') .auditSectionAccessibility('crash-hang') .auditSectionAccessibility('menus') .auditSectionAccessibility('shortcuts') .auditSectionAccessibility('ex-links-file-manager') .auditSectionAccessibility('notifications') .auditSectionAccessibility('dialogs') .auditSectionAccessibility('tray') .auditSectionAccessibility('ipc') .auditSectionAccessibility('app-sys-information') .auditSectionAccessibility('clipboard') .auditSectionAccessibility('protocol') .auditSectionAccessibility('desktop-capturer') }) describe('when clicking on a section from the nav bar', function () { it('it shows the selected section in the main area', function () { return app.client.dismissAboutPage() .selectSection('windows') .isExisting('button.is-selected[data-section="windows"]').should.eventually.be.true .isVisible('#menus-section').should.eventually.be.false .selectSection('menus') .isVisible('#windows-section').should.eventually.be.false .isExisting('button.is-selected[data-section="windows"]').should.eventually.be.false .isExisting('button.is-selected[data-section="menus"]').should.eventually.be.true }) }) describe('when a demo title is clicked', function () { it('it expands the demo content', function () { let onlyFirstVisible = Array(30).fill(false) onlyFirstVisible[0] = true return app.client.dismissAboutPage() .collapseDemos() .selectSection('windows') .click('.js-container-target') .waitForVisible('.demo-box') .isVisible('.demo-box').should.eventually.deep.equal(onlyFirstVisible) }) }) describe('when the app is restarted after use', function () { it('it launches at last visited section & demo', function () { let onlyFirstVisible = Array(30).fill(false) onlyFirstVisible[0] = true return app.client.waitForVisible('#windows-section') .then(restartApp) .then(function () { return app.client.waitForVisible('#windows-section') .isVisible('#windows-section').should.eventually.be.true .isVisible('.demo-box').should.eventually.deep.equal(onlyFirstVisible) }) }) }) }) ================================================ FILE: test/setup.js ================================================ 'use strict' const path = require('path') const fs = require('fs') const chaiAsPromised = require('chai-as-promised') const getUserDataPath = function () { const productName = require('../package').productName switch (process.platform) { case 'darwin': return path.join(process.env.HOME, 'Library', 'Application Support', productName) case 'win32': return path.join(process.env.APPDATA, productName) case 'freebsd': case 'linux': case 'sunos': return path.join(process.env.HOME, '.config', productName) default: throw new Error(`Unknown userDataPath path for platform ${process.platform}`) } } const removeStoredPreferences = () => { const userDataPath = getUserDataPath() try { fs.unlinkSync(path.join(userDataPath, 'Settings')) } catch (error) { if (error.code !== 'ENOENT') throw error } } const setupApp = function (app) { app.client.addCommand('dismissAboutPage', function () { return this.isVisible('.js-nav').then(function (navVisible) { if (!navVisible) { return this.click('button[id="get-started"]').pause(500) } }) }) app.client.addCommand('selectSection', function (section) { return this.click('button[data-section="' + section + '"]').pause(100) .waitForVisible('#' + section + '-section') }) app.client.addCommand('expandDemos', function () { return this.execute(function () { for (let demo of document.querySelectorAll('.demo-wrapper')) { demo.classList.add('is-open') } }) }) app.client.addCommand('collapseDemos', function () { return this.execute(function () { for (let demo of document.querySelectorAll('.demo-wrapper')) { demo.classList.remove('is-open') } }) }) app.client.addCommand('auditSectionAccessibility', function (section) { const options = { ignoreRules: ['AX_COLOR_01', 'AX_TITLE_01'] } return this.selectSection(section) .expandDemos() .auditAccessibility(options).then(function (audit) { if (audit.failed) { throw Error(section + ' section failed accessibility audit\n' + audit.message) } }) }) chaiAsPromised.transferPromiseness = app.transferPromiseness return app.client.waitUntilWindowLoaded() } module.exports = { removeStoredPreferences, getUserDataPath, setupApp }