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
Felix Rieseberg 33a236edd1 Merge pull request #203 from electron/load-electron-examples
feat: add support for launch examples from the Electron docs
2019-06-07 21:30:27 +02:00
Felix Rieseberg f7930fb523 test: Fix more tests 2019-06-07 12:15:49 -07:00
Felix Rieseberg 88cad66076 test: Add some tests 2019-06-07 11:01:36 -07:00
Felix Rieseberg f0afedf896 fix: Linter issues 2019-06-07 09:39:36 -07:00
Samuel Attard 38234dd0a3 chore: fix specs 2019-06-06 14:59:53 -07:00
Felix Rieseberg 83e834bea5 Merge pull request #205 from electron/touchbar
feat: Touchbar
2019-06-06 18:23:18 +02:00
Felix Rieseberg 38be3cca14 test: Add touchBarManager creation 2019-06-03 15:59:00 -07:00
Felix Rieseberg 4908cc6110 feat: Sort versions in touch bar 2019-06-03 13:11:11 -07:00
Felix Rieseberg ab4a9015d1 feat: Add a touchbar feature 2019-06-02 21:12:34 -07:00
Felix Rieseberg 73ab813efc Merge pull request #204 from electron/add-about-panel
feat: add more information to about panel
2019-06-02 09:30:00 +02:00
Shelley Vohr f3570c5069 spec: add a spec for about panel functionality 2019-06-01 09:54:25 -07:00
Shelley Vohr 4c564eaa63 feat: add more information to about panel 2019-05-31 22:32:05 -07:00
Samuel Attard a289842e32 feat: add support for launch examples from the Electron docs 2019-05-27 11:03:18 -07:00
Felix Rieseberg 9d697e7a3f Merge pull request #202 from electron/gist-launch
feat: add wiring for protocol launching gists
2019-05-27 00:16:51 -07:00
Felix Rieseberg e96e5345e6 Merge pull request #201 from electron/fix-audit
chore: fix npm audit output
2019-05-27 00:16:00 -07:00
Samuel Attard 7c97bc2ef6 feat: add wiring for protocol launching gists 2019-05-25 15:29:04 -07:00
Samuel Attard d55c5c067d chore: fix npm audit output 2019-05-25 14:08:14 -07:00
Felix Rieseberg 083ac3fbc2 chore: Update monaco-loader 2019-05-17 17:23:47 +09:00
Felix Rieseberg 5c8fe3d6e0 build: Fix x64 releases 2019-05-17 14:00:54 +09:00
Felix Rieseberg f034c5240c chore: Update version number 2019-05-17 13:29:40 +09:00
Felix Rieseberg 6421187db4 Merge pull request #197 from electron/editors-to-panels
feat: Panels
2019-05-17 13:00:26 +09:00
Felix Rieseberg 6fb385a95f fix: Import typo 2019-05-17 12:39:14 +09:00
Felix Rieseberg c47fea09ed fix: Don't whine plz 2019-05-17 12:31:10 +09:00
Felix Rieseberg ef17ccd9cb test: Update the tests 2019-05-17 12:31:10 +09:00
Felix Rieseberg 3f173aa4d4 chore: Better panel titles 2019-05-17 12:31:10 +09:00
Felix Rieseberg faf15081e0 fix: Fix linter issues 2019-05-17 12:31:10 +09:00
Felix Rieseberg 4306f53c70 feat: A first demo 2019-05-17 12:31:10 +09:00
Felix Rieseberg 52a8aac6c7 test: Add a test for the default show-me page 2019-05-17 12:31:10 +09:00
Felix Rieseberg 7660192dcb test: Update the tests 2019-05-17 12:31:10 +09:00
Felix Rieseberg 53ceeb5600 feat: New overview button for docs & demos 2019-05-17 12:31:10 +09:00
Felix Rieseberg 5cd7a57f08 feat: Basic implementation of demo pages 2019-05-17 12:31:10 +09:00
Felix Rieseberg e96a3da09b feat: A generic "this is a subset only" component 2019-05-17 12:31:10 +09:00
Felix Rieseberg 90c198d487 feat: A generic "more information" component 2019-05-17 12:31:10 +09:00
Felix Rieseberg 5b56333516 feat: A utility to get docs urls 2019-05-17 12:31:10 +09:00
Felix Rieseberg c87f65ca76 feat: A generic show me component 2019-05-17 12:31:10 +09:00
Felix Rieseberg 0f27c8aa7a fix: Typo in type-checks 2019-05-17 12:31:10 +09:00
Felix Rieseberg b5880aef46 feat: Give docs their own little button 2019-05-17 12:31:10 +09:00
Felix Rieseberg fb46eaf0e4 feat: Improve panel styles 2019-05-17 12:31:10 +09:00
Felix Rieseberg c1040097e3 fix: A small issue with panel visibility 2019-05-17 12:31:10 +09:00
Felix Rieseberg 333758b73d feat: Basic awareness of opened template 2019-05-17 12:31:10 +09:00
Felix Rieseberg 3c2a4e1b76 test: Improve the tests, cover more 2019-05-17 12:31:10 +09:00
Felix Rieseberg f3d42c107f test: Add test for type check 2019-05-17 12:31:10 +09:00
Felix Rieseberg 2ece4dc728 feat: Editors to Panels, for real 2019-05-17 12:31:10 +09:00
Felix Rieseberg dd2975bd0e feat: Add the ability to render generic panels 2019-05-17 12:31:10 +09:00
Felix Rieseberg fdf03dff9b Merge pull request #198 from electron/updates
feat: Update all dependencies (Electron 5)
2019-05-17 12:30:44 +09:00
Felix Rieseberg 48fa762f7f build: Fine, Node v10 it is >:( 2019-05-17 12:16:26 +09:00
Felix Rieseberg 7cd9a86920 fix: Appease AppVeyor 2019-05-17 12:12:30 +09:00
Felix Rieseberg db2caaad59 feat: Update all dependencies (Electron 5) 2019-05-17 12:09:49 +09:00
Felix Rieseberg 3b2cb93d8b Merge pull request #195 from ajphukan/require-not-defined-bug
Fix: Require not defined bug
2019-05-16 10:58:49 +09:00
Ankur Jyoti Phukan 9822753ffd Update contributors.js 2019-05-09 12:54:48 +05:30
Ankur Jyoti Phukan 8712907182 Removed trailing space 2019-05-09 12:40:00 +05:30
Ankur Jyoti Phukan 40055ee069 Removed trailing space 2019-05-09 12:38:53 +05:30
Ankur Jyoti Phukan a33f175867 Removed trailing space 2019-05-09 12:38:11 +05:30
Ankur Jyoti Phukan 107ba63a5a Removed trailing spcae 2019-05-09 12:36:56 +05:30
Ankur Jyoti Phukan b66ad7e247 Removed trailing space 2019-05-09 12:36:20 +05:30
Ankur Jyoti Phukan 7eb15b6666 Removed Trailing space 2019-05-09 12:35:43 +05:30
Ankur Jyoti Phukan 2ed29f68dd Update main.js 2019-05-09 12:34:46 +05:30
Ankur Jyoti Phukan d89d595b57 Removed Trailing Space 2019-05-09 12:29:07 +05:30
Ankur Jyoti Phukan 2b34ef5e67 Update main.js 2019-05-09 12:23:08 +05:30
Ankur Jyoti Phukan d6f7146447 Update main.js 2019-05-09 12:22:32 +05:30
Ankur Jyoti Phukan 006ba5f0a5 Update main.js 2019-05-09 12:21:55 +05:30
Ankur Jyoti Phukan 5df390b0fd Update main.js 2019-05-09 12:21:13 +05:30
Ankur Jyoti Phukan 3eb25fed91 Update main.js 2019-05-09 12:20:21 +05:30
Ankur Jyoti Phukan 9b06f792ea Update main.js 2019-05-09 12:19:50 +05:30
Ankur Jyoti Phukan c116152605 Update main.js 2019-05-09 12:19:33 +05:30
Ankur Jyoti Phukan b9f8d55809 Update main.js 2019-05-09 12:19:13 +05:30
Ankur Jyoti Phukan 89e697fcd6 Update main.js 2019-05-09 12:18:53 +05:30
Ankur Jyoti Phukan 9df5314c08 Update main.js 2019-05-09 12:17:43 +05:30
Ankur Jyoti Phukan d01ba1cdff Added webPreference 2019-05-09 12:01:19 +05:30
Ankur Jyoti Phukan ed5ccf133f Added webPreference 2019-05-09 12:00:28 +05:30
Ankur Jyoti Phukan e1decd4819 Added webPreference 2019-05-09 11:59:49 +05:30
Ankur Jyoti Phukan 3c9f477b89 Added webPreference 2019-05-09 11:57:22 +05:30
Ankur Jyoti Phukan 5cfbf2cab5 Added webPreference 2019-05-09 11:56:36 +05:30
Ankur Jyoti Phukan 89272fbaa9 Added webPreference 2019-05-09 11:55:47 +05:30
Ankur Jyoti Phukan 5d572f451c Added webPreference 2019-05-09 11:54:52 +05:30
Ankur Jyoti Phukan 319136ecc8 Added WebPreference 2019-05-09 11:51:08 +05:30
Ankur Jyoti Phukan 3a4aa06331 Added WebPreference 2019-05-09 11:48:17 +05:30
Felix Rieseberg e283a118f6 Merge pull request #187 from malept/update-forge-transform
fix: use the latest version of Forge v6 for Fiddle Forge actions
2019-04-12 11:44:24 -07:00
Mark Lee e67d1b0179 fix: use the latest version of Forge v6 for Fiddle Forge actions 2019-04-10 23:28:42 -07:00
Felix Rieseberg 9805d98dd8 Merge pull request #184 from electron/test-100
test: Ensure full coverage
2019-04-02 16:11:20 -07:00
Felix Rieseberg 009ad25983 test: Test GitHub authentication in address bar 2019-04-02 15:52:23 -07:00
Felix Rieseberg 9a65124289 test: Test editors with visible editors 2019-04-02 15:39:44 -07:00
Felix Rieseberg 4d7c81ee69 Merge pull request #183 from electron/panel-management
feat: Panel management (close and open editors)
2019-04-02 15:27:06 -07:00
Felix Rieseberg ed91cb5ba1 test: Increase test coverage 2019-04-01 16:23:17 -07:00
Felix Rieseberg 8b04f67f71 fix: Linting issues 2019-04-01 16:15:34 -07:00
Felix Rieseberg 2e183f0538 fix: Handle setting values on closed editors 2019-04-01 16:13:51 -07:00
Felix Rieseberg 6b022bbea7 test: Improve the tests 2019-04-01 15:54:03 -07:00
Felix Rieseberg 6bdb7d71ec feat: Add a non-ideal state for the editors 2019-03-31 17:31:22 -07:00
Felix Rieseberg 0ebe6ab9b3 fix: Style when there is only one panel 2019-03-31 16:24:05 -07:00
Felix Rieseberg 5e73deeb4f test: Polish and lint tests 2019-03-31 14:04:24 -07:00
Felix Rieseberg 02d81aa407 test: Add spec for the new state methods 2019-03-31 13:59:20 -07:00
Felix Rieseberg 46deeb857e test: Test editors-mosaic-arrangement 2019-03-31 13:35:34 -07:00
Felix Rieseberg ca55d2e620 test: Add a spec for editors-toolbar-button 2019-03-31 13:07:36 -07:00
Felix Rieseberg 88974d277f test: Update the editor spec 2019-03-31 13:01:06 -07:00
Felix Rieseberg 1239afafeb test: Add spec for commands-editor 2019-03-31 12:45:36 -07:00
Felix Rieseberg 415877c067 test: Add spec for devtools 2019-03-31 12:24:54 -07:00
Felix Rieseberg 0a0779ffa1 test: Add tests for the new editor utilities 2019-03-31 12:17:46 -07:00
Felix Rieseberg 9cf34ea983 test: Fix the existing tests 2019-03-31 12:06:04 -07:00
Felix Rieseberg 83a7d23b38 test: Fix Wallaby & Jest 2019-03-31 12:00:09 -07:00
Felix Rieseberg 7d19ad91a6 feat: Add panel management 2019-03-31 10:39:56 -07:00
Felix Rieseberg db13699790 feat: Install dev tools 2019-03-31 10:36:55 -07:00
Felix Rieseberg 4a8fc24ff6 fix: Version number test 2019-03-30 17:02:39 -07:00
Felix Rieseberg 3ed41626b1 chore: Update version, electron versions 2019-03-28 09:29:44 -07:00
Felix Rieseberg b3e25cfe79 Merge pull request #182 from electron/electron-logging
feat: add an execution toggle to enable advanced electron logging
2019-03-27 15:15:49 -07:00
Samuel Attard 00bb15a891 feat: add an execution toggle to enable advanced electron logging 2019-03-27 11:02:12 -07:00
Felix Rieseberg 94da51128a Merge pull request #181 from electron/update-dependencies
feat: Update all dependencies
2019-03-25 14:51:43 -04:00
Felix Rieseberg b573f09494 fix: Lockfile was somehow out of sync 2019-03-25 10:19:51 -07:00
Felix Rieseberg f05e36de34 infra: Update all dependencies 2019-03-25 10:17:14 -07:00
Felix Rieseberg 44c7c70793 Merge pull request #178 from electron/bump-version-06
build: Update the version number
2019-02-22 11:54:49 +00:00
Felix Rieseberg bcbca7aceb build: Update the version number 2019-02-22 11:54:34 +00:00
Felix Rieseberg 9515458545 Merge pull request #177 from electron/electron-405
build: Update to Electron 4.0.5
2019-02-22 11:53:45 +00:00
Felix Rieseberg 219e90cad8 build: Update to Electron 4.0.5 2019-02-22 11:44:27 +00:00
Felix Rieseberg ba23c8019d Merge pull request #175 from electron/update-octokit
feat: Update octokit, handle fiddle load errors
2019-02-22 11:42:18 +00:00
Felix Rieseberg 2fef96a2b2 Merge branch 'master' into update-octokit 2019-02-22 11:31:15 +00:00
Felix Rieseberg 2d8cb9748d Merge pull request #176 from electron/update-dependencies-2
build: Update all the dependencies
2019-02-22 11:30:38 +00:00
Felix Rieseberg eaefbb7ad0 infra: Update all the dependencies 2019-02-21 11:32:11 +00:00
Felix Rieseberg d5f28253f8 feat: Handle loadFiddle() errors 2019-02-21 11:15:50 +00:00
Felix Rieseberg ac451d6b4a infra: Update Octokit methods 2019-02-21 11:01:03 +00:00
Felix Rieseberg b5e0e63766 infra: Update Octokit 2019-02-21 11:00:50 +00:00
Felix Rieseberg 49538ea55a Merge pull request #174 from electron/fix-show-me
fix: Lower-case names for show-me-templates
2019-02-21 10:26:16 +00:00
Felix Rieseberg 23ac9b8e02 fix: Lower-case names for show-me-templates 2019-02-20 16:27:47 +00:00
Felix Rieseberg 4bf2bee7c8 docs: Update the readme with new screenshot 2019-02-01 16:44:58 -08:00
Felix Rieseberg e742d8426c Merge pull request #172 from electron/fix-contributors-script
fix: Allow contributors.js to use GitHub credentials
2019-02-01 15:54:38 -08:00
Felix Rieseberg 7a223fd3dc fix: Allow contributors.js to use GitHub credentials 2019-02-01 15:14:41 -08:00
Felix Rieseberg 6bdc995eb6 Merge pull request #170 from electron/feat-158
feat: Display only Electron versions with a certain state
2019-01-30 22:07:35 -08:00
Felix Rieseberg 72cbcd46d1 test: Update one last test 2019-01-30 21:55:18 -08:00
Felix Rieseberg f1e51c71a3 Merge pull request #171 from electron/properly-quit
fix: On quit, quit properly
2019-01-29 19:01:28 -08:00
Felix Rieseberg 95e88c0eee test: Fix tests 2019-01-27 00:25:26 -08:00
Felix Rieseberg a54037ef0e test: Update snapshot 2019-01-26 19:15:26 -08:00
Felix Rieseberg 83bb6fc73e test: Add more tests, 2019-01-26 19:10:15 -08:00
Felix Rieseberg f8f1f0ec08 fix: On quit, quit properly 2019-01-26 18:59:40 -08:00
Felix Rieseberg f7a19b92a8 Merge pull request #169 from electron/more-enoent-logging
fix: Add some detailed templates logging
2019-01-26 18:39:55 -08:00
Felix Rieseberg dc76b78337 feat: Include it in the version chooser 2019-01-26 18:39:28 -08:00
Felix Rieseberg f6f3e94a97 feat: Add a setting for states to show 2019-01-26 18:36:34 -08:00
Felix Rieseberg 89cede25b1 Merge pull request #168 from electron/fix-167
fix: Correctly load Fiddle without form submit
2019-01-26 18:15:12 -08:00
Felix Rieseberg 8e8eec3cc6 fix: Add some detailed templates logging 2019-01-26 18:14:15 -08:00
Felix Rieseberg a0d8ca4019 fix: Correctly load Fiddle without form submit 2019-01-26 17:58:42 -08:00
Felix Rieseberg 1f0581afbc Merge pull request #166 from electron/bump-version-050
chore: Bump version to 0.5.0
2019-01-23 12:44:07 -08:00
Felix Rieseberg 70d4902b87 chore: Bump version to 0.5.0 2019-01-22 17:37:12 -08:00
Felix Rieseberg a93a99e3f0 Merge pull request #165 from electron/electron-4
feat: Update to Electron 4
2019-01-22 17:32:10 -08:00
Felix Rieseberg 4c209159e6 chore: jk, 4.0.2 2019-01-22 17:21:34 -08:00
Felix Rieseberg b09a248c96 feat: Update to Electron 4 2019-01-22 17:04:29 -08:00
Felix Rieseberg 6f09ed9c2a Merge pull request #163 from electron/forge-6-b32
chore: update Electron Forge to 6.0.0-beta.32
2019-01-21 12:51:56 -08:00
Felix Rieseberg c1ae0ecd86 Merge pull request #162 from electron/miniak/node-integration
feat: support upcoming Electron 5.0 with nodeIntegration disabled by default
2019-01-21 12:51:17 -08:00
Mark Lee 1e9a81fcf4 chore: update Electron Forge to 6.0.0-beta.32 2019-01-16 16:37:40 -08:00
Milan Burda 2061b26ed6 feat: support upcoming Electron 5.0 with nodeIntegration disabled by default 2019-01-16 20:10:52 +01:00
Felix Rieseberg 88ce4a76e9 Merge pull request #160 from bpasero/bpasero-run-f5
feat: Allow to run a fiddle via F5
2019-01-11 14:40:49 -08:00
Benjamin Pasero 9b4ef4c0d2 Allow to run a fiddle via F5
Enables to run a fiddle just with the keyboard.
2019-01-11 11:50:13 +01:00
Felix Rieseberg dca2cd67e5 Merge pull request #156 from electron/mac-better-frame
feat: support double click action on custom mac window title
2019-01-10 14:18:56 -08:00
Samuel Attard 6bab353088 fix: properly animate the opening / closing of the terminal 2019-01-09 13:44:10 +13:00
Samuel Attard e6cc44ca37 feat: support double click action on the window title bar on macOS 2019-01-09 13:43:55 +13:00
Felix Rieseberg d039c5dbf5 Merge pull request #155 from electron/bump-version-042
chore: Bump version to 0.4.2
2019-01-08 12:31:30 -08:00
Felix Rieseberg c155fff8b0 chore: Bump version to 0.4.2 2019-01-08 11:40:45 -08:00
Felix Rieseberg 11202a8d6b Merge pull request #152 from electron/100-coverage
test: Full unit test coverage
2019-01-08 11:38:52 -08:00
Felix Rieseberg 475127f5b1 fix: Travis coverage report 2019-01-08 11:17:13 -08:00
Felix Rieseberg feee5be2f8 test: Improve coverage, fix report 2019-01-08 11:11:56 -08:00
Felix Rieseberg 678c7f1578 chore: Fix coverage report 2019-01-08 10:54:59 -08:00
Felix Rieseberg d2fcb8071b test: Correctly report error 2019-01-07 23:18:57 -08:00
Felix Rieseberg 0ab35dc867 test: Update snapshot 2019-01-07 23:16:07 -08:00
Felix Rieseberg 377d221191 chore: Final cleanup 2019-01-07 23:09:42 -08:00
Felix Rieseberg cbdab9d662 test: Cover all of exec 2019-01-07 23:09:42 -08:00
Felix Rieseberg 6875ab392e test: Cover all of versions 2019-01-07 23:09:42 -08:00
Felix Rieseberg 58ec0f015c test: Cover all of file-manager 2019-01-07 23:09:42 -08:00
Felix Rieseberg 2aef0e776e test: Cover all of app 2019-01-07 23:09:42 -08:00
Felix Rieseberg b2a7d6b2a4 test: Cover all of tour 2019-01-07 23:09:42 -08:00
Felix Rieseberg 303d6bc79a test: Cover all of tour-welcome 2019-01-07 23:09:42 -08:00
Felix Rieseberg d028e7c915 test: Cover all of settings-general-appearance 2019-01-07 23:09:42 -08:00
Felix Rieseberg f04631b6c1 test: Linting, cleanup 2019-01-07 23:09:42 -08:00
Felix Rieseberg c1bb524b59 test: Cover all of settings-electron 2019-01-07 23:09:42 -08:00
Felix Rieseberg 4a38fda293 test: Cover all of editors 2019-01-07 23:09:42 -08:00
Felix Rieseberg b98ad2b572 test: Cover all of dialog-token 2019-01-07 23:09:42 -08:00
Felix Rieseberg 6118b94b09 test: Cover all of add-version 2019-01-07 23:09:42 -08:00
Felix Rieseberg d0c1ee1a82 test: Cover all of publish-button 2019-01-07 23:09:42 -08:00
Felix Rieseberg 525715a911 test: Cover all of address-bar 2019-01-07 23:09:42 -08:00
Felix Rieseberg afbc0c9e6c test: Cover squirrel 2019-01-07 23:09:42 -08:00
Felix Rieseberg c1a9cdf88b chore: Add linter 2019-01-07 23:09:42 -08:00
Felix Rieseberg d281e61b8a test: Cover shared-constants (lol) 2019-01-07 23:09:42 -08:00
Felix Rieseberg 2fbbc10497 test: Cover update 2019-01-07 23:09:42 -08:00
Felix Rieseberg e8226ca97d test: Cover all of protocol 2019-01-07 23:09:42 -08:00
Felix Rieseberg 08f2dbe937 test: Cover all of menu 2019-01-07 23:09:42 -08:00
Felix Rieseberg 68813edb51 test: Cover all of main 2019-01-07 23:09:42 -08:00
Felix Rieseberg a32261414b test: Cover all of ipcMainManager 2019-01-07 23:09:42 -08:00
Felix Rieseberg 42f67e7b21 test: Cover all of first-run 2019-01-07 23:09:41 -08:00
Felix Rieseberg 7d9b0ed79d fix: Use localecompare for invalid versions 2019-01-07 23:09:41 -08:00
Felix Rieseberg 47f1b5c4c3 test: Cover all of files 2019-01-07 23:09:41 -08:00
Felix Rieseberg 432322694d test: Correctly use "it" 2019-01-07 23:09:41 -08:00
Felix Rieseberg 889e76cbae test: Cover all of output 2019-01-07 23:09:41 -08:00
Felix Rieseberg 4a689e5c06 test: Cover all of settings-credtis 2019-01-07 23:09:41 -08:00
Felix Rieseberg 3113f324cc test: Cover all of npm 2019-01-07 23:09:41 -08:00
Felix Rieseberg 64addb1fb0 test: Cover all of state 2019-01-07 23:09:41 -08:00
Felix Rieseberg e6facf60c1 test: Cover all of runner & binary 2019-01-07 23:09:41 -08:00
Felix Rieseberg 00cb5cf6c6 Merge pull request #151 from electron/different-constants
fix: Don't require all of electron
2019-01-07 00:39:19 +08:00
Felix Rieseberg 3e8dbebe42 Merge pull request #150 from electron/update-dependencies
feat: Update Electron, Monaco, React, and others
2019-01-07 00:30:24 +08:00
Felix Rieseberg dcdef4aa4e fix: One more incorrect import 2019-01-07 00:25:25 +08:00
Felix Rieseberg 590389abdc fix: Update tests 2019-01-07 00:14:37 +08:00
Felix Rieseberg 18a8f0c142 fix: Deal with incorrect types 2019-01-07 00:10:44 +08:00
Felix Rieseberg a8e8ab6f04 fix: Don't require all of electron 2019-01-07 00:05:17 +08:00
Felix Rieseberg 45ed35467a feat: Update react-mosaic-component 2019-01-06 23:47:36 +08:00
Felix Rieseberg eb49cb296a feat: Update to Electron 3.0.14 2019-01-06 23:39:48 +08:00
Felix Rieseberg c2344d50d2 feat: Update dependencies 2019-01-06 23:33:32 +08:00
Felix Rieseberg 7786e113d9 Merge pull request #148 from electron/fix-defaultversion
fix: Ensure correct version selection & display
2019-01-06 22:58:05 +08:00
Felix Rieseberg f8b51870d8 Merge pull request #146 from electron/use-classnames
refactor: Use classnames module
2019-01-06 21:51:46 +08:00
Felix Rieseberg 601c28dcf8 fix: Handle EEXIST in unzipper 2019-01-06 21:47:25 +08:00
Felix Rieseberg 49379af856 feat: Display "checking status" in run button 2019-01-06 21:47:11 +08:00
Felix Rieseberg 8347f79756 fix: Correctly choose defaultVersion 2019-01-06 21:32:21 +08:00
Felix Rieseberg c102128d5a infra: Use classnames module 2019-01-06 21:01:42 +08:00
Felix Rieseberg fcff053136 Merge pull request #145 from electron/coverage-report
test: Add coverage reports
2019-01-06 20:27:06 +08:00
Felix Rieseberg c3641cfa04 test: Add coverage reports 2019-01-06 20:09:21 +08:00
Felix Rieseberg 6eba3cbdcc Merge pull request #143 from electron/more-tests-2
test: Write even more tests
2019-01-06 19:57:47 +08:00
Felix Rieseberg 6393c59d5c test: One more app spec 2019-01-04 23:56:53 +08:00
Felix Rieseberg 543e86bc91 chore: Fix linter issue 2019-01-04 20:38:34 +08:00
Felix Rieseberg ea5fdd426c test: Add an app setup() spec 2019-01-04 20:33:45 +08:00
Felix Rieseberg fecc499cc5 test: Add an exec test case 2019-01-04 20:13:07 +08:00
Felix Rieseberg 8d0a1524b9 test: Remove impossible case in focused-editor 2019-01-04 20:12:58 +08:00
Felix Rieseberg 918e91965a test: Add a version spec 2019-01-04 19:59:38 +08:00
Felix Rieseberg 15bab9ff86 chore: Fix linter issues 2019-01-04 19:49:37 +08:00
Felix Rieseberg 7d879b59cf test: Add runner specs 2019-01-04 19:42:33 +08:00
Felix Rieseberg 72f7e058d6 test: Add some state tests 2019-01-04 19:26:07 +08:00
Felix Rieseberg a65e044b5c test: Add some file manager tests 2019-01-04 17:13:41 +08:00
Felix Rieseberg a4c4590816 test: Remove obselete snapshot 2019-01-04 16:58:14 +08:00
Felix Rieseberg 6fbb816be3 test: Add MockState 2019-01-04 16:56:32 +08:00
Felix Rieseberg ae0ba026f2 test: More setting tests 2019-01-04 16:56:27 +08:00
Felix Rieseberg cc2a44d10a test: More app tests 2019-01-04 16:56:03 +08:00
Felix Rieseberg 3e6eb16974 Merge pull request #142 from electron/more-tests
test: More test, more minor refactoring
2019-01-02 22:47:52 +08:00
Felix Rieseberg 4212284fc4 test: Update Electron mock 2019-01-02 21:54:46 +08:00
Felix Rieseberg 8b35ff8f95 test: Add octokit spec 2019-01-02 21:54:39 +08:00
Felix Rieseberg 4d00ee9fd4 test: Add files spec 2019-01-02 21:54:27 +08:00
Felix Rieseberg 590db5304d test: Cover sorted map better 2019-01-02 21:54:18 +08:00
Felix Rieseberg e519ce2f13 test: Add highlight spec 2019-01-02 21:54:09 +08:00
Felix Rieseberg 8221920fab test: Add main spec 2019-01-02 21:53:48 +08:00
Felix Rieseberg 5de7ed7d9d test: Move out first-run, add tests 2019-01-02 21:53:36 +08:00
Felix Rieseberg a1586e864a Merge pull request #141 from electron/tests-and-security
test: Increase test coverage, make some code safer
2019-01-02 09:31:36 +08:00
Felix Rieseberg 60745ff8bb test: Add fancy-import test 2018-12-30 19:09:28 +01:00
Felix Rieseberg 4ac20aa5c2 test: Add highlight-text test 2018-12-30 19:09:20 +01:00
Felix Rieseberg 48faec9741 test: Add tests for firstRun, use outputFile 2018-12-30 18:51:08 +01:00
Felix Rieseberg c0f4e95a67 test: Add some version tests 2018-12-30 18:39:39 +01:00
Felix Rieseberg d5c8c8bd96 test: Add some editors tests 2018-12-30 18:18:39 +01:00
Felix Rieseberg 80ff396879 test: Add dialog-warning tests 2018-12-30 17:34:35 +01:00
Felix Rieseberg bf42abc88d test: Update dialog-token tests 2018-12-30 17:11:49 +01:00
Felix Rieseberg eaa064ea8d test: Update dialog-add-version tests 2018-12-30 17:01:57 +01:00
Felix Rieseberg e804436669 test: Update commands-runner tests 2018-12-30 16:58:30 +01:00
Felix Rieseberg 08d07b34a4 chore: Safer commands-runner 2018-12-30 16:53:42 +01:00
Felix Rieseberg 74ba7db13c Merge pull request #140 from electron/fix-cert-file
ci: Properly find Windows codesign certificate
2018-12-24 18:06:42 +01:00
Felix Rieseberg 0fced3aefd ci: Resolve path correctly 2018-12-24 17:56:41 +01:00
Felix Rieseberg 13c9217f9f ci: Fix variable test 2018-12-24 17:52:31 +01:00
Felix Rieseberg 346742b242 ci: Properly find Windows codesign certificate 2018-12-24 17:21:15 +01:00
Felix Rieseberg aefe273516 Merge pull request #139 from electron/bump-041
chore: Bump version number
2018-12-24 16:20:59 +01:00
Felix Rieseberg 337d19c33a chore: Bump version number 2018-12-24 16:15:58 +01:00
Felix Rieseberg ebc6f91dea Merge pull request #138 from electron/fix-tour
fix: The Tour
2018-12-24 16:09:50 +01:00
Felix Rieseberg 25446aa9e4 chore: Update snapshots 2018-12-24 15:27:29 +01:00
Felix Rieseberg f2859c053b chore: Goodbye, dialog component 2018-12-24 14:39:56 +01:00
Felix Rieseberg 49cd192817 feat: Modernize and improve tour 2018-12-24 14:26:09 +01:00
Felix Rieseberg 9bdd09c6b5 fix: Tour selectors 2018-12-24 13:48:13 +01:00
Felix Rieseberg c3d36b29b0 feat: Modernize the tour buttons 2018-12-24 13:44:15 +01:00
Felix Rieseberg 1b69a2ff52 Merge pull request #137 from electron/ci-publish-win
ci: Allow AppVeyor to codesign
2018-12-24 12:19:18 +00:00
Felix Rieseberg 311497d080 ci: AppVeyor: Use Node 10 2018-12-24 13:01:13 +01:00
Felix Rieseberg 55b7739d36 ci: AppVeyor: Upload build artifacts, build ia32 2018-12-24 13:00:28 +01:00
Felix Rieseberg 0fda97eb2f ci: AppVeyor: Print build artifacts 2018-12-24 12:52:45 +01:00
Felix Rieseberg 879815fc02 ci: Allow AppVeyor to codesign 2018-12-24 12:39:33 +01:00
Felix Rieseberg c469c3e856 Merge pull request #136 from electron/fix-windows-tests
test: Fix Windows tests
2018-12-24 08:29:31 +00:00
Felix Rieseberg 1b6750e0c5 Merge pull request #135 from electron/ci-publish
ci: Allow Travis to create signed macOS builds
2018-12-24 08:23:41 +00:00
Felix Rieseberg f14bd5c041 test: Fix Windows tests 2018-12-24 09:22:15 +01:00
Felix Rieseberg 76115a3e9c fix: Little linter error 2018-12-24 09:13:43 +01:00
Felix Rieseberg 65f86e0177 Merge branch 'ci-publish' of https://github.com/electron/fiddle into ci-publish 2018-12-24 09:10:36 +01:00
Felix Rieseberg 18cacb1532 chore: Silence the import 2018-12-24 09:10:27 +01:00
Mark Leeandfelixrieseberg 6a924ac88b Update forge.config.js
Co-Authored-By: felixrieseberg <felix@felixrieseberg.com>
2018-12-24 07:56:55 +00:00
Felix Rieseberg d125af6775 chore: Update the CI config 2018-12-24 08:51:15 +01:00
Felix Rieseberg 1a9a0579a5 chore: Add intermediate macOS certificates 2018-12-24 07:21:30 +01:00
Felix Rieseberg acee131288 chore: Install publisher-github 2018-12-24 07:20:45 +01:00
Felix Rieseberg 66536a5260 Merge pull request #134 from electron/fix-data-dir
feat: Prune appData directories after runs
2018-12-24 05:27:42 +00:00
Shelley Vohr 7941a83109 chore: fix 8 low severity vulns 2018-12-23 10:26:57 -07:00
Felix Rieseberg b312b80b69 fix: Throw error in contributors.js 2018-12-23 18:26:22 +01:00
Shelley Vohr b3c3b73b7d feat: prompt users to move to application folder (#132) 2018-12-23 10:25:37 -07:00
Shelley Vohr 0a8e9cde1b chore: add codebytere to contributors 2018-12-23 10:21:55 -07:00
Felix Rieseberg ca3f48697d chore: Add some tests 2018-12-23 17:57:51 +01:00
Felix Rieseberg 067e5cd5e2 feat: Delete app data dir after run 2018-12-23 17:57:51 +01:00
Felix Rieseberg 7e16c8c1f2 chore: Move runner logic out of component 2018-12-23 17:57:51 +01:00
Felix Rieseberg e932aad47b Merge pull request #131 from electron/electron-311
feat: Update to Electron 3.0.11
2018-12-20 09:45:49 -08:00
Felix Rieseberg 82fb8290c3 Merge pull request #130 from electron/modern-add-version-dialog
feat: Modernize 'add local build' dialog
2018-12-20 09:38:29 -08:00
Felix Rieseberg 2eb8bb3cf5 infra: Update to Electron 3.0.11 2018-12-20 09:38:04 -08:00
Felix Rieseberg 3d6448030a feat: Modernize 'add local build' dialog 2018-12-20 09:28:50 -08:00
Felix Rieseberg 65855908d1 Merge pull request #128 from electron/fix-confusing-dialog
fix: Confusing "Fiddle unsaved" dialog
2018-12-20 09:11:08 -08:00
Felix Rieseberg 9e986e24e2 Merge pull request #129 from electron/bump-040
chore: Update version number
2018-12-20 08:57:38 -08:00
Felix Rieseberg 9abc358bd9 fix: Linter issues 2018-12-20 08:57:23 -08:00
Felix Rieseberg 7f7785c69c chore: Update version number 2018-12-19 17:17:40 -08:00
Felix Rieseberg f36f300feb feat: Improve exit dialog, too 2018-12-19 17:16:59 -08:00
Felix Rieseberg 2c49214a25 fix: Confusing unsaved dialog 2018-12-19 16:57:52 -08:00
Felix Rieseberg c7a8163990 Merge pull request #127 from electron/fix-show-me
fix: "Show me" casing issue on Windows
2018-12-19 11:03:03 -08:00
Felix Rieseberg 97995b0ec2 fix: Lowercase for all templates 2018-12-19 10:24:48 -08:00
Felix Rieseberg d2377dcc30 Merge pull request #126 from electron/feat-npm-releases
feat: Use npm registry to fetch releases
2018-12-18 14:39:41 -08:00
Felix Rieseberg ac1197faaa fix: Two more fixes 2018-12-18 13:59:43 -08:00
Felix Rieseberg 9fb9aa6c92 fix: Make linter happy 2018-12-18 13:44:53 -08:00
Felix Rieseberg 6c5e41a2f1 feat: Use npm to download releases 2018-12-18 12:32:13 -08:00
Felix Rieseberg 4b52c81101 chore: Update the built-in releases file 2018-12-18 12:31:47 -08:00
Felix Rieseberg 35f4f1293c infra: Have fetch-releases use npm 2018-12-18 12:31:47 -08:00
Felix Rieseberg 8840b1ef7f Merge pull request #125 from electron/forge-arch-squirrel
chore: automatically add arch to squirrel installer filename
2018-12-18 12:31:16 -08:00
Felix Rieseberg d516522af2 Merge pull request #124 from electron/feat-public-private
feat: Publish fiddles as public/private
2018-12-18 12:30:35 -08:00
Mark Lee 0d0a51a900 infra: automatically add arch to squirrel installer filename 2018-12-17 23:53:46 -08:00
Felix Rieseberg be3b1e4a5b feat: Publish fiddles as public/private 2018-12-17 16:42:55 -08:00
Felix Rieseberg 2acc7112b7 fix: gistId in address bar without url 2018-12-17 16:04:13 -08:00
Felix Rieseberg 407bf3caeb Merge pull request #120 from electron/redesign
feat: Minor redesign, new component library
2018-12-17 15:54:04 -08:00
Felix Rieseberg 49123116d5 fix: Incorrect "arch" reference 2018-12-17 14:08:57 -08:00
Felix Rieseberg 9f6bc2f26f fix: Remove FontAwesome 2018-12-17 09:04:20 -08:00
Felix Rieseberg 5585e2d5d0 feat: Modernize the Token Dialog 2018-12-13 16:37:51 -08:00
Felix Rieseberg 4235969526 fix: forge.config.js 2018-12-13 16:20:25 -08:00
Felix Rieseberg 659d41f1c8 feat: Modernize Electron settings, credits 2018-12-12 18:23:59 -08:00
Felix Rieseberg 2549992782 feat: Modernize settings menu items 2018-12-12 18:23:59 -08:00
Felix Rieseberg b7314b83ee feat: Modernize the GitHub settings 2018-12-12 18:23:59 -08:00
Felix Rieseberg 39c2a9164a feat: Modernize appearance settings 2018-12-12 18:23:59 -08:00
Felix Rieseberg 1a9dc1fbf7 chore: Reorganize some more files 2018-12-12 18:23:59 -08:00
Felix Rieseberg 0eb01c4692 feat: Modernize the publish button 2018-12-12 18:23:59 -08:00
Felix Rieseberg 96667a3b04 chore: Rename publish-button to commands-publish-button 2018-12-12 18:23:59 -08:00
Felix Rieseberg 4365513f57 feat: Modernize address bar 2018-12-12 18:23:59 -08:00
Felix Rieseberg 913b37c285 chore: Rename address-bar 2018-12-12 18:23:59 -08:00
Felix Rieseberg 66c55d39c0 fix: Correctly display colors 2018-12-12 18:23:59 -08:00
Felix Rieseberg 0f3a3aca72 chore: Reorganize styles 2018-12-12 18:23:59 -08:00
Felix Rieseberg 3fc974a758 feat: Modernize the run button 2018-12-12 18:23:59 -08:00
Felix Rieseberg 72b805059c feat: Update version selector 2018-12-12 18:23:59 -08:00
Felix Rieseberg 291882ac5c infra: Simplify and improve parcel build 2018-12-12 18:23:59 -08:00
Felix Rieseberg 3c05cc8ec7 Merge pull request #119 from electron/update-windows-installer-icon
chore: update windows installer icon
2018-12-12 15:30:13 -08:00
Mark Lee 75d2f9ff72 🎨 update windows installer icon 2018-12-12 13:30:35 -08:00
Felix Rieseberg 0119f0ce69 Merge pull request #118 from electron/new-icon
docs: New icon
2018-12-12 13:22:48 -08:00
Mark Lee 8ed5ea94b5 Add SVG to .deb file 2018-12-11 23:53:04 -08:00
simurai deaac4e15c docs: Add icon as SVG 2018-12-12 12:47:53 +09:00
simurai 48e58c095d docs: Use transparent background for README icon 2018-12-11 16:48:07 +09:00
simurai 28f0eca429 docs: Replace loading gif 2018-12-11 12:02:02 +09:00
simurai 529906e1a9 docs: Replace icons 2018-12-11 09:20:15 +09:00
simurai 40c9aaeb7a docs: Add icon to README 2018-12-10 16:36:03 +09:00
Felix Rieseberg 15924c7741 fix: Make the linter happy 2018-11-27 15:47:56 -08:00
Felix Rieseberg c62041a000 Merge pull request #115 from electron/updates-2
chore: Updates for everyone
2018-11-27 15:46:50 -08:00
Felix Rieseberg ff3e931d14 fix: Apply lint fixes 2018-11-27 13:40:58 -08:00
Felix Rieseberg 7cc16b6fab infra: Update to electron-forge v6 beta 30 2018-11-27 13:40:08 -08:00
Felix Rieseberg 45e8e311da Merge pull request #111 from electron/tour-buttons
fix: friendlier button text for intro tour
2018-11-27 13:08:39 -08:00
Felix Rieseberg b09e792583 chore: Fix type errors in npm 2018-11-27 13:07:57 -08:00
Felix Rieseberg cdb862442c infra: Use library mode in fontawesome 2018-11-27 13:03:19 -08:00
Felix Rieseberg 1e1566dbdb infra: Update TypeScript 2018-11-27 13:03:04 -08:00
Felix Rieseberg e571fcca4f fix: style and logic in npm.ts 2018-11-27 13:02:50 -08:00
Felix Rieseberg e61e49987e infra: Update node-abi, node-fetch 2018-11-27 11:47:45 -08:00
Felix Rieseberg 133411c150 infra: Update mobx 2018-11-27 11:46:48 -08:00
Felix Rieseberg db8c7c32ab infra: Update react, semver 2018-11-27 11:44:59 -08:00
Felix Rieseberg 003e27f57e infra: Update monaco-editor 2018-11-27 11:41:57 -08:00
Felix Rieseberg 72d717bd48 infra: Update Electron to 3.0.10 2018-11-26 15:11:34 -08:00
Felix Rieseberg 675500fe25 infra: Update fontawesome 2018-11-26 12:38:23 -08:00
Felix Rieseberg fbe2311d53 infra: Update electron-forge to lastest beta 2018-11-26 12:37:09 -08:00
Felix Rieseberg ef42b1affe tests: Fix tests 2018-11-23 11:35:53 -08:00
Felix Rieseberg 2adde7d9ce Merge pull request #114 from ada-lovecraft/master
fix: erroneous require behavior
2018-11-23 11:28:54 -08:00
Ada Lovecraft 5fa0ddcc73 cleaned up npm module searching and added inline docs 2018-11-21 12:22:46 -06:00
Ada Lovecraft 5d501f0827 fixed npm module loading 2018-11-21 00:33:35 -06:00
Jeremy Apthorp d738936952 friendlier button text for intro tour
In general I like it when buttons are labeled with what's going to happen when I click on them
2018-11-09 13:33:01 -08:00
Shelley Vohr 57ce8b0b79 chore: remove unused requires in main (#109) 2018-10-30 16:55:08 -07:00
Felix Rieseberg 46f4efb4b4 Merge pull request #106 from electron/minor-fixups
feat: Add key property for buttons, update electron and typescript
2018-10-11 16:35:51 -04:00
HashimotoYT 10a696a9d7 Update test snapshot 2018-10-11 23:25:38 +03:00
HashimotoYT 585c0e1183 Update Electron, add myself to contributors. 2018-10-11 21:40:16 +03:00
HashimotoYT 946d32fdd0 Update TypeScript and Electron 2018-10-11 02:19:17 +03:00
HashimotoYT f09363ec26 Add key prop for buttons in token dialog 2018-10-11 02:11:40 +03:00
Felix Rieseberg 1a6876497a Merge pull request #104 from electron/hashi/gist-error-message
feat: provide error message for Token dialog
2018-10-08 16:49:56 -07:00
HashimotoYT 6bc04f5d2a Trying revert templates 2018-10-09 02:16:46 +03:00
HashimotoYT 9f7bf9d299 Provide error message for token dialog 2018-10-08 01:33:09 +03:00
Felix Rieseberg 3b7873917f Merge pull request #103 from electron/local-versions
feat: Add support for local Electron builds (with versions)
2018-10-05 19:17:04 -07:00
Felix Rieseberg 4616f12d6c 👷 Fix snapshot issues 2018-10-05 19:02:57 -07:00
Felix Rieseberg 63f37c9553 👷 Add a few tests 2018-10-05 18:56:22 -07:00
Felix Rieseberg 09edfa16b0 👷 Update snapshots 2018-10-05 18:56:22 -07:00
Felix Rieseberg da3a607bc1 🔧 Pretty version chooser 2018-10-05 18:56:22 -07:00
Felix Rieseberg 5fcc34b035 🔧 Parse the version 2018-10-05 18:56:22 -07:00
Felix Rieseberg dfed297226 👷 Fix existing tests 2018-10-05 18:56:22 -07:00
Felix Rieseberg 7cb2c4d188 🔧 Run local versions 2018-10-05 18:56:22 -07:00
Felix Rieseberg f0215d8c90 🔧 Add local version dialog 2018-10-05 18:56:21 -07:00
Felix Rieseberg ffdb57e2f3 🔧 More of it all 2018-10-05 18:56:11 -07:00
Felix Rieseberg e5dd6ffb4e 🎨 Use the correct delete icon 2018-09-28 16:11:32 -07:00
Felix Rieseberg 1083b6c2de 🔧 Cleanup 2018-09-28 13:45:46 -07:00
Felix Rieseberg 73b6cb0cb2 🔧 Get all versions during refresh 2018-09-28 13:14:53 -07:00
Felix Rieseberg 3e5914b192 🔧 Basic state support for local versions 2018-09-28 13:11:30 -07:00
Felix Rieseberg fe81d5c14c Merge pull request #102 from electron/update-dependencies
chore: Update all the dependencies
2018-09-27 13:16:49 -07:00
Felix Rieseberg c85536a8b9 🔧 Implement deprecations 2018-09-27 13:05:03 -07:00
Felix Rieseberg 8d90dbed09 🔧 Update tests 2018-09-27 13:01:44 -07:00
Felix Rieseberg b918c13c72 🔧 Fix standardJs lint issues 2018-09-27 11:43:47 -07:00
Felix Rieseberg 570bab9c29 🔧 Refactor and fix code lint issues 2018-09-27 11:42:50 -07:00
Felix Rieseberg f0f7ed22ee 📦 Update stylelint and standard 2018-09-27 10:40:43 -07:00
Felix Rieseberg a62c1550a1 📦 Update TypeScript 2018-09-27 10:39:39 -07:00
Felix Rieseberg 754473e10a 📦 Update to Electron 3.0.1 2018-09-27 10:32:21 -07:00
Felix Rieseberg 732f81cd33 📦 Update namor 2018-09-27 10:20:01 -07:00
Felix Rieseberg 47e5182117 📦 Update Octokit 2018-09-27 10:18:55 -07:00
Felix Rieseberg 92c831cc7b 📦 Update FontAwesome 2018-09-27 10:18:04 -07:00
Felix Rieseberg 8ffb215797 📦 Bump React 2018-09-27 10:17:30 -07:00
Felix Rieseberg 616264f017 📦 Bump mobx 2018-09-27 10:16:21 -07:00
Felix Rieseberg ee6e474b04 📦 Bump the version number 2018-09-27 10:15:46 -07:00
Felix Rieseberg ee8fa678f9 Merge pull request #101 from electron/fix-download-link
fix: don't pin download link to 0.1 tag
2018-09-27 09:44:25 -07:00
383 changed files with 25174 additions and 12728 deletions
+35 -4
View File
@@ -1,10 +1,10 @@
language: node_js
node_js: "8"
node_js: "12"
os:
- linux
- osx
dist: trusty
osx_image: xcode8.3
osx_image: xcode10
sudo: false
cache:
@@ -25,9 +25,40 @@ branches:
install:
- npm install
- |
if [[ "$TRAVIS_OS_NAME" == "osx" && "$TRAVIS_SECURE_ENV_VARS" == "true" ]]; then
export CERTIFICATE_P12=cert.p12;
echo $MACOS_CERT_P12 | base64 --decode > $CERTIFICATE_P12;
export KEYCHAIN=build.keychain;
# Create the keychain with a password
security create-keychain -p travis $KEYCHAIN;
# Make the custom keychain default, so xcodebuild will use it for signing
security default-keychain -s $KEYCHAIN;
# Unlock the keychain
security unlock-keychain -p travis $KEYCHAIN;
# Add certificates to keychain and allow codesign to access them
# Apple Worldwide Developer Relations Certification Authority
security import ./tools/certs/apple.cer -k ~/Library/Keychains/$KEYCHAIN -T /usr/bin/codesign
# Developer Authentication Certification Authority
security import ./tools/certs/dac.cer -k ~/Library/Keychains/$KEYCHAIN -T /usr/bin/codesign
# Developer ID Felix
security import $CERTIFICATE_P12 -k $KEYCHAIN -P $MACOS_CERT_PASSWORD -T /usr/bin/codesign 2>&1 >/dev/null;
rm $CERTIFICATE_P12;
security set-key-partition-list -S apple-tool:,apple: -s -k travis $KEYCHAIN
# Echo the identity
security find-identity -v -p codesigning
fi
script:
- npm run lint
- npm run test -- --runInBand
- npm run test:ci
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then npm run test:coverage; fi
- if test -z "$TRAVIS_TAG"; then npm run make; fi
after_success: if test -n "$TRAVIS_TAG"; then npm run publish; fi
after_success: if test -n "$TRAVIS_TAG"; then npm run publish; fi
+7 -7
View File
@@ -1,12 +1,12 @@
# Electron Fiddle
# <img src="https://user-images.githubusercontent.com/378023/49785546-4b7f7000-fd64-11e8-8033-a52c73a07fbf.png" width="60px" align="center" alt="Electron Fiddle icon"> Electron Fiddle
[![Build Status](https://travis-ci.org/electron/fiddle.svg?branch=master)](https://travis-ci.org/electron/fiddle)
[![Build Status](https://travis-ci.org/electron/fiddle.svg?branch=master)](https://travis-ci.org/electron/fiddle) [![Coverage Status](https://coveralls.io/repos/github/electron/fiddle/badge.svg?branch=master)](https://coveralls.io/github/electron/fiddle?branch=master)
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">
@@ -15,13 +15,13 @@ address bar.
</a>
</h3>
![Screenshot](https://user-images.githubusercontent.com/1426799/43873471-8893e3a8-9b3b-11e8-975b-e357f8039b5c.png)
<img src="https://user-images.githubusercontent.com/1426799/52155868-d3357c80-2639-11e9-9496-fa97b1dc7897.jpg" width="880px" alt="Electron Fiddle screenshot">
# Features
### Explore Electron
![Screenshot: Electron App running](https://user-images.githubusercontent.com/1426799/43873856-5f66e56e-9b3d-11e8-8472-3a14d6a08c62.png)
![Screenshot: Electron App running](https://user-images.githubusercontent.com/1426799/52155856-c0bb4300-2639-11e9-9962-a6354d08dc5a.jpg)
Try Electron without installing any dependencies: Fiddle includes everything
you'll need to explore the platform. It also includes examples for every API
@@ -40,7 +40,7 @@ all Electron APIs only a few keystrokes away.
### Compile and Package
![Screenshot: Fiddle's Tasks Menu](https://user-images.githubusercontent.com/1426799/43874349-3f5abd74-9b40-11e8-9225-ddd1f1087a47.png)
![Screenshot: Fiddle's Tasks Menu](https://user-images.githubusercontent.com/1426799/52155857-c0bb4300-2639-11e9-8776-e05dc528439c.png)
Fiddle can automatically turn your experiment into binaries you can share with
your friends, coworkers, or grandparents. It does so thanks to
+37
View File
@@ -0,0 +1,37 @@
environment:
matrix:
- nodejs_version: "12"
init:
- git config --global core.symlinks true
install:
# Setup the code signing certificate
- ps: >-
if (Test-Path Env:\WINDOWS_CERTIFICATE_P12) {
$workingDirectory = Convert-Path (Resolve-Path -path ".")
$filename = "$workingDirectory\cert.p12"
$bytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_P12)
[IO.File]::WriteAllBytes($filename, $bytes)
}
- ps: Install-Product node $env:nodejs_version x64
- node --version
- npm ci
cache:
- '%APPDATA%\npm-cache -> appveyor.yml'
test_script:
- node --version
- npm --version
- npm run lint
- npm run test
artifacts:
- path: 'out\make\squirrel.windows\**\*.exe'
build_script:
- if %APPVEYOR_REPO_TAG% EQU false npm run make
- if %APPVEYOR_REPO_TAG% EQU true npm run publish
- if %APPVEYOR_REPO_TAG% EQU true npm run publish -- --arch=ia32
- ps: Tree ./out/make /F
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 968 KiB

After

Width:  |  Height:  |  Size: 140 KiB

+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="SVG" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Icon">
<circle id="BG" stroke="#FFFFFF" stroke-width="36" fill="#E79537" cx="512" cy="512" r="430"></circle>
<g id="Glyph" transform="translate(336.000000, 280.000000)">
<path d="M92,335.02442 L92,44 C92,30.745166 102.745166,20 116,20 L356,20 C369.254834,20 380,30.745166 380,44 C380,57.254834 369.254834,68 356,68 L140,68 L140,188 L239.428571,188 C250.789858,188 260,198.745166 260,212 C260,225.254834 250.789858,236 239.428571,236 L140,236 L140,335.02442 C181.405453,345.681447 212,383.267854 212,428 C212,481.019336 169.019336,524 116,524 C62.980664,524 20,481.019336 20,428 C20,383.267854 50.5945469,345.681447 92,335.02442 Z" id="Shadow" fill="#000000" fill-rule="nonzero" opacity="0.0980980283"></path>
<path d="M72,315.02442 L72,24 C72,10.745166 82.745166,0 96,0 L336,0 C349.254834,0 360,10.745166 360,24 C360,37.254834 349.254834,48 336,48 L120,48 L120,168 L219.428571,168 C230.789858,168 240,178.745166 240,192 C240,205.254834 230.789858,216 219.428571,216 L120,216 L120,315.02442 C161.405453,325.681447 192,363.267854 192,408 C192,461.019336 149.019336,504 96,504 C42.980664,504 0,461.019336 0,408 C0,363.267854 30.5945469,325.681447 72,315.02442 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
<path d="M96,456 C122.509668,456 144,434.509668 144,408 C144,381.490332 122.509668,360 96,360 C69.490332,360 48,381.490332 48,408 C48,434.509668 69.490332,456 96,456 Z" id="Dot" fill="#EB5E00"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 23 KiB

+73 -20
View File
@@ -1,9 +1,13 @@
/* tslint:disable */
const path = require('path');
const package = require('./package.json');
const path = require('path')
const fs = require('fs')
const packageJson = require('./package.json')
module.exports = {
const { version } = packageJson
const iconDir = path.resolve(__dirname, 'assets', 'icons')
const config = {
hooks: {
generateAssets: require('./tools/generateAssets')
},
@@ -28,25 +32,40 @@ 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: [
{
name: '@electron-forge/maker-squirrel',
platforms: ['win32'],
config: {
name: 'electron-fiddle',
authors: 'Electron Community',
exe: 'electron-fiddle.exe',
iconUrl: 'https://raw.githubusercontent.com/electron/fiddle/b5bf652df0ba159ec40a62001dbb298649c3b985/assets/icons/fiddle.ico',
loadingGif: './assets/loading.gif',
noMsi: true,
remoteReleases: '',
setupExe: `electron-fiddle-${package.version}-setup-${process.arch}.exe`,
setupIcon: path.resolve(__dirname, 'assets', 'icons', 'fiddle.ico'),
certificateFile: process.env.WINDOWS_CERTIFICATE_FILE,
certificatePassword: process.env.WINDOWS_CERTIFICATE_PASSWORD
config: (arch) => {
const certificateFile = process.env.CI
? path.join(__dirname, 'cert.p12')
: process.env.WINDOWS_CERTIFICATE_FILE;
if (!certificateFile || !fs.existsSync(certificateFile)) {
console.warn(`Warning: Could not find certificate file at ${certificateFile}`)
}
return {
name: 'electron-fiddle',
authors: 'Electron Community',
exe: 'electron-fiddle.exe',
iconUrl: 'https://raw.githubusercontent.com/electron/fiddle/0119f0ce697f5ff7dec4fe51f17620c78cfd488b/assets/icons/fiddle.ico',
loadingGif: './assets/loading.gif',
noMsi: true,
remoteReleases: '',
setupExe: `electron-fiddle-${version}-${arch}-setup.exe`,
setupIcon: path.resolve(iconDir, 'fiddle.ico'),
certificatePassword: process.env.WINDOWS_CERTIFICATE_PASSWORD,
certificateFile
}
}
},
{
@@ -55,7 +74,12 @@ module.exports = {
},
{
name: '@electron-forge/maker-deb',
platforms: ['linux']
platforms: ['linux'],
config: {
icon: {
scalable: path.resolve(iconDir, 'fiddle.svg')
}
}
},
{
name: '@electron-forge/maker-rpm',
@@ -70,8 +94,37 @@ module.exports = {
owner: 'electron',
name: 'fiddle'
},
prerelease: true
draft: 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
+7 -3
View File
@@ -7,7 +7,9 @@
"bail": true,
"resetMocks": true,
"resetModules": true,
"setupTestFrameworkScriptFile": "<rootDir>/tests/setup.js",
"setupFilesAfterEnv": [
"<rootDir>/tests/setup.js"
],
"moduleFileExtensions": [
"js",
"jsx",
@@ -20,11 +22,13 @@
],
"coverageReporters": [
"json",
"html"
"html",
"lcov"
],
"collectCoverageFrom": [
"src/**/*.{ts,tsx}",
"!**/*.d.ts"
"!**/*.d.ts",
"!**/*constants.ts"
],
"coveragePathIgnorePatterns": [
"/node_modules/",
+9141 -6856
View File
File diff suppressed because it is too large Load Diff
+66 -61
View File
@@ -1,17 +1,17 @@
{
"name": "electron-fiddle",
"productName": "Electron Fiddle",
"version": "0.2.0",
"version": "0.11.1",
"description": "The easiest way to get started with Electron",
"repository": "https://github.com/electron/fiddle",
"main": "./dist/main/main",
"main": "./dist/src/main/main",
"scripts": {
"contributors": "node ./tools/contributors.js",
"less": "node ./tools/lessc.js",
"lint:style": "stylelint ./src/less/*.less --fix",
"lint:ts": "tslint -c tslint.json -p tsconfig.json -e \"node_modules/**/*.ts\" --fix",
"lint:tests": "tslint ./tests/**/*.ts{,x} -c tslint.json --fix",
"lint:templates": "standard ./static/templates/**/*.js",
"lint:templates": "standard ./static/show-me/**/*.js",
"lint": "npm-run-all lint:*",
"make": "electron-forge make",
"package": "electron-forge package",
@@ -20,6 +20,8 @@
"publish": "electron-forge publish",
"start": "rimraf ./dist && electron-forge start",
"test": "jest --config=jest.json --coverage",
"test:ci": "jest --config=jest.json --coverage --runInBand",
"test:coverage": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"tsc": "node ./tools/tsc.js",
"electron-releases": "node ./tools/fetch-releases.js"
},
@@ -34,70 +36,73 @@
"forge": "./forge.config.js"
},
"dependencies": {
"@fortawesome/fontawesome": "^1.1.8",
"@fortawesome/fontawesome-free-solid": "^5.0.13",
"@fortawesome/fontawesome-svg-core": "^1.2.0-14",
"@fortawesome/free-solid-svg-icons": "^5.1.0-11",
"@fortawesome/react-fontawesome": "0.1.0-11",
"@octokit/rest": "^15.9.4",
"builtin-modules": "^3.0.0",
"@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",
"electron-devtools-installer": "^2.2.4",
"electron-download": "^4.1.1",
"electron-squirrel-startup": "^1.0.0",
"extract-zip": "^1.6.7",
"fix-path": "^2.1.0",
"fs-extra": "^7.0.0",
"loader-utils": "^1.1.0",
"mobx": "^5.1.0",
"mobx-react": "^5.2.5",
"monaco-loader": "^0.14.0",
"namor": "^1.0.1",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-mosaic-component": "^1.1.1",
"semver": "^5.5.1",
"tmp": "0.0.33",
"tslib": "^1.9.3",
"update-electron-app": "^1.3.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.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.10.0",
"update-electron-app": "^1.5.0"
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.28",
"@electron-forge/maker-deb": "^6.0.0-beta.28",
"@electron-forge/maker-rpm": "^6.0.0-beta.28",
"@electron-forge/maker-squirrel": "^6.0.0-beta.28",
"@electron-forge/maker-zip": "^6.0.0-beta.28",
"@types/builtin-modules": "^2.0.0",
"@types/enzyme": "^3.1.13",
"@types/fs-extra": "^5.0.4",
"@types/jest": "^23.3.1",
"@types/log-symbols": "^2.0.0",
"@types/node": "^10.5.1",
"@types/react": "^16.4.12",
"@types/react-dom": "^16.0.7",
"@types/semver": "^5.5.0",
"@types/tmp": "0.0.33",
"chokidar": "^2.0.4",
"electron": "2.0.8",
"enzyme": "^3.5.0",
"enzyme-adapter-react-16": "^1.3.0",
"enzyme-to-json": "^3.3.4",
"jest": "^23.5.0",
"jest-fetch-mock": "^1.6.5",
"less": "^3.8.1",
"log-symbols": "^2.2.0",
"monaco-editor": "^0.14.3",
"node-fetch": "^2.1.2",
"npm-run-all": "^4.1.3",
"parcel-bundler": "^1.9.7",
"react-test-renderer": "^16.4.2",
"rimraf": "^2.6.2",
"standard": "^11.0.1",
"stylelint": "^9.3.0",
"stylelint-config-standard": "^18.2.0",
"ts-jest": "^23.1.4",
"tslint": "^5.11.0",
"tslint-microsoft-contrib": "^5.2.1",
"tslint-react": "^3.6.0",
"typescript": "^3.0.1"
"@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.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": "^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": "^3.2.2",
"coveralls": "^3.0.7",
"electron": "7.1.3",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"enzyme-to-json": "^3.4.3",
"jest": "^24.9.0",
"jest-fetch-mock": "^2.1.2",
"less": "^3.10.3",
"log-symbols": "^3.0.0",
"node-abi": "^2.12.0",
"node-fetch": "^2.6.0",
"npm-run-all": "^4.1.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"
}
}
+2
View File
@@ -0,0 +1,2 @@
declare module 'fix-path';
declare module 'namor';
-14
View File
@@ -1,14 +0,0 @@
import * as electronModule from 'electron';
import * as path from 'path';
const electron = electronModule.remote
? electronModule.remote
: electronModule;
export const USER_DATA_PATH = electron.app.getPath('userData');
export const INDEX_HTML_NAME = 'index.html';
export const MAIN_JS_NAME = 'main.js';
export const RENDERER_JS_NAME = 'renderer.js';
export const PACKAGE_NAME = 'package.json';
export const CONFIG_PATH = path.join(electron.app.getPath('home'), '.electron-fiddle');
+7 -3
View File
@@ -1,7 +1,5 @@
export const main = `// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')
const url = require('url')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
@@ -9,7 +7,13 @@ let mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600})
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
mainWindow.loadFile('index.html')
+51 -15
View File
@@ -1,33 +1,42 @@
export type ElectronVersionState = 'downloading' | 'ready' | 'unknown';
export type WindowNames = 'main';
export type Files = Map<string, string>;
export type FileTransform = (files: Files) => Promise<Files>;
export interface GitHubVersion {
url: string;
assets_url: string;
html_url: string;
tag_name: string;
target_commitish: string;
name: string;
prerelease: boolean;
created_at: string;
published_at: string;
body: string;
export enum ElectronVersionState {
ready = 'ready',
downloading = 'downloading',
unknown = 'unknown'
}
export enum ElectronVersionSource {
remote = 'remote',
local = 'local'
}
export interface NpmVersion {
version: string;
name?: string;
localPath?: string;
}
export interface EditorValues {
main: string;
renderer: string;
html: string;
preload: string;
package?: string;
}
export interface ElectronVersion extends GitHubVersion {
export interface ElectronVersion extends NpmVersion {
state: ElectronVersionState;
source: ElectronVersionSource;
}
export interface SetFiddleOptions {
filePath?: string;
templateName?: string;
gistId?: string;
}
export interface OutputEntry {
@@ -41,11 +50,38 @@ export interface OutputOptions {
isNotPre?: boolean;
}
export interface WarningDialogTexts {
ok?: string;
cancel?: string;
label: string;
}
export interface Templates {
[index: string]: string | Templates;
}
export type EditorId = 'main' | 'renderer' | 'html';
// Editors
export const enum EditorId {
'main' = 'main',
'renderer' = 'renderer',
'html' = 'html',
'preload' = 'preload'
}
// Panels that can show up as a mosaic
export const enum PanelId {
'docsDemo' = 'docsDemo'
}
export type MosaicId = EditorId | PanelId;
export const ALL_EDITORS = [ EditorId.main, EditorId.renderer, EditorId.preload, EditorId.html ];
export const ALL_PANELS = [ PanelId.docsDemo ];
export const ALL_MOSAICS = [ ...ALL_EDITORS, ...ALL_PANELS ];
export type ArrowPosition = 'top' | 'left' | 'bottom' | 'right';
export const enum DocsDemoPage {
DEFAULT = 'DEFAULT',
DEMO_APP = 'DEMO_APP'
}
+17 -5
View File
@@ -1,10 +1,12 @@
export enum IpcEvents {
OPEN_SETTINGS = 'OPEN_SETTINGS',
LOAD_GIST_REQUEST = 'LOAD_GIST_REQUEST',
LOAD_ELECTRON_EXAMPLE_REQUEST = 'LOAD_ELECTRON_EXAMPLE_REQUEST',
FIDDLE_RUN = 'FIDDLE_RUN',
FIDDLE_PACKAGE = 'FIDDLE_PACKAGE',
FIDDLE_MAKE = 'FIDDLE_MAKE',
MONACO_EXECUTE_COMMAND = 'MONACO_EXECUTE_COMMAND',
MONACO_TOGGLE_OPTION = 'MONACO_TOGGLE_OPTION',
FS_NEW_FIDDLE = 'FS_NEW_FIDDLE',
FS_OPEN_FIDDLE = 'FS_OPEN_FIDDLE',
FS_OPEN_TEMPLATE = 'FS_OPEN_TEMPLATE',
@@ -15,24 +17,31 @@ 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_CONFIRMATION_DIALOG = 'SHOW_CONFIRMATION_DIALOG',
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR',
TOGGLE_SOFT_WRAP = 'TOGGLE_SOFT_WRAP',
TOGGLE_MINI_MAP = 'TOGGLE_MINI_MAP'
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 = [
IpcEvents.OPEN_SETTINGS,
IpcEvents.LOAD_GIST_REQUEST,
IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST,
IpcEvents.FIDDLE_RUN,
IpcEvents.FIDDLE_PACKAGE,
IpcEvents.FIDDLE_MAKE,
IpcEvents.MONACO_EXECUTE_COMMAND,
IpcEvents.MONACO_TOGGLE_OPTION,
IpcEvents.FS_NEW_FIDDLE,
IpcEvents.FS_OPEN_FIDDLE,
IpcEvents.FS_OPEN_TEMPLATE,
@@ -41,6 +50,9 @@ export const ipcRendererEvents = [
IpcEvents.FS_SAVE_FIDDLE_FORGE,
IpcEvents.FS_SAVE_FIDDLE_GIST,
IpcEvents.SHOW_WELCOME_TOUR,
IpcEvents.TOGGLE_SOFT_WRAP,
IpcEvents.TOGGLE_MINI_MAP
IpcEvents.CLEAR_CONSOLE,
IpcEvents.LOAD_LOCAL_VERSION_FOLDER,
IpcEvents.BISECT_COMMANDS_TOGGLE
];
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL = 'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
+45
View File
@@ -0,0 +1,45 @@
@import (inline) "../../node_modules/@blueprintjs/core/lib/css/blueprint.css";
@import (inline) "../../node_modules/@blueprintjs/select/lib/css/blueprint-select.css";
@import (inline) "../../node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css";
@import (inline) "../../node_modules/@blueprintjs/core/lib/less/variables.less";
// Override some of the colors
.fiddle.bp3-dark {
.bp3-menu,
.bp3-popover .bp3-popover-content {
background-color: @background-1;
}
.bp3-popover .bp3-popover-arrow-fill {
fill: @background-1;
}
.bp3-button:not([class*="bp3-intent-"]) {
background-color: @background-1;
}
.bp3-button.bp3-minimal {
background-color: unset;
}
.bp3-button:hover,
.bp3-button.bp3-minimal:hover {
background-color: rgba(138, 155, 168, 0.15);
}
.bp3-menu-item.bp3-active.bp3-intent-primary {
background-color: @foreground-3;
}
.bp3-dialog {
background-color: @background-4;
.bp3-dialog-header {
background-color: @background-3;
}
}
.bp3-running-text {
font-size: 14px;
}
}
-103
View File
@@ -1,103 +0,0 @@
@import "variables.less";
.commands {
display: flex;
justify-content: space-between;
height: 50px;
padding: 10px;
box-sizing: border-box;
font-family: @fonts-common;
button,
input,
select {
-webkit-app-region: no-drag;
margin-bottom: 0;
}
select {
width: 100px;
margin-left: 5px;
}
& > div {
display: flex;
&:last-of-type {
margin-right: 10px;
button {
margin-left: 5px;
}
}
}
.address-bar {
height: 28px;
width: 360px;
transition: width 0.4s;
display: flex;
input,
span {
font-family: @fonts-common;
font-weight: 300;
font-size: 11px;
cursor: auto;
-webkit-app-region: no-drag;
}
input {
flex-grow: 1;
background: white;
padding-left: 1rem;
height: 2.6rem;
}
input:invalid {
box-shadow: inset 0 0 0 1px #e4252d;
}
span {
background: white;
color: #1d1d1d;
border-bottom-color: rgb(209, 209, 209);
border-bottom-style: solid;
border-bottom-left-radius: 4px;
border-bottom-width: 1px;
border-image-outset: 0;
border-image-repeat: stretch;
border-image-slice: 100%;
border-image-source: none;
border-image-width: 1;
border-left-color: rgb(209, 209, 209);
border-left-style: solid;
border-left-width: 1px;
border-top-color: rgb(209, 209, 209);
border-top-left-radius: 4px;
border-top-style: solid;
border-top-width: 1px;
display: inline-block;
height: 1.2rem;
letter-spacing: normal;
margin: 0;
padding: 6px 0 10px 10px;
user-select: text;
width: 132px;
text-rendering: auto;
text-shadow: none;
text-transform: none;
}
&.empty {
width: 200px;
}
&:focus-within {
span,
input {
border-color: @foreground-2;
}
}
}
}
@@ -4,8 +4,9 @@
background-color: @background-1;
border-bottom: @border;
-webkit-app-region: drag;
height: 23px;
height: 24px;
font-size: 1.4rem;
line-height: 15px;
small,
p,
+73
View File
@@ -0,0 +1,73 @@
@import "../variables.less";
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);
background-color: @background-3;
-webkit-app-region: drag;
}
.commands {
display: flex;
justify-content: space-between;
height: 50px;
padding: 10px;
box-sizing: border-box;
font-family: @fonts-common;
button,
input,
select {
-webkit-app-region: no-drag;
margin-bottom: 0;
white-space: nowrap;
}
select {
width: 100px;
margin-left: 5px;
}
fieldset {
border: 0;
padding: 0;
margin: 0;
min-width: 0;
}
& > div {
display: flex;
&:last-of-type {
margin-right: 10px;
.bp3-button-group {
margin-left: 5px;
}
}
}
.bp3-control-group {
margin-right: 0.5rem;
}
.address-bar {
width: 420px;
transition: width 0.4s;
input,
span {
-webkit-app-region: no-drag;
}
&.empty {
width: 300px;
}
&:focus-within {
span,
input {
border-color: @foreground-2;
}
}
}
}
@@ -1,4 +1,4 @@
@import "variables.less";
@import "../variables.less";
.dialogs {
z-index: 10;
@@ -10,10 +10,6 @@
* {
pointer-events: all;
}
.generateTokenText svg {
margin-right: 4px;
}
}
.darwin {
@@ -161,7 +157,6 @@
}
input {
width: 100%;
color: rgba(159, 234, 249, 0.5);
height: 28px;
margin-top: 10px;
@@ -172,25 +167,6 @@
&:focus {
border-color: @foreground-2;
}
&.hasError {
border-color: #df3434;
}
}
}
.tokenDialog {
.tokenSpinner {
position: absolute;
z-index: 3;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
}
}
@@ -207,6 +183,16 @@
opacity: 0.5;
}
.dialog-add-version {
.bp3-file-input {
width: 100%;
}
.bp3-callout {
margin-top: 1rem;
}
}
select {
font-size: 12px;
height: 28px;
+30
View File
@@ -0,0 +1,30 @@
.mosaic-tile {
animation: fadein 0.15s;
}
.mosaic-window-toolbar {
& > div {
display: flex;
justify-content: space-between;
width: 100%;
padding: 0 5px 0 5px;
.mosaic-controls {
transform: scale(0.75);
transform-origin: right;
}
h5 {
margin: 4px 0 0 0;
}
button {
margin-left: 8px;
}
}
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
@@ -1,8 +1,8 @@
@import "variables.less";
@import "../variables.less";
.fiddle {
.mosaic-window-toolbar.draggable {
background: var(--background-2);
.mosaic-window-toolbar {
background: var(--background-1);
box-shadow: inset 0 0 100px 100px rgba(53, 53, 53, 0.1);
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
@@ -34,16 +34,16 @@
flex-wrap: nowrap;
align-items: stretch;
align-content: stretch;
font-size: 12px;
overflow: visible;
z-index: 4;
background-color: @background-4;
}
.mosaic {
height: 100vh;
&.mosaic-blueprint-theme {
background: @background-2;
background: #bdbdbd15;
}
}
@@ -1,4 +1,4 @@
@import "variables.less";
@import "../variables.less";
.output {
font-size: 12px;
@@ -7,21 +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;
transition: padding 0.2s;
-webkit-app-region: no-drag;
&.showing {
border-top: @border;
padding-top: 10px;
height: 150px;
}
p {
margin-bottom: 0;
white-space: pre;
@@ -1,4 +1,4 @@
@import "variables.less";
@import "../variables.less";
.settings {
background: @background-3;
@@ -35,6 +35,25 @@
width: 250px;
}
.bp3-divider {
margin-top: 1rem;
margin-bottom: 1rem;
margin-left: 0;
margin-right: 0;
}
.bp3-callout {
max-width: 850px;
}
.electron-versions-table {
width: 100%;
.action {
text-align: right;
}
}
.settings-content {
height: 100%;
width: 100%;
@@ -45,8 +64,27 @@
}
.settings-electron {
.advanced-options-buttons {
display: flex;
flex-flow: row wrap;
> div {
margin-right: 1rem;
button {
width: 100%;
}
}
> div:last-of-type {
margin-right: 0;
}
}
.versions-to-show {
margin-bottom: 2rem;
border-bottom: 1px solid @border-color-1;
padding-bottom: 1.5rem;
p {
margin-bottom: 1rem;
@@ -59,18 +97,6 @@
}
}
.electron-versions-table {
button {
margin: 0;
min-width: 250px;
}
.action {
text-align: center;
width: 250px;
}
}
.settings-section {
margin-bottom: 20px;
@@ -148,25 +174,16 @@
}
li {
padding-bottom: 5px;
font-size: 1rem;
padding-top: 10px;
padding-bottom: 10px;
border-bottom: @border;
color: var(--text-color-1);
opacity: 0.8;
&:hover {
cursor: pointer;
opacity: 1;
}
&.selected {
color: @foreground-2;
}
}
}
.settings-appearance {
button {
margin-left: 0.5rem;
}
}
}
+21
View File
@@ -0,0 +1,21 @@
.show-me-panel {
padding: 10px;
overflow: scroll;
& > *:first-child {
margin-top: 0;
};
.bp3-callout {
margin-bottom: 10px;
}
}
.show-me-list {
display: flex;
flex-direction: column;
flex-wrap: wrap;
list-style: none;
padding: 0;
max-height: 400px;
}
@@ -6,3 +6,7 @@
width: 100vw;
z-index: 200;
}
.tour-portal {
z-index: 1000;
}
-74
View File
@@ -1,74 +0,0 @@
button,
input[type='button'],
input[type='reset'],
input[type='submit'] {
background-color: @foreground-2;
border: @border;
border-radius: .4rem;
cursor: pointer;
color: @button-text-color;
display: inline-block;
font-size: 1.1rem;
font-weight: 700;
height: 3rem;
letter-spacing: .1rem;
padding: 0 3.0rem;
text-align: center;
text-decoration: none;
text-transform: uppercase;
white-space: nowrap;
&:hover {
box-shadow: inset 0 0 100px 100px rgba(255, 255, 255, 0.15);
background-color: @foreground-1;
border-color: @foreground-1;
outline: 0;
color: initial;
}
&[disabled] {
cursor: default;
opacity: .5;
border-color: @foreground-3;
&:focus,
&:hover {
border-color: @foreground-3;
background: unset;
color: unset;
}
}
&.button-outline {
background-color: transparent;
color: @foreground-2;
border: @border;
&:focus,
&:hover {
background-color: transparent;
border-color: @foreground-1;
color: @foreground-1;
}
}
&.button-clear {
background-color: transparent;
border-color: transparent;
color: @foreground-2;
&:focus,
&:hover {
background-color: transparent;
border-color: transparent;
color: @foreground-1;;
}
&[disabled] {
&:focus,
&:hover {
color: @foreground-2;
}
}
}
}
-38
View File
@@ -1,38 +0,0 @@
// A lot of these styles are based on Milligram, designed by
// CJ Patolio (https://cjpatoilo.com/). While it's *almost*
// perfect for Fiddle, a few of its styles are targeted at
// websites.
//
// Check the framework out at https://github.com/milligram/milligram
//
// -----------------------------------------------------------------------------
//
// The MIT License (MIT)
//
// Copyright © 2017 CJ Patoilo <cjpatoilo@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
@import "typography.less";
@import "table.less";
@import "form.less";
@import "links.less";
@import "button.less";
@import "divider.less";
@import "spacing.less";
-7
View File
@@ -1,7 +0,0 @@
@import "../variables.less";
hr {
border: 0;
border-top: @border;
margin: 2rem 0;
}
-58
View File
@@ -1,58 +0,0 @@
@import "../variables.less";
input[type='email'],
input[type='number'],
input[type='password'],
input[type='search'],
input[type='tel'],
input[type='text'],
input[type='url'],
input[type='color'],
input:not([type]),
textarea,
select {
background-color: transparent;
border: .1rem solid rgb(209, 209, 209);;
border-radius: .4rem;
box-shadow: none;
box-sizing: inherit; // Forced to replace inherit values of the normalize.css
height: 3.8rem;
width: 100%;
&:focus {
border-color: @foreground-2;
outline: 0;
}
}
select {
padding-right: 3.0rem;
}
textarea {
min-height: 6.5rem;
}
label,
legend {
display: block;
font-size: 1.6rem;
font-weight: 700;
margin-bottom: .5rem;
}
fieldset {
border-width: 0;
padding: 0;
}
input[type='checkbox'],
input[type='radio'] {
display: inline;
}
.label-inline {
display: inline-block;
font-weight: normal;
margin-left: .5rem;
}
-5
View File
@@ -1,5 +0,0 @@
@import "../variables.less";
a {
color: @foreground-2;
}
-26
View File
@@ -1,26 +0,0 @@
.button,
button,
dd,
dt,
li {
margin-bottom: 1.0rem;
}
fieldset,
input,
select,
textarea {
margin-bottom: 1.5rem;
}
blockquote,
dl,
figure,
form,
ol,
p,
pre,
table,
ul {
margin-bottom: 2.5rem;
}
-20
View File
@@ -1,20 +0,0 @@
@import "../variables.less";
table {
border-spacing: 0;
width: 100%;
}
td, th {
border-bottom: @border;
padding: 1.2rem 1.5rem;
text-align: left;
&:first-child {
padding-left: 0
}
&:last-child {
padding-right: 0
}
}
-57
View File
@@ -1,57 +0,0 @@
html {
box-sizing: border-box;
font-size: 62.5%;
}
b, strong {
font-weight: bold
}
p {
margin-top: 0
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 300;
letter-spacing: -.1rem;
margin-bottom: 2.0rem;
margin-top: 0;
}
h1 {
font-size: 4.6rem;
line-height: 1.2;
}
h2 {
font-size: 3.6rem;
line-height: 1.25;
}
h3 {
font-size: 2.8rem;
line-height: 1.3;
}
h4 {
font-size: 2.2rem;
letter-spacing: -.08rem;
line-height: 1.35;
}
h5 {
font-size: 1.8rem;
letter-spacing: -.05rem;
line-height: 1.5;
}
h6 {
font-size: 1.6rem;
letter-spacing: 0;
line-height: 1.4;
}
-8
View File
@@ -1,8 +0,0 @@
.mosaic-tile {
animation: fadein 0.15s;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
+5 -13
View File
@@ -1,17 +1,15 @@
body {
padding: 0;
margin: 0;
background: @background-2;
background: @background-1;
overflow: hidden;
display: flex;
flex-direction: column;
height: 100vh;
font-family: @fonts-common;
color: @text-color-1;
font-size: 1.6em;
font-weight: 300;
letter-spacing: 0.01em;
line-height: 1.6;
*:focus {
outline: none;
}
}
.fiddle {
@@ -35,12 +33,6 @@ body {
-webkit-app-region: drag;
}
header {
border-bottom: @border;
background-color: @background-1;
-webkit-app-region: drag;
}
.shadow {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
-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);
}
-33
View File
@@ -1,33 +0,0 @@
@import "variables.less";
input:focus,
button:focus {
outline: none;
}
pre.prettyprint .lit {
color: @foreground-2;
}
.share-icon {
background-color: @foreground-2;
color: @text-color-1;
}
pre {
background: #f4f5f6;
border-left: 0.3rem solid @foreground-2;
&.prettyprint {
border-left: 0.3rem solid @foreground-2 !important;
color: #655d5d;
.tag {
color: @foreground-2;
}
.atn {
color: #0065ce;
}
}
}
+12 -14
View File
@@ -1,21 +1,19 @@
// Core Framework
@import "core/core.less";
// Blueprint
@import "blueprint.less";
// Variables, also imported in each file
@import "variables.less";
// Components
@import "main.less";
@import "container.less";
@import "commands.less";
@import "output.less";
@import "dialogs.less";
@import "mosaic-vendor.less";
@import "mosaic.less";
@import "settings.less";
@import "chrome-mac.less";
@import "editors.less";
@import "tour.less";
// Resets
@import "resets.less";
// Components
@import "components/commands.less";
@import "components/output.less";
@import "components/dialogs.less";
@import "components/mosaic.less";
@import "components/settings.less";
@import "components/chrome-mac.less";
@import "components/editors.less";
@import "components/tour.less";
@import "components/show-me.less";
+1
View File
@@ -4,6 +4,7 @@
@foreground-1: var(--foreground-1);
@foreground-2: var(--foreground-2);
@foreground-3: var(--foreground-3);
@background-4: var(--background-4);
@background-3: var(--background-3);
@background-2: var(--background-2);
@background-1: var(--background-1);
+8 -3
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'
}
@@ -136,17 +141,17 @@ export function createContextMenu(browserWindow: BrowserWindow) {
{
id: 'cut',
label: 'Cut',
role: editFlags.canCut ? 'cut' : '',
role: 'cut',
enabled: editFlags.canCut
}, {
id: 'copy',
label: 'Copy',
role: editFlags.canCopy ? 'copy' : '',
role: 'copy',
enabled: editFlags.canCopy
}, {
id: 'paste',
label: 'Paste',
role: editFlags.canPaste ? 'paste' : '',
role: 'paste',
enabled: editFlags.canPaste
}, {
type: 'separator'
+27
View File
@@ -0,0 +1,27 @@
import { isDevMode } from '../utils/devmode';
/**
* Installs developer tools if we're in dev mode.
*
* @export
* @returns {Promise<void>}
*/
export async function setupDevTools(): Promise<void> {
if (!isDevMode()) return;
const {
default: installExtension,
REACT_DEVELOPER_TOOLS,
REACT_PERF
} = require('electron-devtools-installer');
try {
const react = await installExtension(REACT_DEVELOPER_TOOLS);
console.log(`installDevTools: Installed ${react}`);
const perf = await installExtension(REACT_PERF);
console.log(`installDevTools: Installed ${perf}`);
} catch (error) {
console.warn(`installDevTools: Error occured:`, error);
}
}
+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]]);
}
+25 -23
View File
@@ -2,56 +2,58 @@ import { dialog } from 'electron';
import * as fs from 'fs-extra';
import * as path from 'path';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, RENDERER_JS_NAME } from '../constants';
import { IpcEvents } from '../ipc-events';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, RENDERER_JS_NAME } from '../shared-constants';
import { ipcMainManager } from './ipc';
/**
* Ensures that we're listening to file events
*/
export function setupFileListeners() {
ipcMainManager.on(IpcEvents.FS_SAVE_FIDDLE_DIALOG, () => showSaveDialog());
ipcMainManager.on(IpcEvents.FS_SAVE_FIDDLE_DIALOG, () => {
showSaveDialog();
});
}
/**
* 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] ]);
}
}
/**
+34
View File
@@ -0,0 +1,34 @@
import { app, dialog } from 'electron';
import { isFirstRun } from '../utils/check-first-run';
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 async function onFirstRunMaybe() {
if (isFirstRun()) {
await promptMoveToApplicationsFolder();
}
}
/**
* Ask the user if the app should be moved to the
* applications folder.
*/
async function promptMoveToApplicationsFolder(): Promise<void> {
if (process.platform !== 'darwin') return;
if (isDevMode() || app.isInApplicationsFolder()) return;
const { response } = await dialog.showMessageBox({
type: 'question',
buttons: ['Move to Applications Folder', 'Do Not Move'],
defaultId: 0,
message: 'Move to Applications Folder?',
});
if (response === 0) {
app.moveToApplicationsFolder();
}
}
+21 -1
View File
@@ -1,6 +1,7 @@
import { ipcMain } from 'electron';
import { EventEmitter } from 'events';
import { IpcEvents, ipcMainEvents } from '../ipc-events';
import { IpcEvents, ipcMainEvents, WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
import { getOrCreateMainWindow } from './windows';
/**
@@ -12,12 +13,26 @@ import { getOrCreateMainWindow } from './windows';
* @extends {EventEmitter}
*/
export class IpcMainManager extends EventEmitter {
public readyWebContents = new WeakSet<Electron.WebContents>();
private messageQueue = new WeakMap<Electron.WebContents, Array<[IpcEvents, Array<any> | undefined]>>();
constructor() {
super();
ipcMainEvents.forEach((name) => {
ipcMain.on(name, (...args: Array<any>) => this.emit(name, ...args));
});
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;
for (const item of queue) {
this.send(item[0], item[1], event.sender);
}
});
}
/**
@@ -31,6 +46,11 @@ export class IpcMainManager extends EventEmitter {
public send(channel: IpcEvents, args?: Array<any>, target?: Electron.WebContents) {
const _target = target || getOrCreateMainWindow().webContents;
const _args = args || [];
if (!this.readyWebContents.has(_target)) {
const existing = this.messageQueue.get(_target) || [];
this.messageQueue.set(_target, [...existing, [channel, args]]);
return;
}
_target.send(channel, ..._args);
}
+57 -20
View File
@@ -1,28 +1,25 @@
import { app } from 'electron';
import { isDevMode } from '../utils/devmode';
import { setupAboutPanel } from '../utils/set-about-panel';
import { setupDevTools } from './devtools';
import { setupDialogs } from './dialogs';
import { onFirstRunMaybe } from './first-run';
import { listenForProtocolHandler, setupProtocolHandler } from './protocol';
import { shouldQuit } from './squirrel';
import { setupUpdates } from './update';
import { getOrCreateMainWindow } from './windows';
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
app.quit();
}
app.setName('Electron Fiddle');
listenForProtocolHandler();
app.on('ready', async () => {
// If we're packaged, we want to run
// React in production mode.
if (!isDevMode()) {
process.env.NODE_ENV = 'production';
}
/**
* Handle the app's "ready" event. This is essentially
* the method that takes care of booting the application.
*/
export async function onReady() {
await onFirstRunMaybe();
if (!isDevMode()) process.env.NODE_ENV = 'production';
getOrCreateMainWindow();
setupAboutPanel();
const { setupMenu } = await import('./menu');
const { setupFileListeners } = await import('./files');
@@ -32,15 +29,55 @@ app.on('ready', async () => {
setupFileListeners();
setupUpdates();
setupDialogs();
});
setupDevTools();
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
/**
* Handle the "before-quit" event
*
* @export
*/
export function onBeforeQuit() {
(global as any).isQuitting = true;
}
/**
* All windows have been closed, quit on anything but
* macOS.
*/
export function onWindowsAllClosed() {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
}
app.on('activate', getOrCreateMainWindow);
/**
* The main method - and the first function to run
* when Fiddle is launched.
*
* Exported for testing purposes.
*/
export function main() {
// Handle creating/removing shortcuts on Windows when
// installing/uninstalling.
if (shouldQuit()) {
app.quit();
return;
}
// Set the app's name
app.name = 'Electron Fiddle';
// Ensure that there's only ever one Fiddle running
listenForProtocolHandler();
// Launch
app.on('ready', onReady);
app.on('before-quit', onBeforeQuit);
app.on('window-all-closed', onWindowsAllClosed);
app.on('activate', getOrCreateMainWindow);
}
main();
+15 -9
View File
@@ -121,6 +121,7 @@ function getTasksMenu(): MenuItemConstructorOptions {
const tasksMenu: Array<MenuItemConstructorOptions> = [
{
label: 'Run Fiddle...',
accelerator: 'F5',
click: () => ipcMainManager.send(IpcEvents.FIDDLE_RUN)
},
{
@@ -143,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])
};
}
@@ -159,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
};
}
/**
@@ -205,7 +206,7 @@ function getFileMenu(): MenuItemConstructorOptions {
type: 'separator'
},
{
label: 'Save to Gist',
label: 'Publish to Gist',
click: () => ipcMainManager.send(IpcEvents.FS_SAVE_FIDDLE_GIST),
},
{
@@ -247,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.TOGGLE_SOFT_WRAP),
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, ['wordWrap']),
});
item.submenu.push({ type: 'separator' }, {
label: 'Toggle Mini Map',
click: () => ipcMainManager.send(IpcEvents.TOGGLE_MINI_MAP),
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',
});
}
+54 -8
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';
@@ -14,24 +15,66 @@ const handlePotentialProtocolLaunch = (url: string) => {
app.once('ready', () => handlePotentialProtocolLaunch(url));
return;
}
const parsed = nodeUrl.parse(url.replace(/\/$/, ''));
if (!parsed.pathname) return;
const pathParts = parsed.pathname.split('/');
if (pathParts.length <= 0 || pathParts.length > 2) return;
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [parsed.pathname]);
const parsed = nodeUrl.parse(url.replace(/\/$/, ''));
if (!parsed.pathname || !parsed.hostname) return;
const pathParts = parsed.pathname.split('/').slice(1);
switch (parsed.hostname) {
// electron-fiddle://gist/blub
case 'gist':
if (pathParts.length === 1) {
// We only have a gist ID
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [{
id: pathParts[0],
}]);
} else if (pathParts.length === 2) {
// We have a gist owner and gist ID, we can ignore the owner
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [{
id: pathParts[1],
}]);
} else {
// This is a super invalid gist launch
return;
}
break;
// electron-fiddle://electron/{ref}/{path}
case 'electron':
if (pathParts.length > 1) {
// First part of the commit HASH / ref / branch
// Rest is the path to the example
ipcMainManager.send(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, [{
ref: pathParts[0],
path: pathParts.slice(1).join('/'),
}]);
} else {
// This is an invalid electron launch
return;
}
break;
default:
return;
}
};
export const scanArgv = (argv: Array<string>) => {
const protocolArg = argv.find((arg) => arg.startsWith(`${PROTOCOL}://`));
if (protocolArg) {
console.info('Found protocol arg in argv:', protocolArg);
handlePotentialProtocolLaunch(protocolArg);
}
};
export const scanNpmArgv = (argv: string) => {
const parsedArgv = JSON.parse(argv);
const { original: args } = parsedArgv;
scanArgv(args);
};
export const listenForProtocolHandler = () => {
const shouldQuit = app.makeSingleInstance(scanArgv);
if (shouldQuit) return app.quit();
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) app.quit();
app.on('open-url', (_, url) => {
if (url.startsWith(`${PROTOCOL}://`)) {
@@ -39,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);
}
};
+3
View File
@@ -0,0 +1,3 @@
export function shouldQuit() {
return require('electron-squirrel-startup');
}
+3 -1
View File
@@ -5,7 +5,9 @@ export function setupUpdates() {
// We delay this work by 10s to ensure that the
// app doesn't have to worry about updating during launch
setTimeout(() => {
require('update-electron-app')({
const updateApp = require('update-electron-app');
updateApp({
repo: 'electron/fiddle',
updateInterval: '1 hour',
});
+6 -2
View File
@@ -20,7 +20,8 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
acceptFirstMouse: true,
backgroundColor: '#1d2427',
webPreferences: {
webviewTag: false
webviewTag: false,
nodeIntegration: true
}
};
}
@@ -33,10 +34,13 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
* @returns {Electron.BrowserWindow}
*/
export function createMainWindow(): Electron.BrowserWindow {
console.log(`Creating main window`);
const browserWindow = new BrowserWindow(getMainWindowOptions());
browserWindow.loadFile('./dist/index.html');
browserWindow.loadFile('./dist/static/index.html');
browserWindow.webContents.once('dom-ready', () => {
browserWindow.show();
if (browserWindow) {
createContextMenu(browserWindow);
}
+111 -51
View File
@@ -1,11 +1,25 @@
import { when } from 'mobx';
import * as MonacoType from 'monaco-editor';
import { EditorValues } from '../interfaces';
import { ipcRenderer } from 'electron';
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';
import { TouchBarManager } from './touch-bar-manager';
/**
* The top-level class controlling the whole app. This is *not* a React component,
@@ -18,47 +32,79 @@ 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);
}
}
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);
if (!this.state.warningDialogLastResult) {
return false;
}
}
// set values once prompt approves
await this.setEditorValues(editorValues);
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 setValues(values: Partial<EditorValues>): Promise<boolean> {
public async setEditorValues(values: Partial<EditorValues>): Promise<void> {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
}
if (appState.isUnsaved) {
const isUserSure = confirm('Your current fiddle is unsaved. Are you sure you want to overwrite it?');
if (!isUserSure) return false;
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]!);
}
}
}
const { main, html, renderer } = fiddle.editors;
if (html && html.setValue && values.html) {
html.setValue(values.html);
}
if (main && main.setValue && values.main) {
main.setValue(values.main);
}
if (renderer && renderer.setValue && values.renderer) {
renderer.setValue(values.renderer);
}
appState.isUnsaved = false;
this.setupUnsavedOnChangeListener();
return true;
}
/**
@@ -66,22 +112,24 @@ export class App {
*
* @returns {EditorValues}
*/
public async getValues(options?: PackageJsonOptions): Promise<EditorValues> {
public async getEditorValues(
options?: PackageJsonOptions
): Promise<EditorValues> {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
}
const { main, html, renderer } = fiddle.editors;
const values: EditorValues = {
html: html && html.getValue() ? html.getValue() : '',
main: main && main.getValue() ? main.getValue() : '',
renderer: renderer && renderer.getValue() ? renderer.getValue() : '',
html: getEditorValue(EditorId.html),
main: getEditorValue(EditorId.main),
preload: getEditorValue(EditorId.preload),
renderer: getEditorValue(EditorId.renderer)
};
if (options && options.include !== false) {
values.package = await getPackageJson(appState, values, options);
if (options && options.include !== false) {
values.package = await getPackageJson(this.state, values, options);
}
return values;
@@ -91,33 +139,39 @@ export class App {
* Initial setup call, loading Monaco and kicking off the React
* render process.
*/
public async setup(): Promise<void> {
public async setup(): Promise<void | Element | React.Component> {
this.setupTheme();
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={appState} />
<Dialogs appState={appState} />
<Editors appState={appState} />
<Dialogs appState={this.state} />
<Header appState={this.state} />
<OutputEditorsWrapper appState={this.state} />
</div>
);
render(app, document.getElementById('app'));
const rendered = render(app, document.getElementById('app'));
this.setupResizeListener();
// Todo: A timer here is terrible. Let's fix this
ipcRenderer.send(WEBCONTENTS_READY_FOR_IPC_SIGNAL);
// TODO: A timer here is terrible. Let's fix this
// and ensure we actually do it once Editors have mounted.
setTimeout(() => {
this.setupUnsavedOnChangeListener();
}, 1500);
return rendered;
}
/**
@@ -129,7 +183,7 @@ export class App {
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
const editor = window.ElectronFiddle.editors[key];
const disposable = editor.onDidChangeModelContent(() => {
appState.isUnsaved = true;
this.state.isUnsaved = true;
disposable.dispose();
});
});
@@ -141,12 +195,20 @@ export class App {
* @returns {Promise<void>}
*/
public async setupTheme(): Promise<void> {
const tag: HTMLStyleElement | null = document.querySelector('style#fiddle-theme');
const theme = await getTheme(appState.theme);
const tag: HTMLStyleElement | null = document.querySelector(
'style#fiddle-theme'
);
const theme = await getTheme(this.state.theme);
if (tag && theme.css) {
tag.innerHTML = theme.css;
}
if (theme.isDark || theme.name.includes('dark')) {
document.body.classList.add('bp3-dark');
} else {
document.body.classList.remove('bp3-dark');
}
}
/**
@@ -158,10 +220,8 @@ export class App {
}
}
// tslint:disable-next-line:no-string-literal
if (!process.env.TEST && !process.env.JEST_WORKER_ID) {
window.ElectronFiddle.contentChangeListeners = [];
window.ElectronFiddle.app = new App();
window.ElectronFiddle.app.setup()
.catch((error) => console.error(error));
}
window.ElectronFiddle = window.ElectronFiddle || {};
window.ElectronFiddle.contentChangeListeners =
window.ElectronFiddle.contentChangeListeners || [];
window.ElectronFiddle.app = window.ElectronFiddle.app || new App();
window.ElectronFiddle.app.setup();
+73 -22
View File
@@ -1,9 +1,10 @@
import * as fsType from 'fs-extra';
import * as path from 'path';
import { USER_DATA_PATH } from '../constants';
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);
}
}
@@ -78,9 +96,19 @@ export class BinaryManager {
const zipPath = await eDownload({ version });
const extractPath = this.getDownloadPath(version);
console.log(`BinaryManager: Electron ${version} downloaded, now unpacking`);
const electronFiles = await this.unzip(zipPath, extractPath);
console.log(electronFiles);
console.log(`BinaryManager: Electron ${version} downloaded, now unpacking to ${extractPath}`);
try {
// Ensure the target path is empty
await fs.emptyDir(extractPath);
const electronFiles = await this.unzip(zipPath, extractPath);
console.log(`Unzipped ${version}`, electronFiles);
} catch (error) {
console.warn(`Failure while unzipping ${version}`, error);
// TODO: Handle this case
}
this.state[version] = 'ready';
}
@@ -89,11 +117,13 @@ export class BinaryManager {
* Gets the expected path for the binary of a given Electron version
*
* @param {string} version
* @param {string} dir
* @returns {string}
*/
public getElectronBinaryPath(version: string): string {
const dir = this.getDownloadPath(version);
public getElectronBinaryPath(
version: string,
dir: string = this.getDownloadPath(version),
): string {
switch (process.platform) {
case 'darwin':
return path.join(dir, 'Electron.app/Contents/MacOS/Electron');
@@ -138,14 +168,30 @@ export class BinaryManager {
* Did we already download a given version?
*
* @param {string} version
* @param {string} dir
* @returns {boolean}
*/
public async getIsDownloaded(version: string): Promise<boolean> {
const expectedPath = this.getElectronBinaryPath(version);
public async getIsDownloaded(version: string, dir?: string): Promise<boolean> {
const expectedPath = this.getElectronBinaryPath(version, dir);
const fs = await fancyImport<typeof fsType>('fs-extra');
return fs.existsSync(expectedPath);
}
public async removeTypeDefsForVersion(version: string) {
const fs = await fancyImport<typeof fsType>('fs-extra');
const _version = normalizeVersion(version);
const typeDefsDir = path.dirname(getOfflineTypeDefinitionPath(_version));
if (fs.existsSync(typeDefsDir)) {
try {
await fs.remove(typeDefsDir);
} catch (error) {
throw error;
}
}
}
/**
* Gets the expected path for a given Electron version
*
@@ -156,8 +202,9 @@ export class BinaryManager {
return path.join(USER_DATA_PATH, 'electron-bin', version);
}
/**
* Unzips an electron package so that we can actaully use it.
* Unzips an electron package so that we can actually use it.
*
* @param {string} zipPath
* @param {string} extractPath
@@ -169,7 +216,11 @@ export class BinaryManager {
process.noAsar = true;
extract(zipPath, { dir: extractPath }, (error: Error) => {
const options = {
dir: extractPath,
};
extract(zipPath, options, (error: Error) => {
if (error) {
reject(error);
return;
+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);
}
}
-135
View File
@@ -1,135 +0,0 @@
import { reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../constants';
import { classNames } from '../../utils/classnames';
import { getTitle } from '../../utils/get-title';
import { idFromUrl } from '../../utils/gist';
import { getOctokit } from '../../utils/octokit';
import { AppState } from '../state';
export interface AddressBarProps {
appState: AppState;
}
export interface AddressBarState {
value: string;
}
@observer
export class AddressBar extends React.Component<AddressBarProps, AddressBarState> {
constructor(props: AddressBarProps) {
super(props);
this.loadFiddle = this.loadFiddle.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.state = {
value: this.props.appState.gistId
};
}
/**
* Handle the form's submit event, trying to load whatever
* URL was entered.
*
* @param {React.SyntheticEvent<HTMLFormElement>} event
*/
public async handleSubmit(event: React.SyntheticEvent<HTMLFormElement>) {
event.preventDefault();
this.props.appState.gistId = idFromUrl(this.state.value) || this.state.value;
console.log('Loading');
if (this.state.value) {
this.loadFiddle();
}
}
/**
* Once the component mounts, we'll subscribe to gistId changes
*/
public componentDidMount() {
reaction(
() => this.props.appState.gistId,
(gistId: string) => this.setState({ value: gistId })
);
}
/**
* Handle the change event, which usually just updates the address bar's value
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public handleChange(event: React.ChangeEvent<HTMLInputElement>) {
this.setState({ value: event.target.value });
}
/**
* Handles invalid input to the address bar
*
* @param {React.InvalidEvent<HTMLInputElement>} event
*/
public handleInvalid(event: React.InvalidEvent<HTMLInputElement>) {
event.target.setCustomValidity('URL should begin with https://gist.github.com');
}
/**
* Load a fiddle
*
* @returns {Promise<boolean>}
* @memberof AddressBar
*/
public async loadFiddle(): Promise<boolean> {
const { appState } = this.props;
if (!confirm('Are you sure you want to load a new fiddle, all current progress will be lost?')) {
return false;
}
try {
const octo = await getOctokit();
const gist = await octo.gists.get({
gist_id: appState.gistId,
id: appState.gistId,
});
await window.ElectronFiddle.app.setValues({
html: gist.data.files[INDEX_HTML_NAME].content,
main: gist.data.files[MAIN_JS_NAME].content,
renderer: gist.data.files[RENDERER_JS_NAME].content,
});
document.title = getTitle(appState);
appState.localPath = null;
} catch (error) {
console.warn(`Loading Fiddle failed`, error);
return false;
}
return true;
}
public render() {
const { isUnsaved } = this.props.appState;
const { value } = this.state;
const isEmpty = /https:\/\/gist\.github\.com\/(.+)$/.test(value);
const className = classNames('address-bar', isUnsaved, { empty: !isEmpty });
return (
<form className={className} onSubmit={this.handleSubmit}>
<input
key='addressbar'
pattern='https:\/\/gist\.github\.com\/(.+)$'
placeholder='https://gist.github.com/...'
value={value}
onChange={this.handleChange}
onInvalid={this.handleInvalid}
/>
</form>
);
}
}
-20
View File
@@ -1,20 +0,0 @@
environment:
matrix:
- nodejs_version: "8"
init:
- git config --global core.symlinks true
install:
- ps: Install-Product node $env:nodejs_version x64
- node --version
- npm ci
cache:
- '%APPDATA%\npm-cache -> appveyor.yml'
test_script:
- npm run lint
- npm run test
build: off
+16 -1
View File
@@ -1,3 +1,4 @@
import { remote } from 'electron';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -10,11 +11,25 @@ export interface ChromeMacProps {
@observer
export class ChromeMac extends React.Component<ChromeMacProps> {
public handleDoubleClick = () => {
const doubleClickAction = remote.systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
const win = remote.getCurrentWindow();
if (doubleClickAction === 'Minimize') {
win.minimize();
} else if (doubleClickAction === 'Maximize') {
if (!win.isMaximized()) {
win.maximize();
} else {
win.unmaximize();
}
}
}
public render() {
if (process.platform !== 'darwin') return null;
return (
<div className='chrome drag'>
<div className='chrome drag' onDoubleClick={this.handleDoubleClick}>
<small>{getTitle(this.props.appState)}</small>
</div>
);
@@ -0,0 +1,131 @@
import { Button, InputGroup, Intent } from '@blueprintjs/core';
import classnames from 'classnames';
import { reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { IpcEvents } from '../../ipc-events';
import { idFromUrl, urlFromId } from '../../utils/gist';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
export interface AddressBarProps {
appState: AppState;
}
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.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.submit = this.submit.bind(this);
const { gistId } = this.props.appState;
const value = gistId ? urlFromId(gistId) : '';
const { remoteLoader } = window.ElectronFiddle.app;
this.state = {
value,
loaders: {
gist: remoteLoader.loadFiddleFromGist.bind(remoteLoader),
example: remoteLoader.loadFiddleFromElectronExample.bind(remoteLoader)
}
};
}
/**
* Handle the form's submit event, trying to load whatever
* URL was entered.
*
* @param {React.SyntheticEvent<HTMLFormElement>} event
*/
public handleSubmit(event: React.SyntheticEvent<HTMLFormElement>) {
event.preventDefault();
this.submit();
}
/**
* Commit the address bar's value to app state and load the fiddle.
*
* @memberof AddressBar
*/
public submit() {
const { remoteLoader } = window.ElectronFiddle.app;
if (this.state.value) {
remoteLoader.fetchGistAndLoad(idFromUrl(this.state.value) || this.state.value);
}
}
/**
* Once the component mounts, we'll subscribe to gistId changes
*/
public componentDidMount() {
const { appState } = this.props;
const { loaders } = this.state;
reaction(
() => appState.gistId,
(gistId: string) => this.setState({ value: urlFromId(gistId) })
);
ipcRendererManager.on(IpcEvents.LOAD_GIST_REQUEST, loaders.gist);
ipcRendererManager.on(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, loaders.example);
}
public componentWillUnmount() {
const { loaders } = this.state;
ipcRendererManager.removeListener(IpcEvents.LOAD_GIST_REQUEST, loaders.gist);
ipcRendererManager.removeListener(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, loaders.example);
}
/**
* Handle the change event, which usually just updates the address bar's value
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public handleChange(event: React.ChangeEvent<HTMLInputElement>) {
this.setState({ value: event.target.value });
}
public renderLoadButton(isValueCorrect: boolean): JSX.Element {
return (
<Button
disabled={!isValueCorrect}
icon='cloud-download'
text='Load Fiddle'
onClick={this.submit}
/>
);
}
public render() {
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}>
<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>
);
}
}
@@ -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}
/>
);
}
}
}
@@ -0,0 +1,100 @@
import { Button, Menu, MenuItem, Popover, Position } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ALL_EDITORS, MosaicId, PanelId } from '../../interfaces';
import { getVisibleMosaics } from '../../utils/editors-mosaic-arrangement';
import { AppState } from '../state';
import { TITLE_MAP } from './editors';
export interface EditorDropdownState {
value: string;
}
export interface EditorDropdownProps {
appState: AppState;
}
/**
* A dropdown allowing users to toggle the various editors
*
* @class EditorDropdown
* @extends {React.Component<EditorDropdownProps, EditorDropdownState>}
*/
@observer
export class EditorDropdown extends React.Component<EditorDropdownProps, EditorDropdownState> {
constructor(props: EditorDropdownProps) {
super(props);
this.onItemClick = this.onItemClick.bind(this);
}
public render() {
return (
<>
<Popover content={this.renderMenu()} position={Position.BOTTOM}>
<Button icon='applications' text='Editors' />
</Popover>
{this.renderDocsDemos()}
</>
);
}
public renderDocsDemos() {
if (!process.env.FIDDLE_DOCS_DEMOS) {
return null;
}
return (
<Button
icon='help'
text='Docs & Demos'
id={PanelId.docsDemo}
onClick={this.onItemClick}
active={!this.props.appState.closedPanels.docsDemo}
/>
);
}
public renderMenu() {
return (
<Menu>
{...this.renderMenuItems()}
</Menu>
);
}
public renderMenuItems() {
const { appState } = this.props;
const result: Array<JSX.Element> = [];
const visibleMosaics = getVisibleMosaics(appState.mosaicArrangement);
for (const id of ALL_EDITORS) {
result.push(
<MenuItem
icon={visibleMosaics.includes(id) ? 'eye-open' : 'eye-off'}
key={id}
text={TITLE_MAP[id]}
id={id}
onClick={this.onItemClick}
/>
);
}
return result;
}
public onItemClick(event: React.MouseEvent) {
const { id } = event.currentTarget;
const { appState } = this.props;
const visibleMosaics = getVisibleMosaics(appState.mosaicArrangement);
if (visibleMosaics.includes(id as MosaicId)) {
console.log(`EditorDropdown: Closing ${id}`);
appState.hideAndBackupMosaic(id as MosaicId);
} else {
console.log(`EditorDropdown: Opening ${id}`);
appState.showMosaic(id as MosaicId);
}
}
}
@@ -1,12 +1,10 @@
import { faSpinner, faUpload } from '@fortawesome/fontawesome-free-solid';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ButtonGroup, Menu, MenuItem, Popover, Position } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { when } from 'mobx';
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../constants';
import { IpcEvents } from '../../ipc-events';
import { classNames } from '../../utils/classnames';
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../shared-constants';
import { getOctokit } from '../../utils/octokit';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
@@ -15,10 +13,6 @@ export interface PublishButtonProps {
appState: AppState;
}
export interface PublishButtonState {
isPublishing: boolean;
}
/**
* The "publish" button takes care of logging you in.
*
@@ -27,13 +21,13 @@ 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);
this.setPublic = this.setPublic.bind(this);
}
public componentDidMount() {
@@ -71,20 +65,17 @@ 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 octo = await getOctokit();
octo.authenticate({
type: 'token',
token: this.props.appState.gitHubToken!
});
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({
public: true,
public: !!gitHubPublishAsPublic,
description: 'Electron Fiddle Gist',
files: {
[INDEX_HTML_NAME]: {
@@ -97,9 +88,9 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
content: values.renderer || '// Empty',
},
},
});
} 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) {
@@ -113,21 +104,72 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
}
this.setState({ isPublishing: false });
appState.isPublishing = false;
}
/**
* Publish fiddles as private.
*
* @memberof PublishButton
*/
public setPrivate() {
this.setPrivacy(false);
}
/**
* Publish fiddles as public.
*
* @memberof PublishButton
*/
public setPublic() {
this.setPrivacy(true);
}
public render() {
const { isPublishing } = this.state;
const className = classNames('button', 'button-publish', isPublishing);
const icon = isPublishing ? faSpinner : faUpload;
const text = isPublishing ? 'Publishing...' : 'Publish';
const { gitHubPublishAsPublic } = this.props.appState;
const { isPublishing } = this.props.appState;
const privacyIcon = gitHubPublishAsPublic ? 'unlock' : 'lock';
const privacyMenu = (
<Menu>
<MenuItem
text='Private'
icon='lock'
active={!gitHubPublishAsPublic}
onClick={this.setPrivate}
/>
<MenuItem
text='Public'
icon='unlock'
active={gitHubPublishAsPublic}
onClick={this.setPublic}
/>
</Menu>
);
return (
<button className={className} onClick={this.handleClick} disabled={isPublishing}>
<FontAwesomeIcon icon={icon} spin={isPublishing} />
<span style={{ marginLeft: 8 }} />
{text}
</button>
<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>
);
}
private setPrivacy(publishAsPublic: boolean) {
this.props.appState.gitHubPublishAsPublic = publishAsPublic;
}
}
@@ -0,0 +1,53 @@
import { Button, IButtonProps, Spinner } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersionState } from '../../interfaces';
import { AppState } from '../state';
export interface RunnerState {
}
export interface RunnerProps {
appState: AppState;
}
/**
* The runner component is responsible for actually launching the fiddle
* with Electron. It also renders the button that does so.
*
* @class Runner
* @extends {React.Component<RunnerProps, RunnerState>}
*/
@observer
export class Runner extends React.Component<RunnerProps, RunnerState> {
public render() {
const { isRunning, currentElectronVersion } = this.props.appState;
const state = currentElectronVersion && currentElectronVersion.state;
const props: IButtonProps = { className: 'button-run' };
if (state === ElectronVersionState.downloading) {
props.text = 'Downloading';
props.disabled = true;
props.icon = <Spinner size={16} />;
} else if (state === ElectronVersionState.ready) {
if (isRunning) {
props.active = true;
props.text = 'Stop';
props.onClick = window.ElectronFiddle.app.runner.stop;
props.icon = 'stop';
} else {
props.text = 'Run';
props.onClick = window.ElectronFiddle.app.runner.run;
props.icon = 'play';
}
} else {
props.text = 'Checking status';
props.disabled = true;
props.icon = <Spinner size={16} />;
}
return <Button {...props} type={undefined} />;
}
}
@@ -0,0 +1,172 @@
import { Button, ButtonGroup, MenuItem } from '@blueprintjs/core';
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../interfaces';
import { highlightText } from '../../utils/highlight-text';
import { sortedElectronMap } from '../../utils/sorted-electron-map';
import { AppState } from '../state';
import { getReleaseChannel } from '../versions';
const ElectronVersionSelect = Select.ofType<ElectronVersion>();
/**
* Helper method: Returns the <Select /> label for an Electron
* version.
*
* @param {ElectronVersion} { source, state }
* @returns {string}
*/
export function getItemLabel({ source, state, name }: ElectronVersion): string {
let label = '';
if (source === ElectronVersionSource.local) {
label = name || 'Local';
} else {
if (state === ElectronVersionState.unknown) {
label = `Not downloaded`;
} else if (state === ElectronVersionState.ready) {
label = `Downloaded`;
} else if (state === ElectronVersionState.downloading) {
label = `Downloading`;
}
}
return label;
}
/**
* Helper method: Returns the <Select /> icon for an Electron
* version.
*
* @param {ElectronVersion} { state }
* @returns
*/
export function getItemIcon({ state }: ElectronVersion) {
return state === 'ready'
? 'saved'
: state === 'downloading' ? 'cloud-download' : 'cloud';
}
/**
* Helper method: Returns the <Select /> predicate for an Electron
* version.
*
* @param {string} query
* @param {ElectronVersion} { version }
* @returns
*/
export const filterItem: ItemPredicate<ElectronVersion> = (query, { version }) => {
return version.toLowerCase().includes(query.toLowerCase());
};
/**
* Helper method: Returns the <Select /> <MenuItem /> for Electron
* versions.
*
* @param {ElectronVersion} item
* @param {IItemRendererProps} { handleClick, modifiers, query }
* @returns
*/
export const renderItem: ItemRenderer<ElectronVersion> = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
}
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
text={highlightText(item.version, query)}
key={item.version}
onClick={handleClick}
label={getItemLabel(item)}
icon={getItemIcon(item)}
/>
);
};
export interface VersionChooserState {
value: string;
}
export interface VersionChooserProps {
appState: AppState;
}
export const getVersionsFromAppState = (appState: AppState) => {
const { versions, versionsToShow, statesToShow } = appState;
return sortedElectronMap<ElectronVersion>(versions, (_key, item) => item)
.filter((item) => {
if (!item) {
return false;
}
// Check if we want to show the version
if (!versionsToShow.includes(getReleaseChannel(item))) {
return false;
}
// Check if we want to show the state
if (!statesToShow.includes(item.state)) {
return false;
}
return true;
});
};
/**
* A dropdown allowing the selection of Electron versions. The actual
* download is managed in the state.
*
* @class VersionChooser
* @extends {React.Component<VersionChooserProps, VersionChooserState>}
*/
@observer
export class VersionChooser extends React.Component<VersionChooserProps, VersionChooserState> {
constructor(props: VersionChooserProps) {
super(props);
this.onItemSelect = this.onItemSelect.bind(this);
}
/**
* Handle change, which usually means that we'd like update
* the selection version.
*
* @param {React.ChangeEvent<HTMLSelectElement>} event
*/
public onItemSelect({ version }: ElectronVersion) {
this.props.appState.setVersion(version);
}
public render() {
const { currentElectronVersion, Bisector } = this.props.appState;
const { version } = currentElectronVersion;
return (
<ButtonGroup>
<ElectronVersionSelect
filterable={true}
items={getVersionsFromAppState(this.props.appState)}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.onItemSelect}
noResults={<MenuItem disabled={true} text='No results.' />}
disabled={!!Bisector}
>
<Button
className='version-chooser'
text={`Electron v${version}`}
icon={getItemIcon(currentElectronVersion)}
disabled={!!Bisector}
/>
</ElectronVersionSelect>
</ButtonGroup>
);
}
}
+30 -11
View File
@@ -1,13 +1,14 @@
import { faTerminal } from '@fortawesome/fontawesome-free-solid';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ControlGroup } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
import { AddressBar } from './address-bar';
import { PublishButton } from './publish-button';
import { Runner } from './runner';
import { VersionChooser } from './version-chooser';
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';
import { VersionChooser } from './commands-version-chooser';
export interface CommandsProps {
appState: AppState;
@@ -28,19 +29,37 @@ export class Commands extends React.Component<CommandsProps, {}> {
public render() {
const { appState } = this.props;
const { isBisectCommandShowing: isBisectCommandShowing } = appState;
return (
<div className='commands'>
<div>
<Runner appState={appState} />
<VersionChooser appState={appState} />
<ControlGroup fill={true} vertical={false}>
<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}
icon='console'
text='Console'
onClick={appState.toggleConsole}
/>
<EditorDropdown appState={appState} />
</ControlGroup>
</div>
<div>
<AddressBar appState={appState} />
<PublishButton appState={appState} />
<button className='button' onClick={() => appState.toggleConsole()}>
<FontAwesomeIcon icon={faTerminal} />
</button>
</div>
</div>
);
@@ -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;
}
}
@@ -0,0 +1,221 @@
import { Button, Callout, Dialog, FileInput, InputGroup, Intent } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as path from 'path';
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 {
appState: AppState;
}
export interface AddVersionDialogState {
isValidElectron: boolean;
isValidVersion: boolean;
folderPath?: string;
version: string;
}
/**
* The "add version" dialog allows users to add custom builds of Electron.
*
* @class AddVersionDialog
* @extends {React.Component<AddVersionDialogProps, AddVersionDialogState>}
*/
@observer
export class AddVersionDialog extends React.Component<AddVersionDialogProps, AddVersionDialogState> {
constructor(props: AddVersionDialogProps) {
super(props);
this.state = {
isValidVersion: false,
isValidElectron: false,
version: ''
};
this.onSubmit = this.onSubmit.bind(this);
this.onClose = this.onClose.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);
}
/**
* Handles a change of the file input
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public async setFolderPath(folderPath: string) {
const { binaryManager } = this.props.appState;
const isValidElectron = !!await binaryManager.getIsDownloaded('custom', folderPath);
this.setState({ folderPath, isValidElectron });
}
public onChangeVersion(event: React.ChangeEvent<HTMLInputElement>) {
const version = event.target.value || '';
const isValidVersion = !!semver.valid(version);
this.setState({
version,
isValidVersion,
});
}
/**
* Handles the submission of the dialog
*
* @returns {Promise<void>}
*/
public async onSubmit(): Promise<void> {
const { folderPath, version } = this.state;
if (!folderPath) return;
const name = folderPath
.slice(-20)
.split(path.sep)
.slice(1)
.join(path.sep);
const toAdd: NpmVersion = {
localPath: folderPath,
version,
name
};
this.props.appState.addLocalVersion(toAdd);
this.onClose();
}
/**
* Closes the dialog
*/
public onClose() {
this.props.appState.isAddVersionDialogShowing = false;
this.reset();
}
get buttons() {
const canSubmit = this.state.isValidElectron && this.state.isValidVersion;
return [
(
<Button
icon='add'
key='submit'
disabled={!canSubmit}
onClick={this.onSubmit}
text='Add'
/>
), (
<Button
icon='cross'
key='cancel'
onClick={this.onClose}
text='Cancel'
/>
)
];
}
public render() {
const { isAddVersionDialogShowing } = this.props.appState;
const inputProps = {
onClick: (e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
e.preventDefault();
ipcRendererManager.send(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG);
}
};
const { folderPath } = this.state;
const text = folderPath ||
`Select the folder containing ${getElectronNameForPlatform()}...`;
return (
<Dialog
isOpen={isAddVersionDialogShowing}
onClose={this.onClose}
title='Add local Electron build'
className='dialog-add-version'
>
<div className='bp3-dialog-body'>
<FileInput
id='custom-electron-version'
inputProps={inputProps as any}
text={text}
/>
<br />
{this.renderPath()}
</div>
<div className='bp3-dialog-footer'>
<div className='bp3-dialog-footer-actions'>
{this.buttons}
</div>
</div>
</Dialog>
);
}
private renderPath(): JSX.Element | null {
const { folderPath, isValidElectron } = this.state;
if (!folderPath) return null;
const info = isValidElectron
? `We found an ${getElectronNameForPlatform()} in this folder.`
: `We did not find a ${getElectronNameForPlatform()} in this folder...`;
return (
<Callout>
{info}
{this.renderVersionInput()}
</Callout>
);
}
private renderVersionInput(): JSX.Element | null {
const { isValidElectron, isValidVersion, version } = this.state;
if (!isValidElectron) return null;
return (
<>
<p>
Please specify a version, used for typings and the name.
Must be <code>semver</code> compliant.
</p>
<InputGroup
intent={isValidVersion ? undefined : Intent.DANGER}
value={version}
onChange={this.onChangeVersion}
placeholder='4.0.0'
/>
</>
);
}
/**
* Reset this component's state
*/
private reset(): void {
this.setState({
isValidElectron: false,
isValidVersion: false,
version: '',
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>
);
}
}
@@ -1,19 +1,18 @@
import { faKey, faSpinner } from '@fortawesome/fontawesome-free-solid';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, Callout, Dialog, InputGroup, Intent } from '@blueprintjs/core';
import { clipboard, shell } from 'electron';
import { observer } from 'mobx-react';
import * as React from 'react';
import { getOctokit } from '../../utils/octokit';
import { AppState } from '../state';
import { Dialog } from './dialog';
export interface TokenDialogProps {
appState: AppState;
}
export interface TokenDialogState {
tokenInput?: string;
tokenInput: string;
verifying: boolean;
error: boolean;
}
@@ -32,16 +31,15 @@ const GENERATE_TOKEN_URL = `https://github.com/settings/tokens/new?scopes=${TOKE
*/
@observer
export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogState> {
private tokenInputRef: React.RefObject<HTMLInputElement>;
constructor(props: TokenDialogProps) {
super(props);
this.state = {
verifying: false,
error: false,
tokenInput: ''
};
this.tokenInputRef = React.createRef();
this.onSubmitToken = this.onSubmitToken.bind(this);
this.openGenerateTokenExternal = this.openGenerateTokenExternal.bind(this);
this.onTokenInputFocused = this.onTokenInputFocused.bind(this);
@@ -57,31 +55,24 @@ export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogSt
*/
public async onSubmitToken(): Promise<void> {
if (!this.state.tokenInput) return;
this.setState({
verifying: true,
});
this.setState({ verifying: true });
this.props.appState.gitHubToken = this.state.tokenInput;
const octo = await getOctokit();
octo.authenticate({
type: 'token',
token: this.state.tokenInput || '',
});
const octo = await getOctokit(this.props.appState);
try {
const { data } = await octo.users.get({});
const { data } = await octo.users.getAuthenticated();
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 });
}
/**
@@ -138,64 +129,68 @@ export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogSt
this.setState({ tokenInput: event.target.value });
}
/**
* A spinner element!
*
* @readonly
*/
get spinner() {
if (!this.state.verifying) return null;
return (
<div className='tokenSpinner'>
<FontAwesomeIcon icon={faSpinner} spin={true} />
</div>
);
}
get buttons() {
const canSubmit = !!this.state.tokenInput;
return [
(
<button
className='button'
<Button
key='done'
disabled={!canSubmit}
onClick={this.onSubmitToken}
>
Done
</button>
loading={this.state.verifying}
text='Done'
icon='log-in'
/>
), (
<button className='cancel' onClick={this.onClose}>Cancel</button>
<Button
key='cancel'
text='Cancel'
icon='log-out'
onClick={this.onClose}
/>
)
];
}
get invalidWarning() {
return (
<>
<Callout intent={Intent.DANGER}>
Please provide a valid GitHub Personal Access Token
</Callout>
<br />
</>
);
}
public render() {
const { isTokenDialogShowing } = this.props.appState;
return (
<Dialog
isShowing={isTokenDialogShowing}
isShowingBackdrop={true}
buttons={this.buttons}
isOpen={isTokenDialogShowing}
onClose={this.onClose}
isCentered={true}
className='tokenDialog'
key='tokenDialog'
title='GitHub Token'
>
{this.spinner}
<span className='generateTokenText'>
<FontAwesomeIcon icon={faKey} />
Generate a <a onClick={this.openGenerateTokenExternal}>GitHub Personal Access Token</a> and paste it here:
</span>
<input
ref={this.tokenInputRef}
value={this.state.tokenInput || ''}
onFocus={this.onTokenInputFocused}
onChange={this.handleChange}
className={this.state.error ? 'hasError' : ''}
/>
<div className='bp3-dialog-body'>
<p>
Generate a <a onClick={this.openGenerateTokenExternal}>GitHub Personal Access Token</a> and paste it here:
</p>
{this.state.error ? this.invalidWarning : null}
<InputGroup
value={this.state.tokenInput || ''}
onFocus={this.onTokenInputFocused}
onChange={this.handleChange}
/>
</div>
<div className='bp3-dialog-footer'>
<div className='bp3-dialog-footer-actions'>
{this.buttons}
</div>
</div>
</Dialog>
);
}
@@ -0,0 +1,50 @@
import { Alert, Intent } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
export interface WarningDialogProps {
appState: AppState;
}
export interface WarningDialogState {
}
/**
* The token dialog prompts the user to either continue or cancel the operation.
*
* @export
* @class WarningDialog
* @extends {React.Component<WarningDialogProps, WarningDialogState>}
*/
@observer
export class WarningDialog extends React.Component<WarningDialogProps, WarningDialogState> {
constructor(props: WarningDialogProps) {
super(props);
this.onClose = this.onClose.bind(this);
}
public onClose(result: boolean) {
this.props.appState.warningDialogLastResult = result;
this.props.appState.toggleWarningDialog();
}
public render() {
const { isWarningDialogShowing, warningDialogTexts } = this.props.appState;
return (
<Alert
isOpen={isWarningDialogShowing}
onClose={this.onClose}
icon='warning-sign'
confirmButtonText={warningDialogTexts.ok}
cancelButtonText={warningDialogTexts.cancel}
intent={Intent.DANGER}
>
<p>{warningDialogTexts.label}</p>
</Alert>
);
}
}
-111
View File
@@ -1,111 +0,0 @@
import * as React from 'react';
import { ArrowPosition } from '../../interfaces';
import { classNames } from '../../utils/classnames';
import { sortButtons } from '../../utils/sort-buttons';
export interface DialogProps {
buttons?: Array<JSX.Element> | null;
style?: React.CSSProperties;
isCentered?: boolean;
className?: string;
isShowing?: boolean;
isShowingBackdrop?: boolean;
arrow?: ArrowPosition;
onClose?: () => void;
onConfirm?: () => void;
}
/**
* A generic dialog class for Fiddle.
*
* @class Dialog
* @extends {React.Component<DialogProps, {}>}
*/
export class Dialog extends React.Component<DialogProps, {}> {
public static defaultProps: Partial<DialogProps> = {
isCentered: false,
isShowing: true,
isShowingBackdrop: false
};
constructor(props: DialogProps) {
super(props);
this.onClose = this.onClose.bind(this);
this.onConfirm = this.onConfirm.bind(this);
}
/**
* Closes the dialog
*/
public onClose() {
if (this.props.onClose) this.props.onClose();
}
/**
* Confirms the dialog
*/
public onConfirm() {
if (this.props.onConfirm) this.props.onConfirm();
}
/**
* Renders the dialog's buttons, using a default "Ok"
* if none were passed.
*
* @returns {Array<JSX.Element> | JSX.Element}
*/
public renderButtons(): Array<JSX.Element | null> | JSX.Element {
const { buttons, onClose } = this.props;
const closeButton = onClose
? <button key='cancel' className='btn-close' onClick={this.onClose}>Cancel</button>
: null;
const okButton = (
<button key='ok' className='btn-ok' onClick={this.onConfirm}>Ok</button>
);
return (
<div className='dialog-buttons'>
{sortButtons(buttons! || [ closeButton, okButton ])}
</div>
);
}
/**
* Optionally renders a backdrop that closes the
* dialog if clicked.
*
* @returns {JSX.Element | null}
*/
public renderBackdrop(): JSX.Element | null {
const { isShowingBackdrop } = this.props;
if (!isShowingBackdrop) return null;
return (
<div key='drop' className='dialogDrop' onClick={this.onClose} />
);
}
public render() {
const { isShowing, isCentered, style, children, className, arrow } = this.props;
const arrowClass = arrow ? `arrow arrow-${arrow}` : '';
const parsedClassName = classNames(
'dialog',
arrowClass,
{ centered: isCentered },
className
);
return isShowing ? (
<>
{this.renderBackdrop()}
<div style={style} className={parsedClassName}>
{children}
{this.renderButtons()}
</div>
</>
) : null;
}
}
+28 -2
View File
@@ -2,8 +2,13 @@ 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';
import { TokenDialog } from './token-dialog';
export interface DialogsProps {
appState: AppState;
@@ -19,18 +24,39 @@ export interface DialogsProps {
export class Dialogs extends React.Component<DialogsProps, {}> {
public render() {
const { appState } = this.props;
const { isTokenDialogShowing, isSettingsShowing } = appState;
const {
isTokenDialogShowing,
isSettingsShowing,
isAddVersionDialogShowing,
isThemeDialogShowing,
isBisectDialogShowing
} = appState;
const maybeToken = isTokenDialogShowing
? <TokenDialog key='dialogs' appState={appState} />
: null;
const maybeSettings = isSettingsShowing
? <Settings key='settings' appState={appState} />
: null;
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}
{maybeMonaco}
{maybeBisect}
{eitherWarningOrPrompt}
</div>
);
}
+48 -62
View File
@@ -3,16 +3,16 @@
import * as MonacoType from 'monaco-editor';
import * as React from 'react';
import { ContentNames, getContent } from '../content';
import { AppState } from '../state';
import { IpcEvents } from '../../ipc-events';
import { ipcRendererManager } from '../ipc';
import { EditorId } from '../../interfaces';
import { getContent } from '../content';
import { AppState } from '../state';
export interface EditorProps {
appState: AppState;
monaco: typeof MonacoType;
id: string;
monacoOptions: MonacoType.editor.IEditorOptions;
id: EditorId;
options?: Partial<MonacoType.editor.IEditorConstructionOptions>;
editorDidMount?: (editor: MonacoType.editor.IStandaloneCodeEditor) => void;
onChange?: (value: string, event: MonacoType.editor.IModelContentChangedEvent) => void;
@@ -29,14 +29,6 @@ export class Editor extends React.Component<EditorProps> {
super(props);
this.language = props.id === 'html' ? 'html' : 'javascript';
this.state = {
'options': {
'wordWrap': 'off',
},
'minimap': {
'enabled': false
}
}
}
public shouldComponentUpdate() {
@@ -44,47 +36,6 @@ export class Editor extends React.Component<EditorProps> {
}
public componentDidMount() {
ipcRendererManager.on(IpcEvents.TOGGLE_SOFT_WRAP, async (_event) => {
const oldState = this.state;
if (this.state['options'].wordWrap === "off") {
oldState['options'].wordWrap = "on";
this.setState(oldState);
this.editor.updateOptions({
'wordWrap': 'on'
});
} else if (this.state['options'].wordWrap === "on") {
oldState['options'].wordWrap = "off";
this.setState(oldState);
this.editor.updateOptions({
'wordWrap': 'off'
});
}
});
ipcRendererManager.on(IpcEvents.TOGGLE_MINI_MAP, async (_event) => {
const oldState = this.state;
if (!this.state['minimap']['enabled']) {
oldState['minimap']['enabled'] = true;
this.setState(oldState);
this.editor.updateOptions({
minimap: {
enabled: true,
}
});
} else if (this.state['minimap']['enabled']) {
oldState['minimap']['enabled'] = false;
this.setState(oldState);
this.editor.updateOptions({
minimap: {
enabled: false,
}
});
}
});
this.initMonaco();
}
@@ -98,11 +49,16 @@ export class Editor extends React.Component<EditorProps> {
*
* @param {MonacoType.editor.IStandaloneCodeEditor} editor
*/
public editorDidMount(editor: MonacoType.editor.IStandaloneCodeEditor) {
public async editorDidMount(editor: MonacoType.editor.IStandaloneCodeEditor) {
const { editorDidMount } = this.props;
// Set the content on the editor
await this.setContent();
// Set the editor as an available object
window.ElectronFiddle.editors[this.props.id] = editor;
// And notify others
if (editorDidMount) {
editorDidMount(editor);
}
@@ -112,21 +68,19 @@ export class Editor extends React.Component<EditorProps> {
* Initialize Monaco.
*/
public async initMonaco() {
const { monaco, id, appState } = this.props;
const { options, minimap } = this.state;
const { version } = appState;
const { monaco, monacoOptions: monacoOptions } = this.props;
const ref = this.containerRef.current;
if (ref) {
this.editor = monaco.editor.create(ref, {
language: this.language,
theme: 'main',
minimap: minimap,
contextmenu: false,
value: await getContent(id as ContentNames, version),
...options
model: null,
...monacoOptions
});
this.editorDidMount(this.editor);
await this.editorDidMount(this.editor);
}
}
@@ -135,6 +89,7 @@ export class Editor extends React.Component<EditorProps> {
*/
public destroyMonaco() {
if (typeof this.editor !== 'undefined') {
console.log('Editor: Disposing');
this.editor.dispose();
}
}
@@ -142,4 +97,35 @@ export class Editor extends React.Component<EditorProps> {
public render() {
return <div className='editorContainer' ref={this.containerRef} />;
}
/**
* Sets the content on the editor, including the model and the view state.
*
* @private
* @memberof Editor
*/
private async setContent() {
const { appState, id, monaco } = this.props;
const { version } = appState;
const backup = appState.getAndRemoveEditorValueBackup(id);
if (backup && backup.model) {
console.log(`Editor: Backup found, restoring state`);
if (backup.viewState) {
this.editor.restoreViewState(backup.viewState);
}
this.editor.setModel(backup.model);
} else {
const value = await getContent(id, version);
const model = monaco.editor.createModel(value, this.language);
model.updateOptions({
tabSize: 2
});
this.editor.setModel(model);
}
}
}
@@ -0,0 +1,24 @@
import * as React from 'react';
import { Button, NonIdealState } from '@blueprintjs/core';
import { EditorId } from '../../interfaces';
import { AppState } from '../state';
export function renderNonIdealState(appState: AppState) {
const allEditors = [ EditorId.html, EditorId.main, EditorId.renderer ];
const resolveButton = (
<Button
text='Open all editors'
onClick={() => appState.setVisibleMosaics(allEditors)}
/>
);
return (
<NonIdealState
action={resolveButton}
icon='applications'
description='You have closed all editors. You can open them again with the button below or the "Editors" button above!'
/>
);
}
@@ -0,0 +1,88 @@
import * as React from 'react';
import { Button } from '@blueprintjs/core';
import { MosaicContext, MosaicRootActions, MosaicWindowContext } from 'react-mosaic-component';
import { DocsDemoPage, MosaicId } from '../../interfaces';
import { AppState } from '../state';
export interface ToolbarButtonProps {
appState: AppState;
id: MosaicId;
}
export abstract class ToolbarButton extends React.PureComponent<ToolbarButtonProps> {
public static contextType = MosaicWindowContext;
public context: MosaicWindowContext;
constructor(props: ToolbarButtonProps) {
super(props);
}
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={onClick}
/>
);
}
}
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);
return (
<Button
icon='cross'
className='bp3-small'
onClick={onClick}
/>
);
}
}
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;
return (
<Button
icon='home'
className='bp3-small'
onClick={onClick}
text='Overview'
/>
);
}
}
+197 -36
View File
@@ -1,34 +1,38 @@
import { reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as MonacoType from 'monaco-editor';
import * as React from 'react';
import { Mosaic, MosaicWindow } from 'react-mosaic-component';
import { Mosaic, MosaicBranch, MosaicNode, MosaicWindow, MosaicWindowProps } from 'react-mosaic-component';
import { EditorId } from '../../interfaces';
import { EditorId, MosaicId, PanelId } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
import { updateEditorLayout } from '../../utils/editor-layout';
import { getFocusedEditor } from '../../utils/focused-editor';
import { ContentNames, getContent } from '../content';
import { getAtPath, setAtPath } from '../../utils/js-path';
import { toggleMonaco } from '../../utils/toggle-monaco';
import { isEditorId } from '../../utils/type-checks';
import { getContent } from '../content';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
import { activateTheme } from '../themes';
import { Editor } from './editor';
import { renderNonIdealState } from './editors-non-ideal-state';
import { DocsDemoGoHomeButton, MaximizeButton, RemoveButton } from './editors-toolbar-button';
import { ShowMe } from './show-me';
const options: any = {
direction: 'row',
first: 'main',
second: {
direction: 'column',
first: 'renderer',
second: 'html',
const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
minimap: {
enabled: false
},
wordWrap: 'on'
};
const ViewIdMosaic = Mosaic.ofType<EditorId>();
const ViewIdMosaicWindow = MosaicWindow.ofType<EditorId>();
const TITLE_MAP: Record<EditorId, string> = {
main: 'Main Process',
renderer: 'Renderer Process',
html: 'HTML'
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',
};
export interface EditorsProps {
@@ -38,16 +42,36 @@ export interface EditorsProps {
export interface EditorsState {
monaco?: typeof MonacoType;
isMounted?: boolean;
monacoOptions: MonacoType.editor.IEditorOptions;
}
@observer
export class Editors extends React.Component<EditorsProps, EditorsState> {
// A reaction: Each time mosaicArrangement is changed, we'll update
// the editor layout. That method is itself debounced.
public disposeLayoutAutorun = reaction(
() => this.props.appState.mosaicArrangement,
() => updateEditorLayout()
);
constructor(props: EditorsProps) {
super(props);
this.state = {};
this.onChange = this.onChange.bind(this);
this.renderEditor = this.renderEditor.bind(this);
this.renderTile = this.renderTile.bind(this);
this.renderGenericPanel = this.renderGenericPanel.bind(this);
this.state = { monacoOptions: defaultMonacoOptions };
this.loadMonaco();
}
/**
* Executed right after the component mounts. We'll setup the IPC listeners here.
*
* @memberof Editors
*/
public componentDidMount() {
ipcRendererManager.on(IpcEvents.MONACO_EXECUTE_COMMAND, (_event, cmd: string) => {
this.executeCommand(cmd);
@@ -56,16 +80,24 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
ipcRendererManager.on(IpcEvents.FS_NEW_FIDDLE, async (_event) => {
const { version } = this.props.appState;
window.ElectronFiddle.app.setValues({
html: await getContent(ContentNames.HTML, version),
renderer: await getContent(ContentNames.RENDERER, version),
main: await getContent(ContentNames.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) => {
this.toggleEditorOption(cmd);
});
this.setState({ isMounted: true });
}
public componentWillUnmount() {
this.disposeLayoutAutorun();
}
/**
* Attempt to execute a given commandId on the currently focused editor
*
@@ -86,6 +118,129 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
}
}
public toggleEditorOption(path: string): boolean {
if (!window.ElectronFiddle.editors) {
return false;
}
try {
const { monacoOptions } = this.state;
const newOptions = { ...monacoOptions };
const currentSetting = getAtPath(path, newOptions);
setAtPath(path, newOptions, toggleMonaco(currentSetting));
Object.keys(window.ElectronFiddle.editors)
.forEach((key) => {
const editor: MonacoType.editor.IStandaloneCodeEditor | null
= window.ElectronFiddle.editors[key];
if (editor) {
editor.updateOptions(newOptions);
}
});
this.setState({ monacoOptions: newOptions });
return true;
} catch (error) {
console.warn(`Editors: Could not toggle property ${path}`, error);
return false;
}
}
/**
* Renders the little tool bar on top of each panel
*
* @param {MosaicWindowProps<MosaicId>} { title }
* @param {MosaicId} id
* @returns {JSX.Element}
*/
public renderToolbar(
{ title }: MosaicWindowProps<MosaicId>, id: MosaicId
): JSX.Element {
const docsDemoGoHomeMaybe = id === PanelId.docsDemo
? <DocsDemoGoHomeButton id={id} appState={this.props.appState} />
: null;
return (
<div>
{/* Left */}
<div>
<h5>
{title}
</h5>
</div>
{/* Middle */}
<div />
{/* Right */}
<div className='mosaic-controls'>
{docsDemoGoHomeMaybe}
<MaximizeButton id={id} appState={this.props.appState} />
<RemoveButton id={id} appState={this.props.appState} />
</div>
</div>
);
}
/**
* Renders a Mosaic tile
*
* @param {string} id
* @param {string} path
* @returns {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 (
<MosaicWindow<EditorId>
className={id}
path={path}
title={TITLE_MAP[id]}
renderToolbar={(props: MosaicWindowProps<MosaicId>) => this.renderToolbar(props, id)}
>
{content}
</MosaicWindow>
);
}
/**
* Renders a generic panel  so not an editor, but something else
*
* @param {PanelId} _id
* @param {AppState} _appState
* @returns {JSX.Element}
*/
public renderGenericPanel(_id: PanelId, appState: AppState): JSX.Element {
return <ShowMe appState={appState} />;
}
/**
* Render an editor
*
* @param {EditorId} id
* @returns {(JSX.Element | null)}
* @memberof Editors
*/
public renderEditor(id: EditorId): JSX.Element | null {
const { appState } = this.props;
const { monaco } = this.state;
return (
<Editor
id={id}
monaco={monaco!}
appState={appState}
monacoOptions={defaultMonacoOptions}
/>
);
}
public render() {
const { appState } = this.props;
const { monaco } = this.state;
@@ -93,27 +248,30 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
if (!monaco) return null;
return (
<ViewIdMosaic
onChange={updateEditorLayout}
// tslint:disable-next-line:jsx-no-multiline-js
renderTile={(id, path) => (
<ViewIdMosaicWindow className={id} path={path} title={TITLE_MAP[id]} toolbarControls={<div />}>
<Editor id={id} monaco={monaco} appState={appState} />
</ViewIdMosaicWindow>
)}
initialValue={options}
<Mosaic<EditorId | PanelId>
onChange={this.onChange}
value={appState.mosaicArrangement}
zeroStateView={renderNonIdealState(appState)}
renderTile={this.renderTile}
/>
);
}
/**
* Handles a change in the visible nodes
*
* @param {(MosaicNode<EditorId> | null)} currentNode
*/
public onChange(currentNode: MosaicNode<EditorId> | null) {
this.props.appState.mosaicArrangement = currentNode;
}
/**
* Loads monaco. If it's already loaded, it'll just set it on the current state.
* We're doing things a bit roundabout to ensure that we're not overloading the
* mobx state with a gigantic Monaco tree.
*
* @private
*/
private async loadMonaco(): Promise<void> {
public async loadMonaco(): Promise<void> {
const { app } = window.ElectronFiddle;
const loader = require('monaco-loader');
const monaco = app.monaco || await loader();
@@ -123,7 +281,10 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
}
if (!this.state || !this.state.isMounted) {
this.state = { monaco };
this.state = {
monaco,
monacoOptions: defaultMonacoOptions
};
} else {
this.setState({ monaco });
}
-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});
}
}
+31 -4
View File
@@ -1,11 +1,16 @@
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;
// Used to keep testing conform
renderTimestamp?: (ts: number) => string;
}
@@ -18,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) {
@@ -27,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;
@@ -70,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
@@ -80,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>
);
+45 -16
View File
@@ -1,21 +1,45 @@
import { Callout, Card } from '@blueprintjs/core';
import { shell } from 'electron';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as React from 'react';
import { AppState } from '../state';
export interface CreditsSettingsProps {
appState: AppState;
contributors?: Array<any>;
}
export interface CreditsSettingsState {
contributors: Array<Contributor>;
}
export interface Contributor {
url: string;
api: string;
login: string;
avatar: string;
name: string;
bio: string;
location: string;
}
/**
* Settings content to manage Credits-related preferences.
*
* @class CreditsSettings
* @extends {React.Component<CreditsSettingsProps, {}>}
* @extends {React.Component<CreditsSettingsProps, CreditsSettingsState>}
*/
export class CreditsSettings extends React.Component<CreditsSettingsProps, {}> {
private contributors: Array<any> | null = null;
export class CreditsSettings extends React.Component<CreditsSettingsProps, CreditsSettingsState> {
constructor(props: CreditsSettingsProps) {
super(props);
this.state = {
contributors: []
};
this.getContributors();
}
/**
* Renders a list of contributors of Electron Fiddle.
@@ -23,15 +47,9 @@ export class CreditsSettings extends React.Component<CreditsSettingsProps, {}> {
* @returns {Array<JSX.Element>}
*/
public renderContributors(): Array<JSX.Element> {
this.contributors = this.contributors
|| this.props.contributors
|| require('../../../static/contributors.json');
const { contributors } = this.state;
if (!this.contributors || !Array.isArray(this.contributors)) {
return [];
}
return this.contributors.map(({ name, avatar, url, login, location, bio }) => {
return contributors.map(({ name, avatar, url, login, location, bio }) => {
const maybeLocation = location
? <p className='location'>📍 {location}</p>
: null;
@@ -44,14 +62,14 @@ export class CreditsSettings extends React.Component<CreditsSettingsProps, {}> {
const onClick = () => shell.openExternal(url);
return (
<div key={login} className='contributor' onClick={onClick}>
<Card interactive={true} key={login} className='contributor' onClick={onClick}>
<div className='avatar' style={style} />
<div className='details'>
<h5 className='name'>{name || login}</h5>
{maybeLocation}
{maybeBio}
</div>
</div>
</Card>
);
});
}
@@ -60,15 +78,26 @@ export class CreditsSettings extends React.Component<CreditsSettingsProps, {}> {
return (
<div>
<h2>Credits</h2>
<p>
<Callout>
Electron Fiddle is, just like Electron, a free open source project
welcoming contributors of all genders, cultures, and backgrounds. We
would like to thank those who helped to make Electron Fiddle:
</p>
</Callout>
<br />
<div className='contributors'>
{this.renderContributors()}
</div>
</div>
);
}
public async getContributors() {
try {
const contributorsFile = path.join(__dirname, '../../static/contributors.json');
const contributors = await fs.readJSON(contributorsFile);
this.setState({ contributors });
} catch (error) {
console.warn(`CreditsSettings: Fetching contributors failed`, error);
}
}
}
+224 -155
View File
@@ -1,14 +1,18 @@
import {
faClipboardList,
faCloudDownloadAlt,
faSpinner,
faTrash
} from '@fortawesome/fontawesome-free-solid';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
Button,
ButtonGroup,
Callout,
Checkbox,
FormGroup,
HTMLTable,
IButtonProps,
Icon,
IconName
} from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ElectronVersion } from '../../interfaces';
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../interfaces';
import { normalizeVersion } from '../../utils/normalize-version';
import { sortedElectronMap } from '../../utils/sorted-electron-map';
import { AppState } from '../state';
@@ -37,8 +41,9 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
this.handleDownloadAll = this.handleDownloadAll.bind(this);
this.handleDeleteAll = this.handleDeleteAll.bind(this);
this.handleChannelChange = this.handleChannelChange.bind(this);
this.handlePagesChange = this.handlePagesChange.bind(this);
this.handleStateChange = this.handleStateChange.bind(this);
this.handleDownloadClick = this.handleDownloadClick.bind(this);
this.handleAddVersion = this.handleAddVersion.bind(this);
this.state = {
isDownloadingAll: false,
@@ -51,15 +56,21 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
}
/**
* Handles a change in how many pages should be displayed.
* Handles a change in which channels should be displayed.
*
* @param {React.ChangeEvent<HTMLSelectElement>} event
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public handlePagesChange(
event: React.ChangeEvent<HTMLSelectElement>
public handleStateChange(
event: React.FormEvent<HTMLInputElement>
) {
const { value } = event.currentTarget;
this.props.appState.versionPagesToFetch = parseInt(value, 10) / 30;
const { id, checked } = event.currentTarget;
const { appState } = this.props;
if (!checked) {
appState.statesToShow = appState.statesToShow.filter((s) => s !== id);
} else {
appState.statesToShow.push(id as ElectronVersionState);
}
}
/**
@@ -68,7 +79,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public handleChannelChange(
event: React.ChangeEvent<HTMLInputElement>
event: React.FormEvent<HTMLInputElement>
) {
const { id, checked } = event.currentTarget;
const { appState } = this.props;
@@ -117,126 +128,158 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
this.setState({ isDeletingAll: false });
}
public render() {
const { isDownloadingAll, isDeletingAll } = this.state;
const isWorking = isDownloadingAll || isDeletingAll;
const downloadFontAwesomeIcon = isDownloadingAll
? <FontAwesomeIcon icon={faSpinner} spin={true} />
: <FontAwesomeIcon icon={faCloudDownloadAlt} />;
const deleteFontAwesomeIcon = isDeletingAll
? <FontAwesomeIcon icon={faSpinner} spin={true} />
: <FontAwesomeIcon icon={faCloudDownloadAlt} />;
/**
* Opens the "add local version" dialog
*/
public handleAddVersion(): void {
this.props.appState.toggleAddVersionDialog();
}
public render() {
return (
<div className='settings-electron'>
<h2>Electron Settings</h2>
<div className='advanced-options settings-section'>
{this.renderVersionOptions()}
<label>Download all versions of Electron.</label>
<button
className='button btn-download-all'
disabled={isWorking}
onClick={this.handleDownloadAll}
>
{downloadFontAwesomeIcon} Download All Versions
</button>
<label>Remove all downloaded versions of Electron.</label>
<button
className='button btn-delete-all'
disabled={isWorking}
onClick={this.handleDeleteAll}
>
{deleteFontAwesomeIcon} Delete All Versions
</button>
</div>
<div>
<h2>Versions</h2>
<Callout>
{this.renderVersionChannelOptions()}
{this.renderVersionStateOptions()}
</Callout>
<br />
<Callout>
{this.renderAdvancedButtons()}
{this.renderTable()}
</div>
</Callout>
</div>
);
}
private renderVersionOptions(): JSX.Element {
/**
* Renders the various buttons for advanced operations
*
* @private
* @returns {JSX.Element}
*/
private renderAdvancedButtons(): JSX.Element {
const { isDownloadingAll, isDeletingAll } = this.state;
const { isUpdatingElectronVersions } = this.props.appState;
const isWorking = isDownloadingAll || isDeletingAll;
return (
<ButtonGroup fill={true}>
<Button
disabled={isUpdatingElectronVersions}
onClick={this.handleDownloadClick}
loading={isUpdatingElectronVersions}
icon='numbered-list'
text='Update Electron Release List'
/>
<Button
disabled={isWorking}
icon='download'
onClick={this.handleDownloadAll}
text='Download All Versions'
/>
<Button
disabled={isWorking}
icon='trash'
onClick={this.handleDeleteAll}
text='Delete All Downloads'
/>
<Button
icon='document-open'
onClick={this.handleAddVersion}
text='Add Local Electron Build'
/>
</ButtonGroup>
);
}
/**
* Renders the various options for which versions should be displayed
* in the small dropdown.
*
* @private
* @returns {JSX.Element}
*/
private renderVersionStateOptions(): JSX.Element {
const { appState } = this.props;
const getIsChecked = (state: ElectronVersionState) => {
return appState.statesToShow.includes(state);
};
return (
<FormGroup
label='Include Electron versions that are:'
>
<Checkbox
checked={getIsChecked(ElectronVersionState.downloading)}
label='Downloading'
id='downloading'
onChange={this.handleStateChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronVersionState.ready)}
label='Downloaded'
id='ready'
onChange={this.handleStateChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronVersionState.unknown)}
label='Not Downloaded'
id='unknown'
onChange={this.handleStateChange}
inline={true}
/>
</FormGroup>
);
}
/**
* Renders the various options for which versions should be displayed
*
* @private
* @returns {JSX.Element}
*/
private renderVersionChannelOptions(): JSX.Element {
const { appState } = this.props;
const getIsChecked = (channel: ElectronReleaseChannel) => {
return appState.versionsToShow.includes(channel);
};
return (
<div className='versions-to-show'>
<p>Include Electron versions from these release channels:</p>
<label>
<input
checked={getIsChecked(ElectronReleaseChannel.stable)}
id={ElectronReleaseChannel.stable}
type='checkbox'
onChange={this.handleChannelChange}
/>
Stable
</label>
<label>
<input
checked={getIsChecked(ElectronReleaseChannel.beta)}
id={ElectronReleaseChannel.beta}
type='checkbox'
onChange={this.handleChannelChange}
/>
Beta
</label>
<label>
<input
checked={getIsChecked(ElectronReleaseChannel.nightly)}
id={ElectronReleaseChannel.nightly}
type='checkbox'
onChange={this.handleChannelChange}
/>
Nightly
</label>
<label>
<input
checked={getIsChecked(ElectronReleaseChannel.unsupported)}
id={ElectronReleaseChannel.unsupported}
type='checkbox'
onChange={this.handleChannelChange}
/>
Unsupported
</label>
<p>Number of recent Electron releases to include:</p>
<select onChange={this.handlePagesChange}>
<option value='1'>30</option>
<option value='2'>60</option>
<option value='3'>90</option>
<option value='4'>120</option>
<option value='5'>150</option>
<option value='6'>180</option>
<option value='7'>210</option>
<option value='8'>240</option>
<option value='9'>270</option>
<option value='10'>300</option>
</select>
<br />
{this.renderUpdateVersionsButton()}
</div>
);
}
private renderUpdateVersionsButton(): JSX.Element {
const { appState } = this.props;
const { isUpdatingElectronVersions } = appState;
const icon = isUpdatingElectronVersions
? <FontAwesomeIcon icon={faSpinner} spin={true}/>
: <FontAwesomeIcon icon={faClipboardList} />;
return (
<button
className='button'
disabled={isUpdatingElectronVersions}
onClick={this.handleDownloadClick}
<FormGroup
label='Include Electron versions from these release channels:'
>
{icon} Update Electron Release List
</button>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.stable)}
label='Stable'
id='Stable'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.beta)}
label='Beta'
id='Beta'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.nightly)}
label='Nightly'
id='Nightly'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.unsupported)}
label='Unsupported'
id='Unsupported'
onChange={this.handleChannelChange}
inline={true}
/>
</FormGroup>
);
}
@@ -248,7 +291,10 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
*/
private renderTable(): JSX.Element {
return (
<table className='electron-versions-table'>
<HTMLTable
className='electron-versions-table'
striped={true}
>
<thead>
<tr>
<th>Version</th>
@@ -259,7 +305,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
<tbody>
{this.renderTableRows()}
</tbody>
</table>
</HTMLTable>
);
}
@@ -270,7 +316,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
* @returns {Array<JSX.Element>}
*/
private renderTableRows(): Array<JSX.Element | null> {
const { versions, versionsToShow } = this.props.appState;
const { versions, versionsToShow, statesToShow } = this.props.appState;
return sortedElectronMap<JSX.Element | null>(versions, (key, item) => {
// Check if we want to show the version
@@ -278,21 +324,47 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
return null;
}
const { tag_name, state } = item;
const humanState = state === 'ready'
? '✅ Downloaded'
: state === 'downloading' ? '⏬ Downloading' : '⏹ Not downloaded';
// Check if we want to show the state
if (!statesToShow.includes(item.state)) {
return null;
}
return (
<tr key={tag_name}>
<td>{tag_name}</td>
<td>{humanState}</td>
<td>{this.renderAction(key, item)}</td>
<tr key={item.version}>
<td>{item.version}</td>
<td>{this.renderHumanState(item)}</td>
<td className='action'>{this.renderAction(key, item)}</td>
</tr>
);
});
}
/**
* Returns a human-readable state indicator for an Electron version
*
* @param {ElectronVersion} item
* @returns {JSX.Element}
*/
private renderHumanState(item: ElectronVersion): JSX.Element {
const { state } = item;
let icon: IconName = 'box';
let humanState = 'Downloaded';
if (state === ElectronVersionState.downloading) {
icon = 'cloud-download';
humanState = 'Downloading';
} else if (state === ElectronVersionState.unknown) {
icon = 'cloud';
humanState = 'Not downloaded';
}
return (
<span>
<Icon icon={icon} /> {humanState}
</span>
);
}
/**
* Renders the action for a single Electron version
*
@@ -302,36 +374,33 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
* @returns {JSX.Element}
*/
private renderAction(key: string, item: ElectronVersion): JSX.Element {
const { state } = item;
const { state, source } = item;
const { appState } = this.props;
const buttonProps: IButtonProps = {
fill: true,
small: true
};
// Already downloaded
if (state === 'ready') {
const remove = () => appState.removeVersion(key);
return (
<button className='button' onClick={remove}>
<FontAwesomeIcon icon={faTrash} /> Delete
</button>
);
buttonProps.onClick = () => appState.removeVersion(key);
buttonProps.icon = 'trash';
buttonProps.text = source === ElectronVersionSource.local
? 'Remove'
: 'Delete';
} else if (state === 'downloading') {
buttonProps.disabled = true;
buttonProps.loading = true;
buttonProps.text = 'Downloading';
buttonProps.icon = 'cloud-download';
} else {
buttonProps.disabled = false;
buttonProps.loading = false;
buttonProps.text = 'Download';
buttonProps.icon = 'cloud-download';
buttonProps.onClick = () => appState.downloadVersion(key);
}
// Downloading
if (state === 'downloading') {
return (
<button className='button' disabled={true}>
<FontAwesomeIcon icon={faSpinner} spin={true}/> Downloading
</button>
);
}
// Unknown
const download = () => appState.setVersion(key);
return (
<button className='button' onClick={download}>
<FontAwesomeIcon icon={faCloudDownloadAlt} /> Download
</button>
);
return <Button {...buttonProps} type={undefined} />;
}
}
@@ -0,0 +1,93 @@
import { Callout, Checkbox, FormGroup } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
export interface ExecutionSettingsProps {
appState: AppState;
}
/**
* Settings content to manage execution-related preferences.
*
* @class ExecutionSettings
* @extends {React.Component<ExecutionSettingsProps, {}>}
*/
@observer
export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {}> {
constructor(props: ExecutionSettingsProps) {
super(props);
this.handleDeleteDataChange = this.handleDeleteDataChange.bind(this);
this.handleElectronLoggingChange = this.handleElectronLoggingChange.bind(this);
}
/**
* Handles a change on whether or not the user data dir should be deleted
* after a run.
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public handleDeleteDataChange(
event: React.FormEvent<HTMLInputElement>
) {
const { checked } = event.currentTarget;
this.props.appState.isKeepingUserDataDirs = checked;
}
/**
* Handles a change on whether or not electron should log more things
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public handleElectronLoggingChange(
event: React.FormEvent<HTMLInputElement>
) {
const { checked } = event.currentTarget;
this.props.appState.isEnablingElectronLogging = checked;
}
public render() {
const { isKeepingUserDataDirs, isEnablingElectronLogging } = this.props.appState;
const deleteUserDirLabel = `
Whenever Electron runs, it creates a user data directory for cookies, the cache,
and various other things that it needs to keep around. Since fiddles are usually
just run once, we delete this directory after your fiddle exits. Enable this
setting to keep the user data directories around.
`.trim();
const electronLoggingLabel = `
There are some flags that Electron uses to log extra information both internally
and through Chromium. Enable this option to make Fiddle produce those logs.`.trim();
return (
<div>
<h2>Execution</h2>
<Callout>
These advanced settings control how Electron Fiddle executes your fiddles.
</Callout>
<br />
<Callout>
<FormGroup label={deleteUserDirLabel}>
<Checkbox
checked={isKeepingUserDataDirs}
label='Do not delete user data directories.'
onChange={this.handleDeleteDataChange}
/>
</FormGroup>
</Callout>
<br />
<Callout>
<FormGroup label={electronLoggingLabel}>
<Checkbox
checked={isEnablingElectronLogging}
label='Enable advanced Electron logging.'
onChange={this.handleElectronLoggingChange}
/>
</FormGroup>
</Callout>
</div>
);
}
}
@@ -1,20 +1,64 @@
import { Button, Callout, FormGroup, MenuItem } from '@blueprintjs/core';
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
import { shell } from 'electron';
import * as fsType from 'fs-extra';
import { observer } from 'mobx-react';
import * as path from 'path';
import * as React from 'react';
import { highlightText } from '../../utils/highlight-text';
import { fancyImport } from '../../utils/import';
import { AppState } from '../state';
import { getAvailableThemes, getTheme, THEMES_PATH } from '../themes';
import { LoadedFiddleTheme } from '../themes-defaults';
const ThemeSelect = Select.ofType<LoadedFiddleTheme>();
/**
* Helper method: Returns the <Select /> predicate for an Electron
* version.
*
* @param {string} query
* @param {ElectronVersion} { version }
* @returns
*/
export const filterItem: ItemPredicate<LoadedFiddleTheme> = (query, { name }) => {
return name.toLowerCase().includes(query.toLowerCase());
};
/**
* Helper method: Returns the <Select /> <MenuItem /> for Electron
* versions.
*
* @param {ElectronVersion} item
* @param {IItemRendererProps} { handleClick, modifiers, query }
* @returns
*/
export const renderItem: ItemRenderer<LoadedFiddleTheme> = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
}
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
text={highlightText(item.name, query)}
key={item.name}
onClick={handleClick}
icon='media'
/>
);
};
export interface AppearanceSettingsProps {
appState: AppState;
}
export interface AppearanceSettingsState {
themes: Array<LoadedFiddleTheme>;
selectedTheme?: LoadedFiddleTheme;
}
/**
@@ -32,24 +76,33 @@ 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: []
};
getAvailableThemes().then((themes) => {
this.setState({ themes });
const { theme } = this.props.appState;
const selectedTheme = theme &&
themes.find(({ file }) => file === theme) || themes[0];
this.setState({ themes, selectedTheme });
});
this.createNewThemeFromCurrent = this.createNewThemeFromCurrent.bind(this);
this.openThemeFolder = this.openThemeFolder.bind(this);
}
/**
* Handle change, which usually means that we'd like update
* the current theme.
*
* @param {React.ChangeEvent<HTMLSelectElement>} event
* @param {LoadedFiddleTheme} theme
*/
public handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
this.props.appState.setTheme(event.target.value);
public handleChange(theme: LoadedFiddleTheme) {
this.setState({ selectedTheme: theme });
this.props.appState.setTheme(theme.file);
}
/**
@@ -59,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');
@@ -74,7 +128,9 @@ export class AppearanceSettings extends React.Component<
css: undefined
}, { spaces: 2 });
shell.openItem(themePath);
shell.showItemInFolder(themePath);
this.setState({themes: await getAvailableThemes()});
return true;
} catch (error) {
@@ -103,49 +159,64 @@ export class AppearanceSettings extends React.Component<
}
}
/**
* Render the theme options.
*
* @returns {Array<JSX.Element>}
*/
public renderOptions(): Array<JSX.Element> {
const { themes } = this.state;
return themes.map(({ name, file }) => {
return (
<option value={file} key={file}>
{name}
</option>
);
});
}
public render() {
const { selectedTheme } = this.state;
const selectedName = selectedTheme && selectedTheme.name || 'Select a theme';
return (
<div className='settings-appearance'>
<h4>Appearance</h4>
<label key='theme-label'>
To add themes, add JSON theme files to <a
id='open-theme-folder'
onClick={() => this.openThemeFolder()}
<FormGroup
label='Choose your theme'
inline={true}
>
<ThemeSelect
filterable={true}
items={this.state.themes}
itemRenderer={renderItem}
itemPredicate={filterItem}
onItemSelect={this.handleChange}
noResults={<MenuItem disabled={true} text='No results.' />}
>
<code>{THEMES_PATH}</code>
</a>.<br />
</label>
<select
className='select-themes'
value={`${this.props.appState.theme}`}
onChange={this.handleChange}
>
{this.renderOptions()}
</select>
<button
id='create-new-theme'
onClick={() => this.createNewThemeFromCurrent()}
>
Create theme from current selection
</button>
<Button
text={selectedName}
icon='tint'
/>
</ThemeSelect>
</FormGroup>
<Callout>
<p>
To add themes, add JSON theme files to <a
id='open-theme-folder'
onClick={this.openThemeFolder}
>
<code>{THEMES_PATH}</code>
</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>
);
}
}
@@ -1,5 +1,4 @@
import { faSignInAlt, faSignOutAlt } from '@fortawesome/fontawesome-free-solid';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, Callout } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -17,53 +16,48 @@ export interface GitHubSettingsProps {
*/
@observer
export class GitHubSettings extends React.Component<GitHubSettingsProps, {}> {
constructor(props: GitHubSettingsProps) {
super(props);
this.signIn = this.signIn.bind(this);
}
/**
* Render the "logged out" settings experience.
*
* @returns {Array<JSX.Element>}
* @returns {JSX.Element}
*/
public renderNotSignedIn(): Array<JSX.Element> {
const signIn = () => {
this.props.appState.isTokenDialogShowing = true;
};
return [
(
<label key='signed-out-label'>
We can publish your fiddles to GitHub as a Gist -
public renderNotSignedIn(): JSX.Element {
return (
<Callout>
<p>
Your fiddles can be published as GitHub Gists -
that way you can share your fiddles with the world!
</label>
),
(
<button className='button' key='sign-in-button' onClick={signIn}>
<FontAwesomeIcon icon={faSignInAlt} /> Sign In
</button>
)
];
</p>
<Button onClick={this.signIn} icon='log-in' text='Sign in'/>
</Callout>
);
}
/**
* Render the "logged in" settings experience.
*
* @returns {Array<JSX.Element>}
* @returns {JSX.Element}
*/
public renderSignedIn(): Array<JSX.Element> {
public renderSignedIn(): JSX.Element {
const { gitHubLogin } = this.props.appState;
const signOut = this.props.appState.signOutGitHub;
return [
(
<label key='signed-in-label'>
return (
<Callout>
<p>
Your fiddles can be published as public GitHub Gists.
Using the personal access token you gave us, we
logged you into GitHub as {gitHubLogin}.
</label>
),
(
<button className='button' key='sign-out-button' onClick={signOut}>
<FontAwesomeIcon icon={faSignOutAlt} /> Remove Token & Sign Out
</button>
)
];
logged you into GitHub as <code>{gitHubLogin}</code>.
</p>
<Button onClick={signOut} icon='log-out' text='Sign out'/>
</Callout>
);
}
public render() {
@@ -80,4 +74,11 @@ export class GitHubSettings extends React.Component<GitHubSettingsProps, {}> {
</div>
);
}
/**
* Simply shows the GitHub Token dialog.``
*/
private signIn() {
this.props.appState.isTokenDialogShowing = true;
}
}
+5 -1
View File
@@ -1,8 +1,10 @@
import { Divider } from '@blueprintjs/core';
import { observer } from 'mobx-react';
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 {
@@ -22,7 +24,9 @@ export class GeneralSettings extends React.Component<GeneralSettingsProps, {}> {
<div>
<h2>General Settings</h2>
<AppearanceSettings appState={this.props.appState} />
<hr />
<Divider />
<ConsoleSettings appState={this.props.appState} />
<Divider />
<GitHubSettings appState={this.props.appState} />
</div>
);
+55 -6
View File
@@ -1,23 +1,24 @@
import { faTimesCircle } from '@fortawesome/fontawesome-free-solid';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Icon, IconName, MenuItem } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { classNames } from '../../utils/classnames';
import { AppState } from '../state';
import { CreditsSettings } from './settings-credits';
import { ElectronSettings } from './settings-electron';
import { ExecutionSettings } from './settings-execution';
import { GeneralSettings } from './settings-general';
enum SettingsSections {
General = 'General',
Electron = 'Electron',
Execution = 'Execution',
Credits = 'Credits'
}
const settingsSections = [
SettingsSections.General,
SettingsSections.Electron,
SettingsSections.Execution,
SettingsSections.Credits
];
@@ -43,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);
}
/**
@@ -63,6 +74,10 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
return <ElectronSettings appState={appState} />;
}
if (section === SettingsSections.Execution) {
return <ExecutionSettings appState={appState} />;
}
if (section === SettingsSections.Credits) {
return <CreditsSettings appState={appState} />;
}
@@ -80,11 +95,17 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
return settingsSections.map((name) => {
const isSelected = section === name;
const className = classNames({ selected: isSelected }, name);
const onClick = () => this.setState({ section: name });
return (
<li onClick={onClick} key={name} className={className}>{name}</li>
<MenuItem
onClick={onClick}
active={isSelected}
key={name}
id={`settings-link-${name}`}
text={name}
icon={this.getIconForSection(name)}
/>
);
});
}
@@ -102,11 +123,39 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
</div>
<div className='settings-content'>
<div className='settings-close' onClick={appState.toggleSettings}>
<FontAwesomeIcon icon={faTimesCircle} />
<Icon icon='cross' />
</div>
{this.renderContent()}
</div>
</div>
);
}
/**
* Get the settings icons
*
* @param {SettingsSections} section
* @memberof Settings
*/
private getIconForSection(section: SettingsSections): IconName {
if (section === SettingsSections.Credits) {
return 'heart';
} else if (section === SettingsSections.Electron) {
return 'floppy-disk';
} else if (section === SettingsSections.Execution) {
return 'play';
}
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;
}
}
}
+30
View File
@@ -0,0 +1,30 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
import { DOCS_DEMO_COMPONENTS } from './show-me/index';
export interface ShowMeProps {
appState: AppState;
}
/**3
* The root component for "show me" content - the fourth helpful
* panel.
*
* @class ShowMe
* @extends {React.Component<ShowMeProps, ShowMeState>}
*/
@observer
export class ShowMe extends React.Component<ShowMeProps, {}> {
public render() {
const { currentDocsDemoPage: showMeName } = this.props.appState;
const Content = DOCS_DEMO_COMPONENTS[showMeName] || DOCS_DEMO_COMPONENTS.DEFAULT;
return (
<div className='show-me-panel'>
<Content appState={this.props.appState} />
</div>
);
}
}
+142
View File
@@ -0,0 +1,142 @@
import * as React from 'react';
import { Button, Callout, Icon } from '@blueprintjs/core';
import { remote } from 'electron';
import { renderMoreDocumentation } from './more-information';
import { getSubsetOnly } from './subset-only';
/**
* Show and hide button examples, only rendered on macOS
*
* @returns {JSX.Element | null}
*/
function ShowHide() {
if (process.platform !== 'darwin') return null;
const onClick = () => {
remote.app.hide();
setTimeout(() => {
remote.app.show();
remote.app.focus();
}, 1500);
};
return (
<>
<p>This button hides Electron Fiddle right away, showing it again in two seconds.</p>
<Button
id='show-hide'
icon='eye-off'
text='Hide Electron Fiddle'
onClick={onClick}
/>
</>
);
}
export function ShowMeApp(_props: any): JSX.Element {
const [ secondsLeft, setSeconds ] = React.useState(-1);
const playFocus = () => {
setSeconds(3);
setTimeout(() => setSeconds(2), 1000);
setTimeout(() => setSeconds(1), 2000);
setTimeout(() => {
remote.app.focus();
setSeconds(-1);
}, 3000);
};
const [ paths, setPaths ] = React.useState('');
const playPaths = () => {
const pathsToQuery = [
'home',
'appData',
'userData',
'temp',
'downloads',
'desktop'
];
let result = '';
pathsToQuery.forEach((item) => {
result += `${item}: ${remote.app.getPath(item as any)}\n`;
});
setPaths(result);
};
const [ metrics, setMetrics ] = React.useState('');
const playMetrics = () => {
setMetrics(JSON.stringify(remote.app.getAppMetrics(), undefined, 2));
};
return (
<>
<Icon icon='help' iconSize={40} style={{ float: 'left', margin: '0 10px 0 0' }} />
<p className='bp3r-running-text'>
The <code>app</code> module controls the app's application life-cycle. Most of the
events and methods available on this module are responsible for handling how your
interacts with the operating system or to set application-wide settings.
</p>
<h3>API Demos</h3>
{getSubsetOnly('app')}
<Callout
title='Hiding, Showing, Focussing'
icon='eye-open'
>
<p>
The app can ask the operating system for window focus. On macOS, it can additionally
request that the app be hidden or shown. Give it a try: Click on the button below,
focus another app, and wait for two seconds to see Electron Fiddle become the focused
app again.
</p>
<Button
id='focus'
icon='lightbulb'
text={`Focus Electron Fiddle${secondsLeft > 0 ? ` in ${secondsLeft}s` : ''}`}
onClick={playFocus}
/>
<ShowHide />
</Callout>
<Callout
title='Paths'
icon='folder-open'
>
<p>
Need to query information about various paths in a cross-platform manner? Electron
can help. The button queries the operating system for some of them.
</p>
<Button
id='special-paths'
icon='play'
text={`Get special directory paths`}
onClick={playPaths}
/>
<pre id='special-paths-content'>
{paths}
</pre>
</Callout>
<Callout
title='Process & Device Information'
icon='pulse'
>
<p>
Need to query information about the process or system? The <code>app</code> module lets
developers query for information about the running app, hardware, and operating system.
A good example are process metrics.
</p>
<Button
id='process-metrics'
icon='play'
text={`Get process metrics`}
onClick={playMetrics}
/>
<pre id='process-metrics-content'>
{metrics}
</pre>
</Callout>
{renderMoreDocumentation()}
</>
);
}
@@ -0,0 +1,43 @@
import { Button, Icon } from '@blueprintjs/core';
import * as React from 'react';
import { DocsDemoPage } from '../../../interfaces';
import { AppState } from '../../state';
import { DOCS_DEMO_COMPONENTS, DOCS_DEMO_NAMES } from './index';
import { renderMoreDocumentation } from './more-information';
export function ShowMeDefault(props: { appState: AppState }): JSX.Element {
const showMeMenu: Array<JSX.Element> = Object.keys(DOCS_DEMO_COMPONENTS)
.filter((key) => key !== DocsDemoPage.DEFAULT)
.map((key) => {
return (
<li key={key}>
<Button
minimal={true}
icon='play'
text={DOCS_DEMO_NAMES[key]}
onClick={() => (props.appState.currentDocsDemoPage = key as DocsDemoPage)}
/>
</li>
);
});
return (
<>
<Icon icon='help' iconSize={40} style={{ float: 'left', margin: '0 10px 0 0' }} />
<p className='bp3r-running-text'>
This panel offers useful information about Electron APIs  and
easy way to try some of the methods it offers. Fiddle has an example fiddle
for every module available in Electron.
</p>
<p>
Clicking on any of the modules below will open up a fiddle showcasing
that particular module.
</p>
<ul className='show-me-list'>
{...showMeMenu}
</ul>
{renderMoreDocumentation()}
</>
);
}
+14
View File
@@ -0,0 +1,14 @@
import { DocsDemoPage } from '../../../interfaces';
import { ShowMeApp } from './app';
import { ShowMeDefault } from './default';
export const DOCS_DEMO_COMPONENTS: Record<DocsDemoPage, any> = {
DEFAULT: ShowMeDefault,
DEMO_APP: ShowMeApp
};
export const DOCS_DEMO_NAMES: Record<DocsDemoPage, string> = {
DEFAULT: 'Home',
DEMO_APP: 'App Demos'
};
@@ -0,0 +1,32 @@
import * as React from 'react';
import { shell } from 'electron';
/**
* Renders a "more documentation" block, typically used at the end of a "show me" documentation piece.
*
* @export
* @param {string} [url='electronjs.org/docs']
* @returns {JSX.Element}
*/
export function renderMoreDocumentation(
url: string = 'electronjs.org/docs'
): JSX.Element {
const fullUrl = `https://${url}`;
const gitHubUrl = `https://github.com/electron/fiddle`;
return (
<>
<br />
<p className='b3-running-text'>
For more documentation, visit <a id='open-url' onClick={() => shell.openExternal(fullUrl)}>{url}</a>, where
you can find the full documentation for Electron.
</p>
<p className='bp3-text-muted'>
By the way, Electron Fiddle and the documentation you see here is entirely open source. If you
have ideas on how to improve it, we'd love to have your contributions! You can find the
repository on <a id='open-github' onClick={() => shell.openExternal(gitHubUrl)}>GitHub</a>.
</p>
</>
);
}
@@ -0,0 +1,26 @@
import * as React from 'react';
import { shell } from 'electron';
import { getDocsUrlForModule } from '../../../utils/docs-urls';
/**
* Returns a common string we use as a disclaimer to explain that this set of demos
* is only meant to be a short demonstration, not displaying all of the modules
* abilities.
*
* @export
* @param {string} moduleName
* @returns {JSX.Element}
*/
export function getSubsetOnly(moduleName: string): JSX.Element {
const { full, short } = getDocsUrlForModule(moduleName);
return (
<p className='bp3-running-text'>
The following demos display only a subset of what the <code>{moduleName}</code>
module is capable of. If you want to see its full abilities, check out the
documentation on <a id='open-url' onClick={() => shell.openExternal(full)}>{short}</a>.
</p>
);
}
+81 -63
View File
@@ -1,8 +1,8 @@
import { Button, Classes, Dialog } from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AppState } from '../state';
import { Dialog } from './dialog';
import { Tour, TourScriptStep, TourStepGetButtonParams } from './tour';
export interface WelcomeTourProps {
@@ -19,14 +19,14 @@ export interface WelcomeTourState {
*
* @returns {Set<TourScriptStep>}
*/
function getWelcomeTour(): Set<TourScriptStep> {
export function getWelcomeTour(): Set<TourScriptStep> {
return new Set([
{
name: 'fiddle-editors',
selector: 'div.mosaic-root',
title: '📝 Fiddle Editors',
content: (
<div>
<h4>📝 Fiddle Editors</h4>
<>
<p>
Electron Fiddle allows you to build little experiments and mini-apps with
Electron. Each Fiddle has three files: A main script, a renderer script,
@@ -37,15 +37,15 @@ function getWelcomeTour(): Set<TourScriptStep> {
it automatically. It will also automatically provide you with autocomplete
information for the <code>electron</code> module.
</p>
</div>
</>
)
},
{
name: 'select-versions',
selector: 'select.select-versions',
selector: 'button.version-chooser',
title: '📇 Choose an Electron Version',
content: (
<div>
<h4>📇 Choose an Electron Version</h4>
<>
<p>
Electron Fiddle knows about all released Electron versions, downloading
your versions automatically in the background.
@@ -54,27 +54,25 @@ function getWelcomeTour(): Set<TourScriptStep> {
Open the preferences to see all available versions and delete those previously
downloaded.
</p>
</div>
</>
)
},
{
name: 'button-run',
selector: 'button.button-run',
selector: '.button-run',
title: '🚀 Run Your Fiddle',
content: (
<div>
<h4>🚀 Run Your Fiddle</h4>
<p>
Hit this button to give your Fiddle a try and start it.
</p>
</div>
<p>
Hit this button to give your Fiddle a try and start it.
</p>
)
},
{
name: 'button-publish',
selector: 'button.button-publish',
selector: '.button-publish',
title: '🗺 Share Your Fiddle',
content: (
<div>
<h4>🗺 Share Your Fiddle</h4>
<>
<p>
Like what you've built? You can save your Fiddle as a public GitHub Gist,
allowing other users to load it by pasting the URL into the address bar.
@@ -85,41 +83,39 @@ function getWelcomeTour(): Set<TourScriptStep> {
You can also package your Fiddle as a standalone binary or as an installer
from the "Tasks" menu.
</p>
</div>
</>
)
},
{
name: 'first-time-electron',
selector: 'div.mosaic-root',
title: '👋 Getting Started With Electron?',
content: (
<div>
<h4>👋 Getting Started With Electron?</h4>
<p>
We've finished our tour of Electron Fiddle, but if this is your
first time using Electron, we could introduce you to its basics.
Interested?
</p>
</div>
<p>
We've finished our tour of Electron Fiddle, but if this is your
first time using Electron, we could introduce you to its basics.
Interested?
</p>
),
getButtons: ({ stop, advance }: TourStepGetButtonParams): Array<JSX.Element> => {
return [
<button key='btn-stop' onClick={stop}>I'm good!</button>,
<button key='btn-adv' onClick={advance}>Electron Basics</button>
<Button key='btn-stop' onClick={stop} text="I'm good!" icon='stop' />,
<Button key='btn-adv' onClick={advance} text='Electron Basics' icon='help' />
];
}
},
{
name: 'main-editor',
selector: 'div.mosaic-window.main',
title: '📝 Main Script',
content: (
<div>
<h4>📝 Main Script</h4>
<>
<p>
Every Electron app starts with a main script, very similar to how
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
@@ -131,42 +127,40 @@ function getWelcomeTour(): Set<TourScriptStep> {
The default fiddle creates a new <code>BrowserWindow</code> and loads
an HTML file.
</p>
</div>
</>
)
},
{
name: 'html-editor',
selector: 'div.mosaic-window.html',
title: '📝 HTML',
content: (
<div>
<h4>📝 HTML</h4>
<p>
In the default fiddle, this HTML file is loaded in the
<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>
require()</code> like we would in Node.js.
</p>
</div>
<p>
In the default fiddle, this HTML file is loaded in the
&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
&nbsp;<code>&lt;script /&gt;</code> tag and notice how we can call <code>
require()</code> like we would in Node.js.
</p>
)
},
{
name: 'renderer-editor',
selector: 'div.mosaic-window.renderer',
title: '📝 Renderer Script',
content: (
<div>
<h4>📝 Renderer Script</h4>
<>
<p>
This is the script we just required from the HTML file. In here, you can
do anything that works in Node.js <i>and</i> anything that works in a browser.
</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>
</div>
</>
)
}
]);
@@ -206,6 +200,25 @@ export class WelcomeTour extends React.Component<WelcomeTourProps, WelcomeTourSt
this.setState({ isTourStarted: true });
}
get buttons() {
return (
<>
<Button
key='cancel'
onClick={this.stopTour}
icon='cross'
text={`I'll figure it out`}
/>
<Button
key='ok'
onClick={this.startTour}
icon='presentation'
text='Show me around'
/>
</>
);
}
public render() {
const { isTourShowing } = this.props.appState;
const { isTourStarted } = this.state;
@@ -216,21 +229,26 @@ export class WelcomeTour extends React.Component<WelcomeTourProps, WelcomeTourSt
return (
<Dialog
key='welcome-tour-dialog'
isCentered={true}
isShowing={true}
isShowingBackdrop={true}
onConfirm={this.startTour}
onClose={this.stopTour}
isOpen={true}
>
<h4>🙋 Hey There!</h4>
<p>
Welcome to Electron Fiddle! If you're new to the app,
we'd like to give you a brief tour of its features.
</p>
<p>
We won't show this dialog again, but you can always
find the tour in the Help menu.
</p>
<div className={Classes.DIALOG_HEADER}>
<h4 className={Classes.HEADING}>🙋 Hey There!</h4>
</div>
<div className={Classes.DIALOG_BODY}>
<p>
Welcome to Electron Fiddle! If you're new to the app,
we'd like to give you a brief tour of its features.
</p>
<p>
We won't show this dialog again, but you can always
find the tour in the Help menu.
</p>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
{this.buttons}
</div>
</div>
</Dialog>
);
} else {
+50 -15
View File
@@ -1,10 +1,12 @@
import { Button, Classes, Dialog } from '@blueprintjs/core';
import * as React from 'react';
import { invertPosition, positionForRect } from '../../utils/position-for-rect';
import { Dialog } from './dialog';
import { positionForRect } from '../../utils/position-for-rect';
export interface TourScriptStep {
name: string;
selector: string;
title: string;
content: JSX.Element;
getButtons?: (handlers: TourStepGetButtonParams) => Array<JSX.Element>;
}
@@ -116,12 +118,29 @@ export class Tour extends React.Component<TourProps, TourState> {
// No? Fine! Are we at the end of the tour?
return this.props.tour.size === this.state.i
? [
<button key='btn-stop' className='btn-stop' onClick={this.stop}>Finish Tour</button>
]
: [
<button key='btn-stop' className='btn-stop' onClick={this.stop}>Stop Tour</button>,
<button key='btn-adv' className='btn-adv' onClick={this.advance}>Continue</button>
? [(
<Button
icon='tick-circle'
onClick={this.stop}
key='btn-stop'
text='Finish Tour'
/>
)] : [
(
<Button
icon='stop'
key='btn-stop'
onClick={this.stop}
text='Stop Tour'
/>
), (
<Button
icon='step-forward'
key='btn-adv'
onClick={this.advance}
text='Continue'
/>
)
];
}
@@ -142,13 +161,31 @@ export class Tour extends React.Component<TourProps, TourState> {
top: position.top,
left: position.left,
width: size.width - margin,
margin: `${margin}px`
margin: `${margin}px`,
zIndex: 1000,
};
const arrow = invertPosition(position.type);
return (
<Dialog key={step.name} buttons={buttons} style={style} arrow={arrow}>
{step.content}
<Dialog
key={step.name}
isOpen={true}
style={style}
portalClassName='tour-portal'
backdropProps={{ style: { visibility: 'hidden' }}}
>
<div className={Classes.DIALOG_HEADER}>
<h4 className={Classes.HEADING}>
{step.title}
</h4>
</div>
<div className={Classes.DIALOG_BODY}>
{step.content}
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
{buttons}
</div>
</div>
</Dialog>
);
}
@@ -161,9 +198,7 @@ export class Tour extends React.Component<TourProps, TourState> {
*/
private getStep(step: TourScriptStep): JSX.Element | null {
const { selector } = step;
const element = document.querySelector(selector);
if (!element) return null;
const element = document.querySelector(selector) || document.body!;
const rect = element.getBoundingClientRect();
const { top, left, width, height } = rect;
@@ -1,80 +0,0 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { sortedElectronMap } from '../../utils/sorted-electron-map';
import { AppState } from '../state';
import { getReleaseChannel } from '../versions';
export interface VersionChooserState {
value: string;
}
export interface VersionChooserProps {
appState: AppState;
}
/**
* A dropdown allowing the selection of Electron versions. The actual
* download is managed in the state.
*
* @class VersionChooser
* @extends {React.Component<VersionChooserProps, VersionChooserState>}
*/
@observer
export class VersionChooser extends React.Component<VersionChooserProps, VersionChooserState> {
constructor(props: VersionChooserProps) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
/**
* Handle change, which usually means that we'd like update
* the selection version.
*
* @param {React.ChangeEvent<HTMLSelectElement>} event
*/
public handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
this.props.appState.setVersion(event.target.value);
}
/**
* Renders the individual options (Electron versions)
*
* @returns {Array<JSX.Element>}
*/
public renderOptions(): Array<JSX.Element | null> {
const { versions, versionsToShow } = this.props.appState;
return sortedElectronMap<JSX.Element | null>(versions, (_key, item) => {
// Check if we want to show the version
if (!item || !versionsToShow.includes(getReleaseChannel(item))) {
return null;
}
const { tag_name, state } = item;
const version = tag_name;
const icon = state === 'ready'
? '✅'
: state === 'downloading' ? '⏬' : '⏹';
return (
<option value={version} key={version}>
{icon} {version}
</option>
);
});
}
public render() {
return (
<select
className='select-versions'
value={`v${this.props.appState.version}`}
onChange={this.handleChange}
>
{this.renderOptions()}
</select>
);
}
}

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