Compare commits

...
Author SHA1 Message Date
Felix Rieseberg 26e054702a fix: Incorrect dropdowns 2019-12-12 14:23:11 -05:00
Felix Rieseberg 16f27132cb Revert "First basis for having Node in there, too"
This reverts commit 7613176f69.
2019-12-12 14:06:34 -05:00
Felix RiesebergandTierney Cyren 7613176f69 First basis for having Node in there, too
Co-authored-by: Tierney Cyren <ticyren@microsoft.com>
2019-12-11 12:19:16 -05:00
dependabot[bot] 470bea4fca build(deps): bump serialize-to-js from 3.0.0 to 3.0.1 (#280)
Bumps [serialize-to-js](https://github.com/commenthol/serialize-to-js) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/commenthol/serialize-to-js/releases)
- [Commits](https://github.com/commenthol/serialize-to-js/compare/v3.0.0...v3.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-10 00:30:59 -08:00
Felix Rieseberg 363febdb73 Merge pull request #279 from electron/notarize
build: Notarize the build with Apple
2019-12-04 13:43:38 -08:00
Felix Rieseberg 062ca4e205 build: Update to electron-forge b46 2019-12-04 10:44:13 -08:00
Felix Rieseberg 49053342a5 Merge pull request #278 from electron/electron-713
chore: Update Electron to 7.1.3
2019-12-04 10:09:10 -08:00
Felix Rieseberg fd582c67a8 build: Use a later macOS image 2019-12-03 18:08:51 -08:00
Felix Rieseberg 706b18808b build: Notarize the build with Apple 2019-12-03 17:52:52 -08:00
Felix Rieseberg a6ae8d5376 chore: Update Electron to 7.1.3 2019-12-03 17:45:02 -08:00
Erick Zhao 5034f8c853 feat: add bisect helper (#257) 2019-11-22 18:46:58 -05:00
Letra e916247013 fix: ensure dev tools only open in dev mode (#277) 2019-11-15 09:22:14 -05:00
Felix Rieseberg c052686de4 0.11.1 2019-11-12 14:39:38 +01:00
Erick Zhao 31aacef131 fix: update folder selection dialog for Electron 7 (#271) 2019-11-05 16:51:04 -05:00
Erick Zhao 932369b705 fix: update deprecated app.setName API (#273) 2019-11-05 16:24:43 -05:00
Shelley Vohr b835c34786 fix: update dialog example for promisification (#272) 2019-11-03 11:36:00 -05:00
Felix Rieseberg c9e1ba815a 0.11.0 2019-10-31 11:49:23 -04:00
Felix Rieseberg 374a85e2f6 Merge pull request #267 from electron/updates-updates
feat: Electron 7 (and more updates)
2019-10-27 11:26:14 -07:00
Felix Rieseberg 4282d0b93d fix: Better typing 2019-10-27 11:16:55 -07:00
Felix Rieseberg 549862b3bb chore: npm audit fix 2019-10-27 11:13:02 -07:00
Felix Rieseberg 4691c1b494 chore: Use a lockfile with latest npm 2019-10-27 11:10:36 -07:00
Felix Rieseberg 8125abc657 build: Update AppVeyor to Node 12 2019-10-27 11:01:39 -07:00
Felix Rieseberg 8cba7739f5 fix: Prepare for Electron 7 2019-10-25 11:59:05 -07:00
Felix Rieseberg 0b39fe40b1 chore: Update all the packages 2019-10-25 11:37:34 -07:00
Felix Rieseberg cae078ffe6 Merge pull request #266 from electron/ignored-commented-out-modules
fix: don't install commented-out requires
2019-10-15 18:14:33 -07:00
Ankit Arun bfb931435f fix: add whitespace to Welcome Tour (#263) 2019-10-14 20:48:40 -04:00
Shelley Vohr c878f72f2e fix: don't install commented-out requires 2019-10-14 13:59:00 -07:00
Felix Rieseberg 32494c057a Merge pull request #254 from electron/preload
feat: Enable support for a `preload.js`
2019-10-05 14:52:58 -10:00
Jason Zeng e732685fff fix: link to docs for content tracing show-me (#256) 2019-09-25 10:16:44 -04:00
Felix Rieseberg d9d5e0cbc9 test: Fix the tests 2019-09-17 16:14:38 -07:00
Felix Rieseberg 0e1d7b541e feat: If a file is newly empty, remove it 2019-09-17 15:59:27 -07:00
Felix Rieseberg 8a602d94b7 feat: Enable support for a preload.js 2019-09-17 15:50:49 -07:00
Erick Zhao 155889dbcc fix: ensure state consistency when replacing Fiddle (#253) 2019-09-16 14:53:53 -04:00
Felix Rieseberg 56ac6662b6 Merge pull request #248 from electron/add-new-themes
feat: ability to add new editor themes to Fiddle
2019-09-10 13:40:08 -04:00
Felix Rieseberg adb196549b chore: Bump version to 0.10 2019-09-08 15:45:59 -07:00
Felix Rieseberg 999facf062 Merge pull request #250 from electron/fix-console-stuff
fix: Console button / display issues
2019-09-08 15:39:55 -07:00
Felix Rieseberg fa0115f2db fix: Correctly toggle console state 2019-09-08 15:29:15 -07:00
Carlos Valdez c0a4c0e48d remove unused import 2019-09-07 11:05:13 -05:00
Carlos Valdez 3a5d2205fe remove unused imports 2019-09-07 11:03:27 -05:00
Carlos Valdez 1083b8f859 add warning dialog 2019-09-07 11:03:11 -05:00
Carlos Valdez 252d4c1de6 remove old code (yikes) 2019-09-07 10:25:10 -05:00
Carlos Valdez 0c44d2c9e4 remove check 2019-09-05 23:54:11 -05:00
Carlos Valdez 5eebc06514 add if statement inside try catch 2019-09-05 18:27:04 -05:00
Carlos Valdez eac1423177 remove comment 2019-09-05 18:18:44 -05:00
Carlos Valdez ae88780281 remove comment 2019-09-05 18:07:56 -05:00
Carlos Valdez 4bf9d48d70 remove commet 2019-09-05 18:07:36 -05:00
Carlos Valdez df6d4b5a9a remove monaco from observable variable name 2019-09-05 17:41:49 -05:00
Carlos Valdez 68202e2b23 change file name and change import 2019-09-05 17:23:40 -05:00
Felix Rieseberg acd5b35ca1 fix: Console button / display issues 2019-08-31 07:07:20 -07:00
Felix Rieseberg fae1ca3138 Merge pull request #249 from electron/update-dependencies
chore: Update all dependencies
2019-08-31 00:25:17 +02:00
Felix Rieseberg bd220d2690 chore: Update more dependencies 2019-08-30 15:07:07 -07:00
Felix Rieseberg d31f1016b8 test: Update all the tests 2019-08-30 15:05:07 -07:00
Felix Rieseberg 3b52be0f79 chore: Update templates for Standard 2019-08-28 09:44:10 +02:00
Felix Rieseberg 0e11c56f7f chore: Update all dependencies 2019-08-28 09:43:52 +02:00
Carlos Valdez 8019981d1a missing ; 2019-08-19 13:29:09 -07:00
Carlos Valdez 403a58e216 update snapshot 2019-08-19 13:28:42 -07:00
Carlos Valdez 3341d0ccaa fix json parsing 2019-08-19 11:13:32 -07:00
Carlos Valdez d42e83100a need to work on successful input 2019-08-16 17:58:27 -07:00
Carlos Valdez 50e9883d82 add new popup using react! 😰 2019-08-16 17:37:22 -07:00
Carlos Valdez f5aedac6a3 change wording of import monaco themes and add better formatting 2019-08-15 11:09:13 -07:00
Carlos Valdez a3f4aa8ed3 remove old code 2019-08-15 10:02:30 -07:00
Carlos Valdez 9c839aaad8 remove sad code 2019-08-15 09:57:30 -07:00
Carlos Valdez 3a7ca8ccd5 Merge branch 'add-new-themes' of github.com:electron/fiddle into add-new-themes 2019-08-15 09:56:15 -07:00
Carlos Valdez fef42293bc account for user checking an incorrect file 2019-08-15 09:56:02 -07:00
Felix Rieseberg 1f9db42c80 Merge pull request #247 from electron/feat/local-version-intellisense
feat: intellisense for local builds
2019-08-14 14:51:18 -07:00
Carlos Valdez 8e5657ef2d the coolest recursive function thanks to Samuel 2019-08-14 13:34:43 -07:00
Carlos Valdez a94105dd27 the coolest recursive function thanks to Samuel 2019-08-14 13:34:29 -07:00
Carlos Valdez cf214dc0d0 remove bad method 2019-08-14 11:03:54 -07:00
Carlos Valdez 6dfdf00437 add in new function and button 2019-08-14 11:03:36 -07:00
Carlos Valdez b3743a4c73 Merge branch 'add-new-themes' of github.com:electron/fiddle into add-new-themes 2019-08-13 17:07:15 -07:00
Carlos Valdez 3c0d6c2293 fix my mess 2019-08-13 17:06:23 -07:00
CarlosandErick Zhao 89b589fd2e Update tests/renderer/components/__snapshots__/settings-general-appearance-spec.tsx.snap
Co-Authored-By: Erick Zhao <erick@hotmail.ca>
2019-08-13 16:53:48 -07:00
CarlosandErick Zhao 42c577988d Update src/renderer/components/settings-general-appearance.tsx
Co-Authored-By: Erick Zhao <erick@hotmail.ca>
2019-08-13 16:53:37 -07:00
CarlosandErick Zhao d0e47b6b4d Update src/renderer/components/settings-general-appearance.tsx
Co-Authored-By: Erick Zhao <erick@hotmail.ca>
2019-08-13 16:41:34 -07:00
CarlosandErick Zhao f44a127012 Update tests/renderer/components/__snapshots__/settings-general-appearance-spec.tsx.snap
Co-Authored-By: Erick Zhao <erick@hotmail.ca>
2019-08-13 16:41:22 -07:00
Carlos Valdez fa2f5454ce don't throw! 2019-08-13 11:24:12 -07:00
Carlos Valdez 1021bb8b2b remove console.warn 2019-08-13 10:34:40 -07:00
Carlos Valdez cf4ad36ca2 remove optional arg 2019-08-13 10:33:38 -07:00
Carlos Valdez 4fe9c627b5 if json has name, set that as the filename otherwise take the filename 2019-08-13 10:25:21 -07:00
Carlos Valdez 262e636e8d update snapshot 2019-08-12 17:53:26 -07:00
Carlos Valdez bbe79ed6e2 added description 2019-08-12 15:32:46 -07:00
Carlos Valdez bc96a5013a added new VS Code option 2019-08-12 15:32:37 -07:00
Erick Zhao 66ba7b74d5 test: add spec 2019-08-12 10:13:08 -07:00
Erick Zhao 38e5aa98e3 test: remove '.only' 2019-08-09 13:06:28 -07:00
Erick Zhao a91504b173 feat: local typedefs 2019-08-09 13:05:07 -07:00
Erick Zhao 4685e88c86 style: run linter 2019-08-09 11:27:08 -07:00
Erick Zhao e0bb82d919 fix: throw appropriately if cannot fetch types 2019-08-08 15:43:14 -07:00
Erick Zhao 7035be3286 fix: adjust styling to avoid hiding intellisense (#243) 2019-07-30 10:43:44 -07:00
Erick Zhao c961cfadb8 chore: merge in settings UX improvements 2019-07-30 10:43:10 -07:00
Erick Zhao 7267a2ee88 fix: load empty files upon opening a fiddle (#241)
* fix: load empty files upon opening a fiddle
* test: add spec for no value passed to setValue
2019-07-30 10:42:16 -07:00
Erick Zhao bc03382dc5 test: test for event listener removal upon unmount 2019-07-29 08:48:59 -07:00
Erick Zhao 891c33ba68 fix: refresh theme list upon creation 2019-07-28 20:02:35 -07:00
Erick Zhao e7260c74a4 feat: close settings panel on Esc 2019-07-28 20:02:35 -07:00
Erick Zhao ed694a7044 chore: describe correct component in spec 2019-07-28 20:02:35 -07:00
Erick Zhao 14fc77c08c chore: 'Exection' -> 'Execution' 2019-07-28 20:02:35 -07:00
Erick Zhao f3079648cf fix: clean up typedefs when removing binaries (#240) 2019-07-26 18:03:57 -07:00
Felix Rieseberg 7272ec7899 Merge pull request #239 from electron/fix/consistent-tab-size
fix: enforce 2-space indents in all editors
2019-07-26 10:25:46 -07:00
Erick Zhao ac9ac701f7 test: add spec for forced tab size 2019-07-25 13:16:56 -07:00
Erick Zhao 7fce572823 fix: enforce tab-size 2 2019-07-25 12:40:53 -07:00
Erick Zhao 6186a80dad chore: 'monoaco' -> 'monaco' (#238) 2019-07-25 11:53:20 -07:00
Mark Lee ade6576285 build: upgrade Electron Forge to 6.0.0-beta.42 (#234) 2019-07-17 23:22:06 -07:00
Felix Rieseberg 61c58277f3 Merge pull request #233 from electron/chore/toogle
chore: `toogle` -> `toggle`
2019-07-17 19:48:00 -07:00
Erick Zhao c361d34a6d chore: toogle -> toggle 2019-07-16 17:54:02 -07:00
Erick Zhao 5887a6615a Merge pull request #232 from electron/feat/protocol-force-electron-version
feat: force electron version when loading from example protocol
2019-07-16 10:34:05 -07:00
Erick Zhao 7ba4cf5be7 style: run linter 2019-07-15 16:47:36 -07:00
Erick Zhao 0daf872d2b chore: conflict resolution w/ confirm prompt 2019-07-15 16:34:05 -07:00
Erick Zhao 75ec8ee9b1 chore: merge in master 2019-07-15 16:24:00 -07:00
Erick Zhao 183c152e40 test: create spec for remote-loader 2019-07-15 16:18:04 -07:00
Erick Zhao d6a807ba41 refactor: move version checking to its own fn 2019-07-15 15:23:30 -07:00
Carlos 24e0abf667 Merge pull request #230 from electron/warning-prompt
feat: Warning prompt
2019-07-15 14:05:37 -07:00
Carlos Valdez eb8261e240 remove obsolete snapshot 2019-07-15 13:38:20 -07:00
Erick Zhao 84cc07da90 feat: load examples with specific electron version 2019-07-15 13:21:44 -07:00
Erick Zhao b0f3837b47 refactor: move remote loading to its own file 2019-07-15 13:20:56 -07:00
Carlos Valdez e29664ca45 fix added event spec 2019-07-15 13:18:55 -07:00
Carlos Valdez ef36ee39f6 - 2019-07-15 11:14:56 -07:00
Carlos Valdez 73dadb815e removed spec test 2019-07-15 11:14:45 -07:00
Carlos Valdez 4945501703 fix test spec 2019-07-15 11:02:08 -07:00
Erick Zhao 77c87fbdeb feat: console resizing (#226)
Adds a `react-mosaic` wrapper to the console component to enable resizing in a similar
fashion to the editor components.
2019-07-15 10:33:01 -07:00
Carlos Valdez 2ef5b42f4a solved conflicts on git rebase 2019-07-15 10:31:07 -07:00
Carlos Valdez 9c4bc2ba52 fix test-spec failing 2019-07-15 10:30:31 -07:00
Carlos Valdez 80da4e73e3 fixed specs 2019-07-15 10:30:31 -07:00
Carlos Valdez eadd58d149 fix test 2019-07-15 10:28:09 -07:00
Carlos Valdez 1fc82f8f6d update specs 2019-07-15 10:28:09 -07:00
CarlosandFelix Rieseberg 9bd7043b2c Update src/renderer/components/dialog-confirm.tsx
Co-Authored-By: Felix Rieseberg <felix@felixrieseberg.com>
2019-07-15 10:28:09 -07:00
Carlos aaeef6fd06 Update commands-address-bar.tsx 2019-07-15 10:28:09 -07:00
CarlosandFelix Rieseberg 1195902bd5 Update src/renderer/components/dialog-warning.tsx
Co-Authored-By: Felix Rieseberg <felix@felixrieseberg.com>
2019-07-15 10:28:09 -07:00
CarlosandFelix Rieseberg c022d0f33d Update src/renderer/components/dialog-warning.tsx
Co-Authored-By: Felix Rieseberg <felix@felixrieseberg.com>
2019-07-15 10:28:09 -07:00
CarlosandFelix Rieseberg 996ae775f3 Update src/renderer/components/commands-address-bar.tsx
Co-Authored-By: Felix Rieseberg <felix@felixrieseberg.com>
2019-07-15 10:28:09 -07:00
Carlos Valdez ed44dda8fb better label 2019-07-15 10:28:09 -07:00
Carlos Valdez effba13e73 add to dialog class 2019-07-15 10:28:09 -07:00
Carlos Valdez 65886c3b52 added dialog-confirm prompt for users 2019-07-15 10:28:09 -07:00
Carlos Valdez 8bd4dd366a fix test-spec failing 2019-07-15 10:16:46 -07:00
Erick Zhao 4e19578e34 feat: add support for unchecked release channels 2019-07-14 18:45:18 -07:00
Erick Zhao c7fd0bc95e feat: force electron version when load w/ protocol 2019-07-14 18:45:18 -07:00
Erick Zhao 29e2f03b1a feat: allow version utility fn to accept a string 2019-07-14 18:45:18 -07:00
Felix Rieseberg 4cd6f330da Merge pull request #227 from electron/deermichel/error-msg
chore: add offline notice when loading fiddles
2019-07-13 16:10:27 -07:00
Carlos Valdez 79521eac7c fixed specs 2019-07-12 16:03:28 -07:00
Carlos Valdez 2736499d68 fix test 2019-07-12 15:36:14 -07:00
Carlos Valdez 1cbb781e8d update specs 2019-07-12 15:25:02 -07:00
CarlosandFelix Rieseberg e849a49f6f Update src/renderer/components/dialog-confirm.tsx
Co-Authored-By: Felix Rieseberg <felix@felixrieseberg.com>
2019-07-12 14:48:40 -07:00
Carlos 0114d2c1eb Update commands-address-bar.tsx 2019-07-12 14:48:09 -07:00
CarlosandFelix Rieseberg 365537012f Update src/renderer/components/dialog-warning.tsx
Co-Authored-By: Felix Rieseberg <felix@felixrieseberg.com>
2019-07-12 14:45:00 -07:00
CarlosandFelix Rieseberg 3e435cb64d Update src/renderer/components/dialog-warning.tsx
Co-Authored-By: Felix Rieseberg <felix@felixrieseberg.com>
2019-07-12 14:44:55 -07:00
CarlosandFelix Rieseberg 6981382793 Update src/renderer/components/commands-address-bar.tsx
Co-Authored-By: Felix Rieseberg <felix@felixrieseberg.com>
2019-07-12 14:42:53 -07:00
Felix Rieseberg f293ba84d1 Merge pull request #229 from electron/feat/protocol-dev-mode-support
feat: add dev mode support for protocol
2019-07-12 14:41:05 -07:00
Carlos Valdez 55cf507a96 better label 2019-07-12 14:23:21 -07:00
Carlos Valdez e71c2439cd add to dialog class 2019-07-12 13:49:52 -07:00
Carlos Valdez 9bd253951c added dialog-confirm prompt for users 2019-07-12 13:49:40 -07:00
Erick Zhao eda08c9aea feat: change args passed in to support yarn
NPM and Yarn both forward arguments via `process.env.npm_config_argv`.
The URL would be in the `remain` property in NPM, but in `original`
in both NPM and Yarn.
2019-07-12 11:50:35 -07:00
Felix Rieseberg 9414f66893 Merge pull request #228 from electron/no-prerelease
build: Don't mark releases as prereleases
2019-07-12 11:44:40 -07:00
Erick Zhao f4725b5dd8 feat: add dev mode support for protocol 2019-07-12 11:35:18 -07:00
Felix Rieseberg fd8be47494 build: Don't mark releases as prereleases 2019-07-12 11:10:09 -07:00
Micha Hanselmann 2cebe2ca7c add more verbose msg when offline 2019-07-12 11:07:51 -07:00
Felix Rieseberg 74135bab67 0.9.0 2019-07-08 10:53:09 -07:00
Felix Rieseberg a856340d01 Merge pull request #225 from electron/update-dependencies
chore: Update the dependencies
2019-07-08 10:52:25 -07:00
Felix Rieseberg bd8939c358 chore: Update the dependencies 2019-07-07 17:15:43 -07:00
Felix Rieseberg 9d3f3d61eb Merge pull request #222 from erickzhao/fix/browserwindow-showme-script
fix: correct button code for BrowserWindow show-me
2019-07-06 12:46:07 -07:00
Felix Rieseberg bdeaffcb7d Merge pull request #223 from DeerMichel/deermichel/nightlies
fix: fetch nightlies from `electron-nightlies` channel
2019-07-06 12:45:40 -07:00
Andrew Cherniavskii 344e1df375 fix: console message in powermonitor example 2019-07-06 09:34:03 -07:00
Micha Hanselmann d5b52dcae7 fix nightlies fetch 2019-07-05 13:48:07 -07:00
Erick Zhao 0faeb24761 fix: correct button code for BrowserWindow show-me 2019-07-03 15:23:07 -07:00
Micha Hanselmann 284b579d0c fix out-of-scope tray ref (#221) 2019-07-03 11:14:39 -05:00
Felix Rieseberg 5a1c24cdda Merge pull request #220 from erickzhao/feat/publish-ux-fixes
feat: clarify UX for Publish Gist feature
2019-07-02 10:13:02 -07:00
Erick Zhao 024d48a273 test: add spec for publishing gist disabled form 2019-07-01 22:54:44 -07:00
Erick Zhao 32155d652d chore: update enzyme version 2019-07-01 22:54:04 -07:00
Erick Zhao 874a8bb0f0 test: Move isPublishing from local state to store 2019-07-01 21:15:18 -07:00
Erick Zhao 73470640c7 test: Update snapshots to match DOM changes 2019-07-01 21:15:18 -07:00
Erick Zhao 679f731f4c chore: Consistent use of "publish" keyword 2019-07-01 21:15:18 -07:00
Erick Zhao a41ad11f8c chore: Remove "public" from gist setting callout
Users can either set their published Gists as public or private, so it's
best to remove the "public" qualifier from the callout.
2019-07-01 21:15:18 -07:00
Erick Zhao 7cc6d38dc2 feat: Disable fields while publishing gists 2019-07-01 21:15:18 -07:00
Felix Rieseberg 1e6ee7ab37 Merge pull request #216 from drewswaycool/add-missing-spaces
adds missing spaces
2019-06-27 15:43:25 -07:00
Felix Rieseberg c50c31ad80 Merge pull request #219 from electron/bypass-contributor-check
build: Don't throw if missing contributors in PR
2019-06-27 15:35:20 -07:00
Felix Rieseberg 5f8557fb13 build: Don't throw if missing contributors in PR 2019-06-24 19:51:34 -07:00
Felix Rieseberg 47b20fa3e4 Merge pull request #218 from DeerMichel/deermichel/clear-console
feat: add ability to clear console output
2019-06-24 19:47:21 -07:00
Micha Hanselmann b5c8ed60a9 remove clear console top bar button 2019-06-17 21:16:23 -07:00
Micha Hanselmann 5e3d382b85 minor style fix 2019-06-17 09:41:57 -07:00
Micha Hanselmann fed0107dd1 i'm a contributer now... 2019-06-16 00:11:31 -07:00
Micha Hanselmann ca9b9c633a add clear-on-run test 2019-06-15 23:50:36 -07:00
Micha Hanselmann fb4c6b3f33 add ui tests for console settings 2019-06-15 23:38:29 -07:00
Micha Hanselmann aa51c0cc5c implement clear on run 2019-06-15 23:28:20 -07:00
Micha Hanselmann e5236c5500 add settings ui for clear-on-run option 2019-06-15 23:22:49 -07:00
Micha Hanselmann 321f7a8bbc fix top bar buttons test 2019-06-15 23:02:28 -07:00
Micha Hanselmann 1ff7a3fe16 fix context menu test 2019-06-15 23:00:04 -07:00
Micha Hanselmann a1c74c018d add test for clearConsole 2019-06-15 22:53:48 -07:00
Micha Hanselmann 5df39ea51a add context menu entry to clear console 2019-06-15 22:41:44 -07:00
Micha Hanselmann 8a9591be9f add clear console button to top bar 2019-06-15 22:37:25 -07:00
Micha Hanselmann 3cc8fd41f6 add clearConsole method 2019-06-15 22:29:32 -07:00
Drew McMurry 25df3dd989 adds missing spaces 2019-06-14 15:38:39 -06:00
Felix Rieseberg abe7bdab23 Merge pull request #213 from electron/fix-touch-bar-example
fix: correct touch bar constructor in example
2019-06-11 21:16:44 -04:00
Shelley Vohr 16bea01ba5 fix: correct touch bar constructor in example 2019-06-11 14:17:44 -07:00
Felix Rieseberg 7417a82931 0.8.1 2019-06-11 11:35:40 -04:00
Felix Rieseberg 4c41db8595 Merge pull request #212 from electron/no-download-test
test: Mock electron-download
2019-06-11 17:35:23 +02:00
Felix Rieseberg 5da87ffca3 Merge pull request #211 from electron/fix-menu
fix: Don't attempt to set about panel on Windows
2019-06-11 17:05:07 +02:00
Felix Rieseberg 919e9cb6b6 test: Mock electron-download 2019-06-11 11:02:09 -04:00
Felix Rieseberg 14279283d6 fix: Don't attempt to set about panel on Windows 2019-06-11 10:51:42 -04:00
Felix Rieseberg 77a98112a1 Merge pull request #209 from amorist/fix-submit-token
Fix: submit token
2019-06-11 16:43:05 +02:00
amorist 08fb3f52bf fixed submit token 2019-06-11 14:27:47 +08:00
Felix Rieseberg b37ba895e5 0.8.0 2019-06-10 12:09:48 -04:00
143 changed files with 9774 additions and 5635 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ os:
- linux
- osx
dist: trusty
osx_image: xcode8.3
osx_image: xcode10
sudo: false
cache:
+2 -2
View File
@@ -5,8 +5,8 @@
Electron Fiddle lets you create and play with small Electron experiments. It
greets you with a quick-start template after opening change a few things,
choose the version of Electron you want to run it with, and play around. Then,
save your Fiddle either as a GitHub Gist or to a local folder. Once pushed to
GitHub, anyone can quickly try your Fiddle out by just entering it in the
save your Fiddle either as a GitHub Gist or to a local folder. Once published
on GitHub, anyone can quickly try your Fiddle out by just entering it in the
address bar.
<h3 align="center">
+1 -1
View File
@@ -1,6 +1,6 @@
environment:
matrix:
- nodejs_version: "10"
- nodejs_version: "12"
init:
- git config --global core.symlinks true
+36 -3
View File
@@ -7,7 +7,7 @@ const packageJson = require('./package.json')
const { version } = packageJson
const iconDir = path.resolve(__dirname, 'assets', 'icons')
module.exports = {
const config = {
hooks: {
generateAssets: require('./tools/generateAssets')
},
@@ -32,7 +32,12 @@ module.exports = {
OriginalFilename: 'Electron Fiddle',
},
osxSign: {
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)'
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)',
'hardened-runtime': true,
'gatekeeper-assess': false,
'entitlements': 'static/entitlements.plist',
'entitlements-inherit': 'static/entitlements.plist',
'signature-flags': 'library'
}
},
makers: [
@@ -90,8 +95,36 @@ module.exports = {
name: 'fiddle'
},
draft: true,
prerelease: true
prerelease: false
}
}
]
}
function notarizeMaybe() {
if (process.platform !== 'darwin') {
return;
}
if (!process.env.CI) {
console.log(`Not in CI, skipping notarization`);
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;
}
config.packagerConfig.osxNotarize = {
appBundleId: 'com.electron.fiddle',
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASSWORD,
ascProvider: 'LT94ZKYDCJ'
}
}
notarizeMaybe()
// Finally, export it
module.exports = config
+5214 -4061
View File
File diff suppressed because it is too large Load Diff
+50 -50
View File
@@ -1,7 +1,7 @@
{
"name": "electron-fiddle",
"productName": "Electron Fiddle",
"version": "0.7.0",
"version": "0.11.1",
"description": "The easiest way to get started with Electron",
"repository": "https://github.com/electron/fiddle",
"main": "./dist/src/main/main",
@@ -36,9 +36,9 @@
"forge": "./forge.config.js"
},
"dependencies": {
"@blueprintjs/core": "^3.15.1",
"@blueprintjs/select": "^3.8.0",
"@octokit/rest": "^16.25.6",
"@blueprintjs/core": "^3.19.1",
"@blueprintjs/select": "^3.11.1",
"@octokit/rest": "^16.34.0",
"builtin-modules": "^3.1.0",
"classnames": "^2.2.6",
"electron-default-menu": "^1.0.1",
@@ -47,62 +47,62 @@
"electron-squirrel-startup": "^1.0.0",
"extract-zip": "^1.6.7",
"fix-path": "^2.1.0",
"fs-extra": "^8.0.1",
"mobx": "^5.9.4",
"mobx-react": "^5.4.4",
"monaco-editor": "^0.17.0",
"fs-extra": "^8.1.0",
"mobx": "^5.14.2",
"mobx-react": "^6.1.4",
"monaco-editor": "^0.18.1",
"monaco-loader": "1.0.0",
"namor": "^1.1.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-mosaic-component": "^2.1.0",
"semver": "^6.0.0",
"namor": "^1.1.3",
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-mosaic-component": "^3.2.0",
"semver": "^6.3.0",
"tmp": "0.1.0",
"tslib": "^1.9.3",
"update-electron-app": "^1.3.0"
"tslib": "^1.10.0",
"update-electron-app": "^1.5.0"
},
"devDependencies": {
"@babel/core": "^7.4.4",
"@electron-forge/cli": "^6.0.0-beta.34",
"@electron-forge/maker-deb": "^6.0.0-beta.39",
"@electron-forge/maker-rpm": "^6.0.0-beta.39",
"@electron-forge/maker-squirrel": "^6.0.0-beta.39",
"@electron-forge/maker-zip": "^6.0.0-beta.39",
"@electron-forge/publisher-github": "^6.0.0-beta.34",
"@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",
"@types/classnames": "^2.2.7",
"@types/enzyme": "^3.9.2",
"@types/fs-extra": "^7.0.0",
"@types/jest": "^24.0.13",
"@types/classnames": "^2.2.9",
"@types/enzyme": "^3.10.3",
"@types/fs-extra": "^8.0.1",
"@types/jest": "^24.0.19",
"@types/log-symbols": "^3.0.0",
"@types/node": "^10.11.1",
"@types/react": "^16.8.17",
"@types/react-dom": "^16.8.4",
"@types/semver": "^6.0.0",
"@types/node": "^12.11.7",
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.3",
"@types/semver": "^6.0.2",
"@types/tmp": "0.1.0",
"chokidar": "^2.1.6",
"coveralls": "^3.0.3",
"electron": "5.0.1",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.13.0",
"enzyme-to-json": "^3.3.5",
"jest": "^24.8.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.9.0",
"less": "^3.10.3",
"log-symbols": "^3.0.0",
"node-abi": "^2.8.0",
"node-abi": "^2.12.0",
"node-fetch": "^2.6.0",
"npm-run-all": "^4.1.5",
"parcel-bundler": "^1.12.3",
"react-test-renderer": "^16.8.6",
"rimraf": "^2.6.3",
"standard": "^12.0.1",
"stylelint": "^10.0.1",
"stylelint-config-standard": "^18.3.0",
"ts-jest": "^24.0.2",
"tslint": "^5.16.0",
"tslint-microsoft-contrib": "^6.1.1",
"tslint-react": "^4.0.0",
"typescript": "^3.4.5"
"parcel-bundler": "^1.12.4",
"react-test-renderer": "^16.11.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",
"tslint-microsoft-contrib": "^6.2.0",
"tslint-react": "^4.1.0",
"typescript": "^3.6.4"
}
}
+5 -3
View File
@@ -24,6 +24,7 @@ export interface EditorValues {
main: string;
renderer: string;
html: string;
preload: string;
package?: string;
}
@@ -33,9 +34,9 @@ export interface ElectronVersion extends NpmVersion {
}
export interface SetFiddleOptions {
values: EditorValues;
filePath?: string;
templateName?: string;
gistId?: string;
}
export interface OutputEntry {
@@ -63,7 +64,8 @@ export interface Templates {
export const enum EditorId {
'main' = 'main',
'renderer' = 'renderer',
'html' = 'html'
'html' = 'html',
'preload' = 'preload'
}
// Panels that can show up as a mosaic
@@ -73,7 +75,7 @@ export const enum PanelId {
export type MosaicId = EditorId | PanelId;
export const ALL_EDITORS = [ EditorId.main, EditorId.renderer, EditorId.html ];
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 ];
+13 -3
View File
@@ -17,13 +17,20 @@ export enum IpcEvents {
FS_SAVE_FIDDLE_DIALOG = 'FS_SAVE_FIDDLE_DIALOG',
FS_SAVE_FIDDLE_ERROR = 'FS_SAVE_FIDDLE_ERROR',
SHOW_WARNING_DIALOG = 'SHOW_WARNING_DIALOG',
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR'
SHOW_CONFIRMATION_DIALOG = 'SHOW_CONFIRMATION_DIALOG',
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR',
CLEAR_CONSOLE = 'CLEAR_CONSOLE',
LOAD_LOCAL_VERSION_FOLDER = 'LOAD_LOCAL_VERSION_FOLDER',
SHOW_LOCAL_VERSION_FOLDER_DIALOG = 'SHOW_LOCAL_VERSION_FOLDER_DIALOG',
BISECT_COMMANDS_TOGGLE = 'BISECT_COMMANDS_TOGGLE',
}
export const ipcMainEvents = [
IpcEvents.FS_SAVE_FIDDLE_DIALOG,
IpcEvents.FS_SAVE_FIDDLE,
IpcEvents.SHOW_WARNING_DIALOG
IpcEvents.SHOW_WARNING_DIALOG,
IpcEvents.SHOW_CONFIRMATION_DIALOG,
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
];
export const ipcRendererEvents = [
@@ -42,7 +49,10 @@ export const ipcRendererEvents = [
IpcEvents.FS_SAVE_FIDDLE_AS,
IpcEvents.FS_SAVE_FIDDLE_FORGE,
IpcEvents.FS_SAVE_FIDDLE_GIST,
IpcEvents.SHOW_WELCOME_TOUR
IpcEvents.SHOW_WELCOME_TOUR,
IpcEvents.CLEAR_CONSOLE,
IpcEvents.LOAD_LOCAL_VERSION_FOLDER,
IpcEvents.BISECT_COMMANDS_TOGGLE
];
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL = 'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
@@ -1,3 +0,0 @@
button.version-chooser.bp3-button {
border-radius: 3px 0 0 3px;
}
+8 -1
View File
@@ -2,7 +2,6 @@
header {
box-shadow: 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);
//border-bottom: 1px solid rgba(255, 255, 255, 0.1);
background-color: @background-3;
-webkit-app-region: drag;
}
@@ -20,6 +19,7 @@ header {
select {
-webkit-app-region: no-drag;
margin-bottom: 0;
white-space: nowrap;
}
select {
@@ -27,6 +27,13 @@ header {
margin-left: 5px;
}
fieldset {
border: 0;
padding: 0;
margin: 0;
min-width: 0;
}
& > div {
display: flex;
+3 -8
View File
@@ -7,20 +7,15 @@
color: var(--text-color-1);
background: var(--background-2);
box-shadow: inset 0 0 100px 100px rgba(0, 0, 0, 0.15);
border-top: @border;
top: 10vh;
padding: 0;
padding-top: 10px;
padding-left: 10px;
height: 0;
height: 100%;
overflow-y: scroll;
transition: height 0.2s linear, padding 0.2s linear;
-webkit-app-region: no-drag;
&.showing {
border-top: @border;
padding-top: 10px;
height: 150px;
}
p {
margin-bottom: 0;
white-space: pre;
-1
View File
@@ -188,7 +188,6 @@
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
}
-1
View File
@@ -16,5 +16,4 @@
@import "components/chrome-mac.less";
@import "components/editors.less";
@import "components/tour.less";
@import "components/commands-version-chooser.less";
@import "components/show-me.less";
+5
View File
@@ -15,6 +15,11 @@ export function getRunItems(): Array<MenuItemConstructorOptions> {
label: 'Run Fiddle',
click: () => ipcMainManager.send(IpcEvents.FIDDLE_RUN)
},
{
id: 'clear_console',
label: 'Clear Console',
click: () => ipcMainManager.send(IpcEvents.CLEAR_CONSOLE)
},
{
type: 'separator'
}
+1 -1
View File
@@ -7,7 +7,7 @@ import { isDevMode } from '../utils/devmode';
* @returns {Promise<void>}
*/
export async function setupDevTools(): Promise<void> {
if (!isDevMode) return;
if (!isDevMode()) return;
const {
default: installExtension,
+35 -1
View File
@@ -1,4 +1,4 @@
import { dialog } from 'electron';
import { dialog, IpcMainEvent } from 'electron';
import { IpcEvents } from '../ipc-events';
import { ipcMainManager } from './ipc';
import { getOrCreateMainWindow } from './windows';
@@ -12,6 +12,15 @@ export function setupDialogs() {
ipcMainManager.on(IpcEvents.SHOW_WARNING_DIALOG, (_event, args) => {
showWarningDialog(args);
});
ipcMainManager.on(IpcEvents.SHOW_CONFIRMATION_DIALOG, (_event, args) => {
showConfirmationDialog(args);
});
ipcMainManager.on(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, async (event) => {
await showOpenDialog(event);
});
}
/**
@@ -25,3 +34,28 @@ function showWarningDialog(args: Electron.MessageBoxOptions) {
...args
});
}
/**
* Shows a confirmation dialog
*
* @param {Electron.MessageBoxOptions} args
*/
function showConfirmationDialog(args: Electron.MessageBoxOptions) {
dialog.showMessageBox(getOrCreateMainWindow(), {
type: 'warning',
...args
});
}
async function showOpenDialog(event: IpcMainEvent) {
const { filePaths } = await dialog.showOpenDialog({
title: 'Open Folder',
properties: ['openDirectory']
});
if (!filePaths || filePaths.length < 1) {
return;
}
event.reply(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, [filePaths[0]]);
}
+21 -21
View File
@@ -19,41 +19,41 @@ export function setupFileListeners() {
* Shows the "Open Fiddle" dialog and forwards
* the path to the renderer
*/
export function showOpenDialog() {
dialog.showOpenDialog({
export async function showOpenDialog() {
const { filePaths } = await dialog.showOpenDialog({
title: 'Open Fiddle',
properties: ['openDirectory']
}, (filePaths) => {
if (!filePaths || filePaths.length < 1) {
return;
}
ipcMainManager.send(IpcEvents.FS_OPEN_FIDDLE, [ filePaths[0] ]);
});
if (!filePaths || filePaths.length < 1) {
return;
}
ipcMainManager.send(IpcEvents.FS_OPEN_FIDDLE, [ filePaths[0] ]);
}
/**
* Shows the "Save Fiddle" dialog and forwards
* the path to the renderer
*/
export function showSaveDialog(event?: IpcEvents, as?: string) {
export async function showSaveDialog(event?: IpcEvents, as?: string) {
// We want to save to a folder, so we'll use an open dialog here
dialog.showOpenDialog({
const { filePaths } = await dialog.showOpenDialog({
buttonLabel: 'Save here',
properties: ['openDirectory', 'createDirectory'],
title: `Save Fiddle${as ? ` as ${as}` : ''}`
}, async (filePaths) => {
if (!filePaths || filePaths.length < 1) {
return;
}
console.log(`Asked to save to ${filePaths[0]}`);
// Let's confirm real quick if we want this
if (await ensureSaveTargetEmpty(filePaths[0])) {
ipcMainManager.send(event || IpcEvents.FS_SAVE_FIDDLE, [ filePaths[0] ]);
}
});
if (!filePaths || filePaths.length < 1) {
return;
}
console.log(`Asked to save to ${filePaths[0]}`);
// Let's confirm real quick if we want this
if (await ensureSaveTargetEmpty(filePaths[0])) {
ipcMainManager.send(event || IpcEvents.FS_SAVE_FIDDLE, [ filePaths[0] ]);
}
}
/**
+8 -8
View File
@@ -7,9 +7,9 @@ import { isDevMode } from '../utils/devmode';
* Is this the first run of Fiddle? If so, perform
* tasks that we only want to do in this case.
*/
export function onFirstRunMaybe() {
export async function onFirstRunMaybe() {
if (isFirstRun()) {
promptMoveToApplicationsFolder();
await promptMoveToApplicationsFolder();
}
}
@@ -17,18 +17,18 @@ export function onFirstRunMaybe() {
* Ask the user if the app should be moved to the
* applications folder.
*/
function promptMoveToApplicationsFolder(): void {
async function promptMoveToApplicationsFolder(): Promise<void> {
if (process.platform !== 'darwin') return;
if (isDevMode() || app.isInApplicationsFolder()) return;
dialog.showMessageBox({
const { response } = await dialog.showMessageBox({
type: 'question',
buttons: ['Move to Applications Folder', 'Do Not Move'],
defaultId: 0,
message: 'Move to Applications Folder?',
}, (response) => {
if (response === 0) {
app.moveToApplicationsFolder();
}
});
if (response === 0) {
app.moveToApplicationsFolder();
}
}
+3 -1
View File
@@ -1,5 +1,6 @@
import { ipcMain } from 'electron';
import { EventEmitter } from 'events';
import { IpcEvents, ipcMainEvents, WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
import { getOrCreateMainWindow } from './windows';
@@ -22,8 +23,9 @@ export class IpcMainManager extends EventEmitter {
ipcMain.on(name, (...args: Array<any>) => this.emit(name, ...args));
});
ipcMain.on(WEBCONTENTS_READY_FOR_IPC_SIGNAL, (event: Electron.Event) => {
ipcMain.on(WEBCONTENTS_READY_FOR_IPC_SIGNAL, (event: Electron.IpcMainEvent) => {
this.readyWebContents.add(event.sender);
const queue = this.messageQueue.get(event.sender);
this.messageQueue.delete(event.sender);
if (!queue) return;
+2 -2
View File
@@ -15,7 +15,7 @@ import { getOrCreateMainWindow } from './windows';
* the method that takes care of booting the application.
*/
export async function onReady() {
onFirstRunMaybe();
await onFirstRunMaybe();
if (!isDevMode()) process.env.NODE_ENV = 'production';
getOrCreateMainWindow();
@@ -68,7 +68,7 @@ export function main() {
}
// Set the app's name
app.setName('Electron Fiddle');
app.name = 'Electron Fiddle';
// Ensure that there's only ever one Fiddle running
listenForProtocolHandler();
+14 -9
View File
@@ -144,7 +144,7 @@ function getShowMeMenuItem(key: string, item: string | Templates): MenuItemConst
if (typeof item === 'string') {
return {
label: key,
click: () => ipcMainManager.send(IpcEvents.FS_OPEN_TEMPLATE, [ key ])
click: () => ipcMainManager.send(IpcEvents.FS_OPEN_TEMPLATE, [key])
};
}
@@ -160,10 +160,10 @@ function getShowMeMenu(): MenuItemConstructorOptions {
const showMeMenu: Array<MenuItemConstructorOptions> = Object.keys(SHOW_ME_TEMPLATES)
.map((key) => getShowMeMenuItem(key, SHOW_ME_TEMPLATES[key]));
return {
label: 'Show Me',
submenu: showMeMenu
};
return {
label: 'Show Me',
submenu: showMeMenu
};
}
/**
@@ -206,7 +206,7 @@ function getFileMenu(): MenuItemConstructorOptions {
type: 'separator'
},
{
label: 'Save to Gist',
label: 'Publish to Gist',
click: () => ipcMainManager.send(IpcEvents.FS_SAVE_FIDDLE_GIST),
},
{
@@ -248,14 +248,19 @@ 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"
item.submenu.push({ type: 'separator' }, { role: 'resetzoom' }, { role: 'zoomin' }, { role: 'zoomout' }); // Add zooming actions
item.submenu.push({ type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' }); // Add zooming actions
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Soft Wrap',
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, [ 'wordWrap' ]),
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, ['wordWrap']),
});
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Mini Map',
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, [ 'minimap.enabled' ]),
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, ['minimap.enabled']),
});
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Bisect Helper',
click: () => ipcMainManager.send(IpcEvents.BISECT_COMMANDS_TOGGLE),
accelerator: 'CommandorControl+Shift+B',
});
}
+11 -1
View File
@@ -4,6 +4,7 @@ import * as path from 'path';
import * as nodeUrl from 'url';
import { IpcEvents } from '../ipc-events';
import { isDevMode } from '../utils/devmode';
import { ipcMainManager } from './ipc';
const PROTOCOL = 'electron-fiddle';
@@ -65,6 +66,12 @@ export const scanArgv = (argv: Array<string>) => {
}
};
export const scanNpmArgv = (argv: string) => {
const parsedArgv = JSON.parse(argv);
const { original: args } = parsedArgv;
scanArgv(args);
};
export const listenForProtocolHandler = () => {
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) app.quit();
@@ -75,7 +82,10 @@ export const listenForProtocolHandler = () => {
}
});
if (process.platform === 'win32') {
// pass protocol URL via npm start args in dev mode
if (isDevMode() && process.env.npm_config_argv) {
scanNpmArgv(process.env.npm_config_argv);
} else if (process.platform === 'win32') {
scanArgv(process.argv);
}
};
+75 -38
View File
@@ -2,13 +2,20 @@ import { when } from 'mobx';
import * as MonacoType from 'monaco-editor';
import { ipcRenderer } from 'electron';
import { ALL_EDITORS, EditorId, EditorValues } from '../interfaces';
import {
ALL_EDITORS,
EditorId,
EditorValues,
SetFiddleOptions
} from '../interfaces';
import { WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
import { updateEditorLayout } from '../utils/editor-layout';
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 { FileManager } from './file-manager';
import { RemoteLoader } from './remote-loader';
import { Runner } from './runner';
import { appState } from './state';
import { getTheme } from './themes';
@@ -25,32 +32,29 @@ export class App {
public monaco: typeof MonacoType | null = null;
public state = appState;
public fileManager = new FileManager(appState);
public remoteLoader = new RemoteLoader(appState);
public runner = new Runner(appState);
public touchBarManager: TouchBarManager | undefined;
constructor() {
this.getValues = this.getValues.bind(this);
this.setValues = this.setValues.bind(this);
this.getEditorValues = this.getEditorValues.bind(this);
this.setEditorValues = this.setEditorValues.bind(this);
if (process.platform === 'darwin') {
this.touchBarManager = new TouchBarManager(appState);
}
}
/**
* Sets the values on all three editors.
*
* @param {EditorValues} values
* @param {warn} warn - Should we warn before overwriting unsaved data?
*/
public async setValues(values: Partial<EditorValues>, warn: boolean = true): Promise<boolean> {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
}
if (this.state.isUnsaved && warn) {
public async replaceFiddle(
editorValues: Partial<EditorValues>,
{ filePath, gistId, templateName }: Partial<SetFiddleOptions>
) {
// if unsaved, prompt user to make sure they're okay with overwriting and changing directory
if (this.state.isUnsaved) {
this.state.setWarningDialogTexts({
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);
@@ -59,31 +63,58 @@ export class App {
}
}
for (const name of ALL_EDITORS) {
const editor = fiddle.editors[name];
const backup = this.state.closedPanels[name];
// set values once prompt approves
await this.setEditorValues(editorValues);
if (isEditorBackup(backup) && backup.model && values[name]) {
// The editor does not exist, attempt to set it on the backup
backup.model.setValue(values[name]!);
} else if (editor && editor.setValue && values[name]) {
// The editor exists, set the value directly
editor.setValue(values[name]!);
}
}
document.title = getTitle(this.state);
this.state.gistId = gistId || '';
this.state.localPath = filePath;
this.state.templateName = templateName;
// once loaded, we have a "saved" state
this.state.isUnsaved = false;
this.setupUnsavedOnChangeListener();
return true;
}
/**
* Sets the values on all three editors.
*
* @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;
if (!fiddle) {
throw new Error('Fiddle not ready');
}
for (const name of ALL_EDITORS) {
const editor = fiddle.editors[name];
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]!);
} else if (editor && editor.setValue) {
// The editor exists, set the value directly
editor.setValue(values[name]!);
}
}
}
}
/**
* Gets the values on all three editors.
*
* @returns {EditorValues}
*/
public async getValues(options?: PackageJsonOptions): Promise<EditorValues> {
public async getEditorValues(
options?: PackageJsonOptions
): Promise<EditorValues> {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
@@ -93,10 +124,11 @@ export class App {
const values: EditorValues = {
html: getEditorValue(EditorId.html),
main: getEditorValue(EditorId.main),
renderer: getEditorValue(EditorId.renderer),
preload: getEditorValue(EditorId.preload),
renderer: getEditorValue(EditorId.renderer)
};
if (options && options.include !== false) {
if (options && options.include !== false) {
values.package = await getPackageJson(this.state, values, options);
}
@@ -112,16 +144,18 @@ export class App {
const React = await import('react');
const { render } = await import('react-dom');
const { Header } = await import('./components/header');
const { Dialogs } = await import('./components/dialogs');
const { Editors } = await import('./components/editors');
const { OutputEditorsWrapper } = await import(
'./components/output-editors-wrapper'
);
const { Header } = await import('./components/header');
const className = `${process.platform} container`;
const app = (
<div className={className}>
<Header appState={this.state} />
<Dialogs appState={this.state} />
<Editors appState={this.state} />
<Header appState={this.state} />
<OutputEditorsWrapper appState={this.state} />
</div>
);
@@ -131,7 +165,7 @@ export class App {
ipcRenderer.send(WEBCONTENTS_READY_FOR_IPC_SIGNAL);
// Todo: A timer here is terrible. Let's fix this
// TODO: A timer here is terrible. Let's fix this
// and ensure we actually do it once Editors have mounted.
setTimeout(() => {
this.setupUnsavedOnChangeListener();
@@ -161,7 +195,9 @@ export class App {
* @returns {Promise<void>}
*/
public async setupTheme(): Promise<void> {
const tag: HTMLStyleElement | null = document.querySelector('style#fiddle-theme');
const tag: HTMLStyleElement | null = document.querySelector(
'style#fiddle-theme'
);
const theme = await getTheme(this.state.theme);
if (tag && theme.css) {
@@ -185,6 +221,7 @@ export class App {
}
window.ElectronFiddle = window.ElectronFiddle || {};
window.ElectronFiddle.contentChangeListeners = window.ElectronFiddle.contentChangeListeners || [];
window.ElectronFiddle.contentChangeListeners =
window.ElectronFiddle.contentChangeListeners || [];
window.ElectronFiddle.app = window.ElectronFiddle.app || new App();
window.ElectronFiddle.app.setup();
+45 -12
View File
@@ -4,6 +4,7 @@ import * as path from 'path';
import { fancyImport } from '../utils/import';
import { normalizeVersion } from '../utils/normalize-version';
import { USER_DATA_PATH } from './constants';
import { getOfflineTypeDefinitionPath } from './fetch-types';
/**
* The binary manager takes care of downloading Electron versions
@@ -19,31 +20,48 @@ export class BinaryManager {
* four times before giving up if an error occurs.
*
* @param {string} iVersion
* @param {number} iteration
* @returns {Promise<void>}
*/
public async remove(iVersion: string, i: number = 0): Promise<void> {
public async remove(iVersion: string): Promise<void> {
const version = normalizeVersion(iVersion);
const fs = await fancyImport<typeof fsType>('fs-extra');
let isDeleted = false;
try {
// 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;
}
} catch (error) {
console.warn(`Binary Manager: Tried to remove ${version}, but failed`, error);
if (i < 3) {
console.log(`Binary Manager: Trying again`);
return this.remove(version, i + 1);
isDeleted = true;
}
};
const typeDefsCleaner = async () => {
await this.removeTypeDefsForVersion(version);
};
await rerunner(binaryCleaner);
if (isDeleted) {
await rerunner(typeDefsCleaner);
}
}
@@ -89,7 +107,7 @@ export class BinaryManager {
} catch (error) {
console.warn(`Failure while unzipping ${version}`, error);
// Todo: Handle this case
// TODO: Handle this case
}
this.state[version] = 'ready';
@@ -160,6 +178,20 @@ export class BinaryManager {
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;
}
}
}
/**
* Gets the expected path for a given Electron version
*
@@ -170,6 +202,7 @@ export class BinaryManager {
return path.join(USER_DATA_PATH, 'electron-bin', version);
}
/**
* Unzips an electron package so that we can actually use it.
*
+58
View File
@@ -0,0 +1,58 @@
import { ElectronVersion } from '../interfaces';
export class Bisector {
public revList: Array<ElectronVersion>;
public minRev: number;
public maxRev: number;
private pivot: number;
constructor(revList: Array<ElectronVersion>) {
this.getCurrentVersion = this.getCurrentVersion.bind(this);
this.continue = this.continue.bind(this);
this.calculatePivot = this.calculatePivot.bind(this);
this.revList = revList;
this.minRev = 0;
this.maxRev = revList.length - 1;
this.calculatePivot();
}
public getCurrentVersion() {
return this.revList[this.pivot];
}
public continue(isGoodVersion: boolean) {
let isBisectOver = false;
if (this.maxRev - this.minRev <= 1) {
isBisectOver = true;
}
if (isGoodVersion) {
const upPivot = Math.floor((this.maxRev - this.pivot) / 2) + this.pivot;
this.minRev = this.pivot;
if (upPivot !== this.maxRev && upPivot !== this.pivot) {
this.pivot = upPivot;
} else {
isBisectOver = true;
}
} else {
const downPivot = Math.floor((this.pivot - this.minRev) / 2) + this.minRev;
this.maxRev = this.pivot;
if (downPivot !== this.minRev && downPivot !== this.pivot) {
this.pivot = downPivot;
} else {
isBisectOver = true;
}
}
if (isBisectOver) {
return [this.revList[this.minRev], this.revList[this.maxRev]];
} else {
return this.revList[this.pivot];
}
}
private calculatePivot() {
this.pivot = Math.floor((this.maxRev - this.minRev) / 2);
}
}
+36 -184
View File
@@ -1,16 +1,11 @@
import { Button, InputGroup, Intent } from '@blueprintjs/core';
import classnames from 'classnames';
import { reaction, when } from 'mobx';
import { reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { EditorId, EditorValues } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../shared-constants';
import { getTitle } from '../../utils/get-title';
import { idFromUrl, urlFromId } from '../../utils/gist';
import { getOctokit } from '../../utils/octokit';
import { getContent } from '../content';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
@@ -20,26 +15,32 @@ export interface AddressBarProps {
export interface AddressBarState {
value: string;
loaders: {
gist: any;
example: any;
};
}
@observer
export class AddressBar extends React.Component<AddressBarProps, AddressBarState> {
constructor(props: AddressBarProps) {
super(props);
this.fetchExampleAndLoad = this.fetchExampleAndLoad.bind(this);
this.fetchGistAndLoad = this.fetchGistAndLoad.bind(this);
this.loadFiddleFromGist = this.loadFiddleFromGist.bind(this);
this.loadFiddleFromElectronExample = this.loadFiddleFromElectronExample.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.submit = this.submit.bind(this);
this.verifyRemoteLoad = this.verifyRemoteLoad.bind(this);
const { gistId } = this.props.appState;
const value = gistId ? urlFromId(gistId) : '';
this.state = { value };
const { remoteLoader } = window.ElectronFiddle.app;
this.state = {
value,
loaders: {
gist: remoteLoader.loadFiddleFromGist.bind(remoteLoader),
example: remoteLoader.loadFiddleFromElectronExample.bind(remoteLoader)
}
};
}
/**
@@ -59,8 +60,9 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
* @memberof AddressBar
*/
public submit() {
const { remoteLoader } = window.ElectronFiddle.app;
if (this.state.value) {
this.fetchGistAndLoad(idFromUrl(this.state.value) || this.state.value);
remoteLoader.fetchGistAndLoad(idFromUrl(this.state.value) || this.state.value);
}
}
@@ -68,48 +70,20 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
* Once the component mounts, we'll subscribe to gistId changes
*/
public componentDidMount() {
const { appState } = this.props;
const { loaders } = this.state;
reaction(
() => this.props.appState.gistId,
() => appState.gistId,
(gistId: string) => this.setState({ value: urlFromId(gistId) })
);
ipcRendererManager.on(IpcEvents.LOAD_GIST_REQUEST, this.loadFiddleFromGist);
ipcRendererManager.on(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, this.loadFiddleFromElectronExample);
ipcRendererManager.on(IpcEvents.LOAD_GIST_REQUEST, loaders.gist);
ipcRendererManager.on(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, loaders.example);
}
public componentWillUnmount() {
ipcRendererManager.removeListener(IpcEvents.LOAD_GIST_REQUEST, this.loadFiddleFromGist);
ipcRendererManager.removeListener(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, this.loadFiddleFromElectronExample);
}
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');
if (!ok) return;
this.fetchExampleAndLoad(exampleInfo.ref, exampleInfo.path);
}
public async loadFiddleFromGist(_: any, gistInfo: { id: string }) {
const ok = await this.verifyRemoteLoad('gist');
if (!ok) return;
this.fetchGistAndLoad(gistInfo.id);
}
/**
* Verifies from the user that we should be loading this fiddle
*
* @param what What are we loading from (gist, example, etc.)
*/
public async verifyRemoteLoad(what: string): Promise<boolean> {
const { appState } = this.props;
appState.setWarningDialogTexts({
label: `Are you sure you sure you want to load this ${what}? Only load and run it if you trust the source`
});
appState.isWarningDialogShowing = true;
await when(() => !appState.isWarningDialogShowing);
return !!appState.warningDialogLastResult;
const { loaders } = this.state;
ipcRendererManager.removeListener(IpcEvents.LOAD_GIST_REQUEST, loaders.gist);
ipcRendererManager.removeListener(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, loaders.example);
}
/**
@@ -121,85 +95,6 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
this.setState({ value: event.target.value });
}
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
const { appState } = this.props;
try {
const octo = await getOctokit(this.props.appState);
// TODO: Maybe we should fetch the package.json for the given ref to use the correct version of Electron?
const folder = await octo.repos.getContents({
owner: 'electron',
repo: 'electron',
ref,
path,
});
const values = {
html: await getContent(EditorId.html, appState.version),
renderer: await getContent(EditorId.renderer, appState.version),
main: await getContent(EditorId.main, appState.version),
};
const loaders: Array<Promise<void>> = [];
if (!Array.isArray(folder.data)) {
throw new Error('The example Fiddle tried to launch is not a valid Electron example');
}
for (const child of folder.data) {
switch (child.name) {
case MAIN_JS_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.main = t; })
);
break;
case INDEX_HTML_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.html = t; })
);
break;
case RENDERER_JS_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.renderer = t; })
);
break;
default:
break;
}
}
await Promise.all(loaders);
return this.handleLoadingSuccess(values, '');
} catch (error) {
return this.handleLoadingFailed(error);
}
}
/**
* Load a fiddle
*
* @returns {Promise<boolean>}
* @memberof AddressBar
*/
public async fetchGistAndLoad(gistId: string): Promise<boolean> {
try {
const octo = await getOctokit(this.props.appState);
const gist = await octo.gists.get({ gist_id: gistId });
return this.handleLoadingSuccess({
html: gist.data.files[INDEX_HTML_NAME].content,
main: gist.data.files[MAIN_JS_NAME].content,
renderer: gist.data.files[RENDERER_JS_NAME].content,
}, gistId);
} catch (error) {
return this.handleLoadingFailed(error);
}
}
public renderLoadButton(isValueCorrect: boolean): JSX.Element {
return (
<Button
@@ -212,68 +107,25 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
}
public render() {
const { isUnsaved } = this.props.appState;
const { isUnsaved, isPublishing } = this.props.appState;
const { value } = this.state;
const isCorrect = /https:\/\/gist\.github\.com\/(.+)$/.test(value);
const className = classnames('address-bar', isUnsaved, { empty: !value });
return (
<form className={className} onSubmit={this.handleSubmit}>
<InputGroup
key='addressbar'
leftIcon='geosearch'
intent={isCorrect || !value ? undefined : Intent.DANGER}
onChange={this.handleChange}
placeholder='https://gist.github.com/...'
value={value}
rightElement={this.renderLoadButton(isCorrect)}
/>
<fieldset disabled={isPublishing}>
<InputGroup
key='addressbar'
leftIcon='geosearch'
intent={isCorrect || !value ? undefined : Intent.DANGER}
onChange={this.handleChange}
placeholder='https://gist.github.com/...'
value={value}
rightElement={this.renderLoadButton(isCorrect)}
/>
</fieldset>
</form>
);
}
/**
* Loading a fiddle from GitHub succeeded, let's move on.
*
* @private
* @param {Partial<EditorValues>} values
* @param {string} gistId
* @returns {boolean}
*/
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
const { appState } = this.props;
appState.setWarningDialogTexts({
label: 'Loading the fiddle will replace your current unsaved changes. Do you want to discard them?'
});
await window.ElectronFiddle.app.setValues(values);
document.title = getTitle(appState);
appState.gistId = gistId;
appState.localPath = undefined;
appState.templateName = undefined;
return true;
}
/**
* Loading a fiddle from GitHub failed - this method handles this case
* gracefully.
*
* @private
* @param {Error} error
* @returns {boolean}
*/
private handleLoadingFailed(error: Error): false {
this.props.appState.setWarningDialogTexts({
label: `Loading the fiddle failed: ${error}`,
cancel: undefined
});
this.props.appState.toogleWarningDialog();
console.warn(`Loading Fiddle failed`, error);
return false;
}
}
@@ -0,0 +1,68 @@
import { Button } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
interface BisectHandlerProps {
appState: AppState;
}
@observer
export class BisectHandler extends React.Component<BisectHandlerProps> {
constructor(props: BisectHandlerProps) {
super(props);
this.continueBisect = this.continueBisect.bind(this);
this.terminateBisect = this.terminateBisect.bind(this);
}
public continueBisect(isGood: boolean) {
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}.`);
} else {
appState.setVersion(response.version);
}
}
public terminateBisect() {
const { appState } = this.props;
appState.Bisector = undefined;
}
public render() {
const { appState } = this.props;
if (!!appState.Bisector) {
return (
<>
<Button
icon={'thumbs-up'}
onClick={() => this.continueBisect(true)}
/>
<Button
icon={'thumbs-down'}
onClick={() => this.continueBisect(false)}
/>
<Button
icon={'cross'}
onClick={this.terminateBisect}
>
Cancel Bisect
</Button>
</>
);
} else {
return (
<Button
icon='git-branch'
text='Bisect'
onClick={appState.toggleBisectDialog}
/>
);
}
}
}
@@ -13,10 +13,6 @@ export interface PublishButtonProps {
appState: AppState;
}
export interface PublishButtonState {
isPublishing: boolean;
}
/**
* The "publish" button takes care of logging you in.
*
@@ -25,11 +21,9 @@ export interface PublishButtonState {
* @extends {React.Component<PublishButtonProps, PublishButtonState>}
*/
@observer
export class PublishButton extends React.Component<PublishButtonProps, PublishButtonState> {
export class PublishButton extends React.Component<PublishButtonProps> {
constructor(props: PublishButtonProps) {
super(props);
this.state = { isPublishing: false };
this.handleClick = this.handleClick.bind(this);
this.publishFiddle = this.publishFiddle.bind(this);
this.setPrivate = this.setPrivate.bind(this);
@@ -71,12 +65,13 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
* and update all related properties in the app state.
*/
public async publishFiddle(): Promise<void> {
this.setState({ isPublishing: true });
const { appState } = this.props;
appState.isPublishing = true;
const octo = await getOctokit(this.props.appState);
const { gitHubPublishAsPublic } = this.props.appState;
const options = { includeDependencies: true, includeElectron: true };
const values = await window.ElectronFiddle.app.getValues(options);
const values = await window.ElectronFiddle.app.getEditorValues(options);
try {
const gist = await octo.gists.create({
@@ -95,7 +90,7 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
},
} as any); // Note: GitHub messed up, GistsCreateParamsFiles is an incorrect interface
this.props.appState.gistId = gist.data.id;
appState.gistId = gist.data.id;
console.log(`Publish Button: Publishing done`, { gist });
} catch (error) {
@@ -109,7 +104,7 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
}
this.setState({ isPublishing: false });
appState.isPublishing = false;
}
/**
@@ -132,7 +127,7 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
public render() {
const { gitHubPublishAsPublic } = this.props.appState;
const { isPublishing } = this.state;
const { isPublishing } = this.props.appState;
const privacyIcon = gitHubPublishAsPublic ? 'unlock' : 'lock';
const privacyMenu = (
@@ -153,24 +148,24 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
);
return (
<ButtonGroup className='button-publish'>
<Popover
content={privacyMenu}
position={Position.BOTTOM}
disabled={isPublishing}
>
<Button
icon={privacyIcon}
/>
</Popover>
<Button
onClick={this.handleClick}
loading={isPublishing}
disabled={isPublishing}
icon='upload'
text={isPublishing ? 'Publishing...' : 'Publish'}
/>
</ButtonGroup>
<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>
);
}
@@ -1,4 +1,4 @@
import { Button, MenuItem } from '@blueprintjs/core';
import { Button, ButtonGroup, MenuItem } from '@blueprintjs/core';
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -71,7 +71,7 @@ export const filterItem: ItemPredicate<ElectronVersion> = (query, { version }) =
*/
export const renderItem: ItemRenderer<ElectronVersion> = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
return null;
}
return (
@@ -95,6 +95,30 @@ 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.
@@ -120,48 +144,29 @@ export class VersionChooser extends React.Component<VersionChooserProps, Version
this.props.appState.setVersion(version);
}
public getItems(): Array<ElectronVersion> {
const { versions, versionsToShow, statesToShow } = this.props.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;
});
}
public render() {
const { currentElectronVersion } = this.props.appState;
const { currentElectronVersion, Bisector } = this.props.appState;
const { version } = currentElectronVersion;
return (
<ElectronVersionSelect
filterable={true}
items={this.getItems()}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onItemSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
>
<Button
className='version-chooser'
text={`Electron v${version}`}
icon={getItemIcon(currentElectronVersion)}
/>
</ElectronVersionSelect>
<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>
);
}
}
+11
View File
@@ -4,6 +4,7 @@ import * as React from 'react';
import { AppState } from '../state';
import { AddressBar } from './commands-address-bar';
import { BisectHandler } from './commands-bisect';
import { EditorDropdown } from './commands-editors';
import { PublishButton } from './commands-publish-button';
import { Runner } from './commands-runner';
@@ -28,6 +29,7 @@ export class Commands extends React.Component<CommandsProps, {}> {
public render() {
const { appState } = this.props;
const { isBisectCommandShowing: isBisectCommandShowing } = appState;
return (
<div className='commands'>
@@ -36,6 +38,15 @@ export class Commands extends React.Component<CommandsProps, {}> {
<VersionChooser appState={appState} />
<Runner appState={appState} />
</ControlGroup>
{
// tslint:disable-next-line jsx-no-multiline-js
isBisectCommandShowing &&
(
<ControlGroup fill={true} vertical={false}>
<BisectHandler appState={appState} />
</ControlGroup>
)
}
<ControlGroup fill={true} vertical={false}>
<Button
active={appState.isConsoleShowing}
@@ -0,0 +1,160 @@
import { Button, Dialog, FileInput } from '@blueprintjs/core';
import { shell } from 'electron';
import * as fsType from 'fs-extra';
import { observer } from 'mobx-react';
import * as MonacoType from 'monaco-editor';
import * as path from 'path';
import * as React from 'react';
import { getTheme, THEMES_PATH } from '../themes';
import { AppState } from '../state';
import { defaultDark, LoadedFiddleTheme } from '../themes-defaults';
export interface AddThemeDialogProps {
appState: AppState;
}
export interface AddThemeDialogState {
file?: File;
}
/**
* The "add monaco theme" dialog allows users to add custom editor themes.
*
* @class AddThemeDialog
* @extends {React.Component<AddThemeDialogProps, AddThemeDialogState>}
*/
@observer
export class AddThemeDialog extends React.Component<AddThemeDialogProps, AddThemeDialogState> {
public resetState = { file: undefined };
constructor(props: AddThemeDialogProps) {
super(props);
this.state = this.resetState;
this.onSubmit = this.onSubmit.bind(this);
this.onClose = this.onClose.bind(this);
this.onChangeFile = this.onChangeFile.bind(this);
this.reset = this.reset.bind(this);
}
/**
* Handles a change of the file input
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public async onChangeFile(event: React.FormEvent<HTMLInputElement>) {
const { files } = event.target as any;
const file = files && files[0] ? files[0] : undefined;
this.setState({ file });
}
/**
* Handles the submission of the dialog
*
* @returns {Promise<void>}
*/
public async onSubmit(): Promise<void> {
const { file } = this.state;
const defaultTheme = !!this.props.appState.theme ? await getTheme(this.props.appState.theme) : defaultDark;
if (!file) return;
try {
const editor = fsType.readJSONSync(file.path);
if (!editor.base && !editor.rules) throw Error('File does not match specifications'); // has to have these attributes
defaultTheme.editor = editor as Partial<MonacoType.editor.IStandaloneThemeData>;
const newTheme = defaultTheme;
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;
return;
}
this.onClose();
return;
}
public async createNewThemeFromMonaco(name: string, newTheme: LoadedFiddleTheme): Promise<boolean> {
if (!name) return false;
const themePath = path.join(THEMES_PATH, `${name}`);
await fsType.outputJSON(themePath, {
...newTheme,
name,
}, {spaces: 2});
this.props.appState.setTheme(themePath);
shell.showItemInFolder(themePath);
return true;
}
get buttons() {
const canSubmit = !!this.state.file;
return [
(
<Button
icon='add'
key='submit'
disabled={!canSubmit}
onClick={this.onSubmit}
text='Add'
/>
), (
<Button
icon='cross'
key='cancel'
onClick={this.onClose}
text='Cancel'
/>
)
];
}
public onClose() {
this.props.appState.isThemeDialogShowing = false;
this.reset();
}
public render() {
const { isThemeDialogShowing } = this.props.appState;
const inputProps = { accept: '.json' };
const { file } = this.state;
const text = file && file.path ? file.path : `Select the Monaco file...`;
return (
<Dialog
isOpen={isThemeDialogShowing}
onClose={this.onClose}
title='Add theme'
className='dialog-add-version'
>
<div className='bp3-dialog-body'>
<FileInput
onInputChange={this.onChangeFile}
inputProps={inputProps as any}
text={text}
/>
<br />
</div>
<div className='bp3-dialog-footer'>
<div className='bp3-dialog-footer-actions'>
{this.buttons}
</div>
</div>
</Dialog>
);
}
/**
* Reset this component's state
*/
private reset(): void {
this.setState(this.resetState);
return;
}
}
+31 -24
View File
@@ -6,7 +6,9 @@ import * as React from 'react';
import * as semver from 'semver';
import { NpmVersion } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
import { getElectronNameForPlatform } from '../../utils/electron-name';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
export interface AddVersionDialogProps {
@@ -16,7 +18,7 @@ export interface AddVersionDialogProps {
export interface AddVersionDialogState {
isValidElectron: boolean;
isValidVersion: boolean;
file?: File;
folderPath?: string;
version: string;
}
@@ -39,8 +41,15 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
this.onSubmit = this.onSubmit.bind(this);
this.onClose = this.onClose.bind(this);
this.onChangeFile = this.onChangeFile.bind(this);
this.onChangeVersion = this.onChangeVersion.bind(this);
ipcRendererManager.on(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, (_event, [file]) => {
this.setFolderPath(file);
});
}
public componentWillUnmount() {
ipcRendererManager.removeAllListeners(IpcEvents.LOAD_LOCAL_VERSION_FOLDER);
}
/**
@@ -48,16 +57,11 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public async onChangeFile(event: React.FormEvent<HTMLInputElement>) {
const { files } = event.target as any;
public async setFolderPath(folderPath: string) {
const { binaryManager } = this.props.appState;
const file = files && files[0] ? files[0] : undefined;
const isValidElectron = !!await binaryManager.getIsDownloaded('custom', folderPath);
const isValidElectron = !!(file
&& file.path
&& await binaryManager.getIsDownloaded('custom', file.path));
this.setState({ file, isValidElectron });
this.setState({ folderPath, isValidElectron });
}
public onChangeVersion(event: React.ChangeEvent<HTMLInputElement>) {
@@ -67,7 +71,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
this.setState({
version,
isValidVersion,
});
});
}
/**
@@ -76,18 +80,18 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
* @returns {Promise<void>}
*/
public async onSubmit(): Promise<void> {
const { file, version } = this.state;
const { folderPath, version } = this.state;
if (!file) return;
if (!folderPath) return;
const name = file.path
const name = folderPath
.slice(-20)
.split(path.sep)
.slice(1)
.join(path.sep);
const toAdd: NpmVersion = {
localPath: file.path,
localPath: folderPath,
version,
name
};
@@ -129,12 +133,16 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
public render() {
const { isAddVersionDialogShowing } = this.props.appState;
const inputProps = { webkitdirectory: 'true' };
const { file } = this.state;
const inputProps = {
onClick: (e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
e.preventDefault();
ipcRendererManager.send(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG);
}
};
const { folderPath } = this.state;
const text = file && file.path
? file.path
: `Select the folder containing ${getElectronNameForPlatform()}...`;
const text = folderPath ||
`Select the folder containing ${getElectronNameForPlatform()}...`;
return (
<Dialog
@@ -145,7 +153,6 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
>
<div className='bp3-dialog-body'>
<FileInput
onInputChange={this.onChangeFile}
id='custom-electron-version'
inputProps={inputProps as any}
text={text}
@@ -163,9 +170,9 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
}
private renderPath(): JSX.Element | null {
const { file, isValidElectron } = this.state;
const { folderPath, isValidElectron } = this.state;
if (!file || !file.path) return null;
if (!folderPath) return null;
const info = isValidElectron
? `We found an ${getElectronNameForPlatform()} in this folder.`
@@ -208,7 +215,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
isValidElectron: false,
isValidVersion: false,
version: '',
file: undefined
folderPath: undefined
});
}
}
+164
View File
@@ -0,0 +1,164 @@
import { Button, Dialog, Label, MenuItem } from '@blueprintjs/core';
import { Select } from '@blueprintjs/select';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion } from '../../interfaces';
import { Bisector } from '../bisect';
import { AppState } from '../state';
import { filterItem, getItemIcon, getVersionsFromAppState, renderItem } from './commands-version-chooser';
const ElectronVersionSelect = Select.ofType<ElectronVersion>();
export interface BisectDialogProps {
appState: AppState;
}
export interface BisectDialogState {
startIndex?: number;
endIndex?: number;
allVersions: Array<ElectronVersion>;
}
/**
* The "add version" dialog allows users to add custom builds of Electron.
*
* @class AddVersionDialog
* @extends {React.Component<BisectDialogProps, BisectDialogState>}
*/
@observer
export class BisectDialog extends React.Component<BisectDialogProps, BisectDialogState> {
constructor(props: BisectDialogProps) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
this.onClose = this.onClose.bind(this);
this.onBeginSelect = this.onBeginSelect.bind(this);
this.onEndSelect = this.onEndSelect.bind(this);
const allVersions = getVersionsFromAppState(this.props.appState);
this.state = { allVersions };
}
public onBeginSelect(version: ElectronVersion) {
this.setState({ startIndex: this.state.allVersions.indexOf(version) });
}
public onEndSelect(version: ElectronVersion) {
this.setState({ endIndex: this.state.allVersions.indexOf(version) });
}
/**
* Handles the submission of the dialog
*
* @returns {Promise<void>}
*/
public async onSubmit(): Promise<void> {
const { endIndex, startIndex, allVersions } = this.state;
const { appState } = this.props;
if (endIndex === undefined || startIndex === undefined) {
return;
}
const bisectRange = allVersions
.slice(endIndex, startIndex + 1)
.reverse();
appState.Bisector = new Bisector(bisectRange);
const initialBisectPivot = appState.Bisector.getCurrentVersion().version;
appState.setVersion(initialBisectPivot);
this.onClose();
}
/**
* Closes the dialog
*/
public onClose() {
this.props.appState.isBisectDialogShowing = false;
}
get buttons() {
const canSubmit =
!!this.state.startIndex &&
!!this.state.endIndex &&
this.state.startIndex > this.state.endIndex;
return [
(
<Button
icon='play'
key='submit'
disabled={!canSubmit}
onClick={this.onSubmit}
text='Begin'
/>
), (
<Button
icon='cross'
key='cancel'
onClick={this.onClose}
text='Cancel'
/>
)
];
}
public render() {
const { isBisectDialogShowing } = this.props.appState;
const { startIndex, endIndex, allVersions } = this.state;
return (
<Dialog
isOpen={isBisectDialogShowing}
onClose={this.onClose}
title='Start a bisect session'
className='dialog-add-version'
>
<div className='bp3-dialog-body'>
<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}
/>
</ElectronVersionSelect>
</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}
/>
</ElectronVersionSelect>
</Label>
</div>
<div className='bp3-dialog-footer'>
<div className='bp3-dialog-footer-actions'>
{this.buttons}
</div>
</div>
</Dialog>
);
}
}
@@ -0,0 +1,51 @@
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>
);
}
}
+2 -3
View File
@@ -56,6 +56,7 @@ export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogSt
public async onSubmitToken(): Promise<void> {
if (!this.state.tokenInput) return;
this.setState({ verifying: true });
this.props.appState.gitHubToken = this.state.tokenInput;
const octo = await getOctokit(this.props.appState);
@@ -64,16 +65,14 @@ export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogSt
this.props.appState.gitHubAvatarUrl = data.avatar_url;
this.props.appState.gitHubLogin = data.login;
this.props.appState.gitHubName = data.name;
this.setState({ verifying: false, error: false });
} catch (error) {
console.warn(`Authenticating against GitHub failed`, error);
this.setState({ verifying: false, error: true });
return;
}
this.props.appState.gitHubToken = this.state.tokenInput;
this.props.appState.isTokenDialogShowing = false;
this.setState({ verifying: false, error: false });
}
/**
+2 -4
View File
@@ -13,8 +13,7 @@ export interface WarningDialogState {
}
/**
* The token dialog asks the user for a GitHub Personal Access Token.
* It's also responsible for checking if the token is correct.
* The token dialog prompts the user to either continue or cancel the operation.
*
* @export
* @class WarningDialog
@@ -30,12 +29,11 @@ export class WarningDialog extends React.Component<WarningDialogProps, WarningDi
public onClose(result: boolean) {
this.props.appState.warningDialogLastResult = result;
this.props.appState.toogleWarningDialog();
this.props.appState.toggleWarningDialog();
}
public render() {
const { isWarningDialogShowing, warningDialogTexts } = this.props.appState;
return (
<Alert
isOpen={isWarningDialogShowing}
+21 -2
View File
@@ -2,7 +2,10 @@ import { observer } from 'mobx-react';
import * as React from 'react';
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 { TokenDialog } from './dialog-token';
import { WarningDialog } from './dialog-warning';
import { Settings } from './settings';
@@ -21,7 +24,13 @@ export interface DialogsProps {
export class Dialogs extends React.Component<DialogsProps, {}> {
public render() {
const { appState } = this.props;
const { isTokenDialogShowing, isSettingsShowing, isAddVersionDialogShowing } = appState;
const {
isTokenDialogShowing,
isSettingsShowing,
isAddVersionDialogShowing,
isThemeDialogShowing,
isBisectDialogShowing
} = appState;
const maybeToken = isTokenDialogShowing
? <TokenDialog key='dialogs' appState={appState} />
: null;
@@ -31,13 +40,23 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
const maybeAddLocalVersion = isAddVersionDialogShowing
? <AddVersionDialog key='add-version-dialog' appState={appState} />
: null;
const maybeMonaco = isThemeDialogShowing ? <AddThemeDialog appState={appState} />
: null;
const maybeBisect = isBisectDialogShowing
? <BisectDialog key='bisect-dialog' appState={appState} />
: null;
const eitherWarningOrPrompt = appState.isWarningDialogShowing
? <WarningDialog appState={appState} />
: <ConfirmDialog appState={appState} />;
return (
<div key='dialogs' className='dialogs'>
{maybeToken}
{maybeSettings}
{maybeAddLocalVersion}
<WarningDialog appState={appState} />
{maybeMonaco}
{maybeBisect}
{eitherWarningOrPrompt}
</div>
);
}
+6 -3
View File
@@ -11,7 +11,7 @@ import { AppState } from '../state';
export interface EditorProps {
appState: AppState;
monaco: typeof MonacoType;
monoacoOptions: MonacoType.editor.IEditorOptions;
monacoOptions: MonacoType.editor.IEditorOptions;
id: EditorId;
options?: Partial<MonacoType.editor.IEditorConstructionOptions>;
editorDidMount?: (editor: MonacoType.editor.IStandaloneCodeEditor) => void;
@@ -68,7 +68,7 @@ export class Editor extends React.Component<EditorProps> {
* Initialize Monaco.
*/
public async initMonaco() {
const { monaco, monoacoOptions } = this.props;
const { monaco, monacoOptions: monacoOptions } = this.props;
const ref = this.containerRef.current;
if (ref) {
@@ -77,7 +77,7 @@ export class Editor extends React.Component<EditorProps> {
theme: 'main',
contextmenu: false,
model: null,
...monoacoOptions
...monacoOptions
});
await this.editorDidMount(this.editor);
@@ -121,6 +121,9 @@ export class Editor extends React.Component<EditorProps> {
} else {
const value = await getContent(id, version);
const model = monaco.editor.createModel(value, this.language);
model.updateOptions({
tabSize: 2
});
this.editor.setModel(model);
}
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Button } from '@blueprintjs/core';
import { MosaicWindowContext } from 'react-mosaic-component';
import { MosaicContext, MosaicRootActions, MosaicWindowContext } from 'react-mosaic-component';
import { DocsDemoPage, MosaicId } from '../../interfaces';
import { AppState } from '../state';
@@ -11,86 +11,78 @@ export interface ToolbarButtonProps {
id: MosaicId;
}
export class MaximizeButton extends React.PureComponent<ToolbarButtonProps> {
public static contextTypes = MosaicWindowContext;
public context: MosaicWindowContext<MosaicId>;
export abstract class ToolbarButton extends React.PureComponent<ToolbarButtonProps> {
public static contextType = MosaicWindowContext;
public context: MosaicWindowContext;
constructor(props: ToolbarButtonProps) {
super(props);
this.expand = this.expand.bind(this);
}
public render() {
return (
<MosaicContext.Consumer>
{({ mosaicActions }) => this.createButton(mosaicActions)}
</MosaicContext.Consumer>
);
}
/**
* Create a button that performs the actual action
*/
public abstract createButton(_mosaicActions: MosaicRootActions<any>): React.ReactNode;
}
export class MaximizeButton extends ToolbarButton {
/**
* Create a button that can expand this panel
*/
public createButton(mosaicActions: MosaicRootActions<any>) {
const onClick = () => {
mosaicActions.expand(this.context.mosaicWindowActions.getPath());
};
return (
<Button
icon='maximize'
className='bp3-small'
onClick={this.expand}
onClick={onClick}
/>
);
}
/**
* Expand this panel
*/
public expand() {
const path = this.context.mosaicWindowActions.getPath();
return this.context.mosaicActions.expand(path);
}
}
export class RemoveButton extends React.PureComponent<ToolbarButtonProps> {
public static contextTypes = MosaicWindowContext;
public context: MosaicWindowContext<MosaicId>;
export class RemoveButton extends ToolbarButton {
/**
* Create a button that can remove this panel
*/
public createButton(_mosaicActions: MosaicRootActions<any>) {
const onClick = () => this.props.appState.hideAndBackupMosaic(this.props.id);
constructor(props: ToolbarButtonProps) {
super(props);
this.remove = this.remove.bind(this);
}
public render() {
return (
<Button
icon='cross'
className='bp3-small'
onClick={this.remove}
onClick={onClick}
/>
);
}
/**
* Remove this panel
*/
public remove() {
this.props.appState.hideAndBackupMosaic(this.props.id);
}
}
export class DocsDemoGoHomeButton extends React.PureComponent<ToolbarButtonProps> {
public static contextTypes = MosaicWindowContext;
public context: MosaicWindowContext<MosaicId>;
export class DocsDemoGoHomeButton extends ToolbarButton {
/**
* Create a button that can remove this panel
*/
public createButton(_mosaicActions: MosaicRootActions<any>) {
const onClick = () => this.props.appState.currentDocsDemoPage = DocsDemoPage.DEFAULT;
constructor(props: ToolbarButtonProps) {
super(props);
this.goHome = this.goHome.bind(this);
}
public render() {
return (
<Button
icon='home'
className='bp3-small'
onClick={this.goHome}
onClick={onClick}
text='Overview'
/>
);
}
/**
* Remove this panel
*/
public goHome() {
this.props.appState.currentDocsDemoPage = DocsDemoPage.DEFAULT;
}
}
+13 -21
View File
@@ -2,7 +2,7 @@ import { reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as MonacoType from 'monaco-editor';
import * as React from 'react';
import { Mosaic, MosaicNode, MosaicWindow, MosaicWindowProps } from 'react-mosaic-component';
import { Mosaic, MosaicBranch, MosaicNode, MosaicWindow, MosaicWindowProps } from 'react-mosaic-component';
import { EditorId, MosaicId, PanelId } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
@@ -27,14 +27,12 @@ const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
wordWrap: 'on'
};
const ViewIdMosaic = Mosaic.ofType<EditorId>() as any;
const ViewIdMosaicWindow = MosaicWindow.ofType<EditorId>() as any;
export const TITLE_MAP: Record<MosaicId, string> = {
main: 'Main Process (main.js)',
renderer: 'Renderer Process (renderer.js)',
preload: 'Preload (preload.js)',
html: 'HTML (index.html)',
docsDemo: 'Docs & Demos'
docsDemo: 'Docs & Demos',
};
export interface EditorsProps {
@@ -82,17 +80,11 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
ipcRendererManager.on(IpcEvents.FS_NEW_FIDDLE, async (_event) => {
const { version } = this.props.appState;
this.props.appState.setWarningDialogTexts({
label: 'Your current fiddle is unsaved. Do you want to discard it?'
});
window.ElectronFiddle.app.fileManager.setFiddle({
values: {
html: await getContent(EditorId.html, version),
renderer: await getContent(EditorId.renderer, version),
main: await getContent(EditorId.main, version),
}
});
await window.ElectronFiddle.app.replaceFiddle({
html: await getContent(EditorId.html, version),
renderer: await getContent(EditorId.renderer, version),
main: await getContent(EditorId.main, version),
}, {});
});
ipcRendererManager.on(IpcEvents.MONACO_TOGGLE_OPTION, (_event, cmd: string) => {
@@ -199,21 +191,21 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
* @param {string} path
* @returns {JSX.Element | null}
*/
public renderTile(id: MosaicId, path: string): JSX.Element | null {
public renderTile(id: MosaicId, path: Array<MosaicBranch>): JSX.Element {
const { appState } = this.props;
const content = isEditorId(id)
? this.renderEditor(id)
: this.renderGenericPanel(id, appState);
return (
<ViewIdMosaicWindow
<MosaicWindow<EditorId>
className={id}
path={path}
title={TITLE_MAP[id]}
renderToolbar={(props: MosaicWindowProps<MosaicId>) => this.renderToolbar(props, id)}
>
{content}
</ViewIdMosaicWindow>
</MosaicWindow>
);
}
@@ -244,7 +236,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
id={id}
monaco={monaco!}
appState={appState}
monoacoOptions={defaultMonacoOptions}
monacoOptions={defaultMonacoOptions}
/>
);
}
@@ -256,7 +248,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
if (!monaco) return null;
return (
<ViewIdMosaic
<Mosaic<EditorId | PanelId>
onChange={this.onChange}
value={appState.mosaicArrangement}
zeroStateView={renderNonIdealState(appState)}
-2
View File
@@ -3,7 +3,6 @@ import * as React from 'react';
import { AppState } from '../state';
import { ChromeMac } from './chrome-mac';
import { Commands } from './commands';
import { Output } from './output';
import { WelcomeTour } from './tour-welcome';
export interface HeaderProps {
@@ -23,7 +22,6 @@ export class Header extends React.Component<HeaderProps, {}> {
<ChromeMac appState={this.props.appState} />
<header id='header'>
<Commands key='commands' appState={this.props.appState} />
<Output key='output' appState={this.props.appState} />
</header>
<WelcomeTour appState={this.props.appState} />
</>
@@ -0,0 +1,59 @@
import * as React from 'react';
import { Mosaic, MosaicNode } from 'react-mosaic-component';
import { AppState } from '../state';
import { Editors } from './editors';
import { Output } from './output';
export interface WrapperProps {
appState: AppState;
}
export interface WrapperState {
mosaicArrangement: MosaicNode<WrapperMosaicId>;
}
export type WrapperMosaicId = 'output' | 'editors';
export class OutputEditorsWrapper extends React.Component<WrapperProps, WrapperState> {
private MOSAIC_ELEMENTS = {
output: <Output appState={this.props.appState} />,
editors: <Editors appState={this.props.appState} />
};
constructor(props: any) {
super(props);
this.state = {
mosaicArrangement: {
direction: 'column',
first: 'output',
second: 'editors',
splitPercentage: 25,
}
};
}
public render() {
return (
<>
<Mosaic<WrapperMosaicId>
renderTile={(id: string) => this.MOSAIC_ELEMENTS[id]}
resize={{minimumPaneSizePercentage: 0}}
value={this.state.mosaicArrangement}
onChange={this.onChange}
/>
</>
);
}
private onChange = (currentNode: any) => {
const isConsoleShowing = currentNode.splitPercentage !== 0;
if (isConsoleShowing !== this.props.appState.isConsoleShowing) {
this.props.appState.isConsoleShowing = isConsoleShowing;
}
this.setState({mosaicArrangement: currentNode});
}
}
+30 -4
View File
@@ -1,8 +1,12 @@
import { autorun } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { MosaicContext } from 'react-mosaic-component';
import { OutputEntry } from '../../interfaces';
import { AppState } from '../state';
import { WrapperMosaicId } from './output-editors-wrapper';
export interface CommandsProps {
appState: AppState;
@@ -19,6 +23,8 @@ export interface CommandsProps {
*/
@observer
export class Output extends React.Component<CommandsProps, {}> {
public static contextType = MosaicContext;
public context: MosaicContext<WrapperMosaicId>;
private outputRef = React.createRef<HTMLDivElement>();
constructor(props: CommandsProps) {
@@ -28,6 +34,23 @@ export class Output extends React.Component<CommandsProps, {}> {
this.renderEntry = this.renderEntry.bind(this);
}
public componentDidMount() {
autorun(() => {
const { isConsoleShowing } = this.props.appState;
// this context should always exist, but mocking context in enzyme
// is not fully supported, so this condition makes the tests pass
if (this.context.mosaicActions && this.context.mosaicActions.expand) {
if (!isConsoleShowing) {
this.context.mosaicActions.expand(['first'], 0);
} else {
this.context.mosaicActions.expand(['first'], 25);
}
}
});
}
public componentDidUpdate() {
if (this.outputRef.current) {
this.outputRef.current.scrollTop = this.outputRef.current.scrollHeight;
@@ -71,9 +94,12 @@ export class Output extends React.Component<CommandsProps, {}> {
));
}
public render() {
const { isConsoleShowing, output } = this.props.appState;
const className = isConsoleShowing ? 'output showing' : 'output';
public render(): JSX.Element | null {
const { output, isConsoleShowing } = this.props.appState;
if (!isConsoleShowing) {
return null;
}
// The last 1000 lines
const lines = output
@@ -81,7 +107,7 @@ export class Output extends React.Component<CommandsProps, {}> {
.map(this.renderEntry);
return (
<div className={className} ref={this.outputRef}>
<div className='output' ref={this.outputRef}>
{lines}
</div>
);
@@ -63,7 +63,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
return (
<div>
<h2>Exection</h2>
<h2>Execution</h2>
<Callout>
These advanced settings control how Electron Fiddle executes your fiddles.
</Callout>
@@ -76,6 +76,7 @@ export class AppearanceSettings extends React.Component<
this.handleChange = this.handleChange.bind(this);
this.openThemeFolder = this.openThemeFolder.bind(this);
this.handleAddTheme = this.handleAddTheme.bind(this);
this.state = {
themes: []
@@ -111,8 +112,9 @@ export class AppearanceSettings extends React.Component<
* @memberof AppearanceSettings
*/
public async createNewThemeFromCurrent(): Promise<boolean> {
const { appState } = this.props;
const fs = await fancyImport<typeof fsType>('fs-extra');
const theme = await getTheme(this.props.appState.theme);
const theme = await getTheme(appState.theme);
try {
const namor = await fancyImport<any>('namor');
@@ -128,6 +130,8 @@ export class AppearanceSettings extends React.Component<
shell.showItemInFolder(themePath);
this.setState({themes: await getAvailableThemes()});
return true;
} catch (error) {
console.warn(`Themes: Failed to create new theme from current`, error);
@@ -190,13 +194,29 @@ export class AppearanceSettings extends React.Component<
</a>. The easiest way to get started is to clone one of the two existing
themes and to add your own colors.
</p>
<p>
Additionally, if you wish to import a Monaco Editor theme, pick your JSON file and Fiddle will attempt to import it.
</p>
<Button
onClick={this.createNewThemeFromCurrent}
text='Create theme from current selection'
icon='duplicate'
/>
<Button
icon='document-open'
onClick={this.handleAddTheme}
text='Add a Monaco Editor theme'
/>
</Callout>
</div>
);
}
/**
* Opens the "add monaco theme" dialog
*/
public handleAddTheme(): void {
this.props.appState.toggleAddMonacoThemeDialog();
}
}
@@ -0,0 +1,60 @@
import { Callout, Checkbox, FormGroup } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
export interface ConsoleSettingsProps {
appState: AppState;
}
/**
* Settings content to manage console-related preferences.
*
* @class ConsoleSettings
* @extends {React.Component<ConsoleSettingsProps, {}>}
*/
@observer
export class ConsoleSettings extends React.Component<ConsoleSettingsProps, {}> {
constructor(props: ConsoleSettingsProps) {
super(props);
this.handleClearOnRunChange = this.handleClearOnRunChange.bind(this);
}
/**
* Handles a change on whether or not the console should be cleared
* before fiddle is executed.
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public handleClearOnRunChange(
event: React.FormEvent<HTMLInputElement>
) {
const { checked } = event.currentTarget;
this.props.appState.isClearingConsoleOnRun = checked;
}
public render() {
const { isClearingConsoleOnRun } = this.props.appState;
const clearOnRunLabel = `
Enable this option to automatically clear the console whenever you run your
fiddle.`.trim();
return (
<div>
<h4>Console</h4>
<Callout>
<FormGroup label={clearOnRunLabel}>
<Checkbox
checked={isClearingConsoleOnRun}
label='Clear on run.'
onChange={this.handleClearOnRunChange}
/>
</FormGroup>
</Callout>
</div>
);
}
}
@@ -31,7 +31,7 @@ export class GitHubSettings extends React.Component<GitHubSettingsProps, {}> {
return (
<Callout>
<p>
Your fiddles can be published as public GitHub Gists -
Your fiddles can be published as GitHub Gists -
that way you can share your fiddles with the world!
</p>
<Button onClick={this.signIn} icon='log-in' text='Sign in'/>
@@ -4,6 +4,7 @@ import * as React from 'react';
import { AppState } from '../state';
import { AppearanceSettings } from './settings-general-appearance';
import { ConsoleSettings } from './settings-general-console';
import { GitHubSettings } from './settings-general-github';
export interface GeneralSettingsProps {
@@ -24,6 +25,8 @@ export class GeneralSettings extends React.Component<GeneralSettingsProps, {}> {
<h2>General Settings</h2>
<AppearanceSettings appState={this.props.appState} />
<Divider />
<ConsoleSettings appState={this.props.appState} />
<Divider />
<GitHubSettings appState={this.props.appState} />
</div>
);
+20
View File
@@ -44,6 +44,16 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
this.state = {
section: SettingsSections.General
};
this.closeSettingsPanel = this.closeSettingsPanel.bind(this);
}
public componentDidMount() {
window.addEventListener('keyup', this.closeSettingsPanel, true);
}
public componentWillUnmount() {
window.removeEventListener('keyup', this.closeSettingsPanel);
}
/**
@@ -138,4 +148,14 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
return 'cog';
}
/**
* Trigger closing of the settings panel upon Esc
*/
private closeSettingsPanel(event: KeyboardEvent) {
const { appState } = this.props;
if (event.code === 'Escape') {
appState.isSettingsShowing = false;
}
}
}
+1 -1
View File
@@ -60,7 +60,7 @@ export function ShowMeApp(_props: any): JSX.Element {
let result = '';
pathsToQuery.forEach((item) => {
result += `${item}: ${remote.app.getPath(item)}\n`;
result += `${item}: ${remote.app.getPath(item as any)}\n`;
});
setPaths(result);
+4 -4
View File
@@ -115,7 +115,7 @@ export function getWelcomeTour(): Set<TourScriptStep> {
a Node.js application is started. The main script runs in the "main
process". To display a user interface, the main process creates renderer
processes  usually in the form of windows, which Electron calls
<code>BrowserWindow</code>.
&nbsp;<code>BrowserWindow</code>.
</p>
<p>
To get started, pretend that the main process is just like a Node.js
@@ -137,10 +137,10 @@ export function getWelcomeTour(): Set<TourScriptStep> {
content: (
<p>
In the default fiddle, this HTML file is loaded in the
<code>BrowserWindow</code>. Any HTML, CSS, or JavaScript that works
&nbsp;<code>BrowserWindow</code>. Any HTML, CSS, or JavaScript that works
in a browser will work here, too. In addition, Electron allows you
to execute Node.js code. Take a close look at the
<code>&lt;script /&gt;</code> tag and notice how we can call <code>
&nbsp;<code>&lt;script /&gt;</code> tag and notice how we can call <code>
require()</code> like we would in Node.js.
</p>
)
@@ -157,7 +157,7 @@ export function getWelcomeTour(): Set<TourScriptStep> {
</p>
<p>
By the way: If you want to use an <code>npm</code> module here, just
<code>require</code> it. Electron Fiddle will automatically detect that you
&nbsp;<code>require</code> it. Electron Fiddle will automatically detect that you
requested a module and install it as soon as you run your fiddle.
</p>
</>
+3
View File
@@ -18,3 +18,6 @@ export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
second: EditorId.html,
}
};
export const ELECTRON_ORG = 'electron';
export const ELECTRON_REPO = 'electron';
+2 -2
View File
@@ -22,7 +22,7 @@ export async function getContent(
if (name === EditorId.main) {
// We currently only distinguish between loadFile
// and loadURL. Todo: Properly version the quick-start.
// and loadURL. TODO: Properly version the quick-start.
if (version && version.startsWith('1.')) {
return (await import('../content/main-1-x-x')).main;
}
@@ -42,7 +42,7 @@ export async function getContent(
export async function isContentUnchanged(name: EditorId): Promise<boolean> {
if (!window.ElectronFiddle || !window.ElectronFiddle.app) return false;
const values = await window.ElectronFiddle.app.getValues({ include: false });
const values = await window.ElectronFiddle.app.getEditorValues({ include: false });
// Handle main case, which needs to check both possible versions
if (name === EditorId.main) {
+58 -21
View File
@@ -2,28 +2,38 @@ import * as fsType from 'fs-extra';
import * as MonacoType from 'monaco-editor';
import * as path from 'path';
import { ElectronVersion, ElectronVersionSource } from '../interfaces';
import { callIn } from '../utils/call-in';
import { fancyImport } from '../utils/import';
import { USER_DATA_PATH } from './constants';
const definitionPath = path.join(USER_DATA_PATH, 'electron-typedef');
/**
* Fetch TypeScript definitions for the current version of Electron (online)
*
* @param {string} version
* @returns {Promise<string>}
*/
export function fetchTypeDefinitions(version: string): Promise<string> {
export async function fetchTypeDefinitions(version: string): Promise<string> {
const url = `https://unpkg.com/electron@${version}/electron.d.ts`;
return window.fetch(url)
.then((response) => response.text())
.catch((error) => {
console.warn(`Fetch Types: Could not fetch definitions`, error);
return '';
});
let text: string;
try {
const response = await window.fetch(url);
text = await response.text();
} catch (error) {
console.warn(`Fetch Types: Could not fetch definitions`, error);
return '';
}
// for invalid packa
if (text.includes('Cannot find package')) {
console.warn(`Fetch Types: ${text}`);
return '';
} else {
return text;
}
}
/**
@@ -48,27 +58,25 @@ export async function getOfflineTypeDefinitions(version: string): Promise<boolea
}
/**
* Get TypeScript defintions for a version of Electron. If none can't be
* Get TypeScript definitions for a version of Electron. If none can be
* found, returns null.
*
* @param {string} version
* @returns {void}
*/
export async function getTypeDefinitions(version: string): Promise<string | null> {
export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Promise<string | null> {
const fs = await fancyImport<typeof fsType>('fs-extra');
await fs.mkdirp(definitionPath);
await fs.mkdirp(definitionPath);
const offlinePath = getOfflineTypeDefinitionPath(version.version);
const offlinePath = getOfflineTypeDefinitionPath(version);
if (await getOfflineTypeDefinitions(version)) {
if (await getOfflineTypeDefinitions(version.version)) {
try {
return await fs.readFile(offlinePath, 'utf-8');
} catch (error) {
return null;
}
} else {
const typeDefs = await fetchTypeDefinitions(version);
const typeDefs = await fetchTypeDefinitions(version.version);
if (typeDefs && typeDefs.length > 0) {
try {
await fs.outputFile(offlinePath, typeDefs);
@@ -79,17 +87,27 @@ export async function getTypeDefinitions(version: string): Promise<string | null
return typeDefs;
}
}
return null;
}
}
export async function getLocalVersionTypeDefs(version: ElectronVersion) {
if (version.source === ElectronVersionSource.local && !!version.localPath) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const typesPath = getLocalTypePathForVersion(version);
if (!!typesPath && fs.existsSync(typesPath)) {
return fs.readFile(typesPath, 'utf-8');
}
}
return null;
}
/**
* Tries to update the editor with type definitions.
*
* @param {string} version
*/
export async function updateEditorTypeDefinitions(version: string, i: number = 0): Promise<void> {
export async function updateEditorTypeDefinitions(version: ElectronVersion, i: number = 0): Promise<void> {
const defer = async (): Promise<void> => {
if (i > 10) {
console.warn(`Fetch Types: Failed, dependencies do not exist`);
@@ -106,17 +124,36 @@ export async function updateEditorTypeDefinitions(version: string, i: number = 0
const { app } = window.ElectronFiddle;
const monaco: typeof MonacoType = app.monaco!;
const typeDefDisposable: MonacoType.IDisposable = app.typeDefDisposable!;
const typeDefs = await getTypeDefinitions(version);
const getTypeDefs = (version.source === ElectronVersionSource.local) ?
getLocalVersionTypeDefs : getDownloadedVersionTypeDefs;
const typeDefs = await getTypeDefs(version);
if (typeDefDisposable) {
typeDefDisposable.dispose();
}
if (typeDefs) {
console.log(`Fetch Types: Updating Monaco types with electron.d.ts@${version}`);
console.log(`Fetch Types: Updating Monaco types with electron.d.ts@${version.version}`);
const disposable = monaco.languages.typescript.javascriptDefaults.addExtraLib(typeDefs);
window.ElectronFiddle.app.typeDefDisposable = disposable;
} else {
console.log(`Fetch Types: No type definitions for ${version} 😢`);
console.log(`Fetch Types: No type definitions for ${version.version} 😢`);
}
}
export function getLocalTypePathForVersion(version: ElectronVersion) {
if (version.localPath) {
return path.join(
version.localPath,
'gen',
'electron',
'tsc',
'typings',
'electron.d.ts'
);
} else {
return null;
}
}
+38 -32
View File
@@ -1,11 +1,10 @@
import * as fsType from 'fs-extra';
import * as path from 'path';
import { EditorValues, Files, FileTransform, SetFiddleOptions } from '../interfaces';
import { EditorValues, Files, FileTransform } from '../interfaces';
import { IpcEvents } from '../ipc-events';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, RENDERER_JS_NAME } from '../shared-constants';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
import { DEFAULT_OPTIONS, PackageJsonOptions } from '../utils/get-package';
import { getTitle } from '../utils/get-title';
import { fancyImport } from '../utils/import';
import { ipcRendererManager } from './ipc';
import { AppState } from './state';
@@ -38,13 +37,12 @@ export class FileManager {
/**
* Opens a template.
*
* @param {string} name
* @param {string} templateName
* @memberof FileManager
*/
public async openTemplate(name: string) {
const values = await getTemplateValues(name);
this.setFiddle({ values, templateName: name });
public async openTemplate(templateName: string) {
const editorValues = await getTemplateValues(templateName);
await window.ElectronFiddle.app.replaceFiddle(editorValues, {templateName});
}
/**
@@ -60,34 +58,15 @@ export class FileManager {
// We'll do our best and will likely have to
// rewrite this once we support multiple files
const values: EditorValues = {
const editorValues: EditorValues = {
html: await this.readFile(path.join(filePath, INDEX_HTML_NAME)),
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)),
};
this.setFiddle({ values, filePath });
}
/**
* Got values? This method will load them.
*
* @param {SetFiddleOptions} options
* @memberof FileManager
*/
public async setFiddle({ values, filePath, templateName }: SetFiddleOptions) {
this.appState.gistId = '';
this.appState.isMyGist = false;
this.appState.localPath = filePath;
this.appState.templateName = templateName;
this.appState.setWarningDialogTexts({
label: `Opening this fiddle will replace your unsaved changes. Do you want to proceed?`,
ok: 'Yes'
});
await window.ElectronFiddle.app.setValues(values);
document.title = getTitle(this.appState);
window.ElectronFiddle.app.replaceFiddle(editorValues, {filePath});
}
/**
@@ -109,7 +88,15 @@ export class FileManager {
const files = await this.getFiles(undefined, ...transforms);
for (const [fileName, content] of files) {
await this.saveFile(path.join(pathToSave, fileName), content);
const savePath = path.join(pathToSave, fileName);
// If the file has content, save it to disk. If there's no
// content in the file, remove a file that possibly exists.
if (content) {
await this.saveFile(savePath, content);
} else {
await this.removeFile(savePath);
}
}
if (pathToSave !== localPath) {
@@ -132,12 +119,13 @@ export class FileManager {
*/
public async getFiles(options?: PackageJsonOptions, ...transforms: Array<FileTransform>): Promise<Files> {
const pOptions = typeof options === 'object' ? options : DEFAULT_OPTIONS;
const values = await window.ElectronFiddle.app.getValues(pOptions);
const values = await window.ElectronFiddle.app.getEditorValues(pOptions);
let output: Files = new Map();
output.set(RENDERER_JS_NAME, values.renderer);
output.set(MAIN_JS_NAME, values.main);
output.set(INDEX_HTML_NAME, values.html);
output.set(PRELOAD_JS_NAME, values.preload);
output.set(PACKAGE_NAME, values.package!);
for (const transform of transforms) {
@@ -240,4 +228,22 @@ export class FileManager {
ipcRendererManager.send(IpcEvents.FS_SAVE_FIDDLE_ERROR, [filePath]);
}
}
/**
* Safely attempts to remove a file, doesn't crash the app if
* it fails.
*
* @param {string} filePath
* @returns {string}
* @memberof FileManager
*/
private async removeFile(filePath: string): Promise<void> {
try {
const fs = await fancyImport<typeof fsType>('fs-extra');
return await fs.remove(filePath);
} catch (error) {
console.log(`FileManager: Could not remove ${filePath}`, error);
ipcRendererManager.send(IpcEvents.FS_SAVE_FIDDLE_ERROR, [filePath]);
}
}
}
+6 -3
View File
@@ -16,7 +16,7 @@ const ignoredModules: Array<string> = [
];
/* regular expression to both match and extract module names */
const requiregx = /require\(['"](.*?)['"]\)/gm;
const requiregx = /^.*require\(['"](.*?)['"]\)/gm;
/*
@@ -91,8 +91,11 @@ export function findModules(input: string): Array<string> {
/* grab all global require matches in the text */
// tslint:disable-next-line:no-conditional-assignment
while (match = (requiregx.exec(input) || null)) {
const mod = match[1];
modules.push(mod);
// ensure commented-out requires aren't downloaded
if (!match[0].startsWith('//')) {
const mod = match[1];
modules.push(mod);
}
}
/* map and reduce */
+259
View File
@@ -0,0 +1,259 @@
import * as 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 { getOctokit } from '../utils/octokit';
import { sortedElectronMap } from '../utils/sorted-electron-map';
import { ELECTRON_ORG, ELECTRON_REPO } from './constants';
import { getContent } from './content';
import { AppState } from './state';
import { ElectronReleaseChannel, getReleaseChannel } from './versions';
export class RemoteLoader {
constructor(private readonly appState: AppState) {
this.loadFiddleFromElectronExample.bind(this);
this.loadFiddleFromGist.bind(this);
this.verifyRemoteLoad.bind(this);
this.verifyReleaseChannelEnabled.bind(this);
this.fetchExampleAndLoad.bind(this);
this.fetchGistAndLoad.bind(this);
this.setElectronVersionWithRef.bind(this);
this.getPackageVersionFromRef.bind(this);
this.handleLoadingSuccess.bind(this);
this.handleLoadingFailed.bind(this);
}
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);
if (!ok) return;
this.fetchExampleAndLoad(exampleInfo.ref, exampleInfo.path);
}
public async loadFiddleFromGist(_: any, gistInfo: { id: string }) {
const ok = await this.verifyRemoteLoad('gist');
if (!ok) return;
this.fetchGistAndLoad(gistInfo.id);
}
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
try {
const octo = await getOctokit(this.appState);
const folder = await octo.repos.getContents({
owner: ELECTRON_REPO,
repo: ELECTRON_ORG,
ref,
path,
});
const ok = await this.setElectronVersionWithRef(ref);
if (!ok) return false;
const values = {
html: await getContent(EditorId.html, this.appState.version),
renderer: await getContent(EditorId.renderer, this.appState.version),
main: await getContent(EditorId.main, this.appState.version),
preload: await getContent(EditorId.preload, this.appState.version),
};
const loaders: Array<Promise<void>> = [];
if (!Array.isArray(folder.data)) {
throw new Error('The example Fiddle tried to launch is not a valid Electron example');
}
for (const child of folder.data) {
if (!child.download_url) {
console.warn(`Could not find download_url for ${child.name}`);
continue;
}
switch (child.name) {
case MAIN_JS_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.main = t; })
);
break;
case INDEX_HTML_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.html = t; })
);
break;
case RENDERER_JS_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.renderer = t; })
);
break;
case PRELOAD_JS_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.preload = t; })
);
break;
default:
break;
}
}
await Promise.all(loaders);
return this.handleLoadingSuccess(values, '');
} catch (error) {
return this.handleLoadingFailed(error);
}
}
/**
* Get data from a gist. If it doesn't exist, return an empty string.
*
* @param {Octokit.Response<Octokit.GistsGetResponse>} gist
* @param {string} name
* @returns {string}
* @memberof RemoteLoader
*/
public getContentOrEmpty(gist: Octokit.Response<Octokit.GistsGetResponse>, name: string): string {
try {
return gist.data.files[name].content;
} catch (error) {
return '';
}
}
/**
* Load a fiddle
*
* @returns {Promise<boolean>}
* @memberof RemoteLoader
*/
public async fetchGistAndLoad(gistId: string): Promise<boolean> {
try {
const octo = await getOctokit(this.appState);
const gist = await octo.gists.get({ gist_id: gistId });
return this.handleLoadingSuccess({
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)
}, gistId);
} catch (error) {
return this.handleLoadingFailed(error);
}
}
public async setElectronVersionWithRef(ref: string): Promise<boolean> {
const version = await this.getPackageVersionFromRef(ref);
const supportedVersions = sortedElectronMap(this.appState.versions, (k) => k);
if (!supportedVersions.includes(version)) {
this.handleLoadingFailed(new Error('Version of Electron in example not supported'));
return false;
}
// check if version is part of release channel
const versionReleaseChannel: ElectronReleaseChannel = getReleaseChannel(version);
if (!this.appState.versionsToShow.includes(versionReleaseChannel)) {
const ok = await this.verifyReleaseChannelEnabled(versionReleaseChannel);
if (!ok) return false;
this.appState.versionsToShow.push(versionReleaseChannel);
}
this.appState.setVersion(version);
return true;
}
public async getPackageVersionFromRef(ref: string): Promise<string> {
const octo = await getOctokit(this.appState);
const { data: packageJsonData } = await octo.repos.getContents({
owner: ELECTRON_ORG,
repo: ELECTRON_REPO,
ref,
path: 'package.json'
});
if (!Array.isArray(packageJsonData) && !!packageJsonData.content) {
const packageJsonString = Buffer.from(packageJsonData.content, 'base64').toString('utf8');
const { version } = JSON.parse(packageJsonString);
return version;
} else {
console.error(`getPackageVersionFromRef: Received unexpected response from GitHub, could not parse version`, {
packageJsonData
});
return '0.0.0';
}
}
/**
* Verifies from the user that we should be loading this fiddle
*
* @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.`
});
this.appState.isConfirmationPromptShowing = true;
await when(() => !this.appState.isConfirmationPromptShowing);
return !!this.appState.confirmationPromptLastResult;
}
public async verifyReleaseChannelEnabled(channel: string): Promise<boolean> {
this.appState.setWarningDialogTexts({
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);
return !!this.appState.warningDialogLastResult;
}
/**
* Loading a fiddle from GitHub succeeded, let's move on.
*
* @param {Partial<EditorValues>} values
* @param {string} gistId
* @returns {boolean}
*/
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
await window.ElectronFiddle.app.replaceFiddle(values, {gistId});
return true;
}
/**
* Loading a fiddle from GitHub failed - this method handles this case
* gracefully.
*
* @param {Error} error
* @returns {boolean}
*/
private handleLoadingFailed(error: Error): false {
if (navigator.onLine) {
this.appState.setWarningDialogTexts({
label: `Loading the fiddle failed: ${error}`,
cancel: undefined
});
} else {
this.appState.setWarningDialogTexts({
label: `Loading the fiddle failed. Your computer seems to be offline. Error: ${error}`,
cancel: undefined
});
}
this.appState.toggleWarningDialog();
console.warn(`Loading Fiddle failed`, error);
return false;
}
}
+7 -4
View File
@@ -37,14 +37,17 @@ export class Runner {
* @returns {Promise<boolean>}
*/
public async run(): Promise<boolean> {
const { fileManager, getValues } = window.ElectronFiddle.app;
const { fileManager, getEditorValues } = window.ElectronFiddle.app;
const options = { includeDependencies: false, includeElectron: false };
const { binaryManager, currentElectronVersion } = this.appState;
const { version, localPath } = currentElectronVersion;
if (this.appState.isClearingConsoleOnRun) {
this.appState.clearConsole();
}
this.appState.isConsoleShowing = true;
const values = await getValues(options);
const values = await getEditorValues(options);
const dir = await this.saveToTemp(options);
if (!dir) return false;
@@ -197,8 +200,8 @@ export class Runner {
this.appState.isRunning = true;
pushOutput(`Electron v${version} started.`);
this.child.stdout.on('data', (data) => pushOutput(data, { bypassBuffer: false }));
this.child.stderr.on('data', (data) => pushOutput(data, { bypassBuffer: false }));
this.child.stdout!.on('data', (data) => pushOutput(data, { bypassBuffer: false }));
this.child.stderr!.on('data', (data) => pushOutput(data, { bypassBuffer: false }));
this.child.on('close', async (code) => {
const withCode = typeof code === 'number'
? ` with code ${code.toString()}.`
+110 -38
View File
@@ -1,3 +1,4 @@
import * as fsType from 'fs-extra';
import { action, autorun, computed, observable, when } from 'mobx';
import { MosaicNode } from 'react-mosaic-component';
@@ -19,14 +20,17 @@ import { arrayToStringMap } from '../utils/array-to-stringmap';
import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
import { createMosaicArrangement, getVisibleMosaics } from '../utils/editors-mosaic-arrangement';
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 { Bisector } from './bisect';
import { DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
import { getContent, isContentUnchanged } from './content';
import { updateEditorTypeDefinitions } from './fetch-types';
import { getLocalTypePathForVersion, updateEditorTypeDefinitions } from './fetch-types';
import { ipcRendererManager } from './ipc';
import { activateTheme } from './themes';
import {
addLocalVersion,
ElectronReleaseChannel,
@@ -70,36 +74,45 @@ export class AppState {
@observable public gitHubPublishAsPublic: boolean = !!this.retrieve('gitHubPublishAsPublic');
@observable public versionsToShow: Array<ElectronReleaseChannel> =
this.retrieve('versionsToShow') as Array<ElectronReleaseChannel>
|| [ ElectronReleaseChannel.stable, ElectronReleaseChannel.beta ];
|| [ElectronReleaseChannel.stable, ElectronReleaseChannel.beta];
@observable public statesToShow: Array<ElectronVersionState> =
this.retrieve('statesToShow') as Array<ElectronVersionState>
|| [ ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown ];
this.retrieve('statesToShow') as Array<ElectronVersionState>
|| [ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown];
@observable public isKeepingUserDataDirs: boolean = !!this.retrieve('isKeepingUserDataDirs');
@observable public isEnablingElectronLogging: boolean = !!this.retrieve('isEnablingElectronLogging');
@observable public binaryManager: BinaryManager = new BinaryManager();
@observable public isClearingConsoleOnRun: boolean = !!this.retrieve('isClearingConsoleOnRun');
// -- Various session-only state ------------------
@observable public gistId: string = '';
@observable public isMyGist: boolean = false;
@observable public versions: Record<string, ElectronVersion> = arrayToStringMap(knownVersions);
@observable public output: Array<OutputEntry> = [];
@observable public localPath: string | undefined;
@observable public isUpdatingElectronVersions = false;
@observable public warningDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
@observable public confirmationDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
@observable public warningDialogLastResult: boolean | null = null;
@observable public isRunning = false;
@observable public confirmationPromptLastResult: 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 isUpdatingElectronVersions: 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 isSettingsShowing: boolean = false;
@observable public isUnsaved: boolean = false;
@observable public isBisectDialogShowing: boolean = false;
@observable public isAddVersionDialogShowing: boolean = false;
@observable public isThemeDialogShowing: boolean = false;
@observable public isTourShowing: boolean = !localStorage.getItem('hasShownTour');
// -- Editor Values stored when we close the editor ------------------
@@ -119,16 +132,22 @@ export class AppState {
this.setVersion = this.setVersion.bind(this);
this.showTour = this.showTour.bind(this);
this.signOutGitHub = this.signOutGitHub.bind(this);
this.toggleBisectCommands = this.toggleBisectCommands.bind(this);
this.toggleAuthDialog = this.toggleAuthDialog.bind(this);
this.toggleConsole = this.toggleConsole.bind(this);
this.clearConsole = this.clearConsole.bind(this);
this.toggleSettings = this.toggleSettings.bind(this);
this.toggleBisectDialog = this.toggleBisectDialog.bind(this);
this.updateElectronVersions = this.updateElectronVersions.bind(this);
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);
// Setup auto-runs
autorun(() => this.save('theme', this.theme));
autorun(() => this.save('isClearingConsoleOnRun', this.isClearingConsoleOnRun));
autorun(() => this.save('gitHubAvatarUrl', this.gitHubAvatarUrl));
autorun(() => this.save('gitHubLogin', this.gitHubLogin));
autorun(() => this.save('gitHubName', this.gitHubName));
@@ -226,15 +245,30 @@ export class AppState {
this.isConsoleShowing = !this.isConsoleShowing;
}
@action public clearConsole() {
this.output = [];
}
@action public toggleBisectCommands() {
// guard against hiding the commands when executing a bisect
if (!this.Bisector && !this.isBisectDialogShowing) {
this.isBisectCommandShowing = !this.isBisectCommandShowing;
}
}
@action public toggleAddVersionDialog() {
this.isAddVersionDialogShowing = !this.isAddVersionDialogShowing;
}
@action public toggleAddMonacoThemeDialog() {
this.isThemeDialogShowing = !this.isThemeDialogShowing;
}
@action public toggleAuthDialog() {
this.isTokenDialogShowing = !this.isTokenDialogShowing;
}
@action public toogleWarningDialog() {
@action public toggleWarningDialog() {
this.isWarningDialogShowing = !this.isWarningDialogShowing;
if (this.isWarningDialogShowing) {
@@ -242,6 +276,18 @@ export class AppState {
}
}
@action public toggleBisectDialog() {
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.
@@ -275,6 +321,14 @@ export class AppState {
};
}
@action public setConfirmationPromptTexts(input: WarningDialogTexts) {
this.confirmationDialogTexts = {
ok: 'Okay',
cancel: 'Cancel',
...input
};
}
@action public addLocalVersion(input: NpmVersion) {
addLocalVersion(input);
@@ -282,12 +336,12 @@ export class AppState {
this.updateDownloadedVersionState();
}
/**
* Remove a version of Electron
*
* @param {string} input
* @returns {Promise<void>}
*/
/**
* Remove a version of Electron
*
* @param {string} input
* @returns {Promise<void>}
*/
@action public async removeVersion(input: string) {
const version = normalizeVersion(input);
const release = this.versions[version];
@@ -321,12 +375,12 @@ export class AppState {
this.updateDownloadedVersionState();
}
/**
* Download a version of Electron.
*
* @param {string} input
* @returns {Promise<void>}
*/
/**
* Download a version of Electron.
*
* @param {string} input
* @returns {Promise<void>}
*/
@action public async downloadVersion(input: string) {
const version = normalizeVersion(input);
console.log(`State: Downloading Electron ${version}`);
@@ -350,12 +404,12 @@ export class AppState {
}
}
/**
* Select a version of Electron (and download it if necessary).
*
* @param {string} input
* @returns {Promise<void>}
*/
/**
* Select a version of Electron (and download it if necessary).
*
* @param {string} input
* @returns {Promise<void>}
*/
@action public async setVersion(input: string) {
const version = normalizeVersion(input);
@@ -373,21 +427,38 @@ export class AppState {
// Should we update the editor?
if (await isContentUnchanged(EditorId.main)) {
const main = await getContent(EditorId.main, version);
window.ElectronFiddle.app.setValues({ main }, false);
window.ElectronFiddle.app.setEditorValues({ main });
}
// Update TypeScript definitions
updateEditorTypeDefinitions(version);
const versionObject = this.versions[version];
if (versionObject.source === ElectronVersionSource.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);
});
} else {
if (!!this.localTypeWatcher) {
console.info(`TypeDefs: Switched to downloaded version ${version}. Unwatching local typedefs.`);
this.localTypeWatcher.close();
this.localTypeWatcher = undefined;
}
}
await updateEditorTypeDefinitions(versionObject);
// Fetch new binaries, maybe?
await this.downloadVersion(version);
}
/**
* Go and check which versions have already been downloaded.
*
* @returns {Promise<void>}
*/
/**
* Go and check which versions have already been downloaded.
*
* @returns {Promise<void>}
*/
@action public async updateDownloadedVersionState(): Promise<void> {
const downloadedVersions = await this.binaryManager.getDownloadedVersions();
const updatedVersions = { ...this.versions };
@@ -426,7 +497,7 @@ export class AppState {
let strData = data.toString();
const { isNotPre, bypassBuffer } = options;
// Todo: This drops the first part of the buffer... is that fully expected?
// TODO: This drops the first part of the buffer... is that fully expected?
if (process.platform === 'win32' && bypassBuffer === false) {
this.outputBuffer += strData;
strData = this.outputBuffer;
@@ -524,7 +595,7 @@ export class AppState {
*/
@action public showMosaic(id: MosaicId) {
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
this.setVisibleMosaics([ ...currentlyVisible, id ]);
this.setVisibleMosaics([...currentlyVisible, id]);
}
/**
@@ -539,6 +610,7 @@ export class AppState {
this.isTourShowing = false;
this.isConsoleShowing = false;
this.isAddVersionDialogShowing = false;
this.isThemeDialogShowing = false;
if (additionalOptions) {
for (const key in additionalOptions) {
+4 -4
View File
@@ -2,12 +2,11 @@ import * as fsExtraType from 'fs-extra';
import * as pathType from 'path';
import { EditorValues } from '../interfaces';
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 } from '../shared-constants';
import { fancyImport } from '../utils/import';
/**
* Returns expected content for a given name. Currently synchronous,
* but probably shouldn't be.
* Returns expected content for a given name.
*
* @param {string} name
* @returns {Promise<EditorValues>}
@@ -41,6 +40,7 @@ export async function getTemplateValues(name: string): Promise<EditorValues> {
return {
renderer: await getFile(RENDERER_JS_NAME),
main: await getFile(MAIN_JS_NAME),
html: await getFile(INDEX_HTML_NAME)
html: await getFile(INDEX_HTML_NAME),
preload: await getFile(PRELOAD_JS_NAME)
};
}
+15 -9
View File
@@ -45,13 +45,14 @@ export function getDefaultVersion(
* Return the release channel for a given input
* version.
*
* @param {NpmVersion} input
* @param {NpmVersion | string} input
* @returns {ElectronReleaseChannel}
*/
export function getReleaseChannel(
input: NpmVersion
input: NpmVersion | string
): ElectronReleaseChannel {
const tag = input.version || '';
const tag = (typeof input === 'string') ? input : (input.version || '');
if (tag.includes('beta')) {
return ElectronReleaseChannel.beta;
@@ -235,13 +236,18 @@ export async function getUpdatedElectronVersions(
* @returns {Promise<Array<NpmVersion>>}
*/
export async function fetchVersions() {
const response = await window.fetch(`https://registry.npmjs.org/electron`);
const data = await response.json();
const versions: Record<string, any> = data.versions;
const channels = [
`https://registry.npmjs.org/electron`, // stable, beta
`https://registry.npmjs.org/electron-nightly` // nightly
];
const output: Array<NpmVersion> = Object
.keys(versions)
.map((version) => ({ version }));
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 })));
}
if (output && output.length > 0 && isExpectedFormat(output)) {
console.log(`Fetched new Electron versions (Count: ${output.length})`);
+1
View File
@@ -1,4 +1,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 PACKAGE_NAME = 'package.json';
+5 -5
View File
@@ -3,11 +3,11 @@
* a debounced version below.
*/
function _updateEditorLayout() {
const { main, renderer, html } = window.ElectronFiddle.editors;
if (main) main.layout();
if (renderer) renderer.layout();
if (html) html.layout();
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
if (window.ElectronFiddle.editors[key]) {
window.ElectronFiddle.editors[key].layout();
}
});
}
let handle: number;
+2
View File
@@ -6,6 +6,8 @@ import { AboutPanelOptionsOptions, app } from 'electron';
* @returns
*/
export function setupAboutPanel(): void {
if (process.platform === 'win32') return;
const options: AboutPanelOptionsOptions = {
applicationName: 'Electron Fiddle',
applicationVersion: app.getVersion(),
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>
+1 -1
View File
@@ -14,7 +14,7 @@ app.on('ready', () => {
win = null
})
let view = new BrowserView({
const view = new BrowserView({
webPreferences: {
nodeIntegration: false
}
+3 -4
View File
@@ -5,11 +5,10 @@
</head>
<body>
<h1>Hello from your BrowserWindow!</h1>
<button></button>
<button id="close-btn">Close this window</button>
<script>
document.querySelector('Close this window').onclick = () => {
window.close()
}
const button = document.querySelector('#close-btn')
button.addEventListener('click', () => window.close())
</script>
</body>
</html>
+1 -1
View File
@@ -6,7 +6,7 @@
// the result.
//
// For more info, see:
// https://electronjs.org/docs/api/contentTracing
// https://electronjs.org/docs/api/content-tracing
const { app, contentTracing } = require('electron')
+1 -1
View File
@@ -18,6 +18,6 @@ app.on('ready', () => {
mainWindow.loadFile('index.html')
mainWindow.webContents.on('crashed', () => {
console.log(`Window crashed!`)
console.log('Window crashed!')
})
})
+10 -7
View File
@@ -12,13 +12,16 @@ app.on('ready', () => {
// Show an "Open File" dialog and attempt to open
// the chosen file in our window.
//
// If you provide a callback, the method will be asynchronous,
// but in thise case, we'll just use the synchronous variant.
const file = dialog.showOpenDialog({
title: 'Hello!',
dialog.showOpenDialog(mainWindow, {
properties: ['openFile']
}).then(result => {
if (result.canceled) {
console.log('Dialog was canceled')
} else {
const file = result.filePaths[0]
mainWindow.loadURL(`file://${file}`)
}
}).catch(err => {
console.log(err)
})
mainWindow.loadURL(`file://${file}`)
})
+3 -3
View File
@@ -22,13 +22,13 @@ app.on('ready', () => {
// CommandOrControl which represents Command on macOS and Control on
// Linux and Windows to define some accelerators.
globalShortcut.register('CommandOrControl+Y', () => {
console.log(`The global shortkey was pressed!`)
console.log('The global shortkey was pressed!')
})
// It supports "special names". Check out the API documentation for a full
// list.
globalShortcut.register('VolumeUp', () => console.log(`Turn it up!`))
globalShortcut.register('VolumeDown', () => console.log(`Turn it down!`))
globalShortcut.register('VolumeUp', () => console.log('Turn it up!'))
globalShortcut.register('VolumeDown', () => console.log('Turn it down!'))
})
app.on('will-quit', () => {
+5 -5
View File
@@ -20,21 +20,21 @@ app.on('ready', () => {
if (Notification.isSupported()) {
const notification = new Notification({
title: 'Hello World!',
subtitle: `Nice to see you`,
subtitle: 'Nice to see you',
body: 'Are you having a good day?',
hasReply: true
})
notification.on('show', () => console.log(`Notification shown`))
notification.on('click', () => console.log(`Notification clicked`))
notification.on('close', () => console.log(`Notification closed`))
notification.on('show', () => console.log('Notification shown'))
notification.on('click', () => console.log('Notification clicked'))
notification.on('close', () => console.log('Notification closed'))
notification.on('reply', (event, reply) => {
console.log(`Reply: ${reply}`)
})
notification.show()
} else {
console.log(`Hm, are notifications supported on this system?`)
console.log('Hm, are notifications supported on this system?')
}
mainWindow.loadFile('index.html')
+2 -2
View File
@@ -19,10 +19,10 @@ app.on('ready', () => {
})
powerMonitor.on('on-ac', () => {
console.log(`We're on AC power`)
console.log('We\'re on AC power')
})
powerMonitor.on('on-battery', () => {
console.log(`We're on AC power`)
console.log('We\'re on battery power')
})
})
+13 -11
View File
@@ -87,17 +87,19 @@ app.on('ready', () => {
spinning = false
}
const touchBar = new TouchBar([
spin,
new TouchBarSpacer({ size: 'large' }),
reel1,
new TouchBarSpacer({ size: 'small' }),
reel2,
new TouchBarSpacer({ size: 'small' }),
reel3,
new TouchBarSpacer({ size: 'large' }),
result
])
const touchBar = new TouchBar({
items: [
spin,
new TouchBarSpacer({ size: 'large' }),
reel1,
new TouchBarSpacer({ size: 'small' }),
reel2,
new TouchBarSpacer({ size: 'small' }),
reel3,
new TouchBarSpacer({ size: 'large' }),
result
]
})
mainWindow.setTouchBar(touchBar)
})
+3 -1
View File
@@ -5,9 +5,11 @@
const { app, Tray, Menu, nativeImage } = require('electron')
let tray
app.on('ready', () => {
const icon = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAVlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KTMInWQAACsZJREFUWAmtWFlsXFcZ/u82++Jt7IyT2Em6ZFHTpAtWIzspEgjEUhA8VNAiIYEQUvuABBIUwUMkQIVKPCIoEiABLShISEBbhFJwIGRpIKRpbNeJ7bh2HHvssR3PPnPnLnzfmRlju6EQqUc+c++c8y/fv54z1uQOh+/7Glh0TD59TE/TND7lnfa4/64OKsM071QoeZpA/y9WWvk/B4XCC06TUC+Xyw8HTXNQ1+Ww6PpOrMebewXxvBueJ6/XHOdMJBL5J9Y97m2R0SS/wweE6JxkGx5dilWr1S/7dXsEa2o4+LyFmcFcaL5zbX3Y9gh5hpeWYpSB9XV5/H678V89BGYDXnHJlCsWn4gHrGc1K9CXxferOdvPOOKUfF8cH7nUyCtklQZXih/VNNlmirk3GdBSoIcRswW7/vVkLPYi5W2Uze8bh7J+4wLfh4dViFx5/nmrUi7/MhGNvrCkBfpeWqnW/7BUdadqntQ8zwr6vhUV34xpYnDynWvcmwQNaclDXsqgLMqkocPDw7fNx7d5qIX+/PmJxKGD6VdDkeh7ztyqOFfrokGCEWiiZ1mp0uITnuKAosaT7+pNxMYTyefutcQfbA+b1XLpH5fnF97/yD335Fu6mqTqsclDINBVmI4fDxw80KPAvJSt1MZtMcLiGxYUu83p4UkgnJZlqcl3LAj3WnTkIS9lUBYNPJjueVWgg7qocyOgliFqjZsg8gq5tRdiieQTf1gq15Y8CUbRZtyWOzZwc8lEqS3PTCtgqd13ieO68BQ2uNl64tXAewktrFuX2mPdkWAxn3sxnmx7sqUTJGqso8MGS9tbXFz8DMH8bblUX3T9QARVi8RV8qljfcJy0zRlaf6mzHEuzEtmekqCoZB4rqp0OmudHtUnlEWZlE0d1EWd1N3EozourcO65pw4eTIZQTW9VazJtbqvw9XwKVFQMsKDBuNhtp4uvGGFI+IDgKnpMjYyIis3ZsQMBIR7pONsIaMsyqRs6ohY1rPUSd3EQFDqo+kdZ3Fh4aupbdu+99uFQr2A1CBs4uEAjZjIFUMHi4dVxMXzCdCXQj4vBrwVCofl0ulTcv/DAxJJJBUPc8mpoyI2JDw7bFyT+ifTcSubyXytJ51+roWBxwG9Q73WWjZ7eSUU3//nXM0NI+x0PBGrTSgsLS9JFuFxHFrvSqIrJV279gi6tjiVspTza3JjZhY+0CQZj0mlWJSeHTslCro6eFqymCcVVN77kkGjs1p4sy2VOoSlOrFwT+XR+PjkgGaZ+ycKVbRTYUdVrmaImCvzk1dlFCEJdHRJ284+ie/ol0h7p7jFvExcvCCXzp2Rqem3pAMAiqWS6JGYhFI9Mjo6KjevXVUyKEuFHrKpY6JQ8TXT3D8+OTkAHBw6o6LCFo9ag3o4JtlCyTHEt5AxKvS6YUi5kJeZG3Py0NAxlLcJ9xti+K7Mjo/JfGZRuvv6Ze+9+yWEhDZAvzg3JyhX2d6/S7q6e+TimdOS7ElLKBZDwqvmj6rztayr1fVI1IoXi4PAcYZY1tPEEO1wEVlXgRFBDcmIXTqJsS+XyhKLJ5A/OpIVXXptWUYv/UvaenfIocEhMQ2EzHHErlXFCgQl3paU1eVl6QAY8sQTCSmVihKJx1V/ogvgIYF/pACdcMBhqONoHhF88/2d+bojyA6cRvje2IdFjoSjUSnBS8hgyS9lZOzKFdmPxO3o6gQIGzwuDn1dVSCtCKPy1pZXlATXqUsVYMLRmKo87vP4Y1ioqwCdCegmMYx3W/VPn8RrSDwwIMMbcEjkYo29JZVOy+ybI7K4eksODx1VSqvligpReSVLgySM/FI5h2q062jNyL3s7FtoAyGJIlx1225UmwJF6aJRJ3XzHXO9bWvsJa3jQFlBJkz6iuXdu32HzM7MyP0PPNgAU6ko4Qzp6b+flr8MD9OYJg9CwtzL5+T65ITs2bsP3mGxN/ZbBcOn0sk20gAkLQ+huXpFi8vkoY9AoyDjxTR1mbo6Ltt275HpN0dlNxQE40mVM8Ajjxx9VAGhAvQR1akZFCq799ADysMuQqOxh2FNmamEaz51ItGLfFD9+oUJoZkLowHoFA2mljUacqOMflKuVmHpfmnfvlMuvXZeStmMBIMhcWEdjgFJtrUjXI0KchAuAg0ilxLJNoRVBxhIBm0TjjKAuqjTqTs3CQZ6QUUMGFW7eiWMUg6w+yo8YMW7DqtqlZLkUDV2ISfd29KyDwk9MjYmMyOXxQIIKuShqo4VGFNBEgeDQYqVam5N5tEePFQgURIUBCsd1EWd1XrtDUUMLARD9bKaK5ytQ2Gb75g8WMiEP6VkfnZGevv6UF1vSBW5E0PFDAweFRvlfun8WVmamhDNrkmweQ0pwaPt6M4m8mgKTTFXqcrV0ZH1FKBg6qAu6qTuJiCV1Cp2Q0NDr9Uq5Ym+oMEDlSewsoRwrVBEaij7AJ4s7zrOpumxEdm15y6558GHJVe1Zezy6zJx6aJkpq5JFB4z6zVZmBiX1VWUP0IY4CFMYcpQdZ3xqIs6oftCE5DHKwd0q/tzOV8svdDb3nk8VnG9qmgQC0ZURz8Ur91alXgSByZ6ES9kZZTr/PR16UOCh+7dq0CWyyXJ4xqCQ0nKt9YQSlPue2gAeYZzD7yNLk0wmqAreb2WYSxAJ8Dget64wxtEBlDaqVOn/K5dB67t6+t5MhoMJuc8w8UPKiQ9CQR9JK5czhZAQxPt7TKF3OiAIisUViAD2Lg5d0P2HDgoKeRaW0enyqVwBJcO5fFG5dqa7h406qaeX8384uTZL5w9+UqxhYHFp0YLIYA9ddfu3T+4UJF6Rg+YAc9D0+RoIGP1ULhpWspr10evyK7+ftWTrk9PS/++A9KZSm26cih2mMOErem6n/ZsZwA2TM/MPHXs2LEftnSTbh0Q36mIIbx44cLvOnu3f+xUwbWLmoHTCUlF6g2jBQo/GnFrnGNqSHdvr+rIKGMW1KahwEBdzHft98aNwMr8zd8/NDDwccihc0hLi3GubRjY0Bm6H19fPvnZI4c/fHd7PJ2peXYZ+WQ26JufZELjQ6lbAQtnWre0d3apY8TFIdtAo+Qri6mupsB49lBMC+QXF0YefObZT8j0eKWlswVjEyCCOXHihPGb575VCvVuf3lvetsH9rXF0rla3cnhpoIGjgsUPhR3I4TMKYJQV1Z6WO02aEjHa5mNe3OPW3OPRHVrbXFh9Ocvv/KR1372owx1Pf3005uc35Ddgtd8rsf06IdS5777zZ+mUqmPzjm6TPpmvayZOq4LyATeCzkanmiy4qEuC/yXiO8CSMRzvLs1x9phepLNZl868sy3Pyen/5hd1/EfRvWmuvSWNeaRS/RkPDI4+NjE1NSXEoXlpaNB1zqo20abi59/vu/UfM2pie7WUDVq8l3wTwnskeZ+zTbIQ17KoCzKpGzq2KqX32/roRbh8ePHdUzl0s9/5Rv9n/7go19MxCKfCkZiu3V06wrO5gocxL7Dgd/IEobEMH6rejg+auXidL5Y/vWv/vTX53/y/e/MkGajTH7fOt4RUJOY1df4RdtY6ICFRzqTySOhUOA+3Ai3o31H1ZbnlXBruFmt2iMrudy5xx9//BzWV7nXDBGN2xpjbt/5oGUEdhtO3iD47xZOvm8a5CHvpsV38wsUaMwBWsz3rbK5xr0mzdv2t9Jv/f5vhsF4J+Q63IUAAAAASUVORK5CYII=')
const tray = new Tray(icon)
tray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate([
{ label: 'Item1', type: 'radio' },
+17 -16
View File
@@ -43,14 +43,15 @@ describe('context-menu', () => {
const template = (Menu.buildFromTemplate as any).mock.calls[0][0];
expect(template).toHaveLength(7);
expect(template).toHaveLength(8);
expect(template[0].id).toBe('run');
expect(template[1].type).toBe('separator');
expect(template[2].id).toBe('cut');
expect(template[3].id).toBe('copy');
expect(template[4].id).toBe('paste');
expect(template[5].type).toBe('separator');
expect(template[6].id).toBe('inspect');
expect(template[1].id).toBe('clear_console');
expect(template[2].type).toBe('separator');
expect(template[3].id).toBe('cut');
expect(template[4].id).toBe('copy');
expect(template[5].id).toBe('paste');
expect(template[6].type).toBe('separator');
expect(template[7].id).toBe('inspect');
});
it('creates a default context-menu without inspect in production', () => {
@@ -62,7 +63,7 @@ describe('context-menu', () => {
mockWindow.webContents.emit('context-menu', null, mockFlags);
const template = (Menu.buildFromTemplate as any).mock.calls[0][0];
expect(template).toHaveLength(6);
expect(template).toHaveLength(7);
});
it('disables cut/copy/paste if not in editFlags', () => {
@@ -75,12 +76,12 @@ describe('context-menu', () => {
const template = (Menu.buildFromTemplate as any).mock.calls[0][0];
expect(template[2].id).toBe('cut');
expect(template[2].enabled).toBe(false);
expect(template[3].id).toBe('copy');
expect(template[3].id).toBe('cut');
expect(template[3].enabled).toBe(false);
expect(template[4].id).toBe('paste');
expect(template[4].id).toBe('copy');
expect(template[4].enabled).toBe(false);
expect(template[5].id).toBe('paste');
expect(template[5].enabled).toBe(false);
});
it('enables cut/copy/paste if in editFlags', () => {
@@ -100,12 +101,12 @@ describe('context-menu', () => {
const template = (Menu.buildFromTemplate as any).mock.calls[0][0];
expect(template[2].id).toBe('cut');
expect(template[2].enabled).toBe(true);
expect(template[3].id).toBe('copy');
expect(template[3].id).toBe('cut');
expect(template[3].enabled).toBe(true);
expect(template[4].id).toBe('paste');
expect(template[4].id).toBe('copy');
expect(template[4].enabled).toBe(true);
expect(template[5].id).toBe('paste');
expect(template[5].enabled).toBe(true);
});
});
+19 -12
View File
@@ -1,12 +1,6 @@
import { setupDevTools } from '../../src/main/devtools';
let mockIsDevMode = false;
jest.mock('../../src/utils/devmode', () => ({
get isDevMode() {
return mockIsDevMode;
}
}));
import { isDevMode } from '../../src/utils/devmode';
jest.mock('../../src/utils/devmode');
jest.mock('electron-devtools-installer', () => ({
default: jest.fn(),
@@ -17,18 +11,31 @@ jest.mock('electron-devtools-installer', () => ({
describe('devtools', () => {
it('does not set up developer tools if not in dev mode', () => {
const devtools = require('electron-devtools-installer');
(isDevMode as jest.Mock).mockReturnValue(false);
setupDevTools();
expect(devtools.default).toHaveBeenCalledTimes(0);
});
it('sets up developer tools if not in dev mode', () => {
it('sets up developer tools if in dev mode', () => {
const devtools = require('electron-devtools-installer');
mockIsDevMode = true;
(isDevMode as jest.Mock).mockReturnValue(true);
setupDevTools();
expect(devtools.default).toHaveBeenCalledTimes(1);
});
it('catch error in setting up developer tools', async (done) => {
const devtools = require('electron-devtools-installer');
// throw devtool error
devtools.default.mockRejectedValue(new Error('devtool error'));
(isDevMode as jest.Mock).mockReturnValue(true);
try {
await setupDevTools();
done();
} catch (e) {
expect(e).toMatch('error');
}
});
});
+80 -9
View File
@@ -3,25 +3,96 @@ import { setupDialogs } from '../../src/main/dialogs';
import { ipcMainManager } from '../../src/main/ipc';
import { dialog } from 'electron';
import { flushPromises } from '../utils';
jest.mock('../../src/main/windows');
describe('dialogs', () => {
it('sets up dialogs', () => {
setupDialogs();
beforeEach(() => {
setupDialogs();
});
it('sets up dialogs', () => {
expect(ipcMainManager.eventNames()).toEqual([
IpcEvents.SHOW_WARNING_DIALOG
IpcEvents.SHOW_WARNING_DIALOG,
IpcEvents.SHOW_CONFIRMATION_DIALOG,
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
]);
});
it('tries to show a dialog if triggered', () => {
setupDialogs();
describe('warning dialog', () => {
it('shows dialog when triggering IPC event', () => {
ipcMainManager.emit(IpcEvents.SHOW_WARNING_DIALOG, {}, { hi: 'hello' });
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
type: 'warning',
hi: 'hello'
});
});
});
ipcMainManager.emit(IpcEvents.SHOW_WARNING_DIALOG, {}, { hi: 'hello' });
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
type: 'warning',
hi: 'hello'
describe('confirmation dialog', () => {
it('shows dialog when triggering IPC event', () => {
ipcMainManager.emit(IpcEvents.SHOW_CONFIRMATION_DIALOG, {}, { hi: 'hello' });
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
type: 'warning',
hi: 'hello'
});
});
});
describe('local version folder dialog', () => {
it('shows dialog when triggering IPC event', () => {
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
filePaths: []
});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: jest.fn()
});
expect(dialog.showOpenDialog).toHaveBeenCalledWith(expect.objectContaining({
properties: ['openDirectory']
}));
});
it('triggers IPC load local version event', async () => {
const replyFn = jest.fn();
const paths = ['/test/path/'];
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
filePaths: paths
});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: replyFn
});
await flushPromises();
expect(replyFn).toHaveBeenCalledWith(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, paths);
});
it('does nothing if not given a path', async () => {
const replyFn = jest.fn();
// empty array
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
filePaths: []
});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: replyFn
});
await flushPromises();
expect(replyFn).not.toHaveBeenCalled();
// nothing in response
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({});
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
reply: replyFn
});
await flushPromises();
expect(replyFn).not.toHaveBeenCalled();
});
});
});
+29 -56
View File
@@ -15,7 +15,22 @@ jest.mock('fs-extra', () => ({
existsSync: jest.fn()
}));
const mockTarget = {
webContents: {
send: jest.fn()
}
};
describe('files', () => {
beforeEach(() => {
(dialog.showOpenDialog as jest.Mock<any>).mockResolvedValue({
filePaths: [ 'my/fake/path' ]
});
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
});
describe('setupFileListeners()', () => {
it('sets up the listener', () => {
@@ -31,8 +46,8 @@ describe('files', () => {
});
describe('showOpenDialog', () => {
it('tries to open an "open" dialog', () => {
showOpenDialog();
it('tries to open an "open" dialog', async () => {
await showOpenDialog();
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
@@ -44,22 +59,10 @@ describe('files', () => {
});
it('notifies the main window of the event', async () => {
showOpenDialog();
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
const cb = call[1];
const mockTarget = {
webContents: {
send: jest.fn()
}
};
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
await cb('/my/fake/path');
expect(mockTarget.webContents.send).toHaveBeenCalledTimes(1);
await cb('');
await showOpenDialog();
expect(mockTarget.webContents.send).toHaveBeenCalledTimes(1);
});
});
@@ -92,12 +95,11 @@ describe('files', () => {
});
it('handles not getting a path returned', async (done) => {
showSaveDialog();
(dialog.showOpenDialog as jest.Mock<any>).mockResolvedValueOnce({
filePaths: []
});
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
const cb = call[1];
await cb();
await showSaveDialog();
process.nextTick(() => {
expect(fs.existsSync).toHaveBeenCalledTimes(0);
@@ -106,68 +108,39 @@ describe('files', () => {
});
it('ensures that the target is empty on save', async () => {
showSaveDialog();
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
const cb = call[1];
const mockTarget = {
webContents: {
send: jest.fn()
}
};
(dialog.showMessageBox as jest.Mock<any>).mockImplementation(async () => true);
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
await cb('/my/fake/path');
await showSaveDialog();
expect(dialog.showMessageBox).toHaveBeenCalled();
expect(mockTarget.webContents.send).toHaveBeenCalled();
});
it('does not overwrite files without consent', async () => {
showSaveDialog();
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
const cb = call[1];
const mockTarget = {
webContents: {
send: jest.fn()
}
};
(dialog.showMessageBox as jest.Mock<any>).mockImplementation(async () => false);
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
ipcMainManager.readyWebContents.add(mockTarget.webContents as any);
await cb('/my/fake/path');
await showSaveDialog();
expect(dialog.showMessageBox).toHaveBeenCalled();
expect(mockTarget.webContents.send).toHaveBeenCalledTimes(0);
});
it('does not overwrite files if an error happens', async () => {
showSaveDialog();
const call = (dialog.showOpenDialog as jest.Mock<any>).mock.calls[0];
const cb = call[1];
const mockTarget = {
webContents: {
send: jest.fn()
}
};
(dialog.showMessageBox as jest.Mock<any>).mockImplementation(async () => {
throw new Error('Nope');
});
(getOrCreateMainWindow as jest.Mock<any>).mockReturnValue(mockTarget);
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
let errored = false;
try {
await cb('/fake/path');
await showSaveDialog();
} catch (error) {
errored = error;
}
+14 -13
View File
@@ -7,6 +7,10 @@ jest.mock('../../src/utils/check-first-run', () => ({
isFirstRun: jest.fn()
}));
const mockDialogResponse = {
response: 1
};
describe('first-run', () => {
const oldDefaultApp = process.defaultApp;
const oldPlatform = process.platform;
@@ -15,6 +19,8 @@ describe('first-run', () => {
Object.defineProperty(process, 'platform', {
value: 'darwin'
});
(dialog.showMessageBox as jest.Mock<any>).mockResolvedValue(mockDialogResponse);
});
afterEach(() => {
@@ -24,7 +30,7 @@ describe('first-run', () => {
});
afterEach(() => {
process.defaultApp = oldDefaultApp;
(process as any).defaultApp = oldDefaultApp;
});
describe('onFirstRunMaybe()', () => {
@@ -44,7 +50,7 @@ describe('first-run', () => {
it(`doesn't run unless required (dev mode)`, () => {
(isFirstRun as jest.Mock).mockReturnValueOnce(true);
process.defaultApp = true;
(process as any).defaultApp = true;
(app.isInApplicationsFolder as jest.Mock).mockReturnValue(false);
onFirstRunMaybe();
@@ -65,21 +71,16 @@ describe('first-run', () => {
expect(dialog.showMessageBox).toHaveBeenCalledTimes(0);
});
it(`moves the app when requested to do so`, () => {
(isFirstRun as jest.Mock).mockReturnValueOnce(true);
it(`moves the app when requested to do so`, async () => {
(isFirstRun as jest.Mock).mockReturnValue(true);
(app.isInApplicationsFolder as jest.Mock).mockReturnValue(false);
onFirstRunMaybe();
const call = (dialog.showMessageBox as jest.Mock<any>).mock.calls[0];
const cb = call[1];
cb(1);
mockDialogResponse.response = 1;
await onFirstRunMaybe();
expect(app.moveToApplicationsFolder).toHaveBeenCalledTimes(0);
cb(0);
mockDialogResponse.response = 0;
await onFirstRunMaybe();
expect(app.moveToApplicationsFolder).toHaveBeenCalledTimes(1);
});
});
+17 -2
View File
@@ -12,6 +12,7 @@ jest.mock('../../src/main/ipc');
describe('menu', () => {
beforeEach(() => {
(electron.app.getName as any).mockReturnValue('Electron Fiddle');
(electron.dialog.showOpenDialog as any).mockReturnValue(Promise.resolve({}));
});
afterEach(() => {
@@ -77,6 +78,20 @@ describe('menu', () => {
(toggleMap as any).click();
expect(ipcMainManager.send).toHaveBeenCalledTimes(2);
});
it('adds Bisect toggle', () => {
overridePlatform('linux');
setupMenu();
const result = (electron.Menu.buildFromTemplate as any).mock.calls[0][0];
const submenu = result[2].submenu as Array<Electron.MenuItemConstructorOptions>;
const toggleSoftWrap = submenu.find(({ label }) => label === 'Toggle Bisect Helper');
(toggleSoftWrap as any).click();
expect(ipcMainManager.send).toHaveBeenCalledWith(IpcEvents.BISECT_COMMANDS_TOGGLE);
});
});
describe('menu groups', () => {
@@ -173,7 +188,7 @@ describe('menu', () => {
it('attempts to open a template on click', () => {
showMe.submenu[0].submenu[0].click();
expect(ipcMainManager.send)
.toHaveBeenCalledWith(IpcEvents.FS_OPEN_TEMPLATE, [ 'App' ]);
.toHaveBeenCalledWith(IpcEvents.FS_OPEN_TEMPLATE, ['App']);
});
});
@@ -181,7 +196,7 @@ describe('menu', () => {
let tasks: any;
beforeEach(() => {
const mock = (electron.Menu.buildFromTemplate as any).mock;
const mock = (electron.Menu.buildFromTemplate as any).mock;
const menu = mock.calls[0][0];
tasks = menu[menu.length - 3];
});
+5 -2
View File
@@ -1,10 +1,12 @@
import { FileManager } from './file-manager';
import { RemoteLoader } from './remote-loader';
import { RunnerMock } from './runner';
export class AppMock {
public setupUnsavedOnChangeListener = jest.fn();
public setValues = jest.fn();
public getValues = jest.fn(() => ({
public replaceFiddle = jest.fn();
public setEditorValues = jest.fn();
public getEditorValues = jest.fn(() => ({
main: 'main-content',
renderer: 'renderer-content',
html: 'html-content'
@@ -18,6 +20,7 @@ export class AppMock {
public fileManager = new FileManager();
public runner = new RunnerMock();
public remoteLoader = new RemoteLoader();
public monaco = {
editor: {
+2 -3
View File
@@ -116,7 +116,6 @@ const app = {
quit: jest.fn(),
relaunch: jest.fn(),
setJumpList: jest.fn(),
setName: jest.fn(),
requestSingleInstanceLock: jest.fn(),
on: jest.fn(),
once: jest.fn()
@@ -157,8 +156,8 @@ const electronMock = {
writeImage: jest.fn()
},
dialog: {
showOpenDialog: jest.fn(),
showMessageBox: jest.fn()
showOpenDialog: jest.fn(() => Promise.resolve({})),
showMessageBox: jest.fn(() => Promise.resolve({}))
},
ipcMain: new MockIPC(),
ipcRenderer: new MockIPC(),
-1
View File
@@ -1,5 +1,4 @@
export class FileManager {
public saveToTemp = jest.fn(() => '/mock/temp/dir');
public setFiddle = jest.fn();
public cleanup = jest.fn();
}
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
export class RemoteLoader {
public loadFiddleFromGist = jest.fn();
public loadFiddleFromElectronExample = jest.fn();
public fetchGistAndLoad = jest.fn();
}
+2
View File
@@ -4,4 +4,6 @@ export class MockState {
@observable public isWarningDialogShowing = false;
@observable public gistId = '';
@observable public closedPanels = {};
@observable public isConsoleShowing = true;
@observable public output = [];
}
+195 -83
View File
@@ -1,10 +1,13 @@
import { App } from '../../src/renderer/app';
import { AppState } from '../../src/renderer/state';
import { EditorBackup } from '../../src/utils/editor-backup';
import { ElectronFiddleMock } from '../mocks/electron-fiddle';
import { MockState } from '../mocks/state';
import { overridePlatform, resetPlatform } from '../utils';
jest.mock('../../src/renderer/file-manager', () => require('../mocks/file-manager'));
jest.mock('../../src/renderer/file-manager', () =>
require('../mocks/file-manager')
);
jest.mock('../../src/renderer/state', () => ({
appState: {
theme: 'defaultDark',
@@ -18,8 +21,8 @@ jest.mock('../../src/renderer/components/header', () => ({
jest.mock('../../src/renderer/components/dialogs', () => ({
Dialogs: () => 'Dialogs;'
}));
jest.mock('../../src/renderer/components/editors', () => ({
Editors: () => 'Editors;'
jest.mock('../../src/renderer/components/output-editors-wrapper', () => ({
OutputEditorsWrapper: () => 'OutputEditorsWrapper;'
}));
describe('Editors component', () => {
@@ -33,11 +36,11 @@ describe('Editors component', () => {
jest.useFakeTimers();
const app = new App();
const result = await app.setup() as HTMLDivElement;
const result = (await app.setup()) as HTMLDivElement;
app.setupUnsavedOnChangeListener = jest.fn();
jest.runAllTimers();
expect(result.innerHTML).toBe('Header;Dialogs;Editors;');
expect(result.innerHTML).toBe('Dialogs;Header;OutputEditorsWrapper;');
expect(app.setupUnsavedOnChangeListener).toHaveBeenCalled();
jest.useRealTimers();
@@ -54,19 +57,19 @@ describe('Editors component', () => {
it('does not create a touch bar manager on Windows and Linux', () => {
overridePlatform('win32');
expect((new App()).touchBarManager).toBeFalsy();
expect(new App().touchBarManager).toBeFalsy();
overridePlatform('linux');
expect((new App()).touchBarManager).toBeFalsy();
expect(new App().touchBarManager).toBeFalsy();
resetPlatform();
});
});
describe('getValues()', () => {
describe('getEditorValues()', () => {
it('gets values', async () => {
const app = new App();
const b = await app.getValues({});
const b = await app.getEditorValues({});
expect(b.html).toBe('editor-value');
expect(b.main).toBe('editor-value');
@@ -81,7 +84,7 @@ describe('Editors component', () => {
(window as any).ElectronFiddle.editors.renderer = null;
const app = new App();
const result = await app.getValues({});
const result = await app.getEditorValues({});
expect(result.html).toBe('');
expect(result.main).toBe('');
@@ -96,7 +99,7 @@ describe('Editors component', () => {
const app = new App();
let threw = false;
try {
await app.getValues({});
await app.getEditorValues({});
} catch (error) {
threw = true;
}
@@ -105,61 +108,57 @@ describe('Editors component', () => {
});
});
describe('setValues()', () => {
it('attempts to set values', () => {
describe('replaceFiddle()', () => {
it('sets editor values and source info', (done) => {
const app = new App();
app.setValues({
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = false;
app.setEditorValues = jest.fn();
const editorValues = {
html: 'html-value',
main: 'main-value',
renderer: 'renderer-value'
});
renderer: 'renderer-value',
preload: ''
};
expect((window as any).ElectronFiddle.editors.html.setValue)
.toHaveBeenCalledWith('html-value');
expect((window as any).ElectronFiddle.editors.main.setValue)
.toHaveBeenCalledWith('main-value');
expect((window as any).ElectronFiddle.editors.renderer.setValue)
.toHaveBeenCalledWith('renderer-value');
app.replaceFiddle(editorValues, {
gistId: 'gistId',
templateName: 'templateName',
filePath: 'localPath'
})
.then(() => {
expect(app.setEditorValues).toHaveBeenCalledWith(editorValues);
expect(app.state.gistId).toBe('gistId');
expect(app.state.templateName).toBe('templateName');
expect(app.state.localPath).toBe('localPath');
done();
});
});
it('attempts to set values for closed editors', () => {
const oldMainEditor = window.ElectronFiddle.editors.main;
delete window.ElectronFiddle.editors.main;
it('unsets state of previous source when called', (done) => {
const app = new App();
(app.state.closedPanels as any).main = { model: { setValue: jest.fn() } };
app.setValues({
html: 'html-value',
main: 'main-value',
renderer: 'renderer-value'
});
expect((app.state.closedPanels.main as EditorBackup)!.model!.setValue)
.toHaveBeenCalledWith('main-value');
window.ElectronFiddle.editors.main = oldMainEditor;
});
it('warns when the contents are unsaved, does not proceed if denied', (done) => {
const app = new App();
(app.state as any) = new MockState();
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = true;
app.state.localPath = '/fake/path';
app.state.setWarningDialogTexts = jest.fn();
app.setEditorValues = jest.fn();
app.setValues({
const editorValues = {
html: 'html-value',
main: 'main-value',
renderer: 'renderer-value'
}).then((result) => {
expect(result).toBe(true);
expect((window as any).ElectronFiddle.editors.html.setValue)
.toHaveBeenCalledWith('html-value');
expect((window as any).ElectronFiddle.editors.main.setValue)
.toHaveBeenCalledWith('main-value');
expect((window as any).ElectronFiddle.editors.renderer.setValue)
.toHaveBeenCalledWith('renderer-value');
};
done();
});
expect(app.state.localPath).toBe('/fake/path');
app.replaceFiddle(editorValues, {
gistId: 'gistId',
})
.then(() => {
expect(app.state.localPath).toBeUndefined();
done();
});
setTimeout(() => {
expect(app.state.isWarningDialogShowing).toBe(true);
@@ -168,32 +167,135 @@ describe('Editors component', () => {
});
});
it('warns when the contents are unsaved, does proceed if allowed', (done) => {
it('marks the new Fiddle as Saved', (done) => {
const app = new App();
(app.state as any) = new MockState();
app.state.isUnsaved = true;
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = false;
app.setupUnsavedOnChangeListener = jest.fn();
app.setEditorValues = jest.fn();
app.setValues({
const editorValues = {
html: 'html-value',
main: 'main-value',
renderer: 'renderer-value'
}).then((result) => {
expect(result).toBe(false);
expect((window as any).ElectronFiddle.editors.html.setValue)
.toHaveBeenCalledTimes(0);
expect((window as any).ElectronFiddle.editors.main.setValue)
.toHaveBeenCalledTimes(0);
expect((window as any).ElectronFiddle.editors.renderer.setValue)
.toHaveBeenCalledTimes(0);
};
done();
app.replaceFiddle(editorValues, {
gistId: 'gistId',
templateName: 'templateName',
filePath: 'localPath'
})
.then(() => {
expect(app.state.isUnsaved).toBe(false);
expect(app.setupUnsavedOnChangeListener).toHaveBeenCalled();
done();
});
});
describe('when current Fiddle is unsaved and prompt appears', () => {
it('takes no action if prompt is rejected', (done) => {
const app = new App();
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = true;
app.state.setWarningDialogTexts = jest.fn();
app.setEditorValues = jest.fn();
expect(app.state.localPath).toBeUndefined();
expect(app.state.gistId).toBe('');
expect(app.state.templateName).toBeUndefined();
app.replaceFiddle({}, {
gistId: 'gistId',
templateName: 'templateName',
filePath: 'localPath'
})
.then(() => {
expect(app.setEditorValues).not.toHaveBeenCalled();
expect(app.state.localPath).toBeUndefined();
expect(app.state.gistId).toBe('');
expect(app.state.templateName).toBeUndefined();
done();
});
setTimeout(() => {
expect(app.state.isWarningDialogShowing).toBe(true);
app.state.warningDialogLastResult = false;
app.state.isWarningDialogShowing = false;
});
});
setTimeout(() => {
expect(app.state.isWarningDialogShowing).toBe(true);
app.state.isWarningDialogShowing = false;
it('sets editor values and source info if prompt is accepted', (done) => {
const app = new App();
(app.state as Partial<AppState>) = new MockState();
app.state.isUnsaved = true;
app.state.setWarningDialogTexts = jest.fn();
app.setEditorValues = jest.fn();
const editorValues = {
html: 'html-value',
main: 'main-value',
renderer: 'renderer-value'
};
app.replaceFiddle(editorValues, {
gistId: 'gistId',
templateName: 'templateName',
filePath: 'localPath'
})
.then(() => {
expect(app.setEditorValues).toHaveBeenCalledWith(editorValues);
expect(app.state.gistId).toBe('gistId');
expect(app.state.templateName).toBe('templateName');
expect(app.state.localPath).toBe('localPath');
done();
});
setTimeout(() => {
expect(app.state.isWarningDialogShowing).toBe(true);
app.state.warningDialogLastResult = true;
app.state.isWarningDialogShowing = false;
});
});
});
});
describe('setEditorValues()', () => {
it('attempts to set values', () => {
const app = new App();
app.setEditorValues({
html: 'html-value',
main: 'main-value',
renderer: 'renderer-value'
});
expect(
(window as any).ElectronFiddle.editors.html.setValue
).toHaveBeenCalledWith('html-value');
expect(
(window as any).ElectronFiddle.editors.main.setValue
).toHaveBeenCalledWith('main-value');
expect(
(window as any).ElectronFiddle.editors.renderer.setValue
).toHaveBeenCalledWith('renderer-value');
});
it('attempts to set values for closed editors', () => {
const oldMainEditor = window.ElectronFiddle.editors.main;
delete window.ElectronFiddle.editors.main;
const app = new App();
(app.state.closedPanels as any).main = { model: { setValue: jest.fn() } };
app.setEditorValues({
html: 'html-value',
main: 'main-value',
renderer: 'renderer-value',
});
expect(
(app.state.closedPanels.main as EditorBackup)!.model!.setValue
).toHaveBeenCalledWith('main-value');
window.ElectronFiddle.editors.main = oldMainEditor;
});
it('throws if the Fiddle object is not present', async () => {
(window as any).ElectronFiddle = null;
@@ -201,26 +303,35 @@ describe('Editors component', () => {
const app = new App();
let threw = false;
try {
await app.setValues({ html: '', main: '', renderer: ''});
await app.setEditorValues({ html: '', main: '', renderer: '' });
} catch (error) {
threw = true;
}
expect(threw).toBe(true);
});
it('does not set a value if none passed in', async () => {
const app = new App();
await app.setEditorValues({
html: 'html-value',
main: 'main-value'
});
expect(
(window as any).ElectronFiddle.editors.renderer.setValue
).not.toHaveBeenCalled();
});
});
describe('setupUnsavedOnChangeListener()', () => {
it('listens for model change events', async () => {
const app = new App();
await app.setValues({
html: 'html-value',
main: 'main-value',
renderer: 'renderer-value'
});
app.setupUnsavedOnChangeListener();
const fn = window.ElectronFiddle.editors!.renderer!.onDidChangeModelContent;
const fn = window.ElectronFiddle.editors!.renderer!
.onDidChangeModelContent;
const call = (fn as jest.Mock<any>).mock.calls[0];
const cb = call[0];
@@ -237,16 +348,16 @@ describe('Editors component', () => {
const app = new App();
app.setupResizeListener();
expect(window.addEventListener)
.toHaveBeenCalled();
expect((window.addEventListener as jest.Mock).mock.calls[0][0])
.toBe('resize');
expect(window.addEventListener).toHaveBeenCalled();
expect((window.addEventListener as jest.Mock).mock.calls[0][0]).toBe(
'resize'
);
});
});
describe('setupTheme()', () => {
it(`adds the current theme's css to the document`, async () => {
document.head!.innerHTML = '<style id="fiddle-theme"></style>';
document.head!.innerHTML = "<style id='fiddle-theme'></style>";
const app = new App();
await app.setupTheme();
@@ -272,8 +383,9 @@ describe('Editors component', () => {
--error-color: #df3434;
--fonts-common: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
</style>`.replace(/ /gm, ''));
// tslint:enable:max-line-length
</style>`.replace(/ /gm, '')
);
// tslint:enable:max-line-length
});
it('removes the dark theme option if required', async () => {
+50
View File
@@ -1,4 +1,5 @@
import { BinaryManager } from '../../src/renderer/binary';
import { USER_DATA_PATH } from '../../src/renderer/constants';
import { overridePlatform, resetPlatform } from '../utils';
import * as path from 'path';
@@ -57,6 +58,55 @@ describe('binary', () => {
await binaryManager.remove('v3.0.0');
expect(fs.remove).toHaveBeenCalledTimes(4);
});
it('attempts to clean up the version\'s associated typedefs', async () => {
const fs = require('fs-extra');
binaryManager.removeTypeDefsForVersion = jest.fn();
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
await binaryManager.remove('v3.0.0');
expect(fs.remove).toHaveBeenCalled();
expect(binaryManager.removeTypeDefsForVersion).toHaveBeenCalled();
});
it('retries typedef cleanup upon failure', async () => {
const fs = require('fs-extra');
binaryManager.removeTypeDefsForVersion = jest.fn().mockImplementation(() => {
throw new Error('Bwap bwap');
});
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
await binaryManager.remove('v3.0.0');
expect(binaryManager.removeTypeDefsForVersion).toHaveBeenCalledTimes(4);
});
});
describe('removeTypeDefsForVersion()', () => {
it('removes a version\'s typedefs', async () => {
const fs = require('fs-extra');
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
await binaryManager.removeTypeDefsForVersion('v3.0.0');
expect(fs.remove).toHaveBeenCalledWith(path.join(USER_DATA_PATH, 'electron-typedef', '3.0.0'));
});
it('throws upon fs failure', async () => {
const fs = require('fs-extra');
(fs.existsSync as jest.Mock<any>).mockReturnValue(true);
(fs.remove as jest.Mock<any>).mockImplementation(() => {
throw new Error('Bwap bwap');
});
expect.assertions(1);
try {
await binaryManager.removeTypeDefsForVersion('v3.0.0');
} catch (e) {
expect(e).toEqual(new Error('Bwap bwap'));
}
});
});
describe('getDownloadedVersions()', () => {
+70
View File
@@ -0,0 +1,70 @@
import { ElectronVersionSource, ElectronVersionState } from '../../src/interfaces';
import { Bisector } from '../../src/renderer/bisect';
const generateVersionRange = (rangeLength: number) =>
(new Array(rangeLength)).fill(0).map((_, i) => ({
state: ElectronVersionState.ready,
version: `${i + 1}.0.0`,
source: ElectronVersionSource.local
}));
describe('bisect', () => {
let bisector: Bisector;
beforeEach(() => {
const versions = generateVersionRange(9);
bisector = new Bisector(versions);
});
it('selects a pivot in the middle of the array', () => {
const pivot = bisector.getCurrentVersion();
const middleIndex = Math.floor(bisector.revList.length / 2);
expect(pivot).toBe(bisector.revList[middleIndex]);
});
describe('continue()', () => {
it('returns the current version', () => {
const result = bisector.continue(true);
const version = bisector.getCurrentVersion();
expect(result).toBe(version);
});
it('discards lower half of the range if pivot is good version', () => {
const pivotVersion = bisector.getCurrentVersion();
bisector.continue(true);
expect(bisector.revList[bisector.minRev]).toBe(pivotVersion);
});
it('discards upper half of the range if pivot is bad version', () => {
const pivotVersion = bisector.getCurrentVersion();
bisector.continue(false);
expect(bisector.revList[bisector.maxRev]).toBe(pivotVersion);
});
it('terminates if fewer than 2 items are left', () => {
const versions = generateVersionRange(2);
bisector = new Bisector(versions);
expect(bisector.revList.length).toBe(2);
const responseGood = bisector.continue(true);
expect(responseGood).toHaveLength(2);
expect(versions).toContain(responseGood[0]);
expect(versions).toContain(responseGood[1]);
bisector = new Bisector(versions);
expect(bisector.revList.length).toBe(2);
const responseBad = bisector.continue(false);
expect(responseBad).toHaveLength(2);
expect(versions).toContain(responseBad[0]);
expect(versions).toContain(responseBad[1]);
});
});
describe('getCurrentVersion()', () => {
it('returns a version within the range', () => {
const version = bisector.getCurrentVersion();
expect(bisector.revList).toContain(version);
});
});
});
@@ -5,20 +5,22 @@ exports[`AddressBar component renders 1`] = `
className="address-bar empty"
onSubmit={[Function]}
>
<Blueprint3.InputGroup
key="addressbar"
leftIcon="geosearch"
onChange={[Function]}
placeholder="https://gist.github.com/..."
rightElement={
<Blueprint3.Button
disabled={true}
icon="cloud-download"
onClick={[Function]}
text="Load Fiddle"
/>
}
value=""
/>
<fieldset>
<Blueprint3.InputGroup
key="addressbar"
leftIcon="geosearch"
onChange={[Function]}
placeholder="https://gist.github.com/..."
rightElement={
<Blueprint3.Button
disabled={true}
icon="cloud-download"
onClick={[Function]}
text="Load Fiddle"
/>
}
value=""
/>
</fieldset>
</form>
`;
@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Bisect commands component renders bisect dialog button if no bisect instance 1`] = `
<Blueprint3.Button
icon="git-branch"
text="Bisect"
/>
`;
exports[`Bisect commands component renders helper buttons if bisect instance is active 1`] = `
<Fragment>
<Blueprint3.Button
icon="thumbs-up"
onClick={[Function]}
/>
<Blueprint3.Button
icon="thumbs-down"
onClick={[Function]}
/>
<Blueprint3.Button
icon="cross"
onClick={[Function]}
>
Cancel Bisect
</Blueprint3.Button>
</Fragment>
`;
@@ -27,6 +27,16 @@ exports[`EditorDropdown component renders 1`] = `
shouldDismissPopover={true}
text="Renderer Process (renderer.js)"
/>
<Blueprint3.MenuItem
disabled={false}
icon="eye-off"
id="preload"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Preload (preload.js)"
/>
<Blueprint3.MenuItem
disabled={false}
icon="eye-open"
@@ -41,6 +51,7 @@ exports[`EditorDropdown component renders 1`] = `
}
defaultIsOpen={false}
disabled={false}
fill={false}
hasBackdrop={false}
hoverCloseDelay={300}
hoverOpenDelay={150}
@@ -97,6 +108,16 @@ exports[`EditorDropdown component renders the extra button if the FIDDLE_DOCS_DE
shouldDismissPopover={true}
text="Renderer Process (renderer.js)"
/>
<Blueprint3.MenuItem
disabled={false}
icon="eye-off"
id="preload"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Preload (preload.js)"
/>
<Blueprint3.MenuItem
disabled={false}
icon="eye-open"
@@ -111,6 +132,7 @@ exports[`EditorDropdown component renders the extra button if the FIDDLE_DOCS_DE
}
defaultIsOpen={false}
disabled={false}
fill={false}
hasBackdrop={false}
hoverCloseDelay={300}
hoverOpenDelay={150}
@@ -1,62 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Publish button component renders 1`] = `
<Blueprint3.ButtonGroup
className="button-publish"
>
<Blueprint3.Popover
boundary="scrollParent"
captureDismiss={false}
content={
<Blueprint3.Menu>
<Blueprint3.MenuItem
active={false}
disabled={false}
icon="lock"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Private"
/>
<Blueprint3.MenuItem
active={true}
disabled={false}
icon="unlock"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Public"
/>
</Blueprint3.Menu>
}
defaultIsOpen={false}
disabled={false}
hasBackdrop={false}
hoverCloseDelay={300}
hoverOpenDelay={150}
inheritDarkTheme={true}
interactionKind="click"
minimal={false}
modifiers={Object {}}
openOnTargetFocus={true}
position="bottom"
targetTagName="span"
transitionDuration={300}
usePortal={true}
wrapperTagName="span"
<fieldset>
<Blueprint3.ButtonGroup
className="button-publish"
>
<Blueprint3.Popover
boundary="scrollParent"
captureDismiss={false}
content={
<Blueprint3.Menu>
<Blueprint3.MenuItem
active={false}
disabled={false}
icon="lock"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Private"
/>
<Blueprint3.MenuItem
active={true}
disabled={false}
icon="unlock"
multiline={false}
onClick={[Function]}
popoverProps={Object {}}
shouldDismissPopover={true}
text="Public"
/>
</Blueprint3.Menu>
}
defaultIsOpen={false}
disabled={false}
fill={false}
hasBackdrop={false}
hoverCloseDelay={300}
hoverOpenDelay={150}
inheritDarkTheme={true}
interactionKind="click"
minimal={false}
modifiers={Object {}}
openOnTargetFocus={true}
position="bottom"
targetTagName="span"
transitionDuration={300}
usePortal={true}
wrapperTagName="span"
>
<Blueprint3.Button
icon="unlock"
/>
</Blueprint3.Popover>
<Blueprint3.Button
icon="unlock"
icon="upload"
onClick={[Function]}
text="Publish"
/>
</Blueprint3.Popover>
<Blueprint3.Button
disabled={false}
icon="upload"
loading={false}
onClick={[Function]}
text="Publish"
/>
</Blueprint3.ButtonGroup>
</Blueprint3.ButtonGroup>
</fieldset>
`;
@@ -1,46 +1,50 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VersionChooser component handles corrupt data 1`] = `
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
<Blueprint3.ButtonGroup>
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
disabled={false}
icon="saved"
text="Electron v2.0.2"
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
icon="saved"
text="Electron v2.0.2"
/>
</Blueprint3.Select>
</Blueprint3.Select>
</Blueprint3.ButtonGroup>
`;
exports[`VersionChooser component renderItem() renders an item 1`] = `
@@ -62,44 +66,48 @@ exports[`VersionChooser component renderItem() renders an item 1`] = `
`;
exports[`VersionChooser component renders 1`] = `
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
<Blueprint3.ButtonGroup>
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "remote",
"state": "ready",
"version": "2.0.2",
},
Object {
"source": "remote",
"state": "ready",
"version": "2.0.1",
},
Object {
"source": "remote",
"state": "ready",
"version": "1.8.7",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
disabled={false}
icon="saved"
text="Electron v2.0.2"
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
className="version-chooser"
icon="saved"
text="Electron v2.0.2"
/>
</Blueprint3.Select>
</Blueprint3.Select>
</Blueprint3.ButtonGroup>
`;
@@ -16,10 +16,9 @@ exports[`AddVersionDialog component renders 1`] = `
id="custom-electron-version"
inputProps={
Object {
"webkitdirectory": "true",
"onClick": [Function],
}
}
onInputChange={[Function]}
text="/test/file"
/>
<br />
@@ -79,10 +78,9 @@ exports[`AddVersionDialog component renders 2`] = `
id="custom-electron-version"
inputProps={
Object {
"webkitdirectory": "true",
"onClick": [Function],
}
}
onInputChange={[Function]}
text="/test/file"
/>
<br />
@@ -0,0 +1,413 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BisectDialog component renders 1`] = `
<Blueprint3.Dialog
canOutsideClickClose={true}
className="dialog-add-version"
isOpen={false}
onClose={[Function]}
title="Start a bisect session"
>
<div
className="bp3-dialog-body"
>
<Component>
Earliest Version
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
fill={true}
icon="saved"
text="v4.0.0"
/>
</Blueprint3.Select>
</Component>
<Component>
Latest Version
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
disabled={false}
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
</div>
<div
className="bp3-dialog-footer"
>
<div
className="bp3-dialog-footer-actions"
>
<Blueprint3.Button
disabled={true}
icon="play"
key="submit"
onClick={[Function]}
text="Begin"
/>
<Blueprint3.Button
icon="cross"
key="cancel"
onClick={[Function]}
text="Cancel"
/>
</div>
</div>
</Blueprint3.Dialog>
`;
exports[`BisectDialog component renders 2`] = `
<Blueprint3.Dialog
canOutsideClickClose={true}
className="dialog-add-version"
isOpen={false}
onClose={[Function]}
title="Start a bisect session"
>
<div
className="bp3-dialog-body"
>
<Component>
Earliest Version
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
<Component>
Latest Version
<Blueprint3.Select
disabled={true}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
disabled={true}
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
</div>
<div
className="bp3-dialog-footer"
>
<div
className="bp3-dialog-footer-actions"
>
<Blueprint3.Button
disabled={true}
icon="play"
key="submit"
onClick={[Function]}
text="Begin"
/>
<Blueprint3.Button
icon="cross"
key="cancel"
onClick={[Function]}
text="Cancel"
/>
</div>
</div>
</Blueprint3.Dialog>
`;
exports[`BisectDialog component renders 3`] = `
<Blueprint3.Dialog
canOutsideClickClose={true}
className="dialog-add-version"
isOpen={false}
onClose={[Function]}
title="Start a bisect session"
>
<div
className="bp3-dialog-body"
>
<Component>
Earliest Version
<Blueprint3.Select
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "4.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "5.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
fill={true}
icon="saved"
text="v4.0.0"
/>
</Blueprint3.Select>
</Component>
<Component>
Latest Version
<Blueprint3.Select
disabled={false}
filterable={true}
itemPredicate={[Function]}
itemRenderer={[Function]}
items={
Array [
Object {
"source": "local",
"state": "ready",
"version": "1.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "2.0.0",
},
Object {
"source": "local",
"state": "ready",
"version": "3.0.0",
},
]
}
noResults={
<Blueprint3.MenuItem
disabled={true}
multiline={false}
popoverProps={Object {}}
shouldDismissPopover={true}
text="No results."
/>
}
onItemSelect={[Function]}
>
<Blueprint3.Button
disabled={false}
fill={true}
icon="small-minus"
text=""
/>
</Blueprint3.Select>
</Component>
</div>
<div
className="bp3-dialog-footer"
>
<div
className="bp3-dialog-footer-actions"
>
<Blueprint3.Button
disabled={true}
icon="play"
key="submit"
onClick={[Function]}
text="Begin"
/>
<Blueprint3.Button
icon="cross"
key="cancel"
onClick={[Function]}
text="Cancel"
/>
</div>
</div>
</Blueprint3.Dialog>
`;
@@ -1,23 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Dialogs component renders initially without visible dialogs 1`] = `
<div
className="dialogs"
key="dialogs"
>
<WarningDialog
appState={
Object {
"isAddVersionDialogShowing": false,
"isSettingsShowing": false,
"isTokenDialogShowing": false,
"warningDialogTexts": Object {
"cancel": "",
"label": "",
"ok": "",
},
}
}
/>
</div>
`;
@@ -56,7 +56,7 @@ exports[`Editors component renders 1`] = `
maximize
</desc>
<path
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
fillRule="evenodd"
key="0"
/>
@@ -84,7 +84,7 @@ exports[`Editors component renders 1`] = `
cross
</desc>
<path
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
fillRule="evenodd"
key="0"
/>
@@ -229,7 +229,7 @@ exports[`Editors component renders 1`] = `
maximize
</desc>
<path
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
fillRule="evenodd"
key="0"
/>
@@ -257,7 +257,7 @@ exports[`Editors component renders 1`] = `
cross
</desc>
<path
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
fillRule="evenodd"
key="0"
/>
@@ -355,7 +355,7 @@ exports[`Editors component renders 1`] = `
</div>
<div
className="mosaic-tile"
key="html"
key="preload"
style={
Object {
"bottom": "50%",
@@ -364,6 +364,179 @@ exports[`Editors component renders 1`] = `
"top": "0%",
}
}
>
<div
className="mosaic-window mosaic-drop-target preload"
>
<div
className="mosaic-window-toolbar draggable"
>
<div>
<div>
<h5>
Preload (preload.js)
</h5>
</div>
<div />
<div
className="mosaic-controls"
>
<button
className="bp3-button bp3-small"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<span
className="bp3-icon bp3-icon-maximize"
icon="maximize"
>
<svg
data-icon="maximize"
height={16}
viewBox="0 0 16 16"
width={16}
>
<desc>
maximize
</desc>
<path
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</button>
<button
className="bp3-button bp3-small"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<span
className="bp3-icon bp3-icon-cross"
icon="cross"
>
<svg
data-icon="cross"
height={16}
viewBox="0 0 16 16"
width={16}
>
<desc>
cross
</desc>
<path
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
<div
className="mosaic-window-body"
>
Editor
</div>
<div
className="mosaic-window-body-overlay"
onClick={[Function]}
/>
<div
className="mosaic-window-additional-actions-bar"
/>
<div
className="mosaic-preview"
>
<div
className="mosaic-window-toolbar"
>
<div
className="mosaic-window-title"
>
Preload (preload.js)
</div>
</div>
<div
className="mosaic-window-body"
>
<h4>
Preload (preload.js)
</h4>
<span
className="bp3-icon bp3-icon-application"
icon="application"
>
<svg
data-icon="application"
height={72}
viewBox="0 0 20 20"
width={72}
>
<desc>
application
</desc>
<path
d="M3.5 9h9c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-9c-.28 0-.5.22-.5.5s.22.5.5.5zm0 2h5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-5c-.28 0-.5.22-.5.5s.22.5.5.5zM19 1H1c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm-1 16H2V6h16v11zM3.5 13h7c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-7c-.28 0-.5.22-.5.5s.22.5.5.5z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</div>
</div>
<div
className="drop-target-container"
>
<div
className="drop-target top"
/>
<div
className="drop-target bottom"
/>
<div
className="drop-target left"
/>
<div
className="drop-target right"
/>
</div>
</div>
</div>
<div
className="mosaic-split -column"
onMouseDown={[Function]}
style={
Object {
"bottom": "0%",
"left": "50%",
"right": "0%",
"top": "50%",
}
}
>
<div
className="mosaic-split-line"
/>
</div>
<div
className="mosaic-tile"
key="html"
style={
Object {
"bottom": "25%",
"left": "50%",
"right": "0%",
"top": "50%",
}
}
>
<div
className="mosaic-window mosaic-drop-target html"
@@ -402,7 +575,7 @@ exports[`Editors component renders 1`] = `
maximize
</desc>
<path
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
fillRule="evenodd"
key="0"
/>
@@ -430,7 +603,7 @@ exports[`Editors component renders 1`] = `
cross
</desc>
<path
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
fillRule="evenodd"
key="0"
/>
@@ -518,7 +691,7 @@ exports[`Editors component renders 1`] = `
"bottom": "0%",
"left": "50%",
"right": "0%",
"top": "50%",
"top": "75%",
}
}
>
@@ -534,7 +707,7 @@ exports[`Editors component renders 1`] = `
"bottom": "0%",
"left": "50%",
"right": "0%",
"top": "50%",
"top": "75%",
}
}
>
@@ -575,7 +748,7 @@ exports[`Editors component renders 1`] = `
home
</desc>
<path
d="M2 10v5c0 .55.45 1 1 1h3v-5h4v5h3c.55 0 1-.45 1-1v-5L8 4l-6 6zm13.71-2.71L14 5.59V2c0-.55-.45-1-1-1s-1 .45-1 1v1.59L8.71.29C8.53.11 8.28 0 8 0s-.53.11-.71.29l-7 7a1.003 1.003 0 0 0 1.42 1.42L8 2.41l6.29 6.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71z"
d="M2 10v5c0 .55.45 1 1 1h3v-5h4v5h3c.55 0 1-.45 1-1v-5L8 4l-6 6zm13.71-2.71L14 5.59V2c0-.55-.45-1-1-1s-1 .45-1 1v1.59L8.71.29C8.53.11 8.28 0 8 0s-.53.11-.71.29l-7 7a1.003 1.003 0 001.42 1.42L8 2.41l6.29 6.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71z"
fillRule="evenodd"
key="0"
/>
@@ -609,7 +782,7 @@ exports[`Editors component renders 1`] = `
maximize
</desc>
<path
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 0 0-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 0 0-.29.7 1.003 1.003 0 0 0 1.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
d="M5.99 8.99c-.28 0-.53.11-.71.29l-3.29 3.29v-1.59c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1s-.45-1-1-1H3.41L6.7 10.7a1.003 1.003 0 00-.71-1.71zm9-9h-4c-.55 0-1 .45-1 1s.45 1 1 1h1.59l-3.3 3.3a.99.99 0 00-.29.7 1.003 1.003 0 001.71.71l3.29-3.29V5c0 .55.45 1 1 1s1-.45 1-1V1c0-.56-.45-1.01-1-1.01z"
fillRule="evenodd"
key="0"
/>
@@ -637,7 +810,7 @@ exports[`Editors component renders 1`] = `
cross
</desc>
<path
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L9.41 8z"
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
fillRule="evenodd"
key="0"
/>
@@ -715,7 +888,7 @@ exports[`Editors component renders 1`] = `
play
</desc>
<path
d="M12 8c0-.35-.19-.64-.46-.82l.01-.02-6-4-.01.02A.969.969 0 0 0 5 3c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1 .21 0 .39-.08.54-.18l.01.02 6-4-.01-.02c.27-.18.46-.47.46-.82z"
d="M12 8c0-.35-.19-.64-.46-.82l.01-.02-6-4-.01.02A.969.969 0 005 3c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1 .21 0 .39-.08.54-.18l.01.02 6-4-.01-.02c.27-.18.46-.47.46-.82z"
fillRule="evenodd"
key="0"
/>
@@ -869,8 +1042,12 @@ exports[`Editors component renders a toolbar 1`] = `
},
"second": Object {
"direction": "column",
"first": "html",
"second": "docsDemo",
"first": "preload",
"second": Object {
"direction": "column",
"first": "html",
"second": "docsDemo",
},
},
},
"setWarningDialogTexts": [Function],
@@ -893,8 +1070,12 @@ exports[`Editors component renders a toolbar 1`] = `
},
"second": Object {
"direction": "column",
"first": "html",
"second": "docsDemo",
"first": "preload",
"second": Object {
"direction": "column",
"first": "html",
"second": "docsDemo",
},
},
},
"setWarningDialogTexts": [Function],

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