Compare commits

...
141 Commits
Author SHA1 Message Date
Erick Zhao 4347a3be57 Merge branch 'master' into erick/quit-remote 2020-07-07 11:30:55 -07:00
Vlad Hashimoto 351c5caf23 feat: set the appData dir from the main process (#430) 2020-07-06 23:31:34 +03:00
github-actions[bot]andmalept 36780a95e7 build: update Electron releases JSON (#431)
Co-authored-by: malept <electron-bot@users.noreply.github.com>
2020-07-06 03:59:28 +03:00
Shelley Vohr 9065a8678a fix: audio/video entitlements crash (#426)
* fix: add entitlements for audio and video capture

* chore: add usageDescription

* empty commit
2020-07-01 12:49:39 -07:00
github-actions[bot]andmalept 016d3ee78a build: update Electron releases JSON (#419)
Co-authored-by: malept <electron-bot@users.noreply.github.com>
2020-06-29 20:02:40 +03:00
Erick Zhao 147674f176 1 more spec 2020-06-26 13:28:17 -07:00
Erick Zhao 15c278cecf refactor 2020-06-26 13:23:49 -07:00
Erick Zhao ee10789c1f hey it works! 2020-06-26 11:58:47 -07:00
Erick Zhao 6acc05c5c7 wip2 2020-06-25 13:59:27 -07:00
Erick Zhao a37b2941f7 wip 2020-06-25 13:57:17 -07:00
Shelley Vohr 4b40420bba feat: log notification info in adv logging (#421) 2020-06-25 11:30:28 -07:00
Vlad Hashimoto 0e872b0dd0 feat: allow to publish if user would to do it (#413) 2020-06-18 15:11:06 +03:00
github-actions[bot]andmalept 7638111598 build: update Electron releases JSON (#414)
Co-authored-by: malept <electron-bot@users.noreply.github.com>
2020-06-16 08:14:39 -07:00
Benjamin Sova a0e59ad288 docs: fix explanation of darwin auto-quit prevention (#410) 2020-06-11 09:48:38 -07:00
Erick Zhao 3c6103dc50 chore: 0.15.1 (#409) 2020-06-09 10:24:32 -07:00
Erick Zhao b98472f973 fix: make hidden editors interact properly with remote content (#404) 2020-06-08 16:37:20 -07:00
Jeremy Rose 3602eacc88 fix: correct sense of bisect buttons (#408) 2020-06-08 16:19:54 -07:00
github-actions[bot] 8b84c040c7 build: update Electron releases JSON (#399) 2020-06-07 20:36:37 -07:00
Erick Zhao 6aa19eada6 fix: proper async/await usage in editor.tsx (#403) 2020-06-01 14:39:41 -07:00
Erick Zhao 8e92183de2 refactor: replace app.getName() with app.name (#401) 2020-05-27 16:03:17 -07:00
Vlad Hashimoto 1a951f3f44 feat: v0.15.0 (#398) 2020-05-23 02:46:28 +03:00
Will Anderson 66ead93f05 fix: Update documentation link for user-provided flags (#397) 2020-05-23 00:26:09 +03:00
Vlad Hashimoto d22899b4a5 feat: add ability to update gist instead of publishing (#386) 2020-05-22 23:59:27 +03:00
Vlad Hashimoto 77d4bc6fc5 fix: use role: quit for render exit button in menu (#395) 2020-05-21 00:45:41 +03:00
github-actions[bot] 961d42d74f build: update Electron releases JSON (#393) 2020-05-20 21:33:13 +03:00
Vlad Hashimoto c58ade2132 fix: reset github token when authentication failed (#388) 2020-05-20 21:31:00 +03:00
github-actions[bot] 592c220f18 build: update Electron releases JSON (#391) 2020-05-13 21:13:51 +03:00
Vlad Hashimoto 4da0e82e9b fix: set the window to null after destroying (#389) 2020-05-13 20:54:18 +03:00
Vlad Hashimoto f766bef10c fix: doesn't error when unable to watch file (#390) 2020-05-12 19:36:28 -07:00
Natalie 30ba9fb023 docs: improve CONTRIBUTING.md (#380)
This PR adds a Contributing section to the README and descriptive text for running Fiddle from source in CONTRIBUTING.md.
2020-05-12 08:53:17 -07:00
github-actions[bot] f3e847da85 build: update Electron releases JSON (#381) 2020-05-04 23:42:14 -07:00
Kimberlee Johnson 0f854441e4 feat: reset layout button (#374) 2020-04-29 17:35:18 -07:00
Vlad Hashimoto f31a4e38bb fix: don't modify state directly (#385) 2020-04-28 14:18:18 -07:00
Felix Rieseberg 26bb3e4944 Merge pull request #373 from codebytere/update-electron-get
chore: update @electron/get version
2020-04-21 11:03:20 -07:00
Vlad Hashimoto fb1fb7869c Merge pull request #376 from electron/update-releases
build: update Electron releases JSON
2020-04-20 12:21:15 +03:00
malept a1e67400bf build: update Electron releases JSON 2020-04-20 00:04:55 +00:00
Shelley Vohr a26aa1e70c chore: update @electron/get version 2020-04-14 14:22:25 -07:00
github-actions[bot]andElectron Bot 78395412f4 build: update Electron releases JSON (#368)
Co-authored-by: Electron Bot <electron-bot@users.noreply.github.com>
2020-04-13 07:51:52 -07:00
Felix Rieseberg 1f72db3cfc 0.14.0 2020-04-06 10:22:19 -07:00
Felix Rieseberg c333a1e132 Merge pull request #366 from electron/update-releases
build: update Electron releases JSON
2020-04-06 10:19:49 -07:00
malept a0a989d7a7 build: update Electron releases JSON 2020-04-06 00:13:17 +00:00
github-actions[bot]andmalept 5b07b8a706 build: update Electron releases JSON (#364)
Co-authored-by: malept <electron-bot@users.noreply.github.com>
2020-03-30 03:20:17 +03:00
Felix Rieseberg 98a358b756 Merge pull request #358 from electron/download-progress
Refactor BinaryManager, add download progress indicator
2020-03-24 11:14:55 -07:00
Felix Rieseberg 9b55b2ef26 Merge pull request #357 from electron/no-notarize
build: Only notarize during make
2020-03-23 10:45:16 -07:00
Felix Rieseberg 5b91177348 Merge pull request #359 from electron/update-releases
build: update Electron releases JSON
2020-03-23 10:44:51 -07:00
malept f8c43a07f7 build: update Electron releases JSON 2020-03-23 00:04:50 +00:00
Felix Rieseberg 5eba9cdbe2 feat: A fancy spinner for downloading 2020-03-17 18:11:05 -07:00
Felix Rieseberg 8ee42c290e test: Fix the tests post-refactor 2020-03-17 17:58:25 -07:00
Felix Rieseberg c2b3954dfa chore: Refactor BinaryManager, no more classes 2020-03-17 17:07:25 -07:00
Felix Rieseberg e61599e9c5 chore: Better named interfaces 2020-03-17 16:24:58 -07:00
Felix RiesebergandMark Lee 1fed772a27 Update forge.config.js
Co-Authored-By: Mark Lee <malept@users.noreply.github.com>
2020-03-17 16:13:32 -07:00
Felix RiesebergandMark Lee de85d0f324 Update forge.config.js
Co-Authored-By: Mark Lee <malept@users.noreply.github.com>
2020-03-17 16:13:16 -07:00
Erick Zhao c89671d99d feat: improved bisect commands UX (#352) 2020-03-16 10:55:34 -07:00
Felix Rieseberg 155b901ade build: Only notarize during make 2020-03-16 10:47:00 -07:00
github-actions[bot]andmalept 8e098deab8 build: update Electron releases JSON (#356)
Co-authored-by: malept <electron-bot@users.noreply.github.com>
2020-03-16 02:18:36 +02:00
dependabot[bot] 2b5ea7d467 build(deps): bump acorn from 5.7.1 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.1 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.1...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-13 23:49:10 -07:00
Vlad Hashimoto 91efa5a009 v0.13.1 2020-03-13 16:45:08 -07:00
Erick Zhao 4ec5bec719 fix: load unsave handlers only after editors mount (#351) 2020-03-12 11:53:29 -07:00
Erick Zhao 84ac41f147 feat: more sensible version filtering (#344) 2020-03-12 11:46:32 -07:00
Erick Zhao e9a2985291 fix: preserve 'downloading' version state post-refresh (#345) 2020-03-12 11:46:24 -07:00
Erick ZhaoandFelix Rieseberg 9b28919203 feat: show inactive window on quit warning (#348)
Co-authored-by: Felix Rieseberg <felix@felixrieseberg.com>
2020-03-12 11:13:08 -07:00
Erick Zhao 8d43836787 fix: ensure you have at least one editor open (#347) 2020-03-11 12:54:16 -07:00
Shelley Vohr 48d0dadf5d chore: fix test:report bailing (#349) 2020-03-11 12:32:03 -07:00
Erick Zhao 2f9ef21251 fix: ensure hidden panels work on first load (#346) 2020-03-11 12:12:09 -07:00
Erick Zhao 8e4238275c refactor: grab release list from unpkg (#339) 2020-03-09 11:45:54 -07:00
Shelley Vohr 32d5ea99aa chore: add test report generation helper (#340) 2020-03-09 09:28:43 -07:00
Felix Rieseberg da2e17cd37 Merge pull request #342 from electron/update-releases
build: update Electron releases JSON
2020-03-08 18:13:01 -07:00
malept 539f5bce97 build: update Electron releases JSON 2020-03-09 00:06:03 +00:00
Mark Lee 228552c8a0 build: create a PR for releases.json updates instead of trying to push to master (#341)
* Move expected version count & latest Electron version to its own
  fixture file
* Automatically update expected version count & latest Electron version
  when updating the JSON file
* Throw an error if a rejection occurs when the fetch releases script
  is run
* reduce releases update frequency to once a week
2020-03-06 16:17:42 -08:00
Felix Rieseberg ed8d76a54a fix: Sentry is a runtime dependency 2020-03-02 17:14:29 -08:00
Felix Rieseberg 2f4d47a0ab 0.13.0 2020-03-02 16:14:25 -08:00
Mark Lee 9b443de137 build: auto-update the Electron Releases JSON file every day (#331) 2020-03-02 16:08:39 -08:00
Felix Rieseberg efb5bf0e57 Merge pull request #334 from electron/travis-cleanup
build: cache/install node deps per Travis docs, remove deprecated option
2020-03-02 16:03:59 -08:00
Mark Lee c420747bb9 build: cache/install node dependencies per Travis docs, remove deprecated option 2020-03-02 15:40:23 -08:00
Felix Rieseberg 33223e7b37 Merge pull request #333 from electron/use-bionic
build: Use Ubuntu 18.04 in Travis CI
2020-03-02 15:35:04 -08:00
Felix Rieseberg 86cc0c62a1 build: Use Bionic 2020-03-02 14:50:40 -08:00
Felix Rieseberg 96bd2e36dd Merge pull request #330 from electron/update-dependencies
Update dependencies
2020-03-02 13:02:16 -08:00
Felix Rieseberg 9db527773b test: Fix the tests 2020-02-27 17:43:16 -08:00
Felix Rieseberg 9da1acafd9 wip 2020-02-27 16:56:51 -08:00
Felix Rieseberg 5a05d34776 chore: Update local Electron releases 2020-02-27 14:42:39 -08:00
Felix Rieseberg 6b9d2b66e2 wrench: Update dependencies 2020-02-27 14:41:44 -08:00
Felix Rieseberg d4096a6de8 Merge pull request #329 from electron/less-listeners
chore: Unregister various listeners
2020-02-26 12:28:55 -08:00
Erick Zhao 3709d122a6 feat: add CSS support (#327) 2020-02-22 11:48:11 -08:00
Felix Rieseberg 0368cda57a Merge pull request #328 from electron/fix/remove-reload
fix: remove broken "Reload" menu item
2020-02-22 08:53:34 -08:00
Felix Rieseberg 433cb6acd5 chore: Use node environment where available 2020-02-22 08:50:56 -08:00
Felix Rieseberg 56dceaefb0 chore: Don't collect coverage locally 2020-02-22 08:35:35 -08:00
Erick Zhao 80da23c4e4 fix: remove broken "Reload" menu item 2020-02-21 10:09:41 -08:00
Felix Rieseberg 353968ef8e Merge pull request #326 from electron/bisect-constraint
feat: Add some help to the bisect
2020-02-19 13:29:05 -08:00
Felix RiesebergandErick Zhao 64ece1b364 Apply suggestions from code review
Co-Authored-By: Erick Zhao <erick@hotmail.ca>
2020-02-19 12:29:20 -08:00
Felix Rieseberg 13383f7345 chore: Unregister various listeners 2020-02-15 20:50:57 -08:00
Felix Rieseberg cae7222636 feat: Add some help to the bisect 2020-02-15 20:11:08 -08:00
Felix Rieseberg 2aabe80f2b Merge pull request #324 from electron/fix-bisect
fix: Reorganize the bisect helper
2020-02-15 18:54:11 -08:00
Erick Zhao d0683f1392 fix: prevent other editors from clipping tooltips (#294) 2020-02-13 11:45:42 -08:00
Felix Rieseberg 389352cefa test: Get some of that coverage back 2020-02-12 20:50:18 -08:00
Felix Rieseberg d04b654c3e fix: Reorganize the bisect helper 2020-02-12 14:24:57 -08:00
Felix Rieseberg 1369c9b35c Merge pull request #318 from electron/sentry
chore: add sentry for crash / js error reporting
2020-02-11 07:56:12 -08:00
Samuel Attard 9957090d25 Update electron.ts 2020-02-05 15:14:25 -08:00
Samuel Attard 8a002fb0a8 chore: add sentry for crash / js error reporting 2020-02-05 13:15:02 -08:00
Mark Lee cae8dde4c8 refactor(show-me): avoid using a global mainWindow variable (#314) 2020-02-03 14:19:04 -08:00
Jeremy Apthorp cb176534f9 fix: stop holding a reference to mainWindow (#312) 2020-01-30 09:37:34 -08:00
Erick Zhao f7fa9a8ffc chore: merge pull request #306 from issacgerges/bisect-success-dialog
feat: bisect success dialog
2020-01-29 09:54:24 -08:00
Issac Gerges 347384a547 chore: improve GenericDialog test coverage 2020-01-28 19:22:58 -08:00
Issac Gerges e272efb3b9 fix: add quoted escapes to paths to prevent param expansion on zsh. Remove dead code. 2020-01-28 19:16:22 -08:00
Mark LeeandFelix Rieseberg 5255bcb842 chore: add contributing guide (#311)
Co-Authored-By: Felix Rieseberg <felix@felixrieseberg.com>
2020-01-28 18:51:54 -08:00
Felix Rieseberg d3cd53f3e0 Merge pull request #309 from electron/erick/pong-prints-pong
fix: print correct output in ipcRenderer show-me
2020-01-28 17:45:51 -08:00
Erick Zhao 6d28c4c81b fix: print correct output in ipcRender show-me 2020-01-28 14:55:56 -08:00
Issac Gerges a45354e1af chore: quote lint glob to prevent zsh to parse it as parameter expansion 2020-01-27 18:25:50 -08:00
Issac Gerges 28245d36ba refactor: rename DialogType 'message' to 'success' 2020-01-27 14:48:30 -08:00
Issac Gerges 582ef280b5 Merge remote-tracking branch 'upstream/master' into bisect-dialog 2020-01-27 13:00:48 -08:00
Issac Gerges 56b7f2ffb7 refactor: replace warning/confirmation dialogs with generic dialog and type parameter 2020-01-27 12:58:11 -08:00
issacgerges cb3ba4f0a7 chore: Add issacgerges as a contributor (#304) 2020-01-26 13:57:32 -08:00
Felix Rieseberg e238b10f1e Merge pull request #303 from issacgerges/fix-remote-warning
fix: Make example warning more descriptive
2020-01-26 08:59:37 -08:00
Felix Rieseberg bbc22d9ca8 Merge pull request #301 from issacgerges/support-original-fs
fix: Add original-fs to ignored modules
2020-01-26 08:58:34 -08:00
issacgerges 7b3c960c1f chore: fix autoUpdater example to remove spurious event (#302)
Fixes #283
2020-01-25 22:44:06 -08:00
Issac Gerges 4dbc74fccb fix: Make example warning more descriptive by adding path, remove repeated text. Fixes #262. 2020-01-25 22:02:57 -08:00
Issac Gerges f71b1bc8d4 fix: Add original-fs to ignored modules. Fixes #231 2020-01-25 11:36:25 -08:00
Issac Gerges 00c2e1f9a6 style: fixup jsdoc for BisectDialog 2020-01-25 10:28:43 -08:00
Issac Gerges 50f69613dd feat: Add bisect complete dialog. Fixes #299. 2020-01-25 10:28:35 -08:00
Felix Rieseberg 4a23b53b1b 0.12.0 2020-01-15 12:38:05 -08:00
Felix Rieseberg dcd21935a9 Merge pull request #293 from electron/fix-loading-preload
fix: Create initial backup for preload editor
2020-01-15 12:36:43 -08:00
Felix Rieseberg ff37d5cfb9 test: Add tests for new code 2020-01-15 12:10:52 -08:00
Felix Rieseberg bb8053d86d fix: Create a backup for the preload editor 2020-01-15 12:09:59 -08:00
Mark Lee 5efaa54255 feat: publish preload scripts to GitHub gists (#292)
* fix: update welcome tour to mention preload scripts
* chore: update doc comments
2020-01-14 13:41:57 -08:00
Felix Rieseberg b38507230d Merge pull request #291 from electron/update-deps
chore: Update various dependencies
2020-01-14 13:30:30 -08:00
Felix Rieseberg 7451083c70 test: Fix one more test case 2020-01-14 12:16:48 -08:00
Felix Rieseberg be46780674 build: Update tslint rules 2020-01-13 10:55:01 -08:00
Felix Rieseberg 99b5d067df build: Update stylelint 2020-01-13 10:54:25 -08:00
Felix Rieseberg f34eb225ea feat: Update fallback releases 2020-01-13 10:52:49 -08:00
Felix Rieseberg 18cc36a837 feat: Update blueprint 2020-01-13 10:52:23 -08:00
Felix Rieseberg da403e3cbd feat: Update various dependencies 2020-01-13 10:51:04 -08:00
Felix Rieseberg b4431cf426 Merge pull request #285 from electron/direct-fetch-builtins
refactor: directly pull Node.js builtins
2020-01-13 10:46:01 -08:00
Felix Rieseberg e714269b9b Merge pull request #286 from electron/fix-jest-transform
test: avoid transforming JSON files
2020-01-13 10:44:57 -08:00
Felix Rieseberg b9518a63f8 Merge pull request #287 from Loseshape0/patch-1
Update Years
2020-01-13 10:44:28 -08:00
Felix Rieseberg 0627801bb0 Merge pull request #288 from electron/allow-execution-flags
feat: allow users to specify cli flags
2020-01-13 10:43:57 -08:00
Shelley Vohr 3adeece64d spec: fix small test details 2020-01-06 10:52:07 -05:00
Shelley Vohr aed45078c5 spec: update execution settings snapshot 2020-01-06 10:39:10 -05:00
Shelley Vohr 55bfb0241b add handleExecutionFlagChange spec 2020-01-04 21:13:52 -05:00
Shelley Vohr 7e90679706 feat: allow users to specify cli flags 2020-01-04 10:46:59 -05:00
Loseshape0 4897bdbf84 Update Years 2020-01-01 17:41:48 -10:00
Mark Lee 7a02f8cacd test: avoid transforming JSON files 2019-12-27 22:49:16 -08:00
Shelley Vohr a65d594fbc refactor: directly pull Node.js builtins 2019-12-19 21:17:38 -08:00
166 changed files with 12897 additions and 10595 deletions
+31
View File
@@ -0,0 +1,31 @@
const { Octokit } = require('@octokit/action')
const octokit = new Octokit()
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
async function main () {
const prs = await octokit.pulls.list({
owner,
repo,
state: 'open',
head: `${owner}:update-releases`
})
if (prs.data.length === 0) {
const pr = await octokit.pulls.create({
owner,
repo,
title: 'build: update Electron releases JSON',
base: 'master',
head: 'update-releases',
body: 'Auto-update from GitHub Actions.',
maintainer_can_modify: true
})
console.log('Pull request created:', pr.html_url)
} else {
console.log('Pull request updated:', prs.data[0].html_url)
}
}
main()
+51
View File
@@ -0,0 +1,51 @@
name: Auto-update Releases JSON file
on:
schedule:
- cron: '0 0 * * 1'
jobs:
autoupdate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Fetch git branches
run: git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
- uses: actions/setup-node@v1
with:
node-version: '12.x'
- name: Get npm cache directory
id: npm-cache
run: |
echo "::set-output name=dir::$(npm config get cache)"
- uses: actions/cache@v1
with:
path: ${{ steps.npm-cache.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm ci
- name: Switch to release update branch
run: |
if git branch --remotes | grep -q origin/update-releases; then
git checkout update-releases
else
git checkout -b update-releases
fi
- name: Update Releases JSON
run: npm run electron-releases
- name: Commit Changes to Releases JSON
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "machine github.com login $GITHUB_ACTOR password $GITHUB_TOKEN" > ~/.netrc
chmod 600 ~/.netrc
git add static/releases.json tests/fixtures/releases-metadata.json
if test -n "$(git status -s)"; then
git config user.name "$GITHUB_ACTOR"
git config user.email "electron-bot@users.noreply.github.com"
git diff --cached
git commit -m "build: update Electron releases JSON"
git push origin update-releases
node --unhandled-rejections=strict .github/actions/create_releases_pr.js
else
echo No update needed
fi
+3
View File
@@ -31,6 +31,9 @@ yarn-error.log
# Coverage
coverage
# Test report
report.json
# npm package
/npm/dist
/npm/path.txt
+3 -4
View File
@@ -3,13 +3,12 @@ node_js: "12"
os:
- linux
- osx
dist: trusty
dist: bionic
osx_image: xcode10
sudo: false
cache:
npm: true
directories:
- node_modules
- $HOME/.cache/electron
addons:
@@ -24,7 +23,7 @@ branches:
- /^v\d+\.\d+\.\d+/
install:
- npm install
- npm ci
- |
if [[ "$TRAVIS_OS_NAME" == "osx" && "$TRAVIS_SECURE_ENV_VARS" == "true" ]]; then
export CERTIFICATE_P12=cert.p12;
+74
View File
@@ -0,0 +1,74 @@
# Contributing to Electron Fiddle
Electron Fiddle is a community-driven project, overseen by the [Electron Ecosystem Working
Group](https://github.com/electron/governance/tree/master/wg-ecosystem#readme). As such, we welcome
and encourage all sorts of contributions. They include, but are not limited to:
- Constructive feedback
- Bug reports / technical issues
- Documentation changes
- Feature requests
- [Pull requests](#filing-pull-requests)
We strongly suggest that before filing an issue, you search through the existing issues to see
if it has already been filed by someone else.
This project is a part of the Electron ecosystem. As such, all contributions to this project follow
[Electron's code of conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md)
where appropriate.
## Contribution Suggestions
We use the label [`good first issue`](https://github.com/electron/fiddle/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) in the issue tracker to denote fairly-well-scoped-out bugs or feature requests that the community can pick up and work on. If any of those labeled issues do not have enough information, please feel free to ask constructive questions. (This applies to any open issue.)
## Filing Pull Requests
Here are some things to keep in mind as you file pull requests to fix bugs, add new features, etc.:
* If you're unfamiliar with forking, branching, and pull requests, please see [GitHub's extensive
documentation](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests).
* Travis CI and AppVeyor are used to make sure that any new or changed code meets the project's
style guidelines, and that the project's testsuite passes for each new commit.
* Unless it's impractical, please write tests for your changes. This will help us so that we can
spot regressions much easier.
* When creating commit messages and pull request titles, please adhere to the [conventional
commits](https://www.conventionalcommits.org/en/v1.0.0/) standard.
* Please **do not** bump the version number in your pull requests, the maintainers will do that.
Feel free to indicate whether the changes require a major, minor, or patch version bump, as
prescribed by the [semantic versioning specification](http://semver.org/).
### Running Fiddle From Source
1. Clone the Electron Fiddle repository locally.
```sh
git clone https://github.com/electron/fiddle.git
```
2. Change directory to where Fiddle has been cloned.
```sh
cd fiddle
```
3. Install dependencies.
```sh
npm install
```
4. Start Fiddle and Explore!
```sh
npm start
```
### Running Tests
```sh
npm test
```
## Release Process
<!-- TODO @felix add your release process here 😁 -->
+2 -2
View File
@@ -1,4 +1,4 @@
Copyright (c) 2013-2018 GitHub Inc.
Copyright (c) 2013-2020 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ 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.
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+4
View File
@@ -56,6 +56,10 @@ fiddle has grown up, export it as a project with or without
[electron-forge][electron-forge]. Then, use your favorite editor and take on
the world!
## Contributing
Electron Fiddle is a community-driven project that welcomes all sorts of contributions. Please check out our [Contributing Guide](https://github.com/electron/fiddle/blob/master/CONTRIBUTING.md) for more details.
## License
[MIT, please see the LICENSE file for full details](https://github.com/electron/fiddle/blob/master/LICENSE.md).
+14 -5
View File
@@ -16,12 +16,11 @@ const config = {
executableName: 'electron-fiddle',
asar: true,
icon: path.resolve(__dirname, 'assets', 'icons', 'fiddle'),
// TODO: FIXME?
// ignore: [
// /^\/\.vscode\//,
// /^\/tools\//
// ],
appBundleId: 'com.electron.fiddle',
usageDescription: {
Camera: 'Access is needed by certain built-in fiddles in addition to any custom fiddles that use the Camera',
Microphone: 'Access is needed by certain built-in fiddles in addition to any custom fiddles that use the Microphone'
},
appCategoryType: 'public.app-category.developer-tools',
protocols: [{
name: 'Electron Fiddle Launch Protocol',
@@ -106,11 +105,21 @@ function notarizeMaybe() {
return;
}
if (process.env.npm_lifecycle_event !== 'publish') {
console.log(`Not in "publish" mode, skipping notarization`);
return;
}
if (!process.env.CI) {
console.log(`Not in CI, skipping notarization`);
return;
}
if (!process.env.TRAVIS_TAG && !process.env.NOTARIZE_WITHOUT_TAG) {
console.log(`Not a tag, not notarizing`);
return;
}
if (!process.env.APPLE_ID || !process.env.APPLE_ID_PASSWORD) {
console.warn('Should be notarizing, but environment variables APPLE_ID or APPLE_ID_PASSWORD are missing!');
return;
+5 -2
View File
@@ -1,12 +1,15 @@
{
"transform": {
".(js|jsx|ts|tsx)": "ts-jest"
"\\.(js|jsx|ts|tsx)$": "ts-jest"
},
"testURL": "http://localhost",
"testRegex": "(-spec)\\.(ts|tsx)$",
"bail": true,
"resetMocks": true,
"bail": true,
"resetModules": true,
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"setupFilesAfterEnv": [
"<rootDir>/tests/setup.js"
],
+7881 -8166
View File
File diff suppressed because it is too large Load Diff
+55 -54
View File
@@ -1,29 +1,30 @@
{
"name": "electron-fiddle",
"productName": "Electron Fiddle",
"version": "0.11.1",
"version": "0.15.1",
"description": "The easiest way to get started with Electron",
"repository": "https://github.com/electron/fiddle",
"main": "./dist/src/main/main",
"scripts": {
"contributors": "node ./tools/contributors.js",
"less": "node ./tools/lessc.js",
"lint:style": "stylelint ./src/less/*.less --fix",
"lint:style": "stylelint \"./src/less/*.less\" --fix",
"lint:ts": "tslint -c tslint.json -p tsconfig.json -e \"node_modules/**/*.ts\" --fix",
"lint:tests": "tslint ./tests/**/*.ts{,x} -c tslint.json --fix",
"lint:templates": "standard ./static/show-me/**/*.js",
"lint": "npm-run-all lint:*",
"lint:tests": "tslint \"./tests/**/*.ts{,x}\" -c tslint.json --fix",
"lint:templates": "standard \"./static/show-me/**/*.js\"",
"lint": "npm-run-all \"lint:*\"",
"make": "electron-forge make",
"package": "electron-forge package",
"parcel:build": "node ./tools/parcel-build.js",
"parcel:watch": "node ./tools/parcel-watch.js",
"publish": "electron-forge publish",
"start": "rimraf ./dist && electron-forge start",
"test": "jest --config=jest.json --coverage",
"test": "jest --config=jest.json",
"test:ci": "jest --config=jest.json --coverage --runInBand",
"test:coverage": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"tsc": "node ./tools/tsc.js",
"electron-releases": "node ./tools/fetch-releases.js"
"test:report": "jest --config=jest.json --json --bail=false --outputFile=report.json | true",
"tsc": "tsc --noEmit -p .",
"electron-releases": "node --unhandled-rejections=strict ./tools/fetch-releases.js"
},
"keywords": [
"Electron",
@@ -36,73 +37,73 @@
"forge": "./forge.config.js"
},
"dependencies": {
"@blueprintjs/core": "^3.19.1",
"@blueprintjs/select": "^3.11.1",
"@octokit/rest": "^16.34.0",
"builtin-modules": "^3.1.0",
"@blueprintjs/core": "^3.24.0",
"@blueprintjs/select": "^3.12.0",
"@electron/get": "^1.9.0",
"@octokit/rest": "^16.43.1",
"@sentry/electron": "^1.2.1",
"classnames": "^2.2.6",
"electron-default-menu": "^1.0.1",
"electron-devtools-installer": "^2.2.4",
"electron-download": "^4.1.1",
"electron-squirrel-startup": "^1.0.0",
"extract-zip": "^1.6.7",
"fix-path": "^2.1.0",
"fix-path": "^3.0.0",
"fs-extra": "^8.1.0",
"mobx": "^5.14.2",
"mobx-react": "^6.1.4",
"monaco-editor": "^0.18.1",
"mobx": "^5.15.4",
"mobx-react": "^6.1.8",
"monaco-editor": "^0.20.0",
"monaco-loader": "1.0.0",
"namor": "^1.1.3",
"react": "^16.11.0",
"react-dom": "^16.11.0",
"namor": "^2.0.2",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-mosaic-component": "^3.2.0",
"semver": "^6.3.0",
"semver": "^7.1.3",
"tmp": "0.1.0",
"tslib": "^1.10.0",
"tslib": "^1.11.1",
"update-electron-app": "^1.5.0"
},
"devDependencies": {
"@babel/core": "^7.6.4",
"@electron-forge/cli": "^6.0.0-beta.46",
"@electron-forge/maker-deb": "^6.0.0-beta.46",
"@electron-forge/maker-rpm": "^6.0.0-beta.46",
"@electron-forge/maker-squirrel": "^6.0.0-beta.46",
"@electron-forge/maker-zip": "^6.0.0-beta.46",
"@electron-forge/publisher-github": "^6.0.0-beta.46",
"@types/builtin-modules": "^3.1.1",
"@babel/core": "^7.8.6",
"@electron-forge/cli": "^6.0.0-beta.50",
"@electron-forge/maker-deb": "^6.0.0-beta.50",
"@electron-forge/maker-rpm": "^6.0.0-beta.50",
"@electron-forge/maker-squirrel": "^6.0.0-beta.50",
"@electron-forge/maker-zip": "^6.0.0-beta.50",
"@electron-forge/publisher-github": "^6.0.0-beta.50",
"@octokit/action": "^2.0.0",
"@types/classnames": "^2.2.9",
"@types/enzyme": "^3.10.3",
"@types/fs-extra": "^8.0.1",
"@types/jest": "^24.0.19",
"@types/enzyme": "^3.10.5",
"@types/fs-extra": "^8.1.0",
"@types/jest": "^25.1.3",
"@types/log-symbols": "^3.0.0",
"@types/node": "^12.11.7",
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.3",
"@types/semver": "^6.0.2",
"@types/node": "^13.7.6",
"@types/react": "^16.9.23",
"@types/react-dom": "^16.9.5",
"@types/semver": "^7.1.0",
"@types/tmp": "0.1.0",
"chokidar": "^3.2.2",
"coveralls": "^3.0.7",
"electron": "7.1.3",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"enzyme-to-json": "^3.4.3",
"jest": "^24.9.0",
"jest-fetch-mock": "^2.1.2",
"less": "^3.10.3",
"chokidar": "^3.3.1",
"coveralls": "^3.0.9",
"electron": "8.0.2",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"enzyme-to-json": "^3.4.4",
"fetch-mock-jest": "^1.1.0-beta.3",
"jest": "^25.1.0",
"less": "^3.11.1",
"log-symbols": "^3.0.0",
"node-abi": "^2.12.0",
"node-abi": "^2.15.0",
"node-fetch": "^2.6.0",
"npm-run-all": "^4.1.5",
"parcel-bundler": "^1.12.4",
"react-test-renderer": "^16.11.0",
"react-test-renderer": "^16.13.0",
"rimraf": "^3.0.0",
"standard": "^14.3.1",
"stylelint": "^11.1.1",
"stylelint-config-standard": "^19.0.0",
"ts-jest": "^24.1.0",
"tslint": "^5.20.0",
"stylelint": "^13.2.0",
"stylelint-config-standard": "^20.0.0",
"ts-jest": "^25.2.1",
"tslint": "^6.0.0",
"tslint-microsoft-contrib": "^6.2.0",
"tslint-react": "^4.1.0",
"typescript": "^3.6.4"
"tslint-react": "^4.2.0",
"typescript": "^3.8.2"
}
}
+1
View File
@@ -3,6 +3,7 @@ export const html = `<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
<link rel="stylesheet" type="text/css" href="./styles.css">
</head>
<body>
<h1>Hello World!</h1>
+2 -14
View File
@@ -3,13 +3,9 @@ const {app, BrowserWindow} = require('electron')
const path = require('path')
const url = require('url')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600})
const mainWindow = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
mainWindow.loadURL(url.format({
@@ -20,14 +16,6 @@ function createWindow () {
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
@@ -47,7 +35,7 @@ app.on('window-all-closed', function () {
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
+5 -17
View File
@@ -1,13 +1,9 @@
export const main = `// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
@@ -20,14 +16,6 @@ function createWindow () {
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
@@ -35,10 +23,10 @@ function createWindow () {
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
@@ -47,7 +35,7 @@ app.on('window-all-closed', function () {
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
+22 -11
View File
@@ -4,17 +4,18 @@ export type Files = Map<string, string>;
export type FileTransform = (files: Files) => Promise<Files>;
export enum ElectronVersionState {
export enum VersionState {
ready = 'ready',
downloading = 'downloading',
unzipping = 'unzipping',
unknown = 'unknown'
}
export enum ElectronVersionSource {
export enum VersionSource {
remote = 'remote',
local = 'local'
}
export interface NpmVersion {
export interface Version {
version: string;
name?: string;
localPath?: string;
@@ -25,12 +26,14 @@ export interface EditorValues {
renderer: string;
html: string;
preload: string;
css: string;
package?: string;
}
export interface ElectronVersion extends NpmVersion {
state: ElectronVersionState;
source: ElectronVersionSource;
export interface RunnableVersion extends Version {
state: VersionState;
source: VersionSource;
downloadProgress?: number;
}
export interface SetFiddleOptions {
@@ -50,7 +53,8 @@ export interface OutputOptions {
isNotPre?: boolean;
}
export interface WarningDialogTexts {
export interface GenericDialogOptions {
type: GenericDialogType;
ok?: string;
cancel?: string;
label: string;
@@ -60,12 +64,19 @@ export interface Templates {
[index: string]: string | Templates;
}
export const enum GenericDialogType {
'confirm' = 'confirm',
'warning' = 'warning',
'success' = 'success',
}
// Editors
export const enum EditorId {
'main' = 'main',
'renderer' = 'renderer',
'html' = 'html',
'preload' = 'preload'
'preload' = 'preload',
'css' = 'css'
}
// Panels that can show up as a mosaic
@@ -75,9 +86,9 @@ export const enum PanelId {
export type MosaicId = EditorId | PanelId;
export const ALL_EDITORS = [ EditorId.main, EditorId.renderer, EditorId.preload, EditorId.html ];
export const ALL_PANELS = [ PanelId.docsDemo ];
export const ALL_MOSAICS = [ ...ALL_EDITORS, ...ALL_PANELS ];
export const ALL_EDITORS = [EditorId.main, EditorId.renderer, EditorId.preload, EditorId.html, EditorId.css];
export const ALL_PANELS = [PanelId.docsDemo];
export const ALL_MOSAICS = [...ALL_EDITORS, ...ALL_PANELS];
export type ArrowPosition = 'top' | 'left' | 'bottom' | 'right';
+10 -2
View File
@@ -23,6 +23,10 @@ export enum IpcEvents {
LOAD_LOCAL_VERSION_FOLDER = 'LOAD_LOCAL_VERSION_FOLDER',
SHOW_LOCAL_VERSION_FOLDER_DIALOG = 'SHOW_LOCAL_VERSION_FOLDER_DIALOG',
BISECT_COMMANDS_TOGGLE = 'BISECT_COMMANDS_TOGGLE',
SHOW_INACTIVE = 'SHOW_INACTIVE',
BEFORE_QUIT = 'BEFORE_QUIT',
CONFIRM_QUIT = 'CONFIRM_QUIT',
SET_APPDATA_DIR = 'SET_APPDATA_DIR',
}
export const ipcMainEvents = [
@@ -30,7 +34,9 @@ export const ipcMainEvents = [
IpcEvents.FS_SAVE_FIDDLE,
IpcEvents.SHOW_WARNING_DIALOG,
IpcEvents.SHOW_CONFIRMATION_DIALOG,
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG,
IpcEvents.SHOW_INACTIVE,
IpcEvents.CONFIRM_QUIT,
];
export const ipcRendererEvents = [
@@ -52,7 +58,9 @@ export const ipcRendererEvents = [
IpcEvents.SHOW_WELCOME_TOUR,
IpcEvents.CLEAR_CONSOLE,
IpcEvents.LOAD_LOCAL_VERSION_FOLDER,
IpcEvents.BISECT_COMMANDS_TOGGLE
IpcEvents.BISECT_COMMANDS_TOGGLE,
IpcEvents.BEFORE_QUIT,
IpcEvents.SET_APPDATA_DIR,
];
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL = 'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
+15
View File
@@ -24,6 +24,21 @@
}
}
// update if list of Editor ID changes
@editor-ids: main, renderer, html, preload;
// takes each editor ID and increase its z-index if the parent Mosiac root
// has focused__id class for that specific id.
each(@editor-ids, {
.focused__@{value}.mosaic .mosaic-window.@{value} {
z-index: 2;
}
});
.mosaic-window {
z-index: 1;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
+5
View File
@@ -0,0 +1,5 @@
.bp3-fill {
.version-chooser {
width: 100%;
}
}
+1
View File
@@ -17,3 +17,4 @@
@import "components/editors.less";
@import "components/tour.less";
@import "components/show-me.less";
@import "components/version-select.less";
+1
View File
@@ -20,6 +20,7 @@ export class IpcMainManager extends EventEmitter {
super();
ipcMainEvents.forEach((name) => {
ipcMain.removeAllListeners(name);
ipcMain.on(name, (...args: Array<any>) => this.emit(name, ...args));
});
+13 -1
View File
@@ -1,10 +1,15 @@
import { initSentry } from '../sentry';
initSentry();
import { app } from 'electron';
import { IpcEvents } from '../ipc-events';
import { isDevMode } from '../utils/devmode';
import { setupAboutPanel } from '../utils/set-about-panel';
import { setupDevTools } from './devtools';
import { setupDialogs } from './dialogs';
import { onFirstRunMaybe } from './first-run';
import { ipcMainManager } from './ipc';
import { listenForProtocolHandler, setupProtocolHandler } from './protocol';
import { shouldQuit } from './squirrel';
import { setupUpdates } from './update';
@@ -38,7 +43,14 @@ export async function onReady() {
* @export
*/
export function onBeforeQuit() {
(global as any).isQuitting = true;
ipcMainManager.send(IpcEvents.BEFORE_QUIT);
ipcMainManager.on(IpcEvents.CONFIRM_QUIT, quitAppIfConfirmed);
}
export function quitAppIfConfirmed(_: Electron.IpcMainEvent, quitConfirmed: boolean) {
if (quitConfirmed) {
app.quit();
}
}
/**
+5 -5
View File
@@ -105,9 +105,7 @@ function getQuitItems(): Array<MenuItemConstructorOptions> {
{
type: 'separator'
}, {
label: 'Exit',
accelerator: 'Ctrl+Q,',
click: app.quit
role: 'quit'
}
];
}
@@ -239,7 +237,7 @@ export function setupMenu() {
// Append the "Settings" item
if (
process.platform === 'darwin'
&& label === app.getName()
&& label === app.name
&& isSubmenu(item.submenu)
) {
item.submenu.splice(2, 0, ...getPreferencesItems());
@@ -247,7 +245,9 @@ export function setupMenu() {
// Tweak "View" menu
if (label === 'View' && isSubmenu(item.submenu)) {
item.submenu = item.submenu.filter((subItem) => subItem.label !== 'Toggle Developer Tools'); // Remove "Toggle Developer Tools"
// remove "Reload" (has weird behaviour) and "Toggle Developer Tools"
item.submenu = item.submenu
.filter((subItem) => subItem.label !== 'Toggle Developer Tools' && subItem.label !== 'Reload');
item.submenu.push({ type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' }); // Add zooming actions
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Soft Wrap',
+1
View File
@@ -76,6 +76,7 @@ export const listenForProtocolHandler = () => {
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) app.quit();
app.removeAllListeners('open-url');
app.on('open-url', (_, url) => {
if (url.startsWith(`${PROTOCOL}://`)) {
handlePotentialProtocolLaunch(url);
+19 -5
View File
@@ -1,9 +1,11 @@
import { BrowserWindow, shell } from 'electron';
import { app, BrowserWindow, shell } from 'electron';
import { IpcEvents } from '../ipc-events';
import { createContextMenu } from './context-menu';
import { ipcMainManager } from './ipc';
// Keep a global reference of the window objects, if we don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
export let browserWindows: Array<BrowserWindow> = [];
export let browserWindows: Array<BrowserWindow | null> = [];
/**
* Gets default options for the main window
@@ -35,13 +37,14 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
*/
export function createMainWindow(): Electron.BrowserWindow {
console.log(`Creating main window`);
const browserWindow = new BrowserWindow(getMainWindowOptions());
let browserWindow: BrowserWindow | null;
browserWindow = new BrowserWindow(getMainWindowOptions());
browserWindow.loadFile('./dist/static/index.html');
browserWindow.webContents.once('dom-ready', () => {
browserWindow.show();
if (browserWindow) {
browserWindow.show();
createContextMenu(browserWindow);
}
});
@@ -49,6 +52,8 @@ export function createMainWindow(): Electron.BrowserWindow {
browserWindow.on('closed', () => {
browserWindows = browserWindows
.filter((bw) => browserWindow !== bw);
browserWindow = null;
});
browserWindow.webContents.on('new-window', (event, url) => {
@@ -61,6 +66,15 @@ export function createMainWindow(): Electron.BrowserWindow {
shell.openExternal(url);
});
ipcMainManager.on(IpcEvents.SHOW_INACTIVE, () => {
if (browserWindow) {
browserWindow.showInactive();
}
});
const appData = app.getPath('appData');
ipcMainManager.send(IpcEvents.SET_APPDATA_DIR, [appData], browserWindow.webContents);
browserWindows.push(browserWindow);
return browserWindow;
+36 -34
View File
@@ -1,3 +1,6 @@
import { initSentry } from '../sentry';
initSentry();
import { when } from 'mobx';
import * as MonacoType from 'monaco-editor';
@@ -6,6 +9,7 @@ import {
ALL_EDITORS,
EditorId,
EditorValues,
GenericDialogType,
SetFiddleOptions
} from '../interfaces';
import { WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
@@ -14,6 +18,7 @@ import { getEditorValue } from '../utils/editor-value';
import { getPackageJson, PackageJsonOptions } from '../utils/get-package';
import { getTitle } from '../utils/get-title';
import { isEditorBackup } from '../utils/type-checks';
import { EMPTY_EDITOR_CONTENT } from './constants';
import { FileManager } from './file-manager';
import { RemoteLoader } from './remote-loader';
import { Runner } from './runner';
@@ -51,20 +56,30 @@ export class App {
) {
// if unsaved, prompt user to make sure they're okay with overwriting and changing directory
if (this.state.isUnsaved) {
this.state.setWarningDialogTexts({
this.state.setGenericDialogOptions({
type: GenericDialogType.warning,
label: `Opening this Fiddle will replace your unsaved changes. Do you want to proceed?`,
ok: 'Yes'
});
this.state.isWarningDialogShowing = true;
await when(() => !this.state.isWarningDialogShowing);
this.state.isGenericDialogShowing = true;
await when(() => !this.state.isGenericDialogShowing);
if (!this.state.warningDialogLastResult) {
if (!this.state.genericDialogLastResult) {
return false;
}
}
// set values once prompt approves
await this.setEditorValues(editorValues);
// display all editors that have content
const visibleEditors = [];
for (const id of Object.keys(editorValues)) {
const content = editorValues[id];
// if the gist content matches the empty file output, don't show it
if (!Object.values(EMPTY_EDITOR_CONTENT).includes(content)) {
visibleEditors.push(id as EditorId);
}
}
document.title = getTitle(this.state);
this.state.gistId = gistId || '';
@@ -72,17 +87,17 @@ export class App {
this.state.templateName = templateName;
// once loaded, we have a "saved" state
await this.state.setVisibleMosaics(visibleEditors);
await this.setEditorValues(editorValues);
this.state.isUnsaved = false;
this.setupUnsavedOnChangeListener();
return true;
}
/**
* Sets the values on all three editors.
* Sets the contents of all editor panes.
*
* @param {EditorValues} values
* @param {warn} warn - Should we warn before overwriting unsaved data?
*/
public async setEditorValues(values: Partial<EditorValues>): Promise<void> {
const { ElectronFiddle: fiddle } = window;
@@ -96,9 +111,16 @@ export class App {
const backup = this.state.closedPanels[name];
if (typeof values[name] !== 'undefined') {
if (isEditorBackup(backup) && backup.model) {
// The editor does not exist, attempt to set it on the backup
backup.model.setValue(values[name]!);
if (isEditorBackup(backup)) {
// The editor does not exist, attempt to set it on the backup.
// If there's a model, we'll do it on the model. Else, we'll
// set the value.
if (backup.model) {
backup.model.setValue(values[name]!);
} else {
backup.value = values[name]!;
}
} else if (editor && editor.setValue) {
// The editor exists, set the value directly
editor.setValue(values[name]!);
@@ -108,7 +130,7 @@ export class App {
}
/**
* Gets the values on all three editors.
* Retrieves the contents of all editor panes.
*
* @returns {EditorValues}
*/
@@ -122,6 +144,7 @@ export class App {
}
const values: EditorValues = {
css: getEditorValue(EditorId.css),
html: getEditorValue(EditorId.html),
main: getEditorValue(EditorId.main),
preload: getEditorValue(EditorId.preload),
@@ -165,30 +188,9 @@ export class App {
ipcRenderer.send(WEBCONTENTS_READY_FOR_IPC_SIGNAL);
// TODO: A timer here is terrible. Let's fix this
// and ensure we actually do it once Editors have mounted.
setTimeout(() => {
this.setupUnsavedOnChangeListener();
}, 1500);
return rendered;
}
/**
* If the editor is changed for the first time, we'll
* set `isUnsaved` to true. That way, the app can warn you
* if you're about to throw things away.
*/
public setupUnsavedOnChangeListener() {
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
const editor = window.ElectronFiddle.editors[key];
const disposable = editor.onDidChangeModelContent(() => {
this.state.isUnsaved = true;
disposable.dispose();
});
});
}
/**
* Loads theme CSS into the HTML document.
*
+227 -204
View File
@@ -1,236 +1,259 @@
import * as fsType from 'fs-extra';
import * as path from 'path';
import { VersionState } from '../interfaces';
import { fancyImport } from '../utils/import';
import { normalizeVersion } from '../utils/normalize-version';
import { USER_DATA_PATH } from './constants';
import { getOfflineTypeDefinitionPath } from './fetch-types';
import { removeTypeDefsForVersion } from './fetch-types';
import { AppState } from './state';
/**
* The binary manager takes care of downloading Electron versions
* General setup, called with a version. Is called during construction
* to ensure that we always have or download at least one version.
*
* @export
* @class BinaryManager
* @param {string} iVersion
* @returns {Promise<void>}
*/
export class BinaryManager {
public state: Record<string, 'ready' | 'downloading'> = {};
export async function setupBinary(appState: AppState, iVersion: string): Promise<void> {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
/**
* Remove a version from disk. Does not update state. We'll try up to
* four times before giving up if an error occurs.
*
* @param {string} iVersion
* @returns {Promise<void>}
*/
public async remove(iVersion: string): Promise<void> {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
let isDeleted = false;
await fs.mkdirp(getDownloadPath(version));
// utility to re-run removal functions upon failure
// due to windows filesystem lockfile jank
const rerunner = async (func: () => Promise<void>, counter: number = 1) => {
try {
await func();
} catch (error) {
console.warn(`Binary Manager: failed to run ${func.name} for ${version}, but failed`, error);
if (counter < 4) {
console.log(`Binary Manager: Trying again to run ${func.name}`);
await rerunner(func, counter + 1);
}
}
};
const binaryCleaner = async () => {
if (await this.getIsDownloaded(version)) {
// This is necessary since we're messing with .asar files inside
// the Electron binaries. Electron, powering Fiddle, will try to
// "correct" our calls, but we don't want that right here.
process.noAsar = true;
await fs.remove(this.getDownloadPath(version));
process.noAsar = false;
isDeleted = true;
}
};
const typeDefsCleaner = async () => {
await this.removeTypeDefsForVersion(version);
};
await rerunner(binaryCleaner);
if (isDeleted) {
await rerunner(typeDefsCleaner);
}
const { state } = appState.versions[version];
if (state === VersionState.downloading || state === VersionState.unzipping) {
console.log(`Binary: Electron ${version} already downloading.`);
return;
}
/**
* General setup, called with a version. Is called during construction
* to ensure that we always have or download at least one version.
*
* @param {string} iVersion
* @returns {Promise<void>}
*/
public async setup(iVersion: string): Promise<void> {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
const { promisify } = await import('util');
const eDownload = promisify(require('electron-download'));
if (await getIsDownloaded(version)) {
console.log(`Binary: Electron ${version} already downloaded.`);
appState.versions[version].state = VersionState.ready;
return;
}
await fs.mkdirp(this.getDownloadPath(version));
console.log(`Binary: Electron ${version} not present, downloading`);
appState.versions[version].state = VersionState.downloading;
if (this.state[version] === 'downloading') {
console.log(`BinaryManager: Electron ${version} already downloading.`);
return;
}
const zipPath = await download(appState, version);
const extractPath = getDownloadPath(version);
console.log(`Binary: Electron ${version} downloaded, now unpacking to ${extractPath}`);
if (await this.getIsDownloaded(version)) {
console.log(`BinaryManager: Electron ${version} already downloaded.`);
this.state[version] = 'ready';
return;
}
try {
appState.versions[version].state = VersionState.unzipping;
console.log(`BinaryManager: Electron ${version} not present, downloading`);
this.state[version] = 'downloading';
// Ensure the target path is empty
await fs.emptyDir(extractPath);
const zipPath = await eDownload({ version });
const extractPath = this.getDownloadPath(version);
console.log(`BinaryManager: Electron ${version} downloaded, now unpacking to ${extractPath}`);
const electronFiles = await unzip(zipPath, extractPath);
console.log(`Unzipped ${version}`, electronFiles);
} catch (error) {
console.warn(`Failure while unzipping ${version}`, error);
appState.versions[version].state = VersionState.unknown;
return;
}
appState.versions[version].state = VersionState.ready;
}
/**
* Remove a version from disk. Does not update state. We'll try up to
* four times before giving up if an error occurs.
*
* @param {string} iVersion
* @returns {Promise<void>}
*/
export async function removeBinary(iVersion: string) {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
let isDeleted = false;
// utility to re-run removal functions upon failure
// due to windows filesystem lockfile jank
const rerunner = async (func: () => Promise<void>, counter: number = 1) => {
try {
// Ensure the target path is empty
await fs.emptyDir(extractPath);
const electronFiles = await this.unzip(zipPath, extractPath);
console.log(`Unzipped ${version}`, electronFiles);
await func();
} catch (error) {
console.warn(`Failure while unzipping ${version}`, error);
// TODO: Handle this case
}
this.state[version] = 'ready';
}
/**
* Gets the expected path for the binary of a given Electron version
*
* @param {string} version
* @param {string} dir
* @returns {string}
*/
public getElectronBinaryPath(
version: string,
dir: string = this.getDownloadPath(version),
): string {
switch (process.platform) {
case 'darwin':
return path.join(dir, 'Electron.app/Contents/MacOS/Electron');
case 'freebsd':
case 'linux':
return path.join(dir, 'electron');
case 'win32':
return path.join(dir, 'electron.exe');
default:
throw new Error(`Electron builds are not available for ${process.platform}`);
}
}
/**
* Returns an array of all versions downloaded to disk
*
* @returns {Promise<Array<string>>}
*/
public async getDownloadedVersions(): Promise<Array<string>> {
const fs = await fancyImport<typeof fsType>('fs-extra');
const downloadPath = path.join(USER_DATA_PATH, 'electron-bin');
console.log(`BinaryManager: Checking for downloaded versions`);
try {
const directories = await fs.readdir(downloadPath);
const knownVersions: Array<string> = [];
for (const directory of directories) {
if (await this.getIsDownloaded(directory)) {
knownVersions.push(directory);
}
}
return knownVersions;
} catch (error) {
console.warn(`Could not read known Electron versions`);
return [];
}
}
/**
* Did we already download a given version?
*
* @param {string} version
* @param {string} dir
* @returns {boolean}
*/
public async getIsDownloaded(version: string, dir?: string): Promise<boolean> {
const expectedPath = this.getElectronBinaryPath(version, dir);
const fs = await fancyImport<typeof fsType>('fs-extra');
return fs.existsSync(expectedPath);
}
public async removeTypeDefsForVersion(version: string) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const _version = normalizeVersion(version);
const typeDefsDir = path.dirname(getOfflineTypeDefinitionPath(_version));
if (fs.existsSync(typeDefsDir)) {
try {
await fs.remove(typeDefsDir);
} catch (error) {
throw error;
console.warn(`Binary Manager: failed to run ${func.name} for ${version}, but failed`, error);
if (counter < 4) {
console.log(`Binary Manager: Trying again to run ${func.name}`);
await rerunner(func, counter + 1);
}
}
}
/**
* Gets the expected path for a given Electron version
*
* @param {string} version
* @returns {string}
*/
private getDownloadPath(version: string): string {
return path.join(USER_DATA_PATH, 'electron-bin', version);
}
/**
* Unzips an electron package so that we can actually use it.
*
* @param {string} zipPath
* @param {string} extractPath
* @returns {Promise<void>}
*/
private unzip(zipPath: string, extractPath: string): Promise<void> {
return new Promise(async (resolve, reject) => {
const extract = (await fancyImport<any>('extract-zip')).default;
};
const binaryCleaner = async () => {
if (await getIsDownloaded(version)) {
// This is necessary since we're messing with .asar files inside
// the Electron binaries. Electron, powering Fiddle, will try to
// "correct" our calls, but we don't want that right here.
process.noAsar = true;
await fs.remove(getDownloadPath(version));
process.noAsar = false;
const options = {
dir: extractPath,
};
isDeleted = true;
}
};
extract(zipPath, options, (error: Error) => {
if (error) {
reject(error);
return;
}
const typeDefsCleaner = async () => {
await removeTypeDefsForVersion(version);
};
console.log(`BinaryManager: Unpacked!`);
process.noAsar = false;
await rerunner(binaryCleaner);
resolve();
});
});
if (isDeleted) {
await rerunner(typeDefsCleaner);
}
}
/* Did we already download a given version?
*
* @param {string} version
* @param {string} dir
* @returns {boolean}
*/
export async function getIsDownloaded(version: string, dir?: string): Promise<boolean> {
const expectedPath = getElectronBinaryPath(version, dir);
const fs = await fancyImport<typeof fsType>('fs-extra');
return fs.existsSync(expectedPath);
}
/**
* Gets the expected path for the binary of a given Electron version
*
* @param {string} version
* @param {string} dir
* @returns {string}
*/
export function getElectronBinaryPath(
version: string,
dir: string = getDownloadPath(version),
): string {
switch (process.platform) {
case 'darwin':
return path.join(dir, 'Electron.app/Contents/MacOS/Electron');
case 'freebsd':
case 'linux':
return path.join(dir, 'electron');
case 'win32':
return path.join(dir, 'electron.exe');
default:
throw new Error(`Electron builds are not available for ${process.platform}`);
}
}
export function getDownloadingVersions(appState: AppState) {
return Object.entries(appState.versions)
.filter(([_, { state }]) => state === 'downloading')
.map(([version, _]) => version);
}
/**
* Returns an array of all versions downloaded to disk
*
* @returns {Promise<Array<string>>}
*/
export async function getDownloadedVersions(): Promise<Array<string>> {
const fs = await fancyImport<typeof fsType>('fs-extra');
const downloadPath = path.join(USER_DATA_PATH, 'electron-bin');
console.log(`Binary: Checking for downloaded versions`);
try {
const directories = await fs.readdir(downloadPath);
const knownVersions: Array<string> = [];
for (const directory of directories) {
if (await getIsDownloaded(directory)) {
knownVersions.push(directory);
}
}
return knownVersions;
} catch (error) {
console.warn(`Could not read known Electron versions`);
return [];
}
}
/**
* Download an Electron version.
*
* @param {AppState} appState
* @param {string} version
* @returns {Promise<string>}
*/
async function download(appState: AppState, version: string): Promise<string> {
const { download: electronDownload } = await import('@electron/get');
const getProgressCallback = (progress: Progress) => {
const roundedProgress = Math.round(progress.percent * 100) / 100;
if (roundedProgress !== appState.versions[version].downloadProgress) {
console.debug(`Binary: Version ${version} download progress: ${progress.percent}`);
appState.versions[version].downloadProgress = roundedProgress;
}
};
if (!appState.versions[version]) {
throw new Error(`Version ${version} does not exist in state, cannot download`);
}
const zipFilePath = await electronDownload(version, {
downloadOptions: {
quiet: true,
getProgressCallback
}
});
return zipFilePath;
}
/**
* Gets the expected path for a given Electron version
*
* @param {string} version
* @returns {string}
*/
function getDownloadPath(version: string): string {
return path.join(USER_DATA_PATH, 'electron-bin', version);
}
/**
* Unzips an electron package so that we can actually use it.
*
* @param {string} zipPath
* @param {string} extractPath
* @returns {Promise<void>}
*/
function unzip(zipPath: string, extractPath: string): Promise<void> {
return new Promise(async (resolve, reject) => {
const extract = (await fancyImport<any>('extract-zip')).default;
process.noAsar = true;
const options = {
dir: extractPath,
};
extract(zipPath, options, (error: Error) => {
if (error) {
reject(error);
return;
}
console.log(`Binary: Unpacked!`);
process.noAsar = false;
resolve();
});
});
}
interface Progress {
percent: number;
transferred: number;
total: number;
}
+3 -3
View File
@@ -1,12 +1,12 @@
import { ElectronVersion } from '../interfaces';
import { RunnableVersion } from '../interfaces';
export class Bisector {
public revList: Array<ElectronVersion>;
public revList: Array<RunnableVersion>;
public minRev: number;
public maxRev: number;
private pivot: number;
constructor(revList: Array<ElectronVersion>) {
constructor(revList: Array<RunnableVersion>) {
this.getCurrentVersion = this.getCurrentVersion.bind(this);
this.continue = this.continue.bind(this);
this.calculatePivot = this.calculatePivot.bind(this);
+18 -1
View File
@@ -2,6 +2,7 @@ import { Button } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { GenericDialogType, VersionState } from '../../../src/interfaces';
import { AppState } from '../state';
interface BisectHandlerProps {
@@ -18,12 +19,25 @@ export class BisectHandler extends React.Component<BisectHandlerProps> {
}
public continueBisect(isGood: boolean) {
window.ElectronFiddle.app.runner.stop();
const { appState } = this.props;
const response = appState.Bisector!.continue(isGood);
if (Array.isArray(response)) {
this.terminateBisect();
const [minRev, maxRev] = response;
appState.pushOutput(`[BISECT] Complete: Check between versions ${minRev.version} and ${maxRev.version}.`);
const [minVer, maxVer] = [minRev.version, maxRev.version];
const message = `Check between versions ${minVer} and ${maxVer}.`;
appState.pushOutput(`[BISECT] Complete: ${message}`);
appState.setGenericDialogOptions({
type: GenericDialogType.success,
label: `Bisect complete. ${message}`,
cancel: undefined
});
appState.isGenericDialogShowing = true;
} else {
appState.setVersion(response.version);
}
@@ -37,15 +51,18 @@ export class BisectHandler extends React.Component<BisectHandlerProps> {
public render() {
const { appState } = this.props;
if (!!appState.Bisector) {
const isDownloading = appState.currentElectronVersion.state === VersionState.downloading;
return (
<>
<Button
icon={'thumbs-up'}
onClick={() => this.continueBisect(true)}
disabled={isDownloading}
/>
<Button
icon={'thumbs-down'}
onClick={() => this.continueBisect(false)}
disabled={isDownloading}
/>
<Button
icon={'cross'}
+14 -1
View File
@@ -1,4 +1,4 @@
import { Button, Menu, MenuItem, Popover, Position } from '@blueprintjs/core';
import { Button, Menu, MenuDivider, MenuItem, Popover, Position } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -77,10 +77,23 @@ export class EditorDropdown extends React.Component<EditorDropdownProps, EditorD
text={TITLE_MAP[id]}
id={id}
onClick={this.onItemClick}
disabled={appState.mosaicArrangement === id} // can't hide last editor panel
/>
);
}
result.push(
<React.Fragment key={'fragment-reset-layout'}>
<MenuDivider />
<MenuItem
icon='grid-view'
key='reset-layout'
text='Reset Layout'
onClick={appState.resetEditorLayout}
/>
</React.Fragment>
);
return result;
}
@@ -1,11 +1,13 @@
import { Button, ButtonGroup, Menu, MenuItem, Popover, Position } from '@blueprintjs/core';
import { Button, ButtonGroup, IToastProps, Menu, MenuItem, Popover, Position, Toaster } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { when } from 'mobx';
import { EditorValues } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../shared-constants';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME, STYLES_CSS_NAME } from '../../shared-constants';
import { getOctokit } from '../../utils/octokit';
import { EMPTY_EDITOR_CONTENT } from '../constants';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
@@ -13,6 +15,11 @@ export interface PublishButtonProps {
appState: AppState;
}
interface IPublishButtonState {
readonly isUpdating: boolean;
readonly wouldPublish: boolean;
}
/**
* The "publish" button takes care of logging you in.
*
@@ -21,19 +28,33 @@ export interface PublishButtonProps {
* @extends {React.Component<PublishButtonProps, PublishButtonState>}
*/
@observer
export class PublishButton extends React.Component<PublishButtonProps> {
constructor(props: PublishButtonProps) {
export class PublishButton extends React.Component<PublishButtonProps, IPublishButtonState> {
public constructor(props: PublishButtonProps) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.publishFiddle = this.publishFiddle.bind(this);
this.publishOrUpdateFiddle = this.publishOrUpdateFiddle.bind(this);
this.setPrivate = this.setPrivate.bind(this);
this.setPublic = this.setPublic.bind(this);
this.state = {
isUpdating: false,
wouldPublish: false,
};
}
private toaster: Toaster;
private refHandlers = {
toaster: (ref: Toaster) => this.toaster = ref,
};
public componentDidMount() {
ipcRendererManager.on(IpcEvents.FS_SAVE_FIDDLE_GIST, this.handleClick);
}
public componentWillUnmount() {
ipcRendererManager.off(IpcEvents.FS_SAVE_FIDDLE_GIST, this.handleClick);
}
/**
* When the user clicks the publish button, we either show the
* authentication dialog or publish right away.
@@ -56,7 +77,7 @@ export class PublishButton extends React.Component<PublishButtonProps> {
await when(() => !!appState.gitHubToken || !appState.isTokenDialogShowing);
if (appState.gitHubToken) {
return this.publishFiddle();
return this.publishOrUpdateFiddle();
}
}
@@ -64,44 +85,52 @@ export class PublishButton extends React.Component<PublishButtonProps> {
* Connect with GitHub, publish the current Fiddle as a gist,
* and update all related properties in the app state.
*/
public async publishFiddle(): Promise<void> {
public async publishOrUpdateFiddle(): Promise<void> {
const { appState } = this.props;
const { wouldPublish } = this.state;
appState.isPublishing = true;
const octo = await getOctokit(this.props.appState);
const { gitHubPublishAsPublic } = this.props.appState;
const { gitHubPublishAsPublic, gistId } = this.props.appState;
const options = { includeDependencies: true, includeElectron: true };
const values = await window.ElectronFiddle.app.getEditorValues(options);
try {
const gist = await octo.gists.create({
public: !!gitHubPublishAsPublic,
description: 'Electron Fiddle Gist',
files: {
[INDEX_HTML_NAME]: {
content: values.html || '<!-- Empty -->',
},
[MAIN_JS_NAME]: {
content: values.main || '// Empty',
},
[RENDERER_JS_NAME]: {
content: values.renderer || '// Empty',
},
},
} as any); // Note: GitHub messed up, GistsCreateParamsFiles is an incorrect interface
if (gistId && !wouldPublish) {
this.setState({
isUpdating: true
});
const gist = await octo.gists.update({
gist_id: appState.gistId,
files: this.gistFilesList(values) as any,
});
appState.gistId = gist.data.id;
console.log('Updating: Updating done', { gist });
this.renderToast({ message: 'Successfully updated gist!' });
this.setState({
isUpdating: false
});
} else {
try {
const gist = await octo.gists.create({
public: !!gitHubPublishAsPublic,
description: 'Electron Fiddle Gist',
files: this.gistFilesList(values) as any, // Note: GitHub messed up, GistsCreateParamsFiles is an incorrect interface
});
console.log(`Publish Button: Publishing done`, { gist });
} catch (error) {
console.warn(`Could not publish gist`, { error });
appState.gistId = gist.data.id;
const messageBoxOptions: Electron.MessageBoxOptions = {
message: 'Publishing Fiddle to GitHub failed. Are you connected to the Internet?',
detail: `GitHub encountered the following error: ${error.message}`
};
console.log(`Publish Button: Publishing done`, { gist });
this.renderToast({ message: 'Publishing done successfully!' });
} catch (error) {
console.warn(`Could not publish gist`, { error });
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
const messageBoxOptions: Electron.MessageBoxOptions = {
message: 'Publishing Fiddle to GitHub failed. Are you connected to the Internet?',
detail: `GitHub encountered the following error: ${error.message}`
};
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
}
}
appState.isPublishing = false;
@@ -126,8 +155,77 @@ export class PublishButton extends React.Component<PublishButtonProps> {
}
public render() {
const { gitHubPublishAsPublic } = this.props.appState;
const { isPublishing } = this.props.appState;
const { isPublishing, gistId } = this.props.appState;
const { isUpdating, wouldPublish } = this.state;
const getTextForButton = gistId && !wouldPublish
? 'Update'
: isUpdating
? 'Updating...'
: isPublishing
? 'Publishing...'
: 'Publish';
return (
<>
<fieldset disabled={isPublishing}>
<ButtonGroup className='button-publish'>
{this.renderPrivaryMenu()}
<Button
onClick={this.handleClick}
loading={isPublishing}
icon='upload'
text={getTextForButton}
/>
{this.renderMaybePublishMenu()}
</ButtonGroup>
</fieldset>
<Toaster position={Position.BOTTOM_RIGHT} ref={this.refHandlers.toaster} />
</>
);
}
private renderMaybePublishMenu = () => {
const { gistId } = this.props.appState;
const { wouldPublish } = this.state;
if (!gistId) {
return null;
}
const menu = (
<Menu>
<MenuItem
text='Publish'
active={wouldPublish}
onClick={() => this.setWouldPublish(true)}
/>
<MenuItem
text='Update'
active={!wouldPublish}
onClick={() => this.setWouldPublish(false)}
/>
</Menu>
);
return (
<Popover
content={menu}
position={Position.BOTTOM}
>
<Button
icon='wrench'
/>
</Popover>
);
}
private renderPrivaryMenu = () => {
const { gitHubPublishAsPublic, gistId } = this.props.appState;
if (gistId) {
return null;
}
const privacyIcon = gitHubPublishAsPublic ? 'unlock' : 'lock';
const privacyMenu = (
@@ -148,28 +246,48 @@ export class PublishButton extends React.Component<PublishButtonProps> {
);
return (
<fieldset disabled={isPublishing}>
<ButtonGroup className='button-publish'>
<Popover
content={privacyMenu}
position={Position.BOTTOM}
>
<Button
icon={privacyIcon}
/>
</Popover>
<Button
onClick={this.handleClick}
loading={isPublishing}
icon='upload'
text={isPublishing ? 'Publishing...' : 'Publish'}
/>
</ButtonGroup>
</fieldset>
<Popover
content={privacyMenu}
position={Position.BOTTOM}
>
<Button
icon={privacyIcon}
/>
</Popover>
);
}
private setWouldPublish = (wouldPublish: boolean) => {
this.setState({
wouldPublish,
});
}
private setPrivacy(publishAsPublic: boolean) {
this.props.appState.gitHubPublishAsPublic = publishAsPublic;
}
private renderToast = (toast: IToastProps) => {
this.toaster.show(toast);
}
private gistFilesList = (values: EditorValues) => {
return {
[INDEX_HTML_NAME]: {
content: values.html || EMPTY_EDITOR_CONTENT.html,
},
[MAIN_JS_NAME]: {
content: values.main || EMPTY_EDITOR_CONTENT.js,
},
[RENDERER_JS_NAME]: {
content: values.renderer || EMPTY_EDITOR_CONTENT.js,
},
[PRELOAD_JS_NAME]: {
content: values.preload || EMPTY_EDITOR_CONTENT.js,
},
[STYLES_CSS_NAME]: {
content: values.css || EMPTY_EDITOR_CONTENT.css,
},
};
}
}
+9 -6
View File
@@ -2,7 +2,7 @@ import { Button, IButtonProps, Spinner } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersionState } from '../../interfaces';
import { VersionState } from '../../interfaces';
import { AppState } from '../state';
export interface RunnerState {
@@ -25,13 +25,17 @@ export class Runner extends React.Component<RunnerProps, RunnerState> {
const { isRunning, currentElectronVersion } = this.props.appState;
const state = currentElectronVersion && currentElectronVersion.state;
const props: IButtonProps = { className: 'button-run' };
const props: IButtonProps = { className: 'button-run', disabled: true };
if (state === ElectronVersionState.downloading) {
if (state === VersionState.downloading) {
props.text = 'Downloading';
props.disabled = true;
props.icon = <Spinner size={16} value={currentElectronVersion?.downloadProgress} />;
} else if (state === VersionState.unzipping) {
props.text = 'Unzipping';
props.icon = <Spinner size={16} />;
} else if (state === ElectronVersionState.ready) {
} else if (state === VersionState.ready) {
props.disabled = false;
if (isRunning) {
props.active = true;
props.text = 'Stop';
@@ -44,7 +48,6 @@ export class Runner extends React.Component<RunnerProps, RunnerState> {
}
} else {
props.text = 'Checking status';
props.disabled = true;
props.icon = <Spinner size={16} />;
}
@@ -1,172 +1,29 @@
import { Button, ButtonGroup, MenuItem } from '@blueprintjs/core';
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
import { ButtonGroup } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../interfaces';
import { highlightText } from '../../utils/highlight-text';
import { sortedElectronMap } from '../../utils/sorted-electron-map';
import { AppState } from '../state';
import { getReleaseChannel } from '../versions';
const ElectronVersionSelect = Select.ofType<ElectronVersion>();
/**
* Helper method: Returns the <Select /> label for an Electron
* version.
*
* @param {ElectronVersion} { source, state }
* @returns {string}
*/
export function getItemLabel({ source, state, name }: ElectronVersion): string {
let label = '';
if (source === ElectronVersionSource.local) {
label = name || 'Local';
} else {
if (state === ElectronVersionState.unknown) {
label = `Not downloaded`;
} else if (state === ElectronVersionState.ready) {
label = `Downloaded`;
} else if (state === ElectronVersionState.downloading) {
label = `Downloading`;
}
}
return label;
}
/**
* Helper method: Returns the <Select /> icon for an Electron
* version.
*
* @param {ElectronVersion} { state }
* @returns
*/
export function getItemIcon({ state }: ElectronVersion) {
return state === 'ready'
? 'saved'
: state === 'downloading' ? 'cloud-download' : 'cloud';
}
/**
* Helper method: Returns the <Select /> predicate for an Electron
* version.
*
* @param {string} query
* @param {ElectronVersion} { version }
* @returns
*/
export const filterItem: ItemPredicate<ElectronVersion> = (query, { version }) => {
return version.toLowerCase().includes(query.toLowerCase());
};
/**
* Helper method: Returns the <Select /> <MenuItem /> for Electron
* versions.
*
* @param {ElectronVersion} item
* @param {IItemRendererProps} { handleClick, modifiers, query }
* @returns
*/
export const renderItem: ItemRenderer<ElectronVersion> = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
}
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
text={highlightText(item.version, query)}
key={item.version}
onClick={handleClick}
label={getItemLabel(item)}
icon={getItemIcon(item)}
/>
);
};
export interface VersionChooserState {
value: string;
}
import { VersionSelect } from './version-select';
export interface VersionChooserProps {
appState: AppState;
}
export const getVersionsFromAppState = (appState: AppState) => {
const { versions, versionsToShow, statesToShow } = appState;
return sortedElectronMap<ElectronVersion>(versions, (_key, item) => item)
.filter((item) => {
if (!item) {
return false;
}
// Check if we want to show the version
if (!versionsToShow.includes(getReleaseChannel(item))) {
return false;
}
// Check if we want to show the state
if (!statesToShow.includes(item.state)) {
return false;
}
return true;
});
};
/**
* A dropdown allowing the selection of Electron versions. The actual
* download is managed in the state.
*
* @class VersionChooser
* @extends {React.Component<VersionChooserProps, VersionChooserState>}
*/
@observer
export class VersionChooser extends React.Component<VersionChooserProps, VersionChooserState> {
constructor(props: VersionChooserProps) {
super(props);
export const VersionChooser = observer((props: VersionChooserProps) => {
const { currentElectronVersion, Bisector, setVersion } = props.appState;
this.onItemSelect = this.onItemSelect.bind(this);
}
/**
* Handle change, which usually means that we'd like update
* the selection version.
*
* @param {React.ChangeEvent<HTMLSelectElement>} event
*/
public onItemSelect({ version }: ElectronVersion) {
this.props.appState.setVersion(version);
}
public render() {
const { currentElectronVersion, Bisector } = this.props.appState;
const { version } = currentElectronVersion;
return (
<ButtonGroup>
<ElectronVersionSelect
filterable={true}
items={getVersionsFromAppState(this.props.appState)}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onItemSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
disabled={!!Bisector}
>
<Button
className='version-chooser'
text={`Electron v${version}`}
icon={getItemIcon(currentElectronVersion)}
disabled={!!Bisector}
/>
</ElectronVersionSelect>
</ButtonGroup>
);
}
}
return (
<ButtonGroup>
<VersionSelect
appState={props.appState}
onVersionSelect={({ version }) => setVersion(version)}
currentVersion={currentElectronVersion}
disabled={!!Bisector}
/>
</ButtonGroup>
);
});
+6 -2
View File
@@ -7,6 +7,7 @@ import * as path from 'path';
import * as React from 'react';
import { getTheme, THEMES_PATH } from '../themes';
import { GenericDialogType } from '../../../src/interfaces';
import { AppState } from '../state';
import { defaultDark, LoadedFiddleTheme } from '../themes-defaults';
@@ -68,8 +69,11 @@ export class AddThemeDialog extends React.Component<AddThemeDialogProps, AddThem
const name = editor.name ? editor.name : file.name;
await this.createNewThemeFromMonaco(name, newTheme);
} catch (error) {
this.props.appState.setWarningDialogTexts({label: `Error: ${error}, please pick a different file.`});
this.props.appState.isWarningDialogShowing = true;
this.props.appState.setGenericDialogOptions({
type: GenericDialogType.warning,
label: `Error: ${error}, please pick a different file.`
});
this.props.appState.isGenericDialogShowing = true;
return;
}
@@ -5,9 +5,10 @@ import * as path from 'path';
import * as React from 'react';
import * as semver from 'semver';
import { NpmVersion } from '../../interfaces';
import { Version } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
import { getElectronNameForPlatform } from '../../utils/electron-name';
import { getIsDownloaded } from '../binary';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
@@ -58,8 +59,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public async setFolderPath(folderPath: string) {
const { binaryManager } = this.props.appState;
const isValidElectron = !!await binaryManager.getIsDownloaded('custom', folderPath);
const isValidElectron = !!await getIsDownloaded('custom', folderPath);
this.setState({ folderPath, isValidElectron });
}
@@ -90,7 +90,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
.slice(1)
.join(path.sep);
const toAdd: NpmVersion = {
const toAdd: Version = {
localPath: folderPath,
version,
name
+123 -50
View File
@@ -1,29 +1,27 @@
import { Button, Dialog, Label, MenuItem } from '@blueprintjs/core';
import { Select } from '@blueprintjs/select';
import { Button, ButtonGroup, Callout, Dialog, Label } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion } from '../../interfaces';
import { RunnableVersion } from '../../interfaces';
import { Bisector } from '../bisect';
import { AppState } from '../state';
import { filterItem, getItemIcon, getVersionsFromAppState, renderItem } from './commands-version-chooser';
const ElectronVersionSelect = Select.ofType<ElectronVersion>();
import { VersionSelect } from './version-select';
export interface BisectDialogProps {
appState: AppState;
}
export interface BisectDialogState {
startIndex?: number;
endIndex?: number;
allVersions: Array<ElectronVersion>;
startIndex: number;
endIndex: number;
allVersions: Array<RunnableVersion>;
showHelp?: boolean;
}
/**
* The "add version" dialog allows users to add custom builds of Electron.
*
* @class AddVersionDialog
* @class BisectDialog
* @extends {React.Component<BisectDialogProps, BisectDialogState>}
*/
@observer
@@ -35,16 +33,22 @@ export class BisectDialog extends React.Component<BisectDialogProps, BisectDialo
this.onClose = this.onClose.bind(this);
this.onBeginSelect = this.onBeginSelect.bind(this);
this.onEndSelect = this.onEndSelect.bind(this);
this.showHelp = this.showHelp.bind(this);
this.isEarliestItemDisabled = this.isEarliestItemDisabled.bind(this);
this.isLatestItemDisabled = this.isLatestItemDisabled.bind(this);
const allVersions = getVersionsFromAppState(this.props.appState);
this.state = { allVersions };
this.state = {
allVersions: this.props.appState.versionsToShow,
startIndex: 10,
endIndex: 0
};
}
public onBeginSelect(version: ElectronVersion) {
public onBeginSelect(version: RunnableVersion) {
this.setState({ startIndex: this.state.allVersions.indexOf(version) });
}
public onEndSelect(version: ElectronVersion) {
public onEndSelect(version: RunnableVersion) {
this.setState({ endIndex: this.state.allVersions.indexOf(version) });
}
@@ -78,18 +82,30 @@ export class BisectDialog extends React.Component<BisectDialogProps, BisectDialo
this.props.appState.isBisectDialogShowing = false;
}
get buttons() {
const canSubmit =
!!this.state.startIndex &&
!!this.state.endIndex &&
this.state.startIndex > this.state.endIndex;
/**
* Shows the additional help
*/
public showHelp() {
this.setState({ showHelp: true });
}
/**
* Can we get this show on the road?
*/
get canSubmit(): boolean {
return this.state.startIndex > this.state.endIndex;
}
/**
* Renders the buttons
*/
get buttons() {
return [
(
<Button
icon='play'
key='submit'
disabled={!canSubmit}
disabled={!this.canSubmit}
onClick={this.onSubmit}
text='Begin'
/>
@@ -104,6 +120,47 @@ export class BisectDialog extends React.Component<BisectDialogProps, BisectDialo
];
}
/**
* Renders the help
*/
get help() {
let moreHelp = (
<Button
icon='help'
text='Show help'
onClick={this.showHelp}
/>
);
if (this.state.showHelp) {
moreHelp = (
<>
<p>
First, write a fiddle that reproduces a bug or an issue. Then, select the earliest version to
start your search with. Typically, that's the "last known good" version that did not have the bug.
Then, select that latest version to end the search with, usually the "first known bad" version.
</p>
<p>
Once you begin your bisect, Fiddle will run your fiddle with a number of Electron versions, closing
in on the version that introduced the bug. Once completed, you will know which Electron version
introduced your issue.
</p>
</>
);
}
return (
<Callout style={{ marginTop: 0, marginBottom: '1rem' }}>
<p>
A "bisect" is a popular method <a href='https://git-scm.com/docs/git-bisect' target='_blank'>
borrowed from <code>git</code></a> for learning which version of Electron introduced a bug. This
tool helps you perform a bisect.
</p>
{moreHelp}
</Callout>
);
}
public render() {
const { isBisectDialogShowing } = this.props.appState;
const { startIndex, endIndex, allVersions } = this.state;
@@ -116,41 +173,28 @@ export class BisectDialog extends React.Component<BisectDialogProps, BisectDialo
className='dialog-add-version'
>
<div className='bp3-dialog-body'>
{this.help}
<Label>
Earliest Version
<ElectronVersionSelect
filterable={true}
items={allVersions}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onBeginSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
>
<Button
text={startIndex ? `v${allVersions[startIndex].version}` : ``}
icon={startIndex ? getItemIcon(allVersions[startIndex]) : 'small-minus'}
fill={true}
Earliest Version (Last "known good" version)
<ButtonGroup fill={true}>
<VersionSelect
currentVersion={allVersions[startIndex]}
appState={this.props.appState}
onVersionSelect={this.onBeginSelect}
itemDisabled={this.isEarliestItemDisabled}
/>
</ElectronVersionSelect>
</ButtonGroup>
</Label>
<Label>
Latest Version
<ElectronVersionSelect
filterable={true}
items={allVersions.slice(0, startIndex!)}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onEndSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
disabled={!startIndex}
>
<Button
text={endIndex ? `v${allVersions[endIndex].version}` : ``}
icon={endIndex ? getItemIcon(allVersions[endIndex]) : 'small-minus'}
fill={true}
disabled={!startIndex}
Latest Version (First "known bad" version)
<ButtonGroup fill={true}>
<VersionSelect
currentVersion={allVersions[endIndex]}
appState={this.props.appState}
onVersionSelect={this.onEndSelect}
itemDisabled={this.isLatestItemDisabled}
/>
</ElectronVersionSelect>
</ButtonGroup>
</Label>
</div>
<div className='bp3-dialog-footer'>
@@ -161,4 +205,33 @@ export class BisectDialog extends React.Component<BisectDialogProps, BisectDialo
</Dialog>
);
}
/**
* Should an item in the "earliest version" dropdown be disabled?
*
* @param {RunnableVersion} version
* @returns {boolean}
*/
public isEarliestItemDisabled(version: RunnableVersion): boolean {
const { allVersions, endIndex } = this.state;
// In the array, "newer" versions will have a lower index.
// 0: 5.0.0
// 1: 4.0.0
// 2: 3.0.0
// ...
return allVersions.indexOf(version) < endIndex + 1;
}
/**
* Should an item in the "latest version" dropdown be disabled?
*
* @param {RunnableVersion} version
* @returns {boolean}
*/
public isLatestItemDisabled(version: RunnableVersion): boolean {
const { allVersions, startIndex } = this.state;
return allVersions.indexOf(version) > startIndex - 1;
}
}
@@ -1,51 +0,0 @@
import { Alert, Intent } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
export interface ConfirmDialogProps {
appState: AppState;
}
export interface ConfirmDialogState {
}
/**
* The token dialog prompts the user to either continue or cancel the operation.
*
* @export
* @class ConfirmDialog
* @extends {React.Component<ConfirmDialogProps, ConfirmDialogState>}
*/
@observer
export class ConfirmDialog extends React.Component<ConfirmDialogProps, ConfirmDialogState> {
constructor(props: ConfirmDialogProps) {
super(props);
this.onClose = this.onClose.bind(this);
}
public onClose(result: boolean) {
this.props.appState.confirmationPromptLastResult = result;
this.props.appState.toggleConfirmationPromptDialog();
}
public render() {
const { isConfirmationPromptShowing, confirmationDialogTexts } = this.props.appState;
return (
<Alert
isOpen={isConfirmationPromptShowing}
onClose={this.onClose}
icon='help'
confirmButtonText={confirmationDialogTexts.ok}
cancelButtonText={confirmationDialogTexts.cancel}
intent={Intent.PRIMARY}
>
<p>{confirmationDialogTexts.label}</p>
</Alert>
);
}
}
@@ -0,0 +1,73 @@
import { Alert, IconName, Intent} from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { GenericDialogType } from '../../../src/interfaces';
import { AppState } from '../state';
export interface GenericDialogProps {
appState: AppState;
}
export interface GenericDialogState {
}
/**
* The token dialog prompts the user to either continue or cancel the operation.
*
* @export
* @class GenericDialog
* @extends {React.Component<GenericDialogProps, GenericDialogState>}
*/
@observer
export class GenericDialog extends React.Component<GenericDialogProps, GenericDialogState> {
constructor(props: GenericDialogProps) {
super(props);
this.onClose = this.onClose.bind(this);
}
public onClose(result: boolean) {
this.props.appState.genericDialogLastResult = result;
this.props.appState.toggleGenericDialog();
}
public render() {
const { isGenericDialogShowing, genericDialogOptions } = this.props.appState;
const {type, ok, cancel, label} = genericDialogOptions;
let intent: Intent;
let icon: IconName;
switch (type) {
case GenericDialogType.warning:
intent = Intent.DANGER;
icon = 'warning-sign';
break;
case GenericDialogType.confirm:
intent = Intent.PRIMARY;
icon = 'help';
break;
case GenericDialogType.success:
intent = Intent.SUCCESS;
icon = 'info-sign';
break;
default:
intent = Intent.NONE;
icon = 'help';
break;
}
return (
<Alert
isOpen={isGenericDialogShowing}
onClose={this.onClose}
icon={icon}
confirmButtonText={ok}
cancelButtonText={cancel}
intent={intent}
>
<p>{label}</p>
</Alert>
);
}
}
+1
View File
@@ -69,6 +69,7 @@ export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogSt
} catch (error) {
console.warn(`Authenticating against GitHub failed`, error);
this.setState({ verifying: false, error: true });
this.props.appState.gitHubToken = null;
return;
}
@@ -1,50 +0,0 @@
import { Alert, Intent } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
export interface WarningDialogProps {
appState: AppState;
}
export interface WarningDialogState {
}
/**
* The token dialog prompts the user to either continue or cancel the operation.
*
* @export
* @class WarningDialog
* @extends {React.Component<WarningDialogProps, WarningDialogState>}
*/
@observer
export class WarningDialog extends React.Component<WarningDialogProps, WarningDialogState> {
constructor(props: WarningDialogProps) {
super(props);
this.onClose = this.onClose.bind(this);
}
public onClose(result: boolean) {
this.props.appState.warningDialogLastResult = result;
this.props.appState.toggleWarningDialog();
}
public render() {
const { isWarningDialogShowing, warningDialogTexts } = this.props.appState;
return (
<Alert
isOpen={isWarningDialogShowing}
onClose={this.onClose}
icon='warning-sign'
confirmButtonText={warningDialogTexts.ok}
cancelButtonText={warningDialogTexts.cancel}
intent={Intent.DANGER}
>
<p>{warningDialogTexts.label}</p>
</Alert>
);
}
}
+7 -7
View File
@@ -5,9 +5,8 @@ import { AppState } from '../state';
import { AddThemeDialog } from './dialog-add-theme';
import { AddVersionDialog } from './dialog-add-version';
import { BisectDialog } from './dialog-bisect';
import { ConfirmDialog } from './dialog-confirm';
import { GenericDialog } from './dialog-generic';
import { TokenDialog } from './dialog-token';
import { WarningDialog } from './dialog-warning';
import { Settings } from './settings';
export interface DialogsProps {
@@ -29,7 +28,8 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
isSettingsShowing,
isAddVersionDialogShowing,
isThemeDialogShowing,
isBisectDialogShowing
isBisectDialogShowing,
isGenericDialogShowing
} = appState;
const maybeToken = isTokenDialogShowing
? <TokenDialog key='dialogs' appState={appState} />
@@ -45,9 +45,9 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
const maybeBisect = isBisectDialogShowing
? <BisectDialog key='bisect-dialog' appState={appState} />
: null;
const eitherWarningOrPrompt = appState.isWarningDialogShowing
? <WarningDialog appState={appState} />
: <ConfirmDialog appState={appState} />;
const genericDialog = isGenericDialogShowing
? <GenericDialog appState={appState} />
: null;
return (
<div key='dialogs' className='dialogs'>
@@ -56,7 +56,7 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
{maybeAddLocalVersion}
{maybeMonaco}
{maybeBisect}
{eitherWarningOrPrompt}
{genericDialog}
</div>
);
}
+45 -12
View File
@@ -16,6 +16,7 @@ export interface EditorProps {
options?: Partial<MonacoType.editor.IEditorConstructionOptions>;
editorDidMount?: (editor: MonacoType.editor.IStandaloneCodeEditor) => void;
onChange?: (value: string, event: MonacoType.editor.IModelContentChangedEvent) => void;
setFocused: (id: EditorId) => void;
}
export class Editor extends React.Component<EditorProps> {
@@ -28,15 +29,24 @@ export class Editor extends React.Component<EditorProps> {
constructor(props: EditorProps) {
super(props);
this.language = props.id === 'html' ? 'html' : 'javascript';
switch (props.id) {
case 'html':
this.language = 'html';
break;
case 'css':
this.language = 'css';
break;
default:
this.language = 'javascript';
}
}
public shouldComponentUpdate() {
return false;
}
public componentDidMount() {
this.initMonaco();
public async componentDidMount() {
await this.initMonaco();
}
public componentWillUnmount() {
@@ -80,6 +90,12 @@ export class Editor extends React.Component<EditorProps> {
...monacoOptions
});
// mark this editor as focused whenever it is
this.editor.onDidFocusEditorText(() => {
const { id, setFocused } = this.props;
setFocused(id);
});
await this.editorDidMount(this.editor);
}
}
@@ -98,6 +114,23 @@ export class Editor extends React.Component<EditorProps> {
return <div className='editorContainer' ref={this.containerRef} />;
}
/**
* Create a model and attach it to the editor
*
* @private
* @param {string} value
*/
private createModel(value: string) {
const { monaco } = this.props;
const model = monaco.editor.createModel(value, this.language);
model.updateOptions({
tabSize: 2
});
this.editor.setModel(model);
}
/**
* Sets the content on the editor, including the model and the view state.
*
@@ -105,27 +138,27 @@ export class Editor extends React.Component<EditorProps> {
* @memberof Editor
*/
private async setContent() {
const { appState, id, monaco } = this.props;
const { appState, id } = this.props;
const { version } = appState;
const backup = appState.getAndRemoveEditorValueBackup(id);
if (backup && backup.model) {
if (backup) {
console.log(`Editor: Backup found, restoring state`);
if (backup.viewState) {
this.editor.restoreViewState(backup.viewState);
}
this.editor.setModel(backup.model);
// If there's a model, use the model. No model? Use the value
if (backup.model) {
this.editor.setModel(backup.model);
} else {
this.createModel(backup.value ?? '');
}
} else {
const value = await getContent(id, version);
const model = monaco.editor.createModel(value, this.language);
model.updateOptions({
tabSize: 2
});
this.editor.setModel(model);
this.createModel(value);
}
}
}
+40 -10
View File
@@ -32,6 +32,7 @@ export const TITLE_MAP: Record<MosaicId, string> = {
renderer: 'Renderer Process (renderer.js)',
preload: 'Preload (preload.js)',
html: 'HTML (index.html)',
css: 'Stylesheet (styles.css)',
docsDemo: 'Docs & Demos',
};
@@ -43,6 +44,7 @@ export interface EditorsState {
monaco?: typeof MonacoType;
isMounted?: boolean;
monacoOptions: MonacoType.editor.IEditorOptions;
focused?: EditorId;
}
@observer
@@ -61,10 +63,9 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
this.renderEditor = this.renderEditor.bind(this);
this.renderTile = this.renderTile.bind(this);
this.renderGenericPanel = this.renderGenericPanel.bind(this);
this.setFocused = this.setFocused.bind(this);
this.state = { monacoOptions: defaultMonacoOptions };
this.loadMonaco();
}
/**
@@ -72,7 +73,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
*
* @memberof Editors
*/
public componentDidMount() {
public async componentDidMount() {
ipcRendererManager.on(IpcEvents.MONACO_EXECUTE_COMMAND, (_event, cmd: string) => {
this.executeCommand(cmd);
});
@@ -92,10 +93,16 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
});
this.setState({ isMounted: true });
await this.loadMonaco();
this.props.appState.isUnsaved = false;
}
public componentWillUnmount() {
this.disposeLayoutAutorun();
ipcRendererManager.removeAllListeners(IpcEvents.MONACO_EXECUTE_COMMAND);
ipcRendererManager.removeAllListeners(IpcEvents.FS_NEW_FIDDLE);
ipcRendererManager.removeAllListeners(IpcEvents.MONACO_TOGGLE_OPTION);
}
/**
@@ -160,10 +167,22 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
public renderToolbar(
{ title }: MosaicWindowProps<MosaicId>, id: MosaicId
): JSX.Element {
const { appState } = this.props;
const docsDemoGoHomeMaybe = id === PanelId.docsDemo
? <DocsDemoGoHomeButton id={id} appState={this.props.appState} />
? <DocsDemoGoHomeButton id={id} appState={appState} />
: null;
// only show toolbar controls if we have more than 1 visible editor
// Mosaic arrangement is type string if 1 editor, object otherwise
const toolbarControlsMaybe =
(typeof appState.mosaicArrangement !== 'string') &&
(
<>
<MaximizeButton id={id} appState={appState} />
<RemoveButton id={id} appState={appState} />
</>
);
return (
<div>
{/* Left */}
@@ -177,8 +196,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
{/* Right */}
<div className='mosaic-controls'>
{docsDemoGoHomeMaybe}
<MaximizeButton id={id} appState={this.props.appState} />
<RemoveButton id={id} appState={this.props.appState} />
{toolbarControlsMaybe}
</div>
</div>
);
@@ -237,6 +255,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
monaco={monaco!}
appState={appState}
monacoOptions={defaultMonacoOptions}
setFocused={this.setFocused}
/>
);
}
@@ -249,6 +268,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
return (
<Mosaic<EditorId | PanelId>
className={`focused__${this.state.focused}`}
onChange={this.onChange}
value={appState.mosaicArrangement}
zeroStateView={renderNonIdealState(appState)}
@@ -271,7 +291,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
* We're doing things a bit roundabout to ensure that we're not overloading the
* mobx state with a gigantic Monaco tree.
*/
public async loadMonaco(): Promise<void> {
public async loadMonaco() {
const { app } = window.ElectronFiddle;
const loader = require('monaco-loader');
const monaco = app.monaco || await loader();
@@ -281,14 +301,24 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
}
if (!this.state || !this.state.isMounted) {
this.state = {
this.setState({
monaco,
monacoOptions: defaultMonacoOptions
};
});
} else {
this.setState({ monaco });
}
activateTheme(monaco, undefined, this.props.appState.theme);
await activateTheme(monaco, undefined, this.props.appState.theme);
}
/**
* Sets the currently-focused editor. This will impact the editor's
* z-index, ensuring that its intellisense menus don't get clipped
* by the other editor windows.
* @param id Editor ID
*/
public setFocused(id: EditorId): void {
this.setState({ focused: id });
}
}
+66 -53
View File
@@ -7,12 +7,13 @@ import {
HTMLTable,
IButtonProps,
Icon,
IconName
IconName,
Tooltip
} from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../interfaces';
import { RunnableVersion, VersionSource, VersionState } from '../../interfaces';
import { normalizeVersion } from '../../utils/normalize-version';
import { sortedElectronMap } from '../../utils/sorted-electron-map';
import { AppState } from '../state';
@@ -69,7 +70,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
if (!checked) {
appState.statesToShow = appState.statesToShow.filter((s) => s !== id);
} else {
appState.statesToShow.push(id as ElectronVersionState);
appState.statesToShow.push(id as VersionState);
}
}
@@ -85,9 +86,9 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
const { appState } = this.props;
if (!checked) {
appState.versionsToShow = appState.versionsToShow.filter((c) => c !== id);
appState.channelsToShow = appState.channelsToShow.filter((c) => c !== id);
} else {
appState.versionsToShow.push(id as ElectronReleaseChannel);
appState.channelsToShow.push(id as ElectronReleaseChannel);
}
}
@@ -202,7 +203,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
*/
private renderVersionStateOptions(): JSX.Element {
const { appState } = this.props;
const getIsChecked = (state: ElectronVersionState) => {
const getIsChecked = (state: VersionState) => {
return appState.statesToShow.includes(state);
};
@@ -210,22 +211,29 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
<FormGroup
label='Include Electron versions that are:'
>
<Tooltip
content='Always enabled'
position='bottom'
intent='primary'
>
<Checkbox
checked={getIsChecked(VersionState.ready)}
label='Ready'
id='ready'
onChange={this.handleStateChange}
inline={true}
disabled={true}
/>
</Tooltip>
<Checkbox
checked={getIsChecked(ElectronVersionState.downloading)}
checked={getIsChecked(VersionState.downloading)}
label='Downloading'
id='downloading'
onChange={this.handleStateChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronVersionState.ready)}
label='Downloaded'
id='ready'
onChange={this.handleStateChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronVersionState.unknown)}
checked={getIsChecked(VersionState.unknown)}
label='Not Downloaded'
id='unknown'
onChange={this.handleStateChange}
@@ -243,42 +251,47 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
*/
private renderVersionChannelOptions(): JSX.Element {
const { appState } = this.props;
const getIsChecked = (channel: ElectronReleaseChannel) => {
return appState.versionsToShow.includes(channel);
return appState.channelsToShow.includes(channel);
};
const getIsCurrentVersionReleaseChannel = (channel: ElectronReleaseChannel) => {
return getReleaseChannel(appState.version) === channel;
};
const channels = {
stable: ElectronReleaseChannel.stable,
beta: ElectronReleaseChannel.beta,
nightly: ElectronReleaseChannel.nightly,
unsupported: ElectronReleaseChannel.unsupported
};
return (
<FormGroup
label='Include Electron versions from these release channels:'
>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.stable)}
label='Stable'
id='Stable'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.beta)}
label='Beta'
id='Beta'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.nightly)}
label='Nightly'
id='Nightly'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.unsupported)}
label='Unsupported'
id='Unsupported'
onChange={this.handleChannelChange}
inline={true}
/>
{
// tslint:disable-next-line:jsx-no-multiline-js
Object.entries(channels).map(([_, channel]) => (
<Tooltip
content={`Can't disable channel of selected version (${appState.version})`}
disabled={!getIsCurrentVersionReleaseChannel(channel)}
position='bottom'
intent='primary'
key={channel}
>
<Checkbox
checked={getIsChecked(channel)}
label={channel}
id={channel}
onChange={this.handleChannelChange}
disabled={getIsCurrentVersionReleaseChannel(channel)}
inline={true}
/>
</Tooltip>
))
}
</FormGroup>
);
}
@@ -316,11 +329,11 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
* @returns {Array<JSX.Element>}
*/
private renderTableRows(): Array<JSX.Element | null> {
const { versions, versionsToShow, statesToShow } = this.props.appState;
const { versions, channelsToShow, statesToShow } = this.props.appState;
return sortedElectronMap<JSX.Element | null>(versions, (key, item) => {
// Check if we want to show the version
if (!versionsToShow.includes(getReleaseChannel(item))) {
if (!channelsToShow.includes(getReleaseChannel(item))) {
return null;
}
@@ -342,18 +355,18 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
/**
* Returns a human-readable state indicator for an Electron version
*
* @param {ElectronVersion} item
* @param {RunnableVersion} item
* @returns {JSX.Element}
*/
private renderHumanState(item: ElectronVersion): JSX.Element {
private renderHumanState(item: RunnableVersion): JSX.Element {
const { state } = item;
let icon: IconName = 'box';
let humanState = 'Downloaded';
if (state === ElectronVersionState.downloading) {
if (state === VersionState.downloading) {
icon = 'cloud-download';
humanState = 'Downloading';
} else if (state === ElectronVersionState.unknown) {
} else if (state === VersionState.unknown) {
icon = 'cloud';
humanState = 'Not downloaded';
}
@@ -370,10 +383,10 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
*
* @private
* @param {string} key
* @param {ElectronVersion} item
* @param {RunnableVersion} item
* @returns {JSX.Element}
*/
private renderAction(key: string, item: ElectronVersion): JSX.Element {
private renderAction(key: string, item: RunnableVersion): JSX.Element {
const { state, source } = item;
const { appState } = this.props;
const buttonProps: IButtonProps = {
@@ -385,7 +398,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
if (state === 'ready') {
buttonProps.onClick = () => appState.removeVersion(key);
buttonProps.icon = 'trash';
buttonProps.text = source === ElectronVersionSource.local
buttonProps.text = source === VersionSource.local
? 'Remove'
: 'Delete';
} else if (state === 'downloading') {
+40 -2
View File
@@ -1,4 +1,4 @@
import { Callout, Checkbox, FormGroup } from '@blueprintjs/core';
import { Callout, Checkbox, FormGroup, InputGroup } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -21,6 +21,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
this.handleDeleteDataChange = this.handleDeleteDataChange.bind(this);
this.handleElectronLoggingChange = this.handleElectronLoggingChange.bind(this);
this.handleExecutionFlagChange = this.handleExecutionFlagChange.bind(this);
}
/**
@@ -48,8 +49,25 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
this.props.appState.isEnablingElectronLogging = checked;
}
/**
* Handles a change in the execution flags run with the Electron executable
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public handleExecutionFlagChange(
event: React.FormEvent<HTMLInputElement>
) {
const { value } = event.currentTarget;
const flags = value.split('|');
this.props.appState.executionFlags = flags;
}
public render() {
const { isKeepingUserDataDirs, isEnablingElectronLogging } = this.props.appState;
const {
isKeepingUserDataDirs,
isEnablingElectronLogging,
executionFlags = []
} = this.props.appState;
const deleteUserDirLabel = `
Whenever Electron runs, it creates a user data directory for cookies, the cache,
@@ -87,6 +105,26 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
/>
</FormGroup>
</Callout>
<br />
<Callout>
<FormGroup>
<p>
Electron allows starting the executable with <a
href='https://www.electronjs.org/docs/api/command-line-switches'
>
user-provided flags
</a>
, such as '--js-flags=--expose-gc'. Those can be added here as bar-separated (|)
flags to run when you start your Fiddles.
</p>
<br />
<InputGroup
placeholder='--js-flags=--expose-gc|--lang=es'
value={executionFlags.join('|')}
onChange={this.handleExecutionFlagChange}
/>
</FormGroup>
</Callout>
</div>
);
}
@@ -19,7 +19,7 @@ const ThemeSelect = Select.ofType<LoadedFiddleTheme>();
* version.
*
* @param {string} query
* @param {ElectronVersion} { version }
* @param {RunnableVersion} { version }
* @returns
*/
export const filterItem: ItemPredicate<LoadedFiddleTheme> = (query, { name }) => {
@@ -31,7 +31,7 @@ export const filterItem: ItemPredicate<LoadedFiddleTheme> = (query, { name }) =>
* Helper method: Returns the <Select /> <MenuItem /> for Electron
* versions.
*
* @param {ElectronVersion} item
* @param {RunnableVersion} item
* @param {IItemRendererProps} { handleClick, modifiers, query }
* @returns
*/
+2 -2
View File
@@ -29,8 +29,8 @@ export function getWelcomeTour(): Set<TourScriptStep> {
<>
<p>
Electron Fiddle allows you to build little experiments and mini-apps with
Electron. Each Fiddle has three files: A main script, a renderer script,
and an HTML file.
Electron. Each Fiddle has at least three of these files: A main script, a
renderer script, a preload script, and an HTML file.
</p>
<p>
If you <code>require()</code> a module, Fiddle will install
+134
View File
@@ -0,0 +1,134 @@
import { Button, IButtonGroupProps, MenuItem } from '@blueprintjs/core';
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
import { observer } from 'mobx-react';
import * as React from 'react';
import { RunnableVersion, VersionSource, VersionState } from '../../interfaces';
import { highlightText } from '../../utils/highlight-text';
import { AppState } from '../state';
const ElectronVersionSelect = Select.ofType<RunnableVersion>();
/**
* Helper method: Returns the <Select /> label for an Electron
* version.
*
* @param {RunnableVersion} { source, state }
* @returns {string}
*/
export function getItemLabel({ source, state, name }: RunnableVersion): string {
let label = '';
if (source === VersionSource.local) {
label = name || 'Local';
} else {
if (state === VersionState.unknown) {
label = `Not downloaded`;
} else if (state === VersionState.ready) {
label = `Downloaded`;
} else if (state === VersionState.downloading) {
label = `Downloading`;
}
}
return label;
}
/**
* Helper method: Returns the <Select /> icon for an Electron
* version.
*
* @param {RunnableVersion} { state }
* @returns
*/
export function getItemIcon({ state }: RunnableVersion) {
return state === 'ready'
? 'saved'
: state === 'downloading' ? 'cloud-download' : 'cloud';
}
/**
* Helper method: Returns the <Select /> predicate for an Electron
* version.
*
* @param {string} query
* @param {RunnableVersion} { version }
* @returns
*/
export const filterItem: ItemPredicate<RunnableVersion> = (query, { version }) => {
return version.toLowerCase().includes(query.toLowerCase());
};
/**
* Helper method: Returns the <Select /> <MenuItem /> for Electron
* versions.
*
* @param {RunnableVersion} item
* @param {IItemRendererProps} { handleClick, modifiers, query }
* @returns
*/
export const renderItem: ItemRenderer<RunnableVersion> = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
}
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
text={highlightText(item.version, query)}
key={item.version}
onClick={handleClick}
label={getItemLabel(item)}
icon={getItemIcon(item)}
/>
);
};
export interface VersionSelectState {
value: string;
}
export interface VersionSelectProps {
appState: AppState;
disabled?: boolean;
currentVersion: RunnableVersion;
onVersionSelect: (version: RunnableVersion) => void;
buttonGroupProps?: IButtonGroupProps;
itemDisabled?: keyof RunnableVersion | ((item: RunnableVersion, index: number) => boolean);
}
/**
* A dropdown allowing the selection of Electron versions. The actual
* download is managed in the state.
*
* @class VersionSelect
* @extends {React.Component<VersionSelectProps, VersionSelectState>}
*/
@observer
export class VersionSelect extends React.Component<VersionSelectProps, VersionSelectState> {
public render() {
const { currentVersion, itemDisabled } = this.props;
const { version } = currentVersion;
return (
<ElectronVersionSelect
filterable={true}
items={this.props.appState.versionsToShow}
itemRenderer={renderItem}
itemPredicate={filterItem}
itemDisabled={itemDisabled}
onItemSelect={this.props.onVersionSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
disabled={!!this.props.disabled}
>
<Button
className='version-chooser'
text={`Electron v${version}`}
icon={getItemIcon(currentVersion)}
disabled={!!this.props.disabled}
/>
</ElectronVersionSelect>
);
}
}
+13
View File
@@ -3,6 +3,7 @@ import * as path from 'path';
import { MosaicNode } from 'react-mosaic-component';
import { EditorId, MosaicId } from '../interfaces';
import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
// Reminder: When testing, this file is mocked in tests/setup.js
@@ -19,5 +20,17 @@ export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
}
};
export const DEFAULT_CLOSED_PANELS: Partial<Record<MosaicId, EditorBackup | true>> = {
docsDemo: true,
preload: getEditorBackup(EditorId.preload),
css: getEditorBackup(EditorId.css),
};
export const EMPTY_EDITOR_CONTENT = {
html: '<!-- Empty -->',
js: '// Empty',
css: '/* Empty */'
};
export const ELECTRON_ORG = 'electron';
export const ELECTRON_REPO = 'electron';
+28 -7
View File
@@ -2,9 +2,10 @@ import * as fsType from 'fs-extra';
import * as MonacoType from 'monaco-editor';
import * as path from 'path';
import { ElectronVersion, ElectronVersionSource } from '../interfaces';
import { RunnableVersion, VersionSource } from '../interfaces';
import { callIn } from '../utils/call-in';
import { fancyImport } from '../utils/import';
import { normalizeVersion } from '../utils/normalize-version';
import { USER_DATA_PATH } from './constants';
const definitionPath = path.join(USER_DATA_PATH, 'electron-typedef');
@@ -36,6 +37,26 @@ export async function fetchTypeDefinitions(version: string): Promise<string> {
}
}
/**
* Removes the type definition for a given version
*
* @param version
*/
export async function removeTypeDefsForVersion(version: string) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const _version = normalizeVersion(version);
const typeDefsDir = path.dirname(getOfflineTypeDefinitionPath(_version));
if (fs.existsSync(typeDefsDir)) {
try {
await fs.remove(typeDefsDir);
} catch (error) {
throw error;
}
}
}
/**
* Get the path for offline TypeScript definitions
*
@@ -64,7 +85,7 @@ export async function getOfflineTypeDefinitions(version: string): Promise<boolea
* @param {string} version
* @returns {void}
*/
export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Promise<string | null> {
export async function getDownloadedVersionTypeDefs(version: RunnableVersion): Promise<string | null> {
const fs = await fancyImport<typeof fsType>('fs-extra');
await fs.mkdirp(definitionPath);
const offlinePath = getOfflineTypeDefinitionPath(version.version);
@@ -91,8 +112,8 @@ export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Pr
}
}
export async function getLocalVersionTypeDefs(version: ElectronVersion) {
if (version.source === ElectronVersionSource.local && !!version.localPath) {
export async function getLocalVersionTypeDefs(version: RunnableVersion) {
if (version.source === VersionSource.local && !!version.localPath) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const typesPath = getLocalTypePathForVersion(version);
if (!!typesPath && fs.existsSync(typesPath)) {
@@ -107,7 +128,7 @@ export async function getLocalVersionTypeDefs(version: ElectronVersion) {
*
* @param {string} version
*/
export async function updateEditorTypeDefinitions(version: ElectronVersion, i: number = 0): Promise<void> {
export async function updateEditorTypeDefinitions(version: RunnableVersion, i: number = 0): Promise<void> {
const defer = async (): Promise<void> => {
if (i > 10) {
console.warn(`Fetch Types: Failed, dependencies do not exist`);
@@ -125,7 +146,7 @@ export async function updateEditorTypeDefinitions(version: ElectronVersion, i: n
const monaco: typeof MonacoType = app.monaco!;
const typeDefDisposable: MonacoType.IDisposable = app.typeDefDisposable!;
const getTypeDefs = (version.source === ElectronVersionSource.local) ?
const getTypeDefs = (version.source === VersionSource.local) ?
getLocalVersionTypeDefs : getDownloadedVersionTypeDefs;
const typeDefs = await getTypeDefs(version);
@@ -143,7 +164,7 @@ export async function updateEditorTypeDefinitions(version: ElectronVersion, i: n
}
}
export function getLocalTypePathForVersion(version: ElectronVersion) {
export function getLocalTypePathForVersion(version: RunnableVersion) {
if (version.localPath) {
return path.join(
version.localPath,
+8 -2
View File
@@ -3,7 +3,7 @@ import * as path from 'path';
import { EditorValues, Files, FileTransform } from '../interfaces';
import { IpcEvents } from '../ipc-events';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME, STYLES_CSS_NAME } from '../shared-constants';
import { DEFAULT_OPTIONS, PackageJsonOptions } from '../utils/get-package';
import { fancyImport } from '../utils/import';
import { ipcRendererManager } from './ipc';
@@ -17,6 +17,11 @@ export class FileManager {
this.openFiddle = this.openFiddle.bind(this);
this.saveFiddle = this.saveFiddle.bind(this);
ipcRendererManager.removeAllListeners(IpcEvents.FS_OPEN_FIDDLE);
ipcRendererManager.removeAllListeners(IpcEvents.FS_OPEN_TEMPLATE);
ipcRendererManager.removeAllListeners(IpcEvents.FS_SAVE_FIDDLE);
ipcRendererManager.removeAllListeners(IpcEvents.FS_SAVE_FIDDLE_FORGE);
ipcRendererManager.on(IpcEvents.FS_OPEN_FIDDLE, (_event, filePath) => {
this.openFiddle(filePath);
});
@@ -63,6 +68,7 @@ export class FileManager {
main: await this.readFile(path.join(filePath, MAIN_JS_NAME)),
renderer: await this.readFile(path.join(filePath, RENDERER_JS_NAME)),
preload: await this.readFile(path.join(filePath, PRELOAD_JS_NAME)),
css: await this.readFile(path.join(filePath, STYLES_CSS_NAME))
};
@@ -104,7 +110,6 @@ export class FileManager {
}
this.appState.isUnsaved = false;
window.ElectronFiddle.app.setupUnsavedOnChangeListener();
}
}
@@ -126,6 +131,7 @@ export class FileManager {
output.set(MAIN_JS_NAME, values.main);
output.set(INDEX_HTML_NAME, values.html);
output.set(PRELOAD_JS_NAME, values.preload);
output.set(STYLES_CSS_NAME, values.css);
output.set(PACKAGE_NAME, values.package!);
for (const transform of transforms) {
+1
View File
@@ -15,6 +15,7 @@ export class IpcRendererManager extends EventEmitter {
super();
ipcRendererEvents.forEach((name) => {
ipcRenderer.removeAllListeners(name);
ipcRenderer.on(name, (...args: Array<any>) => this.emit(name, ...args));
});
}
+4 -2
View File
@@ -1,6 +1,8 @@
import { EditorValues } from '../interfaces';
import { exec } from '../utils/exec';
const { builtinModules } = require('module');
export interface NpmOperationOptions {
dir: string;
}
@@ -11,8 +13,8 @@ export let isInstalled: boolean | null = null;
/* perhaps we can expose this to the settings module?*/
const ignoredModules: Array<string> = [
'electron',
// tslint:disable-next-line:no-submodule-imports
...require('builtin-modules/static')
'original-fs',
...builtinModules
];
/* regular expression to both match and extract module names */
+38 -24
View File
@@ -1,7 +1,7 @@
import * as Octokit from '@octokit/rest';
import { Octokit } from '@octokit/rest';
import { when } from 'mobx';
import { EditorId, EditorValues } from '../interfaces';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
import { EditorId, EditorValues, GenericDialogType } from '../interfaces';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME, STYLES_CSS_NAME } from '../shared-constants';
import { getOctokit } from '../utils/octokit';
import { sortedElectronMap } from '../utils/sorted-electron-map';
import { ELECTRON_ORG, ELECTRON_REPO } from './constants';
@@ -25,17 +25,20 @@ export class RemoteLoader {
public async loadFiddleFromElectronExample(_: any, exampleInfo: { path: string; ref: string }) {
console.log(`Loading fiddle from Electron example`, _, exampleInfo);
const ok = await this.verifyRemoteLoad('example from the Electron docs', exampleInfo.ref);
const { path, ref } = exampleInfo;
const prettyName = path.replace('docs/fiddles/', '');
const ok = await this.verifyRemoteLoad(`'${prettyName}' example from the Electron docs for version ${ref}`);
if (!ok) return;
this.fetchExampleAndLoad(exampleInfo.ref, exampleInfo.path);
this.fetchExampleAndLoad(ref, path);
}
public async loadFiddleFromGist(_: any, gistInfo: { id: string }) {
const ok = await this.verifyRemoteLoad('gist');
const { id } = gistInfo;
const ok = await this.verifyRemoteLoad(`gist`);
if (!ok) return;
this.fetchGistAndLoad(gistInfo.id);
this.fetchGistAndLoad(id);
}
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
@@ -57,6 +60,7 @@ export class RemoteLoader {
renderer: await getContent(EditorId.renderer, this.appState.version),
main: await getContent(EditorId.main, this.appState.version),
preload: await getContent(EditorId.preload, this.appState.version),
css: await getContent(EditorId.css, this.appState.version)
};
const loaders: Array<Promise<void>> = [];
@@ -95,6 +99,11 @@ export class RemoteLoader {
.then((r) => r.text()).then((t) => { values.preload = t; })
);
case STYLES_CSS_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.css = t; })
);
break;
default:
break;
@@ -140,7 +149,8 @@ export class RemoteLoader {
html: this.getContentOrEmpty(gist, INDEX_HTML_NAME),
main: this.getContentOrEmpty(gist, MAIN_JS_NAME),
renderer: this.getContentOrEmpty(gist, RENDERER_JS_NAME),
preload: this.getContentOrEmpty(gist, PRELOAD_JS_NAME)
preload: this.getContentOrEmpty(gist, PRELOAD_JS_NAME),
css: this.getContentOrEmpty(gist, STYLES_CSS_NAME)
}, gistId);
} catch (error) {
return this.handleLoadingFailed(error);
@@ -159,11 +169,11 @@ export class RemoteLoader {
// check if version is part of release channel
const versionReleaseChannel: ElectronReleaseChannel = getReleaseChannel(version);
if (!this.appState.versionsToShow.includes(versionReleaseChannel)) {
if (!this.appState.channelsToShow.includes(versionReleaseChannel)) {
const ok = await this.verifyReleaseChannelEnabled(versionReleaseChannel);
if (!ok) return false;
this.appState.versionsToShow.push(versionReleaseChannel);
this.appState.channelsToShow.push(versionReleaseChannel);
}
this.appState.setVersion(version);
@@ -197,26 +207,28 @@ export class RemoteLoader {
*
* @param what What are we loading from (gist, example, etc.)
*/
public async verifyRemoteLoad(what: string, fiddlePath?: string): Promise<boolean> {
this.appState.setConfirmationPromptTexts({
label: `Are you sure you sure you want to load this '${what}' from fiddle path '${fiddlePath}'? Only load and run it if you trust the source.`
public async verifyRemoteLoad(what: string): Promise<boolean> {
this.appState.setGenericDialogOptions({
type: GenericDialogType.confirm,
label: `Are you sure you want to load this ${what}? Only load and run it if you trust the source.`
});
this.appState.isConfirmationPromptShowing = true;
await when(() => !this.appState.isConfirmationPromptShowing);
this.appState.isGenericDialogShowing = true;
await when(() => !this.appState.isGenericDialogShowing);
return !!this.appState.confirmationPromptLastResult;
return !!this.appState.genericDialogLastResult;
}
public async verifyReleaseChannelEnabled(channel: string): Promise<boolean> {
this.appState.setWarningDialogTexts({
this.appState.setGenericDialogOptions({
type: GenericDialogType.warning,
label: `You're loading an example with a version of Electron with an unincluded release
channel (${channel}). Do you want to enable the release channel to load the
version of Electron from the example?`
});
this.appState.isWarningDialogShowing = true;
await when(() => !this.appState.isWarningDialogShowing);
this.appState.isGenericDialogShowing = true;
await when(() => !this.appState.isGenericDialogShowing);
return !!this.appState.warningDialogLastResult;
return !!this.appState.genericDialogLastResult;
}
/**
@@ -227,7 +239,7 @@ export class RemoteLoader {
* @returns {boolean}
*/
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
await window.ElectronFiddle.app.replaceFiddle(values, {gistId});
await window.ElectronFiddle.app.replaceFiddle(values, { gistId });
return true;
}
@@ -240,18 +252,20 @@ export class RemoteLoader {
*/
private handleLoadingFailed(error: Error): false {
if (navigator.onLine) {
this.appState.setWarningDialogTexts({
this.appState.setGenericDialogOptions({
type: GenericDialogType.warning,
label: `Loading the fiddle failed: ${error}`,
cancel: undefined
});
} else {
this.appState.setWarningDialogTexts({
this.appState.setGenericDialogOptions({
type: GenericDialogType.warning,
label: `Loading the fiddle failed. Your computer seems to be offline. Error: ${error}`,
cancel: undefined
});
}
this.appState.toggleWarningDialog();
this.appState.toggleGenericDialog();
console.warn(`Loading Fiddle failed`, error);
return false;
+17 -10
View File
@@ -3,9 +3,9 @@ import * as path from 'path';
import { EditorValues, FileTransform } from '../interfaces';
import { IpcEvents } from '../ipc-events';
import { getAppDataDir } from '../utils/app-data-dir';
import { PackageJsonOptions } from '../utils/get-package';
import { maybePlural } from '../utils/plural-maybe';
import { getElectronBinaryPath, getIsDownloaded } from './binary';
import { ipcRendererManager } from './ipc';
import { findModulesInEditors, getIsNpmInstalled, installModules, npmRun } from './npm';
import { AppState } from './state';
@@ -22,6 +22,11 @@ export class Runner {
this.run = this.run.bind(this);
this.stop = this.stop.bind(this);
ipcRendererManager.removeAllListeners(IpcEvents.FIDDLE_RUN);
ipcRendererManager.removeAllListeners(IpcEvents.FIDDLE_PACKAGE);
ipcRendererManager.removeAllListeners(IpcEvents.FIDDLE_MAKE);
ipcRendererManager.on(IpcEvents.FIDDLE_RUN, this.run);
ipcRendererManager.on(IpcEvents.FIDDLE_PACKAGE, () => {
this.performForgeOperation(ForgeCommands.PACKAGE);
@@ -39,7 +44,7 @@ export class Runner {
public async run(): Promise<boolean> {
const { fileManager, getEditorValues } = window.ElectronFiddle.app;
const options = { includeDependencies: false, includeElectron: false };
const { binaryManager, currentElectronVersion } = this.appState;
const { currentElectronVersion } = this.appState;
const { version, localPath } = currentElectronVersion;
if (this.appState.isClearingConsoleOnRun) {
@@ -60,7 +65,7 @@ export class Runner {
return false;
}
const isReady = await binaryManager.getIsDownloaded(version, localPath);
const isReady = await getIsDownloaded(version, localPath);
if (!isReady) {
console.warn(`Runner: Binary ${version} not ready`);
@@ -179,24 +184,26 @@ export class Runner {
* @memberof Runner
*/
public async execute(dir: string): Promise<void> {
const { currentElectronVersion, pushOutput, binaryManager } = this.appState;
const { currentElectronVersion, pushOutput } = this.appState;
const { version, localPath } = currentElectronVersion;
const binaryPath = binaryManager.getElectronBinaryPath(version, localPath);
const binaryPath = getElectronBinaryPath(version, localPath);
console.log(`Runner: Binary ${binaryPath} ready, launching`);
const env = { ...process.env };
if (this.appState.isEnablingElectronLogging) {
env.ELECTRON_ENABLE_LOGGING = 'true';
env.ELECTRON_DEBUG_NOTIFICATIONS = 'true';
env.ELECTRON_ENABLE_STACK_DUMPING = 'true';
} else {
delete env.ELECTRON_ENABLE_LOGGING;
delete env.ELECTRON_DEBUG_NOTIFICATIONS;
delete env.ELECTRON_ENABLE_STACK_DUMPING;
}
this.child = spawn(binaryPath, [ dir, '--inspect' ], {
cwd: dir,
env,
});
// Add user-specified cli flags if any have been set.
const options = [ dir, '--inspect' ].concat(this.appState.executionFlags);
this.child = spawn(binaryPath, options, { cwd: dir, env });
this.appState.isRunning = true;
pushOutput(`Electron v${version} started.`);
@@ -273,7 +280,7 @@ export class Runner {
}
const name = await this.appState.getName();
const appData = getAppDataDir(name);
const appData = path.join(this.appState.appData, name);
console.log(`Cleanup: Deleting data dir ${appData}`);
await window.ElectronFiddle.app.fileManager.cleanup(appData);
+152 -82
View File
@@ -6,14 +6,15 @@ import {
ALL_MOSAICS,
DocsDemoPage,
EditorId,
ElectronVersion,
ElectronVersionSource,
ElectronVersionState,
GenericDialogOptions,
GenericDialogType,
MosaicId,
NpmVersion,
OutputEntry,
OutputOptions,
WarningDialogTexts
RunnableVersion,
Version,
VersionSource,
VersionState
} from '../interfaces';
import { IpcEvents } from '../ipc-events';
import { arrayToStringMap } from '../utils/array-to-stringmap';
@@ -23,19 +24,22 @@ import { getName } from '../utils/get-title';
import { fancyImport } from '../utils/import';
import { normalizeVersion } from '../utils/normalize-version';
import { isEditorBackup, isEditorId, isPanelId } from '../utils/type-checks';
import { BinaryManager } from './binary';
import { getDownloadedVersions, getDownloadingVersions, removeBinary, setupBinary } from './binary';
import { Bisector } from './bisect';
import { DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
import { DEFAULT_CLOSED_PANELS, DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
import { getContent, isContentUnchanged } from './content';
import { getLocalTypePathForVersion, updateEditorTypeDefinitions } from './fetch-types';
import { ipcRendererManager } from './ipc';
import { activateTheme } from './themes';
import { waitForEditorsToMount } from '../utils/editor-mounted';
import { sortedElectronMap } from '../utils/sorted-electron-map';
import {
addLocalVersion,
ElectronReleaseChannel,
getDefaultVersion,
getElectronVersions,
getReleaseChannel,
getUpdatedElectronVersions,
saveLocalVersions
} from './versions';
@@ -72,43 +76,43 @@ export class AppState {
@observable public gitHubLogin: string | null = localStorage.getItem('gitHubLogin');
@observable public gitHubToken: string | null = localStorage.getItem('gitHubToken') || null;
@observable public gitHubPublishAsPublic: boolean = !!this.retrieve('gitHubPublishAsPublic');
@observable public versionsToShow: Array<ElectronReleaseChannel> =
this.retrieve('versionsToShow') as Array<ElectronReleaseChannel>
@observable public channelsToShow: Array<ElectronReleaseChannel> =
this.retrieve('channelsToShow') as Array<ElectronReleaseChannel>
|| [ElectronReleaseChannel.stable, ElectronReleaseChannel.beta];
@observable public statesToShow: Array<ElectronVersionState> =
this.retrieve('statesToShow') as Array<ElectronVersionState>
|| [ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown];
@observable public statesToShow: Array<VersionState> =
this.retrieve('statesToShow') as Array<VersionState>
|| [VersionState.downloading, VersionState.ready, VersionState.unknown];
@observable public isKeepingUserDataDirs: boolean = !!this.retrieve('isKeepingUserDataDirs');
@observable public isEnablingElectronLogging: boolean = !!this.retrieve('isEnablingElectronLogging');
@observable public isClearingConsoleOnRun: boolean = !!this.retrieve('isClearingConsoleOnRun');
@observable public executionFlags: Array<string> =
this.retrieve('executionFlags') as Array<string> === null ?
[] : this.retrieve('executionFlags') as Array<string>;
// -- Various session-only state ------------------
@observable public gistId: string = '';
@observable public versions: Record<string, ElectronVersion> = arrayToStringMap(knownVersions);
@observable public versions: Record<string, RunnableVersion> = arrayToStringMap(knownVersions);
@observable public output: Array<OutputEntry> = [];
@observable public localPath: string | undefined;
@observable public warningDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
@observable public confirmationDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
@observable public warningDialogLastResult: boolean | null = null;
@observable public confirmationPromptLastResult: boolean | null = null;
@observable public genericDialogOptions = { type: GenericDialogType.warning, label: '', ok: 'Okay', cancel: 'Cancel' };
@observable public genericDialogLastResult: boolean | null = null;
@observable public mosaicArrangement: MosaicNode<MosaicId> | null = DEFAULT_MOSAIC_ARRANGEMENT;
@observable public templateName: string | undefined;
@observable public currentDocsDemoPage: DocsDemoPage = DocsDemoPage.DEFAULT;
@observable public localTypeWatcher: fsType.FSWatcher | undefined;
@observable public binaryManager: BinaryManager = new BinaryManager();
@observable public Bisector: Bisector | undefined;
@observable public isPublishing: boolean = false;
@observable public isRunning: boolean = false;
@observable public isUnsaved: boolean = false;
@observable public isUnsaved: boolean;
@observable public isUpdatingElectronVersions: boolean = false;
@observable public isQuitting: boolean = false;
// -- Various "isShowing" settings ------------------
@observable public isBisectCommandShowing: boolean;
@observable public isConsoleShowing: boolean = false;
@observable public isTokenDialogShowing: boolean = false;
@observable public isWarningDialogShowing: boolean = false;
@observable public isConfirmationPromptShowing: boolean = false;
@observable public isGenericDialogShowing: boolean = false;
@observable public isSettingsShowing: boolean = false;
@observable public isBisectDialogShowing: boolean = false;
@observable public isAddVersionDialogShowing: boolean = false;
@@ -116,12 +120,11 @@ export class AppState {
@observable public isTourShowing: boolean = !localStorage.getItem('hasShownTour');
// -- Editor Values stored when we close the editor ------------------
@observable public closedPanels: Partial<Record<MosaicId, EditorBackup | true>> = {
docsDemo: true // Closed by default
};
@observable public closedPanels: Partial<Record<MosaicId, EditorBackup | true>> = DEFAULT_CLOSED_PANELS;
private outputBuffer: string = '';
private name: string;
public appData: string;
constructor() {
// Bind all actions
@@ -139,11 +142,20 @@ export class AppState {
this.toggleSettings = this.toggleSettings.bind(this);
this.toggleBisectDialog = this.toggleBisectDialog.bind(this);
this.updateElectronVersions = this.updateElectronVersions.bind(this);
this.resetEditorLayout = this.resetEditorLayout.bind(this);
this.setIsQuitting = this.setIsQuitting.bind(this);
ipcRendererManager.removeAllListeners(IpcEvents.OPEN_SETTINGS);
ipcRendererManager.removeAllListeners(IpcEvents.SHOW_WELCOME_TOUR);
ipcRendererManager.removeAllListeners(IpcEvents.CLEAR_CONSOLE);
ipcRendererManager.removeAllListeners(IpcEvents.BISECT_COMMANDS_TOGGLE);
ipcRendererManager.on(IpcEvents.OPEN_SETTINGS, this.toggleSettings);
ipcRendererManager.on(IpcEvents.SHOW_WELCOME_TOUR, this.showTour);
ipcRendererManager.on(IpcEvents.CLEAR_CONSOLE, this.clearConsole);
ipcRendererManager.on(IpcEvents.BISECT_COMMANDS_TOGGLE, this.toggleBisectCommands);
ipcRendererManager.on(IpcEvents.BEFORE_QUIT, this.setIsQuitting);
ipcRendererManager.once(IpcEvents.SET_APPDATA_DIR, (_event, dir) => { this.appData = dir; });
// Setup auto-runs
autorun(() => this.save('theme', this.theme));
@@ -155,42 +167,57 @@ export class AppState {
autorun(() => this.save('gitHubPublishAsPublic', this.gitHubPublishAsPublic));
autorun(() => this.save('isKeepingUserDataDirs', this.isKeepingUserDataDirs));
autorun(() => this.save('isEnablingElectronLogging', this.isEnablingElectronLogging));
autorun(() => this.save('executionFlags', this.executionFlags));
autorun(() => this.save('version', this.version));
autorun(() => this.save('versionsToShow', this.versionsToShow));
autorun(() => this.save('channelsToShow', this.channelsToShow));
autorun(() => this.save('statesToShow', this.statesToShow));
autorun(() => {
if (this.isUnsaved) {
if (typeof this.isUnsaved === 'undefined') return;
if (!!this.isUnsaved) {
window.onbeforeunload = () => {
this.setWarningDialogTexts({
ipcRendererManager.send(IpcEvents.SHOW_INACTIVE);
this.setGenericDialogOptions({
type: GenericDialogType.warning,
label: `The current Fiddle is unsaved. Do you want to exit anyway?`,
ok: 'Quit'
});
this.isWarningDialogShowing = true;
this.isGenericDialogShowing = true;
// We'll wait until the warning dialog was closed
when(() => !this.isWarningDialogShowing).then(() => {
when(() => !this.isGenericDialogShowing).then(() => {
const quitConfirmed = this.genericDialogLastResult;
// The user confirmed, let's close for real.
if (this.warningDialogLastResult) {
if (quitConfirmed) {
window.onbeforeunload = null;
window.close();
}
// Should we just close or quit?
const remote = require('electron').remote;
const isQuitting = remote.getGlobal('isQuitting');
if (isQuitting) {
remote.app.quit();
} else {
window.close();
}
// isQuitting checks if we're trying to quit the app
// or just close the window
if (this.isQuitting) {
ipcRendererManager.send(IpcEvents.CONFIRM_QUIT, quitConfirmed);
this.isQuitting = false;
}
});
// return value doesn't matter, we just want to cancel the event
return false;
};
} else {
window.onbeforeunload = null;
// set up editor listeners to verify if unsaved
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
const editor = window.ElectronFiddle.editors[key];
const disposable = editor.onDidChangeModelContent(() => {
this.isUnsaved = true;
disposable.dispose();
});
});
}
});
@@ -202,10 +229,10 @@ export class AppState {
}
/**
* Returns the current ElectronVersion or the first
* Returns the current RunnableVersion or the first
* one that can be found.
*/
@computed get currentElectronVersion(): ElectronVersion {
@computed get currentElectronVersion(): RunnableVersion {
if (this.versions[this.version]) {
return this.versions[this.version];
} else {
@@ -213,6 +240,31 @@ export class AppState {
}
}
/**
* Returns an array of Electron versions to show given the
* current settings for states and channels to display
*/
@computed get versionsToShow(): Array<RunnableVersion> {
return sortedElectronMap<RunnableVersion>(this.versions, (_key, item) => item)
.filter((item) => {
if (!item) {
return false;
}
// Check if we want to show the version
if (!this.channelsToShow.includes(getReleaseChannel(item))) {
return false;
}
// Check if we want to show the state
if (!this.statesToShow.includes(item.state)) {
return false;
}
return true;
});
}
/**
* Update the Electron versions: First, fetch them from GitHub,
* then update their respective downloaded state.
@@ -268,11 +320,11 @@ export class AppState {
this.isTokenDialogShowing = !this.isTokenDialogShowing;
}
@action public toggleWarningDialog() {
this.isWarningDialogShowing = !this.isWarningDialogShowing;
@action public toggleGenericDialog() {
this.isGenericDialogShowing = !this.isGenericDialogShowing;
if (this.isWarningDialogShowing) {
this.warningDialogLastResult = null;
if (this.isGenericDialogShowing) {
this.genericDialogLastResult = null;
}
}
@@ -280,14 +332,6 @@ export class AppState {
this.isBisectDialogShowing = !this.isBisectDialogShowing;
}
@action public toggleConfirmationPromptDialog() {
this.isConfirmationPromptShowing = !this.isConfirmationPromptShowing;
if (this.isConfirmationPromptShowing) {
this.confirmationPromptLastResult = null;
}
}
@action public toggleSettings() {
// We usually don't lose editor focus,
// so you can still type. Let's force-blur.
@@ -298,6 +342,10 @@ export class AppState {
this.resetView({ isSettingsShowing: !this.isSettingsShowing });
}
@action public setIsQuitting() {
this.isQuitting = true;
}
@action public disableTour() {
this.resetView({ isTourShowing: false });
localStorage.setItem('hasShownTour', 'true');
@@ -313,23 +361,16 @@ export class AppState {
window.ElectronFiddle.app.setupTheme();
}
@action public setWarningDialogTexts(input: WarningDialogTexts) {
this.warningDialogTexts = {
@action public setGenericDialogOptions(opts: GenericDialogOptions) {
this.genericDialogOptions = {
type: GenericDialogType.warning,
ok: 'Okay',
cancel: 'Cancel',
...input
...opts
};
}
@action public setConfirmationPromptTexts(input: WarningDialogTexts) {
this.confirmationDialogTexts = {
ok: 'Okay',
cancel: 'Cancel',
...input
};
}
@action public addLocalVersion(input: NpmVersion) {
@action public addLocalVersion(input: Version) {
addLocalVersion(input);
this.versions = arrayToStringMap(getElectronVersions());
@@ -358,7 +399,7 @@ export class AppState {
const updatedVersions = { ...this.versions };
// Actually remove
if (release && release.source === ElectronVersionSource.local) {
if (release && release.source === VersionSource.local) {
delete updatedVersions[version];
const versionsAsArray = Object
@@ -367,8 +408,8 @@ export class AppState {
saveLocalVersions(versionsAsArray);
} else {
await this.binaryManager.remove(version);
updatedVersions[version].state = ElectronVersionState.unknown;
await removeBinary(version);
updatedVersions[version].state = VersionState.unknown;
}
this.versions = updatedVersions;
@@ -386,18 +427,17 @@ export class AppState {
console.log(`State: Downloading Electron ${version}`);
const release = this.versions[version] || { state: '', source: '' };
const isLocal = release.source === ElectronVersionSource.local;
const isLocal = release.source === VersionSource.local;
const isReady = release.state === 'ready';
// Fetch new binaries, maybe?
if (!isLocal && !isReady) {
console.log(`State: Instructing BinaryManager to fetch v${version}`);
console.log(`State: Fetching v${version}`);
const updatedVersions = { ...this.versions };
updatedVersions[version] = updatedVersions[version] || {};
updatedVersions[version].state = ElectronVersionState.downloading;
this.versions = updatedVersions;
await this.binaryManager.setup(version);
await setupBinary(this, version);
this.updateDownloadedVersionState();
} else {
console.log(`State: Version ${version} already downloaded, doing nothing.`);
@@ -433,14 +473,18 @@ export class AppState {
// Update TypeScript definitions
const versionObject = this.versions[version];
if (versionObject.source === ElectronVersionSource.local) {
if (versionObject.source === VersionSource.local) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const typePath = getLocalTypePathForVersion(versionObject);
console.info(`TypeDefs: Watching file for local version ${version} at path ${typePath}`);
this.localTypeWatcher = fs.watch(typePath!, async () => {
console.info(`TypeDefs: Noticed file change at ${typePath}. Updating editor typedefs.`);
await updateEditorTypeDefinitions(versionObject);
});
try {
this.localTypeWatcher = fs.watch(typePath!, async () => {
console.info(`TypeDefs: Noticed file change at ${typePath}. Updating editor typedefs.`);
await updateEditorTypeDefinitions(versionObject);
});
} catch (err) {
console.info('TypeDefs: Unable to start watching.');
}
} else {
if (!!this.localTypeWatcher) {
console.info(`TypeDefs: Switched to downloaded version ${version}. Unwatching local typedefs.`);
@@ -460,14 +504,23 @@ export class AppState {
* @returns {Promise<void>}
*/
@action public async updateDownloadedVersionState(): Promise<void> {
const downloadedVersions = await this.binaryManager.getDownloadedVersions();
const updatedVersions = { ...this.versions };
(downloadedVersions || []).forEach((version) => {
// Keep state of currently downloading binaries first
const downloadingVersions = getDownloadingVersions(this);
(downloadingVersions || []).forEach((version) => {
if (updatedVersions[version]) {
updatedVersions[version].state = ElectronVersionState.ready;
updatedVersions[version].state = VersionState.downloading;
}
});
const downloadedVersions = await getDownloadedVersions();
(downloadedVersions || []).forEach((version) => {
if (updatedVersions[version]) {
updatedVersions[version].state = VersionState.ready;
}
});
console.log(`State: Updated version state`, updatedVersions);
this.versions = updatedVersions;
@@ -502,7 +555,6 @@ export class AppState {
this.outputBuffer += strData;
strData = this.outputBuffer;
const parts = strData.split('\r\n');
for (let partIndex = 0; partIndex < parts.length; partIndex += 1) {
const part = parts[partIndex];
if (partIndex === parts.length - 1) {
@@ -555,7 +607,7 @@ export class AppState {
return null;
}
@action public setVisibleMosaics(visible: Array<MosaicId>) {
@action public async setVisibleMosaics(visible: Array<MosaicId>) {
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
for (const id of ALL_MOSAICS) {
@@ -563,6 +615,9 @@ export class AppState {
this.closedPanels[id] = isEditorId(id)
? getEditorBackup(id)
: true;
// if we have backup, remove active editor
delete window.ElectronFiddle.editors[id];
}
// Remove the backup for panels now. Editors will remove their
@@ -576,6 +631,12 @@ export class AppState {
console.log(`State: Setting visible mosaic panels`, visible, updatedArrangement);
this.mosaicArrangement = updatedArrangement;
// after the mosaicArrangement loads, we want to wait for the Mosaic editors to
// mount to ensure that we can load content into the editors as soon as they're
// declared visible.
await waitForEditorsToMount(visible);
}
/**
@@ -598,6 +659,15 @@ export class AppState {
this.setVisibleMosaics([...currentlyVisible, id]);
}
/**
* Resets editor view to default layout
*
*
*/
@action public resetEditorLayout() {
this.mosaicArrangement = DEFAULT_MOSAIC_ARRANGEMENT;
}
/**
* Resets the view, optionally with certain view flags enabled.
*
+3 -2
View File
@@ -2,7 +2,7 @@ import * as fsExtraType from 'fs-extra';
import * as pathType from 'path';
import { EditorValues } from '../interfaces';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME, STYLES_CSS_NAME } from '../shared-constants';
import { fancyImport } from '../utils/import';
/**
@@ -41,6 +41,7 @@ export async function getTemplateValues(name: string): Promise<EditorValues> {
renderer: await getFile(RENDERER_JS_NAME),
main: await getFile(MAIN_JS_NAME),
html: await getFile(INDEX_HTML_NAME),
preload: await getFile(PRELOAD_JS_NAME)
preload: await getFile(PRELOAD_JS_NAME),
css: await getFile(STYLES_CSS_NAME)
};
}
+3 -3
View File
@@ -5,7 +5,7 @@ import {
} from 'electron';
import { autorun } from 'mobx';
import { ElectronVersion, ElectronVersionState } from '../interfaces';
import { RunnableVersion, VersionState } from '../interfaces';
import { getNiceGreeting } from '../utils/nice-greeting';
import { sortedElectronMap } from '../utils/sorted-electron-map';
import { AppState } from './state';
@@ -22,11 +22,11 @@ const {
* Helper method: Returns an icon (emoji, lol) for an Electron
* version.
*
* @param {Partial<ElectronVersion>} { state }
* @param {Partial<RunnableVersion>} { state }
* @returns {string}
*/
export function getItemIcon(
{ state }: Partial<ElectronVersion> = { state: ElectronVersionState.unknown }
{ state }: Partial<RunnableVersion> = { state: VersionState.unknown }
) {
return state === 'ready'
? '💾'
+53 -55
View File
@@ -1,4 +1,5 @@
import { ElectronVersion, ElectronVersionSource, ElectronVersionState, NpmVersion } from '../interfaces';
import semver from 'semver';
import { RunnableVersion, Version, VersionSource, VersionState } from '../interfaces';
import { normalizeVersion } from '../utils/normalize-version';
export const enum ElectronReleaseChannel {
@@ -11,11 +12,11 @@ export const enum ElectronReleaseChannel {
/**
* Returns a sensible default version string.
*
* @param {Array<ElectronVersion>} knownVersions
* @param {Array<RunnableVersion>} knownVersions
* @returns {string}
*/
export function getDefaultVersion(
knownVersions: Array<ElectronVersion> = []
knownVersions: Array<RunnableVersion> = []
): string {
const ls = localStorage.getItem('version');
@@ -45,13 +46,12 @@ export function getDefaultVersion(
* Return the release channel for a given input
* version.
*
* @param {NpmVersion | string} input
* @param {Version | string} input
* @returns {ElectronReleaseChannel}
*/
export function getReleaseChannel(
input: NpmVersion | string
input: Version | string
): ElectronReleaseChannel {
const tag = (typeof input === 'string') ? input : (input.version || '');
if (tag.includes('beta')) {
@@ -79,17 +79,17 @@ export const enum VersionKeys {
* Retrieve Electron versions from localStorage.
*
* @param {VersionKeys} key
* @param {() => Array<NpmVersion>} fallbackMethod
* @returns {Array<NpmVersion>}
* @param {() => Array<Version>} fallbackMethod
* @returns {Array<Version>}
*/
function getVersions(
key: VersionKeys, fallbackMethod: () => Array<NpmVersion>
): Array<NpmVersion> {
key: VersionKeys, fallbackMethod: () => Array<Version>
): Array<Version> {
const fromLs = window.localStorage.getItem(key);
if (fromLs) {
try {
let result: Array<NpmVersion> = JSON.parse(fromLs);
let result: Array<Version> = JSON.parse(fromLs);
if (!isExpectedFormat(result)) {
// Known versions can just be downloaded again.
@@ -115,9 +115,9 @@ function getVersions(
* Save an array of GitHubVersions to localStorage.
*
* @param {VersionKeys} key
* @param {Array<NpmVersion} versions
* @param {Array<Version} versions
*/
function saveVersions(key: VersionKeys, versions: Array<NpmVersion>) {
function saveVersions(key: VersionKeys, versions: Array<Version>) {
const stringified = JSON.stringify(versions);
window.localStorage.setItem(key, stringified);
}
@@ -125,35 +125,35 @@ function saveVersions(key: VersionKeys, versions: Array<NpmVersion>) {
/**
* Return both known as well as local versions.
*
* @returns {Array<NpmVersion>}
* @returns {Array<Version>}
*/
export function getElectronVersions(): Array<ElectronVersion> {
const known: Array<ElectronVersion> = getKnownVersions().map((version) => {
export function getElectronVersions(): Array<RunnableVersion> {
const known: Array<RunnableVersion> = getKnownVersions().map((version) => {
return {
...version,
source: ElectronVersionSource.remote,
state: ElectronVersionState.unknown
source: VersionSource.remote,
state: VersionState.unknown
};
});
const local: Array<ElectronVersion> = getLocalVersions().map((version) => {
const local: Array<RunnableVersion> = getLocalVersions().map((version) => {
return {
...version,
source: ElectronVersionSource.local,
state: ElectronVersionState.ready
source: VersionSource.local,
state: VersionState.ready
};
});
return [ ...known, ...local ];
return [...known, ...local];
}
/**
* Add a version to the local versions
*
* @param {NpmVersion} input
* @returns {Array<NpmVersion>}
* @param {Version} input
* @returns {Array<Version>}
*/
export function addLocalVersion(input: NpmVersion): Array<NpmVersion> {
export function addLocalVersion(input: Version): Array<Version> {
const versions = getLocalVersions();
if (!versions.find((v) => v.localPath === input.localPath)) {
@@ -168,9 +168,9 @@ export function addLocalVersion(input: NpmVersion): Array<NpmVersion> {
/**
* Retrieves local Electron versions, configured by the user.
*
* @returns {Array<NpmVersion>}
* @returns {Array<Version>}
*/
export function getLocalVersions(): Array<NpmVersion> {
export function getLocalVersions(): Array<Version> {
const versions = getVersions(VersionKeys.local, () => []);
return versions;
@@ -179,12 +179,12 @@ export function getLocalVersions(): Array<NpmVersion> {
/**
* Saves local versions to localStorage.
*
* @param {Array<NpmVersion>} versions
* @param {Array<Version>} versions
*/
export function saveLocalVersions(versions: Array<NpmVersion | ElectronVersion>) {
export function saveLocalVersions(versions: Array<Version | RunnableVersion>) {
const filteredVersions = versions.filter((v) => {
if (isElectronVersion(v)) {
return v.source === ElectronVersionSource.local;
return v.source === VersionSource.local;
}
return true;
@@ -197,18 +197,18 @@ export function saveLocalVersions(versions: Array<NpmVersion | ElectronVersion>)
* Retrieves our best guess regarding the latest Electron versions. Tries to
* fetch them from localStorage, then from a static releases.json file.
*
* @returns {Array<NpmVersion>}
* @returns {Array<Version>}
*/
export function getKnownVersions(): Array<NpmVersion> {
export function getKnownVersions(): Array<Version> {
return getVersions(VersionKeys.known, () => require('../../static/releases.json'));
}
/**
* Saves known versions to localStorage.
*
* @param {Array<NpmVersion>} versions
* @param {Array<Version>} versions
*/
export function saveKnownVersions(versions: Array<NpmVersion>) {
export function saveKnownVersions(versions: Array<Version>) {
return saveVersions(VersionKeys.known, versions);
}
@@ -217,10 +217,10 @@ export function saveKnownVersions(versions: Array<NpmVersion>) {
* saved after.
*
* @export
* @returns {Promise<Array<ElectronVersion>>}
* @returns {Promise<Array<RunnableVersion>>}
*/
export async function getUpdatedElectronVersions(
): Promise<Array<ElectronVersion>> {
): Promise<Array<RunnableVersion>> {
try {
await fetchVersions();
} catch (error) {
@@ -233,23 +233,21 @@ export async function getUpdatedElectronVersions(
/**
* Fetch the latest known versions directly from npm.
*
* @returns {Promise<Array<NpmVersion>>}
* @returns {Promise<Array<Version>>}
*/
export async function fetchVersions() {
const channels = [
`https://registry.npmjs.org/electron`, // stable, beta
`https://registry.npmjs.org/electron-nightly` // nightly
];
const response = await window.fetch('https://unpkg.com/electron-releases/lite.json');
const data = await response.json();
const output: Array<NpmVersion> = [];
for (const channelUrl of channels) {
const response = await window.fetch(channelUrl);
const data = await response.json();
const versions: Record<string, any> = data.versions;
output.push(...Object.keys(versions).map((version) => ({ version })));
}
// pre-0.24.0 versions were technically 'atom-shell' and cannot
// be downloaded with @electron/get
const MIN_DOWNLOAD_VERSION = '0.24.0';
if (output && output.length > 0 && isExpectedFormat(output)) {
const output = data
.map(({ version }: any) => ({ version }))
.filter(({ version }: any) => semver.gte(version, MIN_DOWNLOAD_VERSION));
if (output?.length > 0 && isExpectedFormat(output)) {
console.log(`Fetched new Electron versions (Count: ${output.length})`);
saveKnownVersions(output);
}
@@ -271,9 +269,9 @@ export function isExpectedFormat(input: Array<any>): boolean {
* Migrates old versions, if necessary
*
* @param {Array<any>} input
* @returns {Array<NpmVersion>}
* @returns {Array<Version>}
*/
export function migrateVersions(input: Array<any> = []): Array<NpmVersion> {
export function migrateVersions(input: Array<any> = []): Array<Version> {
return input
.filter((item) => !!item)
.map((item) => {
@@ -287,11 +285,11 @@ export function migrateVersions(input: Array<any> = []): Array<NpmVersion> {
localPath: url
};
})
.filter((item) => !!item) as Array<NpmVersion>;
.filter((item) => !!item) as Array<Version>;
}
export function isElectronVersion(
input: NpmVersion | ElectronVersion
): input is ElectronVersion {
return (input as ElectronVersion).source !== undefined;
input: Version | RunnableVersion
): input is RunnableVersion {
return (input as RunnableVersion).source !== undefined;
}
+7
View File
@@ -0,0 +1,7 @@
import * as Sentry from '@sentry/electron';
export function initSentry() {
if (!(global as any).__JEST__) {
Sentry.init({dsn: 'https://966a5b01ac8d4941b81e4ebd0ab4c991@sentry.io/1882540'});
}
}
+1
View File
@@ -2,4 +2,5 @@ export const INDEX_HTML_NAME = 'index.html';
export const MAIN_JS_NAME = 'main.js';
export const RENDERER_JS_NAME = 'renderer.js';
export const PRELOAD_JS_NAME = 'preload.js';
export const STYLES_CSS_NAME = 'styles.css';
export const PACKAGE_NAME = 'package.json';
-16
View File
@@ -1,16 +0,0 @@
import * as path from 'path';
import { remote } from 'electron';
let appData = '';
/**
* Returns an appData path for a given input
*
* @param input {string}
* @returns {string}
*/
export function getAppDataDir(input: string): string {
appData = appData || remote.app.getPath('appData');
return path.join(appData, input);
}
+5 -5
View File
@@ -1,16 +1,16 @@
import { ElectronVersion } from '../interfaces';
import { RunnableVersion } from '../interfaces';
import { normalizeVersion } from './normalize-version';
/**
* Takes an array of GitHub releases and returns a StringMap of
* Electron releases.
*
* @param {Array<ElectronVersion>} input
* @returns {Record<string, ElectronVersion>}
* @param {Array<RunnableVersion>} input
* @returns {Record<string, RunnableVersion>}
*/
export function arrayToStringMap(
input: Array<ElectronVersion>
): Record<string, ElectronVersion> {
input: Array<RunnableVersion>
): Record<string, RunnableVersion> {
const output = {};
input.forEach((version) => {
+3 -3
View File
@@ -6,9 +6,9 @@ import { getEditorValue } from './editor-value';
import { getEditorViewState } from './editor-viewstate';
export interface EditorBackup {
value: string;
model: editor.ITextModel | null;
viewState: editor.ICodeEditorViewState | null;
value?: string;
model?: editor.ITextModel | null;
viewState?: editor.ICodeEditorViewState | null;
}
/**
+1 -1
View File
@@ -12,7 +12,7 @@ export function getEditorModel(id: EditorId): editor.ITextModel | null {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
return null;
}
if (fiddle.editors[id] && fiddle.editors[id]!.getModel) {
+24
View File
@@ -0,0 +1,24 @@
import { MosaicId } from '../interfaces';
/**
* Waits for editors to mount on a list of Mosaic IDs
* @param editors
*/
export function waitForEditorsToMount(editors: Array<MosaicId>) {
let time = 0;
const maxTime = 3000;
const interval = 100;
return new Promise((resolve, reject) => {
(function checkMountedEditors() {
const allMounted = editors.every((id) => !!window.ElectronFiddle.editors[id]);
if (allMounted) {
return resolve();
}
time += interval;
if (time > maxTime) {
return reject(`Timed out after ${maxTime}ms: can't mount editors onto mosaics.`);
}
setTimeout(checkMountedEditors, 100);
})();
});
}
+8 -2
View File
@@ -1,4 +1,5 @@
import { EditorId } from '../interfaces';
import { EditorBackup } from './editor-backup';
/**
* Return the value for a given editor
@@ -10,11 +11,16 @@ export function getEditorValue(id: EditorId): string {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
return '';
}
if (fiddle.editors[id] && fiddle.editors[id]!.getValue) {
const editor = fiddle.editors[id];
const backup = fiddle.app?.state?.closedPanels[id] as EditorBackup;
if (editor?.getValue) {
return fiddle.editors[id]!.getValue();
} else if (backup?.value) {
return backup.value;
}
return '';
+1 -1
View File
@@ -13,7 +13,7 @@ export function getEditorViewState(id: EditorId): editor.ICodeEditorViewState |
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
return null;
}
if (fiddle.editors[id] && fiddle.editors[id]!.saveViewState) {
+4 -4
View File
@@ -1,8 +1,8 @@
import * as GitHubType from '@octokit/rest';
import { Octokit } from '@octokit/rest';
import { AppState } from '../renderer/state';
let _Octokit: typeof GitHubType;
let _octo: GitHubType;
let _Octokit: typeof Octokit;
let _octo: Octokit;
/**
* Returns a loaded Octokit. If state is passed and authentication
@@ -13,7 +13,7 @@ let _octo: GitHubType;
*/
export async function getOctokit(
appState?: AppState
): Promise<GitHubType> {
): Promise<Octokit> {
_Octokit = _Octokit || (await import('@octokit/rest') as any).default;
_octo = _octo || new _Octokit();
+5 -5
View File
@@ -1,18 +1,18 @@
import * as semver from 'semver';
import { ElectronVersion } from '../interfaces';
import { RunnableVersion } from '../interfaces';
/**
* Sorts Electron versions and returns the result of a
* map function.
*
* @param {Record<string, ElectronVersion>} versions
* @param {(key: string, version: ElectronVersion) => void} mapFn
* @param {Record<string, RunnableVersion>} versions
* @param {(key: string, version: RunnableVersion) => void} mapFn
* @returns {Array<T>}
*/
export function sortedElectronMap<T>(
versions: Record<string, ElectronVersion>,
mapFn: (key: string, version: ElectronVersion) => T
versions: Record<string, RunnableVersion>,
mapFn: (key: string, version: RunnableVersion) => T
) {
return Object.keys(versions)
.sort((a, b) => {
+4
View File
@@ -8,5 +8,9 @@
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -13,5 +13,5 @@ app.on('browser-window-created', () => console.log('A window was created!'))
app.on('browser-window-focus', () => console.log('...and focused!'))
console.log(`The app lives at: ${app.getAppPath()}`)
console.log(`It's named: ${app.getName()}`)
console.log(`It's named: ${app.name}`)
console.log(`It has the version: ${app.getVersion()}`)
-4
View File
@@ -25,10 +25,6 @@ app.on('ready', () => {
console.log('The autoUpdater is checking for an update')
})
autoUpdater.on('update-available', () => {
console.log('The autoUpdater has found an update!')
})
autoUpdater.on('update-available', () => {
console.log('The autoUpdater has found an update and is now downloading it!')
})
+1 -3
View File
@@ -5,10 +5,8 @@
const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
+1 -3
View File
@@ -5,10 +5,8 @@
const { app, BrowserWindow, session } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
+1 -3
View File
@@ -5,10 +5,8 @@
const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
+1 -3
View File
@@ -8,10 +8,8 @@
const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
const mainWindow = new BrowserWindow({ height: 600, width: 600 })
mainWindow.loadFile('index.html')
+1 -3
View File
@@ -6,10 +6,8 @@
const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
+1 -3
View File
@@ -5,10 +5,8 @@
const { app, BrowserWindow, dialog } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
const mainWindow = new BrowserWindow({ height: 600, width: 600 })
// Show an "Open File" dialog and attempt to open
// the chosen file in our window.
+1 -3
View File
@@ -7,10 +7,8 @@
const { app, BrowserWindow, ipcMain } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
+1 -1
View File
@@ -4,6 +4,6 @@ const { ipcRenderer } = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
// prints "pong"
ipcRenderer.on('asynchronous-reply', (...args) => console.log(args))
ipcRenderer.on('asynchronous-reply', (_, ...args) => console.log(...args))
ipcRenderer.send('asynchronous-message', 'ping')
+1 -3
View File
@@ -5,10 +5,8 @@
const { app, BrowserWindow, Menu } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
const mainWindow = new BrowserWindow({ height: 600, width: 600 })
mainWindow.loadFile('index.html')
const template = [
+1 -3
View File
@@ -6,10 +6,8 @@
const { app, BrowserWindow, Notification } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
+1 -3
View File
@@ -5,10 +5,8 @@
const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
+1 -3
View File
@@ -5,8 +5,6 @@
const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
// We cannot require the screen module until the
// app is ready
@@ -17,7 +15,7 @@ app.on('ready', () => {
const primaryDisplay = screen.getPrimaryDisplay()
const { width, height } = primaryDisplay.workAreaSize
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
width,
height,
webPreferences: {
+1 -3
View File
@@ -5,10 +5,8 @@
const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
+1 -3
View File
@@ -6,10 +6,8 @@
const { app, BrowserWindow, TouchBar } = require('electron')
const { TouchBarLabel, TouchBarButton, TouchBarSpacer } = TouchBar
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
const mainWindow = new BrowserWindow({ height: 600, width: 600 })
mainWindow.loadFile('index.html')
// This API only works on macOS devices with a TouchBar.
+1 -3
View File
@@ -5,10 +5,8 @@
const { app, BrowserWindow, webContents } = require('electron')
let mainWindow
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
const mainWindow = new BrowserWindow({ height: 600, width: 600 })
mainWindow.loadFile('index.html')
setTimeout(() => {
+1 -3
View File
@@ -5,10 +5,8 @@
const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({
const mainWindow = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
+12
View File
@@ -0,0 +1,12 @@
import { EditorId } from '../../src/interfaces';
import { App as AppType } from '../../src/renderer/app';
import { EditorMock } from './mocks/editors';
declare global {
interface Window {
ElectronFiddle: {
app: AppType;
editors: Record<EditorId, EditorMock>;
};
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"expectedVersionCount": 371,
"lastElectronVersion": "10.0.0-beta.8"
}
+7
View File
@@ -10,3 +10,10 @@ declare global {
};
}
}
interface Window {
ElectronFiddle: {
app: AppType;
editors: Record<EditorId, EditorMock>;
};
}
+4
View File
@@ -1,3 +1,7 @@
/**
* @jest-environment node
*/
import { createContextMenu, getInspectItems, getMonacoItems, getRunItems } from '../../src/main/context-menu';
import { ipcMainManager } from '../../src/main/ipc';
import { isDevMode } from '../../src/utils/devmode';
+5
View File
@@ -1,5 +1,10 @@
/**
* @jest-environment node
*/
import { setupDevTools } from '../../src/main/devtools';
import { isDevMode } from '../../src/utils/devmode';
jest.mock('../../src/utils/devmode');
jest.mock('electron-devtools-installer', () => ({
+4
View File
@@ -1,3 +1,7 @@
/**
* @jest-environment node
*/
import { IpcEvents } from '../../src/ipc-events';
import { setupDialogs } from '../../src/main/dialogs';
import { ipcMainManager } from '../../src/main/ipc';
+4
View File
@@ -1,3 +1,7 @@
/**
* @jest-environment node
*/
import { IpcEvents } from '../../src/ipc-events';
import {
setupFileListeners,
+4
View File
@@ -1,3 +1,7 @@
/**
* @jest-environment node
*/
import { app, dialog } from 'electron';
import { onFirstRunMaybe } from '../../src/main/first-run';
+4
View File
@@ -1,3 +1,7 @@
/**
* @jest-environment node
*/
import * as electron from 'electron';
import { IpcEvents } from '../../src/ipc-events';
import { ipcMainManager } from '../../src/main/ipc';
+23 -4
View File
@@ -1,6 +1,12 @@
/**
* @jest-environment node
*/
import { app } from 'electron';
import { main, onBeforeQuit, onReady, onWindowsAllClosed } from '../../src/main/main';
import { IpcEvents } from '../../src/ipc-events';
import { ipcMainManager } from '../../src/main/ipc';
import { main, onBeforeQuit, onReady, onWindowsAllClosed, quitAppIfConfirmed } from '../../src/main/main';
import { shouldQuit } from '../../src/main/squirrel';
import { setupUpdates } from '../../src/main/update';
import { getOrCreateMainWindow } from '../../src/main/windows';
@@ -22,6 +28,8 @@ jest.mock('../../src/main/squirrel', () => ({
shouldQuit: jest.fn(() => false)
}));
jest.mock('../../src/main/ipc');
/**
* This test is very basic and some might say that it's
* just testing that methods are being called as written.
@@ -58,11 +66,22 @@ describe('main', () => {
});
describe('onBeforeQuit()', () => {
it('sets a global', () => {
it('sets up IPC so app can quit if dialog confirmed', () => {
onBeforeQuit();
expect(ipcMainManager.send).toHaveBeenCalledWith(IpcEvents.BEFORE_QUIT);
expect(ipcMainManager.on).toHaveBeenCalledWith(IpcEvents.CONFIRM_QUIT, quitAppIfConfirmed);
});
});
expect((global as any).isQuitting).toBe(true);
(global as any).isQuitting = false;
describe('quitAppIfConfirmed()', () => {
it('quits app if passed a truthy value', () => {
quitAppIfConfirmed({} as any, true);
expect(app.quit).toHaveBeenCalledTimes(1);
});
it('does nothing if passed a value', () => {
quitAppIfConfirmed({} as any, false);
expect(app.quit).toHaveBeenCalledTimes(0);
});
});

Some files were not shown because too many files have changed in this diff Show More