Compare commits

...
138 Commits
Author SHA1 Message Date
Samuel Attard dd5ca989be feat: handle incoming electron-fiddle://token/* launches 2019-06-07 16:25:54 -07: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 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
243 changed files with 9750 additions and 6652 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
language: node_js
node_js: "8"
node_js: "12"
os:
- linux
- osx
+3 -3
View File
@@ -15,13 +15,13 @@ address bar.
</a>
</h3>
<img src="https://user-images.githubusercontent.com/378023/49716908-7d2b0500-fc98-11e8-8f52-def504802509.png" width="880px" alt="Electron Fiddle screenshot">
<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
+3 -1
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",
+4886 -4663
View File
File diff suppressed because it is too large Load Diff
+54 -52
View File
@@ -1,7 +1,7 @@
{
"name": "electron-fiddle",
"productName": "Electron Fiddle",
"version": "0.5.0",
"version": "0.7.0",
"description": "The easiest way to get started with Electron",
"repository": "https://github.com/electron/fiddle",
"main": "./dist/src/main/main",
@@ -11,7 +11,7 @@
"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",
@@ -36,71 +36,73 @@
"forge": "./forge.config.js"
},
"dependencies": {
"@blueprintjs/core": "^3.10.0",
"@blueprintjs/select": "^3.4.0",
"@octokit/rest": "^15.12.1",
"builtin-modules": "^3.0.0",
"@blueprintjs/core": "^3.15.1",
"@blueprintjs/select": "^3.8.0",
"@octokit/rest": "^16.25.6",
"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.1",
"mobx": "^5.8.0",
"mobx-react": "^5.4.3",
"monaco-loader": "^0.15.0",
"namor": "^1.1.1",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-mosaic-component": "^2.0.2",
"semver": "^5.6.0",
"tmp": "0.0.33",
"fs-extra": "^8.0.1",
"mobx": "^5.9.4",
"mobx-react": "^5.4.4",
"monaco-editor": "^0.17.0",
"monaco-loader": "1.0.0",
"namor": "^1.1.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-mosaic-component": "^2.1.0",
"semver": "^6.0.0",
"tmp": "0.1.0",
"tslib": "^1.9.3",
"update-electron-app": "^1.3.0"
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.32",
"@electron-forge/maker-deb": "^6.0.0-beta.32",
"@electron-forge/maker-rpm": "^6.0.0-beta.32",
"@electron-forge/maker-squirrel": "^6.0.0-beta.32",
"@electron-forge/maker-zip": "^6.0.0-beta.32",
"@electron-forge/publisher-github": "^6.0.0-beta.32",
"@types/builtin-modules": "^2.0.0",
"@babel/core": "^7.4.4",
"@electron-forge/cli": "^6.0.0-beta.34",
"@electron-forge/maker-deb": "^6.0.0-beta.39",
"@electron-forge/maker-rpm": "^6.0.0-beta.39",
"@electron-forge/maker-squirrel": "^6.0.0-beta.39",
"@electron-forge/maker-zip": "^6.0.0-beta.39",
"@electron-forge/publisher-github": "^6.0.0-beta.34",
"@types/builtin-modules": "^3.1.1",
"@types/classnames": "^2.2.7",
"@types/enzyme": "^3.1.13",
"@types/fs-extra": "^5.0.4",
"@types/jest": "^23.3.2",
"@types/log-symbols": "^2.0.0",
"@types/enzyme": "^3.9.2",
"@types/fs-extra": "^7.0.0",
"@types/jest": "^24.0.13",
"@types/log-symbols": "^3.0.0",
"@types/node": "^10.11.1",
"@types/react": "^16.7.18",
"@types/react-dom": "^16.0.11",
"@types/semver": "^5.5.0",
"@types/tmp": "0.0.33",
"chokidar": "^2.0.4",
"coveralls": "^3.0.2",
"electron": "4.0.2",
"enzyme": "^3.6.0",
"enzyme-adapter-react-16": "^1.5.0",
"@types/react": "^16.8.17",
"@types/react-dom": "^16.8.4",
"@types/semver": "^6.0.0",
"@types/tmp": "0.1.0",
"chokidar": "^2.1.6",
"coveralls": "^3.0.3",
"electron": "5.0.1",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.13.0",
"enzyme-to-json": "^3.3.5",
"jest": "^23.6.0",
"jest-fetch-mock": "^1.6.6",
"less": "^3.8.1",
"log-symbols": "^2.2.0",
"monaco-editor": "^0.15.6",
"node-abi": "^2.5.1",
"node-fetch": "^2.3.0",
"jest": "^24.8.0",
"jest-fetch-mock": "^2.1.2",
"less": "^3.9.0",
"log-symbols": "^3.0.0",
"node-abi": "^2.8.0",
"node-fetch": "^2.6.0",
"npm-run-all": "^4.1.5",
"parcel-bundler": "^1.11.0",
"react-test-renderer": "^16.7.0",
"parcel-bundler": "^1.12.3",
"react-test-renderer": "^16.8.6",
"rimraf": "^2.6.3",
"standard": "^12.0.1",
"stylelint": "^9.9.0",
"stylelint-config-standard": "^18.2.0",
"ts-jest": "^23.10.5",
"tslint": "^5.12.0",
"tslint-microsoft-contrib": "^6.0.0",
"tslint-react": "^3.6.0",
"typescript": "^3.2.2"
"stylelint": "^10.0.1",
"stylelint-config-standard": "^18.3.0",
"ts-jest": "^24.0.2",
"tslint": "^5.16.0",
"tslint-microsoft-contrib": "^6.1.1",
"tslint-react": "^4.0.0",
"typescript": "^3.4.5"
}
}
+28 -1
View File
@@ -32,6 +32,12 @@ export interface ElectronVersion extends NpmVersion {
source: ElectronVersionSource;
}
export interface SetFiddleOptions {
values: EditorValues;
filePath?: string;
templateName?: string;
}
export interface OutputEntry {
text: string;
timestamp: number;
@@ -53,6 +59,27 @@ export interface Templates {
[index: string]: string | Templates;
}
export type EditorId = 'main' | 'renderer' | 'html';
// Editors
export const enum EditorId {
'main' = 'main',
'renderer' = 'renderer',
'html' = 'html'
}
// 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.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'
}
+8 -2
View File
@@ -1,6 +1,7 @@
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',
@@ -16,7 +17,8 @@ export enum IpcEvents {
FS_SAVE_FIDDLE_DIALOG = 'FS_SAVE_FIDDLE_DIALOG',
FS_SAVE_FIDDLE_ERROR = 'FS_SAVE_FIDDLE_ERROR',
SHOW_WARNING_DIALOG = 'SHOW_WARNING_DIALOG',
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR'
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR',
SET_GITHUB_TOKEN = 'SET_GITHUB_TOKEN'
}
export const ipcMainEvents = [
@@ -28,6 +30,7 @@ export const ipcMainEvents = [
export const ipcRendererEvents = [
IpcEvents.OPEN_SETTINGS,
IpcEvents.LOAD_GIST_REQUEST,
IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST,
IpcEvents.FIDDLE_RUN,
IpcEvents.FIDDLE_PACKAGE,
IpcEvents.FIDDLE_MAKE,
@@ -40,5 +43,8 @@ export const ipcRendererEvents = [
IpcEvents.FS_SAVE_FIDDLE_AS,
IpcEvents.FS_SAVE_FIDDLE_FORGE,
IpcEvents.FS_SAVE_FIDDLE_GIST,
IpcEvents.SHOW_WELCOME_TOUR
IpcEvents.SHOW_WELCOME_TOUR,
IpcEvents.SET_GITHUB_TOKEN
];
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL = 'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
+13
View File
@@ -18,6 +18,15 @@
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;
}
@@ -29,4 +38,8 @@
background-color: @background-3;
}
}
.bp3-running-text {
font-size: 14px;
}
}
+22
View File
@@ -2,6 +2,28 @@
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; }
+2 -2
View File
@@ -1,7 +1,7 @@
@import "../variables.less";
.fiddle {
.mosaic-window-toolbar.draggable {
.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,9 +34,9 @@
flex-wrap: nowrap;
align-items: stretch;
align-content: stretch;
font-size: 12px;
overflow: visible;
z-index: 4;
background-color: @background-4;
}
.mosaic {
+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;
}
+1 -1
View File
@@ -3,7 +3,6 @@
// Variables, also imported in each file
@import "variables.less";
@import "main.less";
@import "container.less";
@import "mosaic-vendor.less";
@@ -18,3 +17,4 @@
@import "components/editors.less";
@import "components/tour.less";
@import "components/commands-version-chooser.less";
@import "components/show-me.less";
+3 -3
View File
@@ -136,17 +136,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);
}
}
+19 -1
View File
@@ -1,6 +1,6 @@
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 +12,25 @@ 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.Event) => {
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 +44,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);
}
+14
View File
@@ -1,6 +1,8 @@
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';
@@ -17,6 +19,7 @@ export async function onReady() {
if (!isDevMode()) process.env.NODE_ENV = 'production';
getOrCreateMainWindow();
setupAboutPanel();
const { setupMenu } = await import('./menu');
const { setupFileListeners } = await import('./files');
@@ -26,6 +29,16 @@ export async function onReady() {
setupFileListeners();
setupUpdates();
setupDialogs();
setupDevTools();
}
/**
* Handle the "before-quit" event
*
* @export
*/
export function onBeforeQuit() {
(global as any).isQuitting = true;
}
/**
@@ -62,6 +75,7 @@ export function main() {
// Launch
app.on('ready', onReady);
app.on('before-quit', onBeforeQuit);
app.on('window-all-closed', onWindowsAllClosed);
app.on('activate', getOrCreateMainWindow);
}
+43 -4
View File
@@ -16,17 +16,56 @@ const handlePotentialProtocolLaunch = (url: string) => {
}
const parsed = nodeUrl.parse(url.replace(/\/$/, ''));
if (!parsed.pathname) return;
if (!parsed.pathname || !parsed.hostname) return;
const pathParts = parsed.pathname.split('/');
if (pathParts.length <= 0 || pathParts.length > 2) return;
const pathParts = parsed.pathname.split('/').slice(1);
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [parsed.pathname]);
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;
case 'token':
if (pathParts.length === 1) {
// We have a token
ipcMainManager.send(IpcEvents.SET_GITHUB_TOKEN, [pathParts[0]]);
}
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);
}
};
+2 -1
View File
@@ -20,7 +20,8 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
acceptFirstMouse: true,
backgroundColor: '#1d2427',
webPreferences: {
webviewTag: false
webviewTag: false,
nodeIntegration: true
}
};
}
+26 -16
View File
@@ -1,13 +1,18 @@
import { when } from 'mobx';
import * as MonacoType from 'monaco-editor';
import { EditorValues } from '../interfaces';
import { ipcRenderer } from 'electron';
import { ALL_EDITORS, EditorId, EditorValues } 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 { isEditorBackup } from '../utils/type-checks';
import { FileManager } from './file-manager';
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,
@@ -21,10 +26,15 @@ export class App {
public state = appState;
public fileManager = new FileManager(appState);
public runner = new Runner(appState);
public touchBarManager: TouchBarManager | undefined;
constructor() {
this.getValues = this.getValues.bind(this);
this.setValues = this.setValues.bind(this);
if (process.platform === 'darwin') {
this.touchBarManager = new TouchBarManager(appState);
}
}
/**
@@ -49,18 +59,17 @@ export class App {
}
}
const { main, html, renderer } = fiddle.editors;
for (const name of ALL_EDITORS) {
const editor = fiddle.editors[name];
const backup = this.state.closedPanels[name];
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);
if (isEditorBackup(backup) && backup.model && values[name]) {
// The editor does not exist, attempt to set it on the backup
backup.model.setValue(values[name]!);
} else if (editor && editor.setValue && values[name]) {
// The editor exists, set the value directly
editor.setValue(values[name]!);
}
}
this.state.isUnsaved = false;
@@ -81,11 +90,10 @@ export class App {
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),
renderer: getEditorValue(EditorId.renderer),
};
if (options && options.include !== false) {
@@ -121,6 +129,8 @@ export class App {
this.setupResizeListener();
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(() => {
+1 -1
View File
@@ -171,7 +171,7 @@ export class BinaryManager {
}
/**
* 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 -29
View File
@@ -1,13 +1,17 @@
import { Button, InputGroup, Intent } from '@blueprintjs/core';
import classnames from 'classnames';
import { reaction } from 'mobx';
import { reaction, when } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { EditorId, EditorValues } from '../../interfaces';
import { IpcEvents } from '../../ipc-events';
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../shared-constants';
import { getTitle } from '../../utils/get-title';
import { idFromUrl, urlFromId } from '../../utils/gist';
import { getOctokit } from '../../utils/octokit';
import { getContent } from '../content';
import { ipcRendererManager } from '../ipc';
import { AppState } from '../state';
export interface AddressBarProps {
@@ -23,9 +27,14 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
constructor(props: AddressBarProps) {
super(props);
this.loadFiddle = this.loadFiddle.bind(this);
this.fetchExampleAndLoad = this.fetchExampleAndLoad.bind(this);
this.fetchGistAndLoad = this.fetchGistAndLoad.bind(this);
this.loadFiddleFromGist = this.loadFiddleFromGist.bind(this);
this.loadFiddleFromElectronExample = this.loadFiddleFromElectronExample.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.submit = this.submit.bind(this);
this.verifyRemoteLoad = this.verifyRemoteLoad.bind(this);
const { gistId } = this.props.appState;
const value = gistId ? urlFromId(gistId) : '';
@@ -39,15 +48,19 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
*
* @param {React.SyntheticEvent<HTMLFormElement>} event
*/
public async handleSubmit(event: React.SyntheticEvent<HTMLFormElement>) {
public handleSubmit(event: React.SyntheticEvent<HTMLFormElement>) {
event.preventDefault();
this.submit();
}
this.props.appState.gistId = idFromUrl(this.state.value) || this.state.value;
console.log('Loading');
/**
* Commit the address bar's value to app state and load the fiddle.
*
* @memberof AddressBar
*/
public submit() {
if (this.state.value) {
this.loadFiddle();
this.fetchGistAndLoad(idFromUrl(this.state.value) || this.state.value);
}
}
@@ -59,6 +72,44 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
() => this.props.appState.gistId,
(gistId: string) => this.setState({ value: urlFromId(gistId) })
);
ipcRendererManager.on(IpcEvents.LOAD_GIST_REQUEST, this.loadFiddleFromGist);
ipcRendererManager.on(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, this.loadFiddleFromElectronExample);
}
public componentWillUnmount() {
ipcRendererManager.removeListener(IpcEvents.LOAD_GIST_REQUEST, this.loadFiddleFromGist);
ipcRendererManager.removeListener(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, this.loadFiddleFromElectronExample);
}
public async loadFiddleFromElectronExample(_: any, exampleInfo: { path: string; ref: string }) {
console.log(`Loading fiddle from Electron example`, _, exampleInfo);
const ok = await this.verifyRemoteLoad('example from the Electron docs');
if (!ok) return;
this.fetchExampleAndLoad(exampleInfo.ref, exampleInfo.path);
}
public async loadFiddleFromGist(_: any, gistInfo: { id: string }) {
const ok = await this.verifyRemoteLoad('gist');
if (!ok) return;
this.fetchGistAndLoad(gistInfo.id);
}
/**
* Verifies from the user that we should be loading this fiddle
*
* @param what What are we loading from (gist, example, etc.)
*/
public async verifyRemoteLoad(what: string): Promise<boolean> {
const { appState } = this.props;
appState.setWarningDialogTexts({
label: `Are you sure you sure you want to load this ${what}? Only load and run it if you trust the source`
});
appState.isWarningDialogShowing = true;
await when(() => !appState.isWarningDialogShowing);
return !!appState.warningDialogLastResult;
}
/**
@@ -70,39 +121,83 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
this.setState({ value: event.target.value });
}
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
const { appState } = this.props;
try {
const octo = await getOctokit(this.props.appState);
// TODO: Maybe we should fetch the package.json for the given ref to use the correct version of Electron?
const folder = await octo.repos.getContents({
owner: 'electron',
repo: 'electron',
ref,
path,
});
const values = {
html: await getContent(EditorId.html, appState.version),
renderer: await getContent(EditorId.renderer, appState.version),
main: await getContent(EditorId.main, appState.version),
};
const loaders: Array<Promise<void>> = [];
if (!Array.isArray(folder.data)) {
throw new Error('The example Fiddle tried to launch is not a valid Electron example');
}
for (const child of folder.data) {
switch (child.name) {
case MAIN_JS_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.main = t; })
);
break;
case INDEX_HTML_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.html = t; })
);
break;
case RENDERER_JS_NAME:
loaders.push(fetch(child.download_url)
.then((r) => r.text()).then((t) => { values.renderer = t; })
);
break;
default:
break;
}
}
await Promise.all(loaders);
return this.handleLoadingSuccess(values, '');
} catch (error) {
return this.handleLoadingFailed(error);
}
}
/**
* Load a fiddle
*
* @returns {Promise<boolean>}
* @memberof AddressBar
*/
public async loadFiddle(): Promise<boolean> {
const { appState } = this.props;
public async fetchGistAndLoad(gistId: string): Promise<boolean> {
try {
const octo = await getOctokit();
const gist = await octo.gists.get({
gist_id: appState.gistId
});
const octo = await getOctokit(this.props.appState);
const gist = await octo.gists.get({ gist_id: gistId });
this.props.appState.setWarningDialogTexts({
label: 'Loading the fiddle will replace your current unsaved changes. Do you want to discard them?'
});
await window.ElectronFiddle.app.setValues({
return this.handleLoadingSuccess({
html: gist.data.files[INDEX_HTML_NAME].content,
main: gist.data.files[MAIN_JS_NAME].content,
renderer: gist.data.files[RENDERER_JS_NAME].content,
});
document.title = getTitle(appState);
appState.localPath = null;
}, gistId);
} catch (error) {
console.warn(`Loading Fiddle failed`, error);
return false;
return this.handleLoadingFailed(error);
}
return true;
}
public renderLoadButton(isValueCorrect: boolean): JSX.Element {
@@ -111,7 +206,7 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
disabled={!isValueCorrect}
icon='cloud-download'
text='Load Fiddle'
onClick={this.loadFiddle}
onClick={this.submit}
/>
);
}
@@ -136,4 +231,49 @@ export class AddressBar extends React.Component<AddressBarProps, AddressBarState
</form>
);
}
/**
* Loading a fiddle from GitHub succeeded, let's move on.
*
* @private
* @param {Partial<EditorValues>} values
* @param {string} gistId
* @returns {boolean}
*/
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
const { appState } = this.props;
appState.setWarningDialogTexts({
label: 'Loading the fiddle will replace your current unsaved changes. Do you want to discard them?'
});
await window.ElectronFiddle.app.setValues(values);
document.title = getTitle(appState);
appState.gistId = gistId;
appState.localPath = undefined;
appState.templateName = undefined;
return true;
}
/**
* Loading a fiddle from GitHub failed - this method handles this case
* gracefully.
*
* @private
* @param {Error} error
* @returns {boolean}
*/
private handleLoadingFailed(error: Error): false {
this.props.appState.setWarningDialogTexts({
label: `Loading the fiddle failed: ${error}`,
cancel: undefined
});
this.props.appState.toogleWarningDialog();
console.warn(`Loading Fiddle failed`, error);
return false;
}
}
@@ -0,0 +1,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);
}
}
}
@@ -73,12 +73,7 @@ export class PublishButton extends React.Component<PublishButtonProps, PublishBu
public async publishFiddle(): Promise<void> {
this.setState({ isPublishing: true });
const octo = await getOctokit();
octo.authenticate({
type: 'token',
token: this.props.appState.gitHubToken!
});
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);
+1 -1
View File
@@ -48,6 +48,6 @@ export class Runner extends React.Component<RunnerProps, RunnerState> {
props.icon = <Spinner size={16} />;
}
return <Button {...props} />;
return <Button {...props} type={undefined} />;
}
}
@@ -121,10 +121,26 @@ export class VersionChooser extends React.Component<VersionChooserProps, Version
}
public getItems(): Array<ElectronVersion> {
const { versions, versionsToShow } = this.props.appState;
const { versions, versionsToShow, statesToShow } = this.props.appState;
return sortedElectronMap<ElectronVersion>(versions, (_key, item) => item)
.filter((item) => !!item && versionsToShow.includes(getReleaseChannel(item)));
.filter((item) => {
if (!item) {
return false;
}
// Check if we want to show the version
if (!versionsToShow.includes(getReleaseChannel(item))) {
return false;
}
// Check if we want to show the state
if (!statesToShow.includes(item.state)) {
return false;
}
return true;
});
}
public render() {
+2
View File
@@ -4,6 +4,7 @@ import * as React from 'react';
import { AppState } from '../state';
import { AddressBar } from './commands-address-bar';
import { EditorDropdown } from './commands-editors';
import { PublishButton } from './commands-publish-button';
import { Runner } from './commands-runner';
import { VersionChooser } from './commands-version-chooser';
@@ -42,6 +43,7 @@ export class Commands extends React.Component<CommandsProps, {}> {
text='Console'
onClick={appState.toggleConsole}
/>
<EditorDropdown appState={appState} />
</ControlGroup>
</div>
<div>
+2 -6
View File
@@ -57,14 +57,10 @@ export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogSt
if (!this.state.tokenInput) return;
this.setState({ verifying: true });
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;
+42 -7
View File
@@ -4,14 +4,15 @@
import * as MonacoType from 'monaco-editor';
import * as React from 'react';
import { ContentNames, getContent } from '../content';
import { EditorId } from '../../interfaces';
import { getContent } from '../content';
import { AppState } from '../state';
export interface EditorProps {
appState: AppState;
monaco: typeof MonacoType;
monoacoOptions: MonacoType.editor.IEditorOptions;
id: string;
id: EditorId;
options?: Partial<MonacoType.editor.IEditorConstructionOptions>;
editorDidMount?: (editor: MonacoType.editor.IStandaloneCodeEditor) => void;
onChange?: (value: string, event: MonacoType.editor.IModelContentChangedEvent) => void;
@@ -48,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);
}
@@ -62,8 +68,7 @@ export class Editor extends React.Component<EditorProps> {
* Initialize Monaco.
*/
public async initMonaco() {
const { monaco, id, appState, monoacoOptions } = this.props;
const { version } = appState;
const { monaco, monoacoOptions } = this.props;
const ref = this.containerRef.current;
if (ref) {
@@ -71,10 +76,11 @@ export class Editor extends React.Component<EditorProps> {
language: this.language,
theme: 'main',
contextmenu: false,
value: await getContent(id as ContentNames, version),
model: null,
...monoacoOptions
});
this.editorDidMount(this.editor);
await this.editorDidMount(this.editor);
}
}
@@ -83,6 +89,7 @@ export class Editor extends React.Component<EditorProps> {
*/
public destroyMonaco() {
if (typeof this.editor !== 'undefined') {
console.log('Editor: Disposing');
this.editor.dispose();
}
}
@@ -90,4 +97,32 @@ 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);
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,96 @@
import * as React from 'react';
import { Button } from '@blueprintjs/core';
import { MosaicWindowContext } from 'react-mosaic-component';
import { DocsDemoPage, MosaicId } from '../../interfaces';
import { AppState } from '../state';
export interface ToolbarButtonProps {
appState: AppState;
id: MosaicId;
}
export class MaximizeButton extends React.PureComponent<ToolbarButtonProps> {
public static contextTypes = MosaicWindowContext;
public context: MosaicWindowContext<MosaicId>;
constructor(props: ToolbarButtonProps) {
super(props);
this.expand = this.expand.bind(this);
}
public render() {
return (
<Button
icon='maximize'
className='bp3-small'
onClick={this.expand}
/>
);
}
/**
* Expand this panel
*/
public expand() {
const path = this.context.mosaicWindowActions.getPath();
return this.context.mosaicActions.expand(path);
}
}
export class RemoveButton extends React.PureComponent<ToolbarButtonProps> {
public static contextTypes = MosaicWindowContext;
public context: MosaicWindowContext<MosaicId>;
constructor(props: ToolbarButtonProps) {
super(props);
this.remove = this.remove.bind(this);
}
public render() {
return (
<Button
icon='cross'
className='bp3-small'
onClick={this.remove}
/>
);
}
/**
* Remove this panel
*/
public remove() {
this.props.appState.hideAndBackupMosaic(this.props.id);
}
}
export class DocsDemoGoHomeButton extends React.PureComponent<ToolbarButtonProps> {
public static contextTypes = MosaicWindowContext;
public context: MosaicWindowContext<MosaicId>;
constructor(props: ToolbarButtonProps) {
super(props);
this.goHome = this.goHome.bind(this);
}
public render() {
return (
<Button
icon='home'
className='bp3-small'
onClick={this.goHome}
text='Overview'
/>
);
}
/**
* Remove this panel
*/
public goHome() {
this.props.appState.currentDocsDemoPage = DocsDemoPage.DEFAULT;
}
}
+152 -31
View File
@@ -1,28 +1,24 @@
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, 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 { getAtPath, setAtPath } from '../../utils/js-path';
import { toggleMonaco } from '../../utils/toggle-monaco';
import { ContentNames, getContent } from '../content';
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';
const options: any = {
direction: 'row',
first: 'main',
second: {
direction: 'column',
first: 'renderer',
second: 'html',
},
};
import { renderNonIdealState } from './editors-non-ideal-state';
import { DocsDemoGoHomeButton, MaximizeButton, RemoveButton } from './editors-toolbar-button';
import { ShowMe } from './show-me';
const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
minimap: {
@@ -34,10 +30,11 @@ const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
const ViewIdMosaic = Mosaic.ofType<EditorId>() as any;
const ViewIdMosaicWindow = MosaicWindow.ofType<EditorId>() as any;
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)',
html: 'HTML (index.html)',
docsDemo: 'Docs & Demos'
};
export interface EditorsProps {
@@ -50,13 +47,33 @@ export interface EditorsState {
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.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);
@@ -69,10 +86,12 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
label: 'Your current fiddle is unsaved. Do you want to discard it?'
});
window.ElectronFiddle.app.setValues({
html: await getContent(ContentNames.HTML, version),
renderer: await getContent(ContentNames.RENDERER, version),
main: await getContent(ContentNames.MAIN, version),
window.ElectronFiddle.app.fileManager.setFiddle({
values: {
html: await getContent(EditorId.html, version),
renderer: await getContent(EditorId.renderer, version),
main: await getContent(EditorId.main, version),
}
});
});
@@ -83,6 +102,10 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
this.setState({ isMounted: true });
}
public componentWillUnmount() {
this.disposeLayoutAutorun();
}
/**
* Attempt to execute a given commandId on the currently focused editor
*
@@ -135,27 +158,122 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
}
}
/**
* 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: string): JSX.Element | null {
const { appState } = this.props;
const content = isEditorId(id)
? this.renderEditor(id)
: this.renderGenericPanel(id, appState);
return (
<ViewIdMosaicWindow
className={id}
path={path}
title={TITLE_MAP[id]}
renderToolbar={(props: MosaicWindowProps<MosaicId>) => this.renderToolbar(props, id)}
>
{content}
</ViewIdMosaicWindow>
);
}
/**
* 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}
monoacoOptions={defaultMonacoOptions}
/>
);
}
public render() {
const { appState } = this.props;
const { monaco } = this.state;
if (!monaco) return null;
return (
<ViewIdMosaic
onChange={updateEditorLayout}
// tslint:disable-next-line:jsx-no-multiline-js
renderTile={(id: any, path: any) => (
<ViewIdMosaicWindow className={id} path={path} title={TITLE_MAP[id]} toolbarControls={<div />}>
<Editor id={id} monaco={monaco} appState={appState} monoacoOptions={defaultMonacoOptions} />
</ViewIdMosaicWindow>
)}
initialValue={options}
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
@@ -171,7 +289,10 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
}
if (!this.state || !this.state.isMounted) {
this.state = { monaco, monacoOptions: defaultMonacoOptions };
this.state = {
monaco,
monacoOptions: defaultMonacoOptions
};
} else {
this.setState({ monaco });
}
+106 -39
View File
@@ -41,6 +41,7 @@ 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.handleStateChange = this.handleStateChange.bind(this);
this.handleDownloadClick = this.handleDownloadClick.bind(this);
this.handleAddVersion = this.handleAddVersion.bind(this);
@@ -54,6 +55,24 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
this.props.appState.updateElectronVersions();
}
/**
* Handles a change in which channels should be displayed.
*
* @param {React.ChangeEvent<HTMLInputElement>} event
*/
public handleStateChange(
event: React.FormEvent<HTMLInputElement>
) {
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);
}
}
/**
* Handles a change in which channels should be displayed.
*
@@ -120,7 +139,10 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
return (
<div className='settings-electron'>
<h2>Electron Settings</h2>
{this.renderVersionOptions()}
<Callout>
{this.renderVersionChannelOptions()}
{this.renderVersionStateOptions()}
</Callout>
<br />
<Callout>
{this.renderAdvancedButtons()}
@@ -173,51 +195,91 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
/**
* Renders the various options for which versions should be displayed
* in the small dropdown.
*
* @private
* @returns {JSX.Element}
*/
private renderVersionOptions(): 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 (
<Callout>
<FormGroup
label='Include Electron versions from these release channels:'
>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.stable)}
label='Stable'
id='Stable'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.beta)}
label='Beta'
id='Beta'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.nightly)}
label='Nightly'
id='Nightly'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.unsupported)}
label='Unsupported'
id='Unsupported'
onChange={this.handleChannelChange}
inline={true}
/>
</FormGroup>
</Callout>
<FormGroup
label='Include Electron versions from these release channels:'
>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.stable)}
label='Stable'
id='Stable'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.beta)}
label='Beta'
id='Beta'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.nightly)}
label='Nightly'
id='Nightly'
onChange={this.handleChannelChange}
inline={true}
/>
<Checkbox
checked={getIsChecked(ElectronReleaseChannel.unsupported)}
label='Unsupported'
id='Unsupported'
onChange={this.handleChannelChange}
inline={true}
/>
</FormGroup>
);
}
@@ -254,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
@@ -262,6 +324,11 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
return null;
}
// Check if we want to show the state
if (!statesToShow.includes(item.state)) {
return null;
}
return (
<tr key={item.version}>
<td>{item.version}</td>
@@ -273,7 +340,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
}
/**
* Returns a human-readable state indicator for an Electorn version
* Returns a human-readable state indicator for an Electron version
*
* @param {ElectronVersion} item
* @returns {JSX.Element}
@@ -334,6 +401,6 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
buttonProps.onClick = () => appState.downloadVersion(key);
}
return <Button {...buttonProps} />;
return <Button {...buttonProps} type={undefined} />;
}
}
+36 -9
View File
@@ -20,6 +20,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
super(props);
this.handleDeleteDataChange = this.handleDeleteDataChange.bind(this);
this.handleElectronLoggingChange = this.handleElectronLoggingChange.bind(this);
}
/**
@@ -35,14 +36,30 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
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 } = this.props.appState;
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>
@@ -52,14 +69,24 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
</Callout>
<br />
<Callout>
<FormGroup label={deleteUserDirLabel}>
<Checkbox
checked={isKeepingUserDataDirs}
label='Do not delete user data directories.'
onChange={this.handleDeleteDataChange}
/>
</FormGroup>
</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>
);
}
+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)}\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>
);
}
+15
View File
@@ -1,5 +1,20 @@
import { remote } from 'electron';
import * as path from 'path';
import { MosaicNode } from 'react-mosaic-component';
import { EditorId, MosaicId } from '../interfaces';
// Reminder: When testing, this file is mocked in tests/setup.js
export const USER_DATA_PATH = remote.app.getPath('userData');
export const CONFIG_PATH = path.join(remote.app.getPath('home'), '.electron-fiddle');
export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
direction: 'row',
first: EditorId.main,
second: {
direction: 'column',
first: EditorId.renderer,
second: EditorId.html,
}
};
+12 -16
View File
@@ -1,30 +1,26 @@
export const enum ContentNames {
HTML = 'html',
RENDERER = 'renderer',
MAIN = 'main'
}
import { EditorId } from '../interfaces';
/**
* Returns expected content for a given name.
*
* @export
* @param {ContentNames} name
* @param {EditorId} name
* @param {string} [version]
* @returns {Promise<string>}
*/
export async function getContent(
name: ContentNames,
version?: string
name: EditorId,
version?: string,
): Promise<string> {
if (name === ContentNames.HTML) {
if (name === EditorId.html) {
return (await import('../content/html')).html;
}
if (name === ContentNames.RENDERER) {
if (name === EditorId.renderer) {
return (await import('../content/renderer')).renderer;
}
if (name === ContentNames.MAIN) {
if (name === EditorId.main) {
// We currently only distinguish between loadFile
// and loadURL. Todo: Properly version the quick-start.
if (version && version.startsWith('1.')) {
@@ -40,18 +36,18 @@ export async function getContent(
/**
* Did the content change?
*
* @param {ContentNames} name
* @param {EditorId} name
* @returns {Promise<boolean>}
*/
export async function isContentUnchanged(name: ContentNames): Promise<boolean> {
export async function isContentUnchanged(name: EditorId): Promise<boolean> {
if (!window.ElectronFiddle || !window.ElectronFiddle.app) return false;
const values = await window.ElectronFiddle.app.getValues({ include: false });
// Handle main case, which needs to check both possible versions
if (name === ContentNames.MAIN) {
const isChanged1x = await getContent(ContentNames.MAIN, '1.0') === values.main;
const isChangedOther = await getContent(ContentNames.MAIN) === values.main;
if (name === EditorId.main) {
const isChanged1x = await getContent(EditorId.main, '1.0') === values.main;
const isChangedOther = await getContent(EditorId.main) === values.main;
return isChanged1x || isChangedOther;
} else {
+8 -6
View File
@@ -1,7 +1,7 @@
import * as fsType from 'fs-extra';
import * as path from 'path';
import { EditorValues, Files, FileTransform } from '../interfaces';
import { EditorValues, Files, FileTransform, SetFiddleOptions } from '../interfaces';
import { IpcEvents } from '../ipc-events';
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, RENDERER_JS_NAME } from '../shared-constants';
import { DEFAULT_OPTIONS, PackageJsonOptions } from '../utils/get-package';
@@ -43,7 +43,8 @@ export class FileManager {
*/
public async openTemplate(name: string) {
const values = await getTemplateValues(name);
this.setFiddle(values);
this.setFiddle({ values, templateName: name });
}
/**
@@ -65,19 +66,20 @@ export class FileManager {
renderer: await this.readFile(path.join(filePath, RENDERER_JS_NAME)),
};
this.setFiddle(values);
this.setFiddle({ values, filePath });
}
/**
* Got values? This method will load them.
*
* @param {EditorValues} values
* @param {SetFiddleOptions} options
* @memberof FileManager
*/
public async setFiddle(values: EditorValues, filePath?: string) {
public async setFiddle({ values, filePath, templateName }: SetFiddleOptions) {
this.appState.gistId = '';
this.appState.isMyGist = false;
this.appState.localPath = filePath || null;
this.appState.localPath = filePath;
this.appState.templateName = templateName;
this.appState.setWarningDialogTexts({
label: `Opening this fiddle will replace your unsaved changes. Do you want to proceed?`,
+13 -1
View File
@@ -181,7 +181,19 @@ export class Runner {
const binaryPath = binaryManager.getElectronBinaryPath(version, localPath);
console.log(`Runner: Binary ${binaryPath} ready, launching`);
this.child = spawn(binaryPath, [ dir, '--inspect' ]);
const env = { ...process.env };
if (this.appState.isEnablingElectronLogging) {
env.ELECTRON_ENABLE_LOGGING = 'true';
env.ELECTRON_ENABLE_STACK_DUMPING = 'true';
} else {
delete env.ELECTRON_ENABLE_LOGGING;
delete env.ELECTRON_ENABLE_STACK_DUMPING;
}
this.child = spawn(binaryPath, [ dir, '--inspect' ], {
cwd: dir,
env,
});
this.appState.isRunning = true;
pushOutput(`Electron v${version} started.`);
+112 -5
View File
@@ -1,9 +1,14 @@
import { action, autorun, computed, observable, when } from 'mobx';
import { MosaicNode } from 'react-mosaic-component';
import {
ALL_MOSAICS,
DocsDemoPage,
EditorId,
ElectronVersion,
ElectronVersionSource,
ElectronVersionState,
MosaicId,
NpmVersion,
OutputEntry,
OutputOptions,
@@ -11,10 +16,14 @@ import {
} from '../interfaces';
import { IpcEvents } from '../ipc-events';
import { arrayToStringMap } from '../utils/array-to-stringmap';
import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
import { createMosaicArrangement, getVisibleMosaics } from '../utils/editors-mosaic-arrangement';
import { getName } from '../utils/get-title';
import { normalizeVersion } from '../utils/normalize-version';
import { isEditorBackup, isEditorId, isPanelId } from '../utils/type-checks';
import { BinaryManager } from './binary';
import { ContentNames, getContent, isContentUnchanged } from './content';
import { DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
import { getContent, isContentUnchanged } from './content';
import { updateEditorTypeDefinitions } from './fetch-types';
import { ipcRendererManager } from './ipc';
import { activateTheme } from './themes';
@@ -62,7 +71,11 @@ export class AppState {
@observable public versionsToShow: Array<ElectronReleaseChannel> =
this.retrieve('versionsToShow') as Array<ElectronReleaseChannel>
|| [ ElectronReleaseChannel.stable, ElectronReleaseChannel.beta ];
@observable public statesToShow: Array<ElectronVersionState> =
this.retrieve('statesToShow') as Array<ElectronVersionState>
|| [ ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown ];
@observable public isKeepingUserDataDirs: boolean = !!this.retrieve('isKeepingUserDataDirs');
@observable public isEnablingElectronLogging: boolean = !!this.retrieve('isEnablingElectronLogging');
@observable public binaryManager: BinaryManager = new BinaryManager();
@@ -71,11 +84,14 @@ export class AppState {
@observable public isMyGist: boolean = false;
@observable public versions: Record<string, ElectronVersion> = arrayToStringMap(knownVersions);
@observable public output: Array<OutputEntry> = [];
@observable public localPath: string | null = null;
@observable public localPath: string | undefined;
@observable public isUpdatingElectronVersions = false;
@observable public warningDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
@observable public warningDialogLastResult: boolean | null = null;
@observable public isRunning = false;
@observable public mosaicArrangement: MosaicNode<MosaicId> | null = DEFAULT_MOSAIC_ARRANGEMENT;
@observable public templateName: string | undefined;
@observable public currentDocsDemoPage: DocsDemoPage = DocsDemoPage.DEFAULT;
// -- Various "isShowing" settings ------------------
@observable public isConsoleShowing: boolean = false;
@@ -86,6 +102,11 @@ export class AppState {
@observable public isAddVersionDialogShowing: boolean = false;
@observable public isTourShowing: boolean = !localStorage.getItem('hasShownTour');
// -- Editor Values stored when we close the editor ------------------
@observable public closedPanels: Partial<Record<MosaicId, EditorBackup | true>> = {
docsDemo: true // Closed by default
};
private outputBuffer: string = '';
private name: string;
@@ -95,6 +116,7 @@ export class AppState {
this.pushError = this.pushError.bind(this);
this.pushOutput = this.pushOutput.bind(this);
this.removeVersion = this.removeVersion.bind(this);
this.setToken = this.setToken.bind(this);
this.setVersion = this.setVersion.bind(this);
this.showTour = this.showTour.bind(this);
this.signOutGitHub = this.signOutGitHub.bind(this);
@@ -105,6 +127,7 @@ export class AppState {
ipcRendererManager.on(IpcEvents.OPEN_SETTINGS, this.toggleSettings);
ipcRendererManager.on(IpcEvents.SHOW_WELCOME_TOUR, this.showTour);
ipcRendererManager.on(IpcEvents.SET_GITHUB_TOKEN, this.setToken);
// Setup auto-runs
autorun(() => this.save('theme', this.theme));
@@ -114,8 +137,10 @@ export class AppState {
autorun(() => this.save('gitHubToken', this.gitHubToken));
autorun(() => this.save('gitHubPublishAsPublic', this.gitHubPublishAsPublic));
autorun(() => this.save('isKeepingUserDataDirs', this.isKeepingUserDataDirs));
autorun(() => this.save('isEnablingElectronLogging', this.isEnablingElectronLogging));
autorun(() => this.save('version', this.version));
autorun(() => this.save('versionsToShow', this.versionsToShow));
autorun(() => this.save('statesToShow', this.statesToShow));
autorun(() => {
if (this.isUnsaved) {
@@ -132,7 +157,16 @@ export class AppState {
// The user confirmed, let's close for real.
if (this.warningDialogLastResult) {
window.onbeforeunload = null;
window.close();
// Should we just close or quit?
const remote = require('electron').remote;
const isQuitting = remote.getGlobal('isQuitting');
if (isQuitting) {
remote.app.quit();
} else {
window.close();
}
}
});
@@ -229,6 +263,19 @@ export class AppState {
this.resetView({ isTourShowing: true });
}
@action public async setToken(_: any, token: string) {
this.setWarningDialogTexts({
label: 'Do you want to sign in to Electron Fiddle using this token?',
ok: 'Yes',
cancel: 'No'
});
this.isWarningDialogShowing = true;
await when(() => !this.isWarningDialogShowing);
if (this.warningDialogLastResult) {
this.gitHubToken = token;
}
}
@action public setTheme(fileName?: string) {
this.theme = fileName || '';
activateTheme(undefined, undefined, fileName);
@@ -339,8 +386,8 @@ export class AppState {
this.version = version;
// Should we update the editor?
if (await isContentUnchanged(ContentNames.MAIN)) {
const main = await getContent(ContentNames.MAIN, version);
if (await isContentUnchanged(EditorId.main)) {
const main = await getContent(EditorId.main, version);
window.ElectronFiddle.app.setValues({ main }, false);
}
@@ -435,6 +482,66 @@ export class AppState {
console.warn(error);
}
/**
* Sets the editor value for a given editor. Deletes the value after
* accessing it.
*
* @param {EditorId} id
*/
@action public getAndRemoveEditorValueBackup(id: EditorId): EditorBackup | null {
const value = this.closedPanels[id];
if (isEditorBackup(value)) {
delete this.closedPanels[id];
return value;
}
return null;
}
@action public setVisibleMosaics(visible: Array<MosaicId>) {
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
for (const id of ALL_MOSAICS) {
if (!visible.includes(id) && currentlyVisible.includes(id)) {
this.closedPanels[id] = isEditorId(id)
? getEditorBackup(id)
: true;
}
// Remove the backup for panels now. Editors will remove their
// backup once the data has been loaded.
if (isPanelId(id) && visible.includes(id) && !currentlyVisible.includes(id)) {
delete this.closedPanels[id];
}
}
const updatedArrangement = createMosaicArrangement(visible);
console.log(`State: Setting visible mosaic panels`, visible, updatedArrangement);
this.mosaicArrangement = updatedArrangement;
}
/**
* Hides the panel for a given MosaicId.
*
* @param {MosaicId} id
*/
@action public hideAndBackupMosaic(id: MosaicId) {
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
this.setVisibleMosaics(currentlyVisible.filter((v) => v !== id));
}
/**
* Shows the editor value for a given editor.
*
* @param {MosaicId} id
*/
@action public showMosaic(id: MosaicId) {
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
this.setVisibleMosaics([ ...currentlyVisible, id ]);
}
/**
* Resets the view, optionally with certain view flags enabled.
*
+11 -3
View File
@@ -15,8 +15,8 @@ import { fancyImport } from '../utils/import';
export async function getTemplateValues(name: string): Promise<EditorValues> {
const path = await fancyImport<typeof pathType>('path');
const fs = await fancyImport<typeof fsExtraType>('fs-extra');
const templatePath = path.join(__dirname, '../../static/templates', name.toLowerCase());
const templatesPath = path.join(__dirname, '../../static/show-me');
const templatePath = path.join(templatesPath, name.toLowerCase());
const getFile = async (fileName: string) => {
try {
@@ -25,7 +25,15 @@ export async function getTemplateValues(name: string): Promise<EditorValues> {
return content;
} catch (error) {
console.warn(`Could not get template file:`, error);
console.warn(`getTemplateValues(): Could not get template file:`, error);
if (fs.existsSync(templatesPath)) {
const contents = fs.readdirSync(templatesPath);
console.log(`getTemplateValues(): ${templatesPath} contents:`, contents);
} else {
console.log(`getTemplateValues(): ${templatesPath} does not exist`);
}
return '';
}
};
+283
View File
@@ -0,0 +1,283 @@
import {
remote,
TouchBarButtonConstructorOptions,
TouchBarScrubberConstructorOptions
} from 'electron';
import { autorun } from 'mobx';
import { ElectronVersion, ElectronVersionState } from '../interfaces';
import { getNiceGreeting } from '../utils/nice-greeting';
import { sortedElectronMap } from '../utils/sorted-electron-map';
import { AppState } from './state';
const { TouchBar } = remote;
const {
TouchBarButton,
TouchBarScrubber,
TouchBarSpacer,
TouchBarLabel
} = TouchBar;
/**
* Helper method: Returns an icon (emoji, lol) for an Electron
* version.
*
* @param {Partial<ElectronVersion>} { state }
* @returns {string}
*/
export function getItemIcon(
{ state }: Partial<ElectronVersion> = { state: ElectronVersionState.unknown }
) {
return state === 'ready'
? '💾'
: state === 'downloading' ? '⏬' : '☁';
}
export class TouchBarManager {
public browserWindow: Electron.BrowserWindow;
public consoleBtn: Electron.TouchBarButton;
public startStopBtn: Electron.TouchBarButton;
public touchBar: Electron.TouchBar;
public versionSelector: Electron.TouchBarScrubber;
public versionSelectorBtn: Electron.TouchBarButton;
public selectedVersion: string = '';
public versionSelectorSelectBtn: Electron.TouchBarButton;
// Lol have mercy, TS
public items: Array<Electron.TouchBarButton
| Electron.TouchBarColorPicker
| Electron.TouchBarGroup
| Electron.TouchBarLabel
| Electron.TouchBarPopover
| Electron.TouchBarScrubber
| Electron.TouchBarSegmentedControl
| Electron.TouchBarSlider
| Electron.TouchBarSpacer>
| undefined;
constructor(public readonly appState: AppState) {
this.selectVersion = this.selectVersion.bind(this);
this.updateStartStopBtn = this.updateStartStopBtn.bind(this);
this.updateVersionSelectorItems = this.updateVersionSelectorItems.bind(this);
this.updateVersionButton = this.updateVersionButton.bind(this);
this.startStopBtn = new TouchBarButton(this.getStartStopButtonOptions());
this.consoleBtn = new TouchBarButton(this.getConsoleButtonOptions());
this.versionSelectorBtn = new TouchBarButton(this.getVersionButtonOptions());
this.versionSelectorSelectBtn = new TouchBarButton(this.getVersionSelectorSelectButtonOptions());
this.versionSelector = new TouchBarScrubber(this.getVersionSelectorOptions());
this.setupTouchBar();
}
/**
* This method is called right after we instantiate the manager. It sets up
* autoruns that respond to state changes (like new versions) and also
* greets the user with a kind little message.
*/
public setupTouchBar() {
// First, the welcome
this.setWelcomeItems();
// Then, go with the intro
setTimeout(() => {
this.setDefaultItems();
}, 4000);
autorun(this.updateStartStopBtn);
autorun(this.updateVersionSelectorItems);
autorun(this.updateVersionButton);
}
/**
* Whenever run/stop status changes, we'll update it (thanks, mobx)
*/
public updateStartStopBtn() {
const { label } = this.getStartStopButtonOptions();
this.startStopBtn.label = label!;
}
/**
* Whenever the electron versions change, we'll update
* the items in the scrubber (thanks, mobx)
*/
public updateVersionSelectorItems() {
this.versionSelector.items = this.getVersions();
}
/**
* Whenever the Electron version changes, update the version button
*/
public updateVersionButton() {
const { label } = this.getVersionButtonOptions();
this.versionSelectorBtn.label = label!;
}
public getVersions() {
return sortedElectronMap(this.appState.versions || {}, (key, version) => ({
label: `${getItemIcon(version)} ${key}`,
}));
}
/**
* Safely set the touch bar on the current window
*
* @param {Partial<Electron.TouchBarConstructorOptions>} options
*/
public setTouchBar(options: Partial<Electron.TouchBarConstructorOptions>) {
try {
this.items = options.items;
this.touchBar = new TouchBar(options as Electron.TouchBarConstructorOptions);
this.browserWindow = this.browserWindow || remote.getCurrentWindow();
this.browserWindow.setTouchBar(this.touchBar);
} catch (error) {
console.warn(`Could not set touch bar`, { error });
}
}
/**
* A bar that just contains a little welcome message
*/
public setWelcomeItems() {
this.setTouchBar({
items: [
new TouchBarSpacer({ size: 'flexible' }),
new TouchBarLabel({ label: getNiceGreeting() }),
new TouchBarSpacer({ size: 'flexible' })
]
});
}
/**
* A bar with the default items
*/
public setDefaultItems() {
this.setTouchBar({
items: [
this.startStopBtn,
this.consoleBtn,
new TouchBarSpacer({ size: 'flexible' }),
this.versionSelectorBtn,
new TouchBarSpacer({ size: 'flexible' })
]
});
}
/**
* A bar that just contains the version scrubber
*/
public setVersionSelectorItems() {
this.setTouchBar({
items: [
this.versionSelector,
this.versionSelectorSelectBtn,
],
escapeItem: new TouchBarButton(
this.getVersionSelectorEscButtonOptions()
)
});
}
/**
* The version selector gets a custom "escape" button that'll
* take the user back to the "default" touch bar interface
*/
public getVersionSelectorEscButtonOptions() {
return {
label: `Back`,
click: () => {
this.setDefaultItems();
}
};
}
/**
* The version selector also gets a "select" button that'll
* commit the selected version
*/
public getVersionSelectorSelectButtonOptions() {
return {
label: `🎯 Select`,
click: () => {
if (this.selectedVersion) {
this.appState.setVersion(this.selectedVersion);
}
this.setDefaultItems();
}
};
}
/**
* The start button is wired up to launching the current
* fiddle.
*
* @returns {TouchBarButtonConstructorOptions}
*/
public getStartStopButtonOptions(): TouchBarButtonConstructorOptions {
const label = this.appState.isRunning
? '🛑 Stop'
: '🚀 Run';
const click = () => this.appState.isRunning
? window.ElectronFiddle.app.runner.stop()
: window.ElectronFiddle.app.runner.run();
return { label, click };
}
/**
* The console button is wired up to toggle the console
*
* @returns {TouchBarButtonConstructorOptions}
*/
public getConsoleButtonOptions(): TouchBarButtonConstructorOptions {
const label = '🖥 Console';
const click = this.appState.toggleConsole;
return { label, click };
}
/**
* The version button opens the scrubber, allowing selection
* of a version
*
* @returns {TouchBarButtonConstructorOptions}
*/
public getVersionButtonOptions(): TouchBarButtonConstructorOptions {
const label = `🔖 Electron ${this.appState.version}`;
const click = () => this.setVersionSelectorItems();
return { label, click };
}
/**
* Options for the version selection scrubber
*
* @returns {TouchBarScrubberConstructorOptions}
*/
public getVersionSelectorOptions(
): TouchBarScrubberConstructorOptions {
return {
select: this.selectVersion,
highlight: this.selectVersion,
items: [],
selectedStyle: 'background',
overlayStyle: 'outline',
mode: 'free',
continuous: true,
showArrowButtons: false
};
}
/**
* Called once the version selection scrubber is closed (
* with confirmation)
*
* @param {number} index
*/
public selectVersion(index: number) {
const versions = sortedElectronMap(this.appState.versions || {}, (k) => k);
this.selectedVersion = versions[index];
}
}
+5 -5
View File
@@ -15,11 +15,11 @@ export async function forgeTransform(files: Files): Promise<Files> {
// devDependencies
parsed.devDependencies = parsed.devDependencies || {};
parsed.devDependencies['@electron-forge/cli'] = '6.0.0-beta.22';
parsed.devDependencies['@electron-forge/maker-deb'] = '6.0.0-beta.22';
parsed.devDependencies['@electron-forge/maker-rpm'] = '6.0.0-beta.22';
parsed.devDependencies['@electron-forge/maker-squirrel'] = '6.0.0-beta.22';
parsed.devDependencies['@electron-forge/maker-zip'] = '6.0.0-beta.22';
parsed.devDependencies['@electron-forge/cli'] = '6.0.0-beta.34';
parsed.devDependencies['@electron-forge/maker-deb'] = '6.0.0-beta.34';
parsed.devDependencies['@electron-forge/maker-rpm'] = '6.0.0-beta.34';
parsed.devDependencies['@electron-forge/maker-squirrel'] = '6.0.0-beta.34';
parsed.devDependencies['@electron-forge/maker-zip'] = '6.0.0-beta.34';
// Scripts
parsed.scripts = parsed.scripts || {};
+13
View File
@@ -0,0 +1,13 @@
/**
* Get all the documentation urls for a given module name.
*
* @export
* @param {string} moduleName
*/
export function getDocsUrlForModule(moduleName: string) {
return {
full: `https://electronjs.org/docs/api/${moduleName}`,
repo: `https://github.com/electron/electron/blob/master/docs/api/${moduleName}.md`,
short: `electronjs.org/docs/api/${moduleName}`
};
}
+26
View File
@@ -0,0 +1,26 @@
import { editor } from 'monaco-editor';
import { EditorId } from '../interfaces';
import { getEditorModel } from './editor-model';
import { getEditorValue } from './editor-value';
import { getEditorViewState } from './editor-viewstate';
export interface EditorBackup {
value: string;
model: editor.ITextModel | null;
viewState: editor.ICodeEditorViewState | null;
}
/**
* Returns a backup for a given editor
*
* @param {EditorId} id
* @returns {EditorBackup}
*/
export function getEditorBackup(id: EditorId): EditorBackup {
return {
value: getEditorValue(id),
model: getEditorModel(id),
viewState: getEditorViewState(id)
};
}
+23
View File
@@ -0,0 +1,23 @@
import { editor } from 'monaco-editor';
import { EditorId } from '../interfaces';
/**
* Return the model for a given editor
*
* @param {EditorId} id
* @returns {editor.ITextModel | null}
*/
export function getEditorModel(id: EditorId): editor.ITextModel | null {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
}
if (fiddle.editors[id] && fiddle.editors[id]!.getModel) {
return fiddle.editors[id]!.getModel();
}
return null;
}
+21
View File
@@ -0,0 +1,21 @@
import { EditorId } from '../interfaces';
/**
* Return the value for a given editor
*
* @param {EditorId} id
* @returns {string}
*/
export function getEditorValue(id: EditorId): string {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
}
if (fiddle.editors[id] && fiddle.editors[id]!.getValue) {
return fiddle.editors[id]!.getValue();
}
return '';
}
+24
View File
@@ -0,0 +1,24 @@
import { editor } from 'monaco-editor';
import { EditorId } from '../interfaces';
/**
* Returns the view state for a given editor.
*
* @export
* @param {EditorId} id
* @returns {(editor.ICodeEditorViewState | null)}
*/
export function getEditorViewState(id: EditorId): editor.ICodeEditorViewState | null {
const { ElectronFiddle: fiddle } = window;
if (!fiddle) {
throw new Error('Fiddle not ready');
}
if (fiddle.editors[id] && fiddle.editors[id]!.saveViewState) {
return fiddle.editors[id]!.saveViewState();
}
return null;
}
+57
View File
@@ -0,0 +1,57 @@
import { MosaicDirection, MosaicNode } from 'react-mosaic-component';
import { MosaicId } from '../interfaces';
/**
* Create a mosaic arrangement given an array of editor ids.
*
* @export
* @param {Array<MosaicId>} input
* @returns {MosaicNode<MosaicId>}
*/
export function createMosaicArrangement(
input: Array<MosaicId>, direction: MosaicDirection = 'row'
): MosaicNode<MosaicId> {
if (input.length === 1) {
return input[0];
}
// This cuts out the first half of input. Input becomes the second half.
const secondHalf = [ ...input ];
const firstHalf = secondHalf.splice(0, Math.floor(secondHalf.length / 2));
return {
direction,
first: createMosaicArrangement(firstHalf, 'column'),
second: createMosaicArrangement(secondHalf, 'column')
};
}
/**
* Returns an array of visible editors given a mosaic arrangement.
*
* @export
* @param {MosaicNode<MosaicId> | null} input
* @returns {Array<MosaicId>}
*/
export function getVisibleMosaics(input: MosaicNode<MosaicId> | null): Array<MosaicId> {
// Handle the unlikely null case
if (!input) return [];
// Handle the case where only one editor is visible
if (typeof input === 'string') {
return [ input ];
}
// Handle the other cases (2 - 4)
const result = [];
for (const node of [ input.first, input.second ]) {
if (typeof node === 'string') {
result.push(node);
} else {
result.push(...getVisibleMosaics(node));
}
}
return result;
}
+25
View File
@@ -0,0 +1,25 @@
import { getRandomNumber } from './random';
/**
* Get a random nice greeting
*
* @export
* @returns
*/
export function getNiceGreeting() {
const greetings = [
'👋 Go build a great app',
'💖 You got this',
`💖 We think you're great`,
'🤘 Your app will rock, we know it',
'🙇‍♀️ Thanks for trying out Electron & Fiddle',
'🐕 Go pet a dog today',
'🐈 Go pet a cat today',
'💧 Stay hydrated',
];
const min = 0;
const max = greetings.length;
return greetings[getRandomNumber(min, max)];
}
+18 -3
View File
@@ -1,15 +1,30 @@
import * as GitHubType from '@octokit/rest';
import { AppState } from '../renderer/state';
let _Octokit: typeof GitHubType;
let _octo: GitHubType;
/**
* Returns a loaded Octokit.
* Returns a loaded Octokit. If state is passed and authentication
* is available, we'll token-authenticate.
*
* @export
* @returns {Promise<typeof GitHubType>}
*/
export async function getOctokit(): Promise<GitHubType> {
export async function getOctokit(
appState?: AppState
): Promise<GitHubType> {
_Octokit = _Octokit || (await import('@octokit/rest') as any).default;
return _octo = _octo || new _Octokit();
_octo = _octo || new _Octokit();
// You can load Gists without being authenticated,
// but we get better rate limits when authenticated
if (appState && appState.gitHubToken) {
_octo.authenticate({
type: 'token',
token: appState.gitHubToken!
});
}
return _octo;
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Dumb helper: Get a random number between two numbers
*
* @param {number} [min=0]
* @param {number} [max=1]
* @returns {number}
*/
export function getRandomNumber(
min: number = 0, max: number = 1
): number {
return Math.floor(Math.random() * max) + min;
}
+26
View File
@@ -0,0 +1,26 @@
import { AboutPanelOptionsOptions, app } from 'electron';
/**
* Sets Fiddle's About panel options on Linux and macOS
*
* @returns
*/
export function setupAboutPanel(): void {
const options: AboutPanelOptionsOptions = {
applicationName: 'Electron Fiddle',
applicationVersion: app.getVersion(),
version: process.versions.electron,
copyright: '© Electron Authors',
};
switch (process.platform) {
case 'linux':
options.website = 'https://electronjs.org/fiddle';
case 'darwin':
options.credits = 'https://github.com/electron/fiddle/graphs/contributors';
default:
// fallthrough
}
app.setAboutPanelOptions(options);
}
+35
View File
@@ -0,0 +1,35 @@
import { ALL_EDITORS, EditorId, MosaicId, PanelId } from '../interfaces';
import { EditorBackup } from './editor-backup';
/**
* Is the given string of type `EditorId`?
*
* @export
* @param {(EditorId | PanelId | MosaicId)} input
* @returns {input is EditorId}
*/
export function isEditorId(input: EditorId | PanelId | MosaicId): input is EditorId {
return (ALL_EDITORS as any).includes(input);
}
/**
* Is the given string of type `isPanelId`?
*
* @export
* @param {(EditorId | PanelId | MosaicId)} input
* @returns {input is MosaicId}
*/
export function isPanelId(input: EditorId | PanelId | MosaicId): input is PanelId {
return !(ALL_EDITORS as any).includes(input);
}
/**
* Is the given input of type `EditorBackup`?
*
* @export
* @param {(EditorBackup | true)} input
* @returns {input is EditorBackup}
*/
export function isEditorBackup(input?: EditorBackup | true | null): input is EditorBackup {
return !!(input && input !== true);
}
+1 -1
View File
@@ -1 +1 @@
[{"version":"0.1.0"},{"version":"0.1.1"},{"version":"0.1.2"},{"version":"0.2.0"},{"version":"0.2.1"},{"version":"0.3.0"},{"version":"0.4.0"},{"version":"0.4.1"},{"version":"1.3.1"},{"version":"1.3.2"},{"version":"1.3.3"},{"version":"1.3.4"},{"version":"1.3.5"},{"version":"1.3.6"},{"version":"1.4.0"},{"version":"1.4.1"},{"version":"1.3.7"},{"version":"1.4.2"},{"version":"1.4.3"},{"version":"1.4.4"},{"version":"1.3.8"},{"version":"1.4.5"},{"version":"1.4.6"},{"version":"1.4.7"},{"version":"1.3.9"},{"version":"1.3.10"},{"version":"1.4.8"},{"version":"1.3.12"},{"version":"1.4.10"},{"version":"1.3.13"},{"version":"1.4.11"},{"version":"1.4.12"},{"version":"1.4.13"},{"version":"1.4.14"},{"version":"1.4.15"},{"version":"1.5.0"},{"version":"1.5.1"},{"version":"1.6.0"},{"version":"1.6.1"},{"version":"1.6.2"},{"version":"1.6.3"},{"version":"1.3.14"},{"version":"1.6.4"},{"version":"1.6.5"},{"version":"1.4.16"},{"version":"1.6.6"},{"version":"1.6.7"},{"version":"1.3.15"},{"version":"1.6.8"},{"version":"1.6.9"},{"version":"1.7.0"},{"version":"1.6.10"},{"version":"1.7.1"},{"version":"1.6.11"},{"version":"1.7.2"},{"version":"1.7.3"},{"version":"1.7.4"},{"version":"1.7.5"},{"version":"1.6.12"},{"version":"1.7.6"},{"version":"1.7.7"},{"version":"1.6.13"},{"version":"1.7.8"},{"version":"1.6.14"},{"version":"1.8.1"},{"version":"1.7.9"},{"version":"1.6.15"},{"version":"1.8.2-beta.1"},{"version":"1.8.2-beta.2"},{"version":"1.8.2-beta.3"},{"version":"1.7.10"},{"version":"1.7.11"},{"version":"1.6.16"},{"version":"1.8.2-beta.4"},{"version":"1.8.2-beta.5"},{"version":"1.7.12"},{"version":"1.6.17"},{"version":"1.8.2"},{"version":"2.0.0-beta.1"},{"version":"2.0.0-beta.2"},{"version":"1.8.3"},{"version":"2.0.0-beta.3"},{"version":"1.7.13"},{"version":"2.0.0-beta.4"},{"version":"1.8.4"},{"version":"2.0.0-beta.5"},{"version":"2.0.0-beta.6"},{"version":"2.0.0-beta.7"},{"version":"2.0.0-beta.8"},{"version":"1.8.5"},{"version":"1.7.14"},{"version":"1.8.6"},{"version":"2.0.0"},{"version":"1.6.18"},{"version":"1.7.15"},{"version":"1.8.7"},{"version":"2.0.1"},{"version":"2.0.2"},{"version":"3.0.0-beta.1"},{"version":"2.0.3"},{"version":"2.0.4"},{"version":"3.0.0-beta.2"},{"version":"2.0.5"},{"version":"3.0.0-beta.3"},{"version":"2.0.6"},{"version":"3.0.0-beta.4"},{"version":"2.0.7"},{"version":"2.1.0-unsupported.20180809"},{"version":"3.0.0-beta.5"},{"version":"4.0.0-nightly.20180816"},{"version":"4.0.0-nightly.20180817"},{"version":"3.0.0-nightly.20180818"},{"version":"4.0.0-nightly.20180819"},{"version":"2.0.8-nightly.20180819"},{"version":"3.0.0-beta.6"},{"version":"2.0.8-nightly.20180820"},{"version":"3.0.0-nightly.20180821"},{"version":"4.0.0-nightly.20180821"},{"version":"3.0.0-beta.7"},{"version":"2.0.8"},{"version":"1.8.8"},{"version":"1.7.16"},{"version":"3.0.0-beta.8"},{"version":"3.0.0-nightly.20180904"},{"version":"3.0.0-beta.9"},{"version":"3.0.0-beta.10"},{"version":"2.0.9"},{"version":"3.0.0-beta.11"},{"version":"3.0.0-beta.12"},{"version":"3.0.0-beta.13"},{"version":"3.0.0"},{"version":"2.0.10"},{"version":"3.0.1"},{"version":"3.0.2"},{"version":"4.0.0-nightly.20180929"},{"version":"2.0.11"},{"version":"4.0.0-nightly.20181006"},{"version":"3.0.3"},{"version":"4.0.0-nightly.20181010"},{"version":"3.0.4"},{"version":"4.0.0-beta.1"},{"version":"4.0.0-beta.2"},{"version":"4.0.0-beta.3"},{"version":"3.0.5"},{"version":"2.0.12"},{"version":"4.0.0-beta.4"},{"version":"4.0.0-beta.5"},{"version":"3.0.6"},{"version":"3.0.7"},{"version":"2.0.13"},{"version":"4.0.0-beta.6"},{"version":"3.0.8"},{"version":"4.0.0-beta.7"},{"version":"3.0.9"},{"version":"2.0.14"},{"version":"3.0.10"},{"version":"4.0.0-beta.8"},{"version":"3.1.0-beta.1"},{"version":"3.1.0-beta.2"},{"version":"3.0.11"},{"version":"4.0.0-beta.9"},{"version":"2.0.15"},{"version":"3.0.12"},{"version":"3.1.0-beta.3"},{"version":"3.0.13"},{"version":"3.1.0-beta.4"},{"version":"4.0.0-beta.10"}]
[{"version":"0.1.0"},{"version":"0.1.1"},{"version":"0.1.2"},{"version":"0.2.0"},{"version":"0.2.1"},{"version":"0.3.0"},{"version":"0.4.0"},{"version":"0.4.1"},{"version":"1.3.1"},{"version":"1.3.2"},{"version":"1.3.3"},{"version":"1.3.4"},{"version":"1.3.5"},{"version":"1.3.6"},{"version":"1.4.0"},{"version":"1.4.1"},{"version":"1.3.7"},{"version":"1.4.2"},{"version":"1.4.3"},{"version":"1.4.4"},{"version":"1.3.8"},{"version":"1.4.5"},{"version":"1.4.6"},{"version":"1.4.7"},{"version":"1.3.9"},{"version":"1.3.10"},{"version":"1.4.8"},{"version":"1.3.12"},{"version":"1.4.10"},{"version":"1.3.13"},{"version":"1.4.11"},{"version":"1.4.12"},{"version":"1.4.13"},{"version":"1.4.14"},{"version":"1.4.15"},{"version":"1.5.0"},{"version":"1.5.1"},{"version":"1.6.0"},{"version":"1.6.1"},{"version":"1.6.2"},{"version":"1.6.3"},{"version":"1.3.14"},{"version":"1.6.4"},{"version":"1.6.5"},{"version":"1.4.16"},{"version":"1.6.6"},{"version":"1.6.7"},{"version":"1.3.15"},{"version":"1.6.8"},{"version":"1.6.9"},{"version":"1.7.0"},{"version":"1.6.10"},{"version":"1.7.1"},{"version":"1.6.11"},{"version":"1.7.2"},{"version":"1.7.3"},{"version":"1.7.4"},{"version":"1.7.5"},{"version":"1.6.12"},{"version":"1.7.6"},{"version":"1.7.7"},{"version":"1.6.13"},{"version":"1.7.8"},{"version":"1.6.14"},{"version":"1.8.1"},{"version":"1.7.9"},{"version":"1.6.15"},{"version":"1.8.2-beta.1"},{"version":"1.8.2-beta.2"},{"version":"1.8.2-beta.3"},{"version":"1.7.10"},{"version":"1.7.11"},{"version":"1.6.16"},{"version":"1.8.2-beta.4"},{"version":"1.8.2-beta.5"},{"version":"1.7.12"},{"version":"1.6.17"},{"version":"1.8.2"},{"version":"2.0.0-beta.1"},{"version":"2.0.0-beta.2"},{"version":"1.8.3"},{"version":"2.0.0-beta.3"},{"version":"1.7.13"},{"version":"2.0.0-beta.4"},{"version":"1.8.4"},{"version":"2.0.0-beta.5"},{"version":"2.0.0-beta.6"},{"version":"2.0.0-beta.7"},{"version":"2.0.0-beta.8"},{"version":"1.8.5"},{"version":"1.7.14"},{"version":"1.8.6"},{"version":"2.0.0"},{"version":"1.6.18"},{"version":"1.7.15"},{"version":"1.8.7"},{"version":"2.0.1"},{"version":"2.0.2"},{"version":"3.0.0-beta.1"},{"version":"2.0.3"},{"version":"2.0.4"},{"version":"3.0.0-beta.2"},{"version":"2.0.5"},{"version":"3.0.0-beta.3"},{"version":"2.0.6"},{"version":"3.0.0-beta.4"},{"version":"2.0.7"},{"version":"2.1.0-unsupported.20180809"},{"version":"3.0.0-beta.5"},{"version":"4.0.0-nightly.20180816"},{"version":"4.0.0-nightly.20180817"},{"version":"3.0.0-nightly.20180818"},{"version":"4.0.0-nightly.20180819"},{"version":"2.0.8-nightly.20180819"},{"version":"3.0.0-beta.6"},{"version":"2.0.8-nightly.20180820"},{"version":"3.0.0-nightly.20180821"},{"version":"4.0.0-nightly.20180821"},{"version":"3.0.0-beta.7"},{"version":"2.0.8"},{"version":"1.8.8"},{"version":"1.7.16"},{"version":"3.0.0-beta.8"},{"version":"3.0.0-nightly.20180904"},{"version":"3.0.0-beta.9"},{"version":"3.0.0-beta.10"},{"version":"2.0.9"},{"version":"3.0.0-beta.11"},{"version":"3.0.0-beta.12"},{"version":"3.0.0-beta.13"},{"version":"3.0.0"},{"version":"2.0.10"},{"version":"3.0.1"},{"version":"3.0.2"},{"version":"4.0.0-nightly.20180929"},{"version":"2.0.11"},{"version":"4.0.0-nightly.20181006"},{"version":"3.0.3"},{"version":"4.0.0-nightly.20181010"},{"version":"3.0.4"},{"version":"4.0.0-beta.1"},{"version":"4.0.0-beta.2"},{"version":"4.0.0-beta.3"},{"version":"3.0.5"},{"version":"2.0.12"},{"version":"4.0.0-beta.4"},{"version":"4.0.0-beta.5"},{"version":"3.0.6"},{"version":"3.0.7"},{"version":"2.0.13"},{"version":"4.0.0-beta.6"},{"version":"3.0.8"},{"version":"4.0.0-beta.7"},{"version":"3.0.9"},{"version":"2.0.14"},{"version":"3.0.10"},{"version":"4.0.0-beta.8"},{"version":"3.1.0-beta.1"},{"version":"3.1.0-beta.2"},{"version":"3.0.11"},{"version":"4.0.0-beta.9"},{"version":"2.0.15"},{"version":"3.0.12"},{"version":"3.1.0-beta.3"},{"version":"3.0.13"},{"version":"3.1.0-beta.4"},{"version":"4.0.0-beta.10"},{"version":"2.0.16"},{"version":"4.0.0-beta.11"},{"version":"4.0.0"},{"version":"3.1.0-beta.5"},{"version":"4.0.1"},{"version":"3.0.14"},{"version":"3.1.0"},{"version":"3.1.1"},{"version":"4.0.2"},{"version":"5.0.0-beta.1"},{"version":"3.1.2"},{"version":"4.0.3"},{"version":"2.0.17"},{"version":"3.0.15"},{"version":"3.1.3"},{"version":"4.0.4"},{"version":"5.0.0-beta.2"},{"version":"4.0.5"},{"version":"5.0.0-beta.3"},{"version":"3.1.4"},{"version":"4.0.6"},{"version":"5.0.0-beta.4"},{"version":"3.1.5"},{"version":"4.0.7"},{"version":"5.0.0-beta.5"},{"version":"2.0.18"},{"version":"3.1.6"},{"version":"3.0.16"},{"version":"4.0.8"},{"version":"4.1.0"},{"version":"5.0.0-beta.6"},{"version":"4.1.1"},{"version":"3.1.7"},{"version":"5.0.0-beta.7"}]
@@ -8,7 +8,13 @@ const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
nodeIntegration: true
}
})
mainWindow.loadFile('index.html')
})
@@ -8,7 +8,13 @@ const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
nodeIntegration: true
}
})
mainWindow.loadFile('index.html')
mainWindow.webContents.on('crashed', () => {
@@ -9,7 +9,13 @@ const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
nodeIntegration: true
}
})
mainWindow.loadFile('index.html')
})
@@ -10,7 +10,13 @@ const { app, BrowserWindow, ipcMain } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
nodeIntegration: true
}
})
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
@@ -9,7 +9,13 @@ const { app, BrowserWindow, Notification } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
nodeIntegration: true
}
})
if (Notification.isSupported()) {
const notification = new Notification({
@@ -8,6 +8,12 @@ const { app, BrowserWindow } = require('electron')
let mainWindow = null
app.on('ready', () => {
mainWindow = new BrowserWindow({ height: 600, width: 600 })
mainWindow = new BrowserWindow({
height: 600,
width: 600,
webPreferences: {
nodeIntegration: true
}
})
mainWindow.loadFile('index.html')
})
@@ -17,7 +17,13 @@ app.on('ready', () => {
const primaryDisplay = screen.getPrimaryDisplay()
const { width, height } = primaryDisplay.workAreaSize
mainWindow = new BrowserWindow({ width, height })
mainWindow = new BrowserWindow({
width,
height,
webPreferences: {
nodeIntegration: true
}
})
mainWindow.loadFile('index.html')
})

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