Compare commits
118
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a423bc294 | ||
|
|
98a358b756 | ||
|
|
9b55b2ef26 | ||
|
|
5b91177348 | ||
|
|
f8c43a07f7 | ||
|
|
5eba9cdbe2 | ||
|
|
8ee42c290e | ||
|
|
c2b3954dfa | ||
|
|
e61599e9c5 | ||
|
|
1fed772a27 | ||
|
|
de85d0f324 | ||
|
|
c89671d99d | ||
|
|
155b901ade | ||
|
|
8e098deab8 | ||
|
|
2b5ea7d467 | ||
|
|
91efa5a009 | ||
|
|
4ec5bec719 | ||
|
|
84ac41f147 | ||
|
|
e9a2985291 | ||
|
|
9b28919203 | ||
|
|
8d43836787 | ||
|
|
48d0dadf5d | ||
|
|
2f9ef21251 | ||
|
|
8e4238275c | ||
|
|
32d5ea99aa | ||
|
|
da2e17cd37 | ||
|
|
539f5bce97 | ||
|
|
228552c8a0 | ||
|
|
ed8d76a54a | ||
|
|
2f4d47a0ab | ||
|
|
9b443de137 | ||
|
|
efb5bf0e57 | ||
|
|
c420747bb9 | ||
|
|
33223e7b37 | ||
|
|
86cc0c62a1 | ||
|
|
96bd2e36dd | ||
|
|
9db527773b | ||
|
|
9da1acafd9 | ||
|
|
5a05d34776 | ||
|
|
6b9d2b66e2 | ||
|
|
d4096a6de8 | ||
|
|
3709d122a6 | ||
|
|
0368cda57a | ||
|
|
433cb6acd5 | ||
|
|
56dceaefb0 | ||
|
|
80da23c4e4 | ||
|
|
353968ef8e | ||
|
|
64ece1b364 | ||
|
|
13383f7345 | ||
|
|
cae7222636 | ||
|
|
2aabe80f2b | ||
|
|
d0683f1392 | ||
|
|
389352cefa | ||
|
|
d04b654c3e | ||
|
|
1369c9b35c | ||
|
|
9957090d25 | ||
|
|
8a002fb0a8 | ||
|
|
cae8dde4c8 | ||
|
|
cb176534f9 | ||
|
|
f7fa9a8ffc | ||
|
|
347384a547 | ||
|
|
e272efb3b9 | ||
|
|
5255bcb842 | ||
|
|
d3cd53f3e0 | ||
|
|
6d28c4c81b | ||
|
|
a45354e1af | ||
|
|
28245d36ba | ||
|
|
582ef280b5 | ||
|
|
56b7f2ffb7 | ||
|
|
cb3ba4f0a7 | ||
|
|
e238b10f1e | ||
|
|
bbc22d9ca8 | ||
|
|
7b3c960c1f | ||
|
|
4dbc74fccb | ||
|
|
f71b1bc8d4 | ||
|
|
00c2e1f9a6 | ||
|
|
50f69613dd | ||
|
|
4a23b53b1b | ||
|
|
dcd21935a9 | ||
|
|
ff37d5cfb9 | ||
|
|
bb8053d86d | ||
|
|
5efaa54255 | ||
|
|
b38507230d | ||
|
|
7451083c70 | ||
|
|
be46780674 | ||
|
|
99b5d067df | ||
|
|
f34eb225ea | ||
|
|
18cc36a837 | ||
|
|
da403e3cbd | ||
|
|
b4431cf426 | ||
|
|
e714269b9b | ||
|
|
b9518a63f8 | ||
|
|
0627801bb0 | ||
|
|
3adeece64d | ||
|
|
aed45078c5 | ||
|
|
55bfb0241b | ||
|
|
7e90679706 | ||
|
|
4897bdbf84 | ||
|
|
7a02f8cacd | ||
|
|
a65d594fbc | ||
|
|
f32fce2f80 | ||
|
|
26e054702a | ||
|
|
16f27132cb | ||
|
|
7613176f69 | ||
|
|
470bea4fca | ||
|
|
363febdb73 | ||
|
|
062ca4e205 | ||
|
|
49053342a5 | ||
|
|
fd582c67a8 | ||
|
|
706b18808b | ||
|
|
a6ae8d5376 | ||
|
|
5034f8c853 | ||
|
|
e916247013 | ||
|
|
c052686de4 | ||
|
|
31aacef131 | ||
|
|
932369b705 | ||
|
|
b835c34786 | ||
|
|
c9e1ba815a |
@@ -0,0 +1,31 @@
|
||||
const { Octokit } = require('@octokit/action')
|
||||
|
||||
const octokit = new Octokit()
|
||||
|
||||
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
|
||||
|
||||
async function main () {
|
||||
const prs = await octokit.pulls.list({
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
head: `${owner}:update-releases`
|
||||
})
|
||||
|
||||
if (prs.data.length === 0) {
|
||||
const pr = await octokit.pulls.create({
|
||||
owner,
|
||||
repo,
|
||||
title: 'build: update Electron releases JSON',
|
||||
base: 'master',
|
||||
head: 'update-releases',
|
||||
body: 'Auto-update from GitHub Actions.',
|
||||
maintainer_can_modify: true
|
||||
})
|
||||
console.log('Pull request created:', pr.html_url)
|
||||
} else {
|
||||
console.log('Pull request updated:', prs.data[0].html_url)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Auto-update Releases JSON file
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
jobs:
|
||||
autoupdate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch git branches
|
||||
run: git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.x'
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(npm config get cache)"
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
- run: npm ci
|
||||
- name: Switch to release update branch
|
||||
run: |
|
||||
if git branch --remotes | grep -q origin/update-releases; then
|
||||
git checkout update-releases
|
||||
else
|
||||
git checkout -b update-releases
|
||||
fi
|
||||
- name: Update Releases JSON
|
||||
run: npm run electron-releases
|
||||
- name: Commit Changes to Releases JSON
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "machine github.com login $GITHUB_ACTOR password $GITHUB_TOKEN" > ~/.netrc
|
||||
chmod 600 ~/.netrc
|
||||
git add static/releases.json tests/fixtures/releases-metadata.json
|
||||
if test -n "$(git status -s)"; then
|
||||
git config user.name "$GITHUB_ACTOR"
|
||||
git config user.email "electron-bot@users.noreply.github.com"
|
||||
git diff --cached
|
||||
git commit -m "build: update Electron releases JSON"
|
||||
git push origin update-releases
|
||||
node --unhandled-rejections=strict .github/actions/create_releases_pr.js
|
||||
else
|
||||
echo No update needed
|
||||
fi
|
||||
+3
-1
@@ -27,11 +27,13 @@ compile_commands.json
|
||||
.cache
|
||||
yarn-error.log
|
||||
.DS_Store
|
||||
.webpack
|
||||
|
||||
# Coverage
|
||||
coverage
|
||||
|
||||
# Test report
|
||||
report.json
|
||||
|
||||
# npm package
|
||||
/npm/dist
|
||||
/npm/path.txt
|
||||
|
||||
+4
-5
@@ -3,13 +3,12 @@ node_js: "12"
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
dist: trusty
|
||||
osx_image: xcode8.3
|
||||
sudo: false
|
||||
dist: bionic
|
||||
osx_image: xcode10
|
||||
|
||||
cache:
|
||||
npm: true
|
||||
directories:
|
||||
- node_modules
|
||||
- $HOME/.cache/electron
|
||||
|
||||
addons:
|
||||
@@ -24,7 +23,7 @@ branches:
|
||||
- /^v\d+\.\d+\.\d+/
|
||||
|
||||
install:
|
||||
- npm install
|
||||
- npm ci
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" && "$TRAVIS_SECURE_ENV_VARS" == "true" ]]; then
|
||||
export CERTIFICATE_P12=cert.p12;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# Contributing to Electron Fiddle
|
||||
|
||||
Electron Fiddle is a community-driven project, overseen by the [Electron Ecosystem Working
|
||||
Group](https://github.com/electron/governance/tree/master/wg-ecosystem#readme). As such, we welcome
|
||||
and encourage all sorts of contributions. They include, but are not limited to:
|
||||
|
||||
- Constructive feedback
|
||||
- Bug reports / technical issues
|
||||
- Documentation changes
|
||||
- Feature requests
|
||||
- [Pull requests](#filing-pull-requests)
|
||||
|
||||
We strongly suggest that before filing an issue, you search through the existing issues to see
|
||||
if it has already been filed by someone else.
|
||||
|
||||
This project is a part of the Electron ecosystem. As such, all contributions to this project follow
|
||||
[Electron's code of conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md)
|
||||
where appropriate.
|
||||
|
||||
## Contribution Suggestions
|
||||
|
||||
We use the label [`good first issue`](https://github.com/electron/fiddle/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) in the issue tracker to denote fairly-well-scoped-out bugs or feature requests that the community can pick up and work on. If any of those labeled issues do not have enough information, please feel free to ask constructive questions. (This applies to any open issue.)
|
||||
|
||||
## Filing Pull Requests
|
||||
|
||||
Here are some things to keep in mind as you file pull requests to fix bugs, add new features, etc.:
|
||||
|
||||
* If you're unfamiliar with forking, branching, and pull requests, please see [GitHub's extensive
|
||||
documentation](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests).
|
||||
* Travis CI and AppVeyor are used to make sure that any new or changed code meets the project's
|
||||
style guidelines, and that the project's testsuite passes for each new commit.
|
||||
* Unless it's impractical, please write tests for your changes. This will help us so that we can
|
||||
spot regressions much easier.
|
||||
* When creating commit messages and pull request titles, please adhere to the [conventional
|
||||
commits](https://www.conventionalcommits.org/en/v1.0.0/) standard.
|
||||
* Please **do not** bump the version number in your pull requests, the maintainers will do that.
|
||||
Feel free to indicate whether the changes require a major, minor, or patch version bump, as
|
||||
prescribed by the [semantic versioning specification](http://semver.org/).
|
||||
|
||||
### Running Fiddle From Source
|
||||
|
||||
```sh
|
||||
git clone https://github.com/electron/fiddle
|
||||
cd fiddle
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```sh
|
||||
npm test
|
||||
```
|
||||
|
||||
## Release Process
|
||||
|
||||
<!-- TODO @felix add your release process here 😁 -->
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2013-2018 GitHub Inc.
|
||||
Copyright (c) 2013-2020 GitHub Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
+47
-19
@@ -7,14 +7,14 @@ const packageJson = require('./package.json')
|
||||
const { version } = packageJson
|
||||
const iconDir = path.resolve(__dirname, 'assets', 'icons')
|
||||
|
||||
module.exports = {
|
||||
const config = {
|
||||
hooks: {
|
||||
//generateAssets: require('./tools/generateAssets')
|
||||
generateAssets: require('./tools/generateAssets')
|
||||
},
|
||||
packagerConfig: {
|
||||
name: 'Electron Fiddle',
|
||||
executableName: 'electron-fiddle',
|
||||
asar: false,
|
||||
asar: true,
|
||||
icon: path.resolve(__dirname, 'assets', 'icons', 'fiddle'),
|
||||
// TODO: FIXME?
|
||||
// ignore: [
|
||||
@@ -32,7 +32,12 @@ module.exports = {
|
||||
OriginalFilename: 'Electron Fiddle',
|
||||
},
|
||||
osxSign: {
|
||||
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)'
|
||||
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)',
|
||||
'hardened-runtime': true,
|
||||
'gatekeeper-assess': false,
|
||||
'entitlements': 'static/entitlements.plist',
|
||||
'entitlements-inherit': 'static/entitlements.plist',
|
||||
'signature-flags': 'library'
|
||||
}
|
||||
},
|
||||
makers: [
|
||||
@@ -93,20 +98,43 @@ module.exports = {
|
||||
prerelease: false
|
||||
}
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
['@electron-forge/plugin-webpack', {
|
||||
mainConfig: './webpack.main.config.js',
|
||||
renderer: {
|
||||
config: './webpack.renderer.config.js',
|
||||
entryPoints: [
|
||||
{
|
||||
html: './static/index.html',
|
||||
js: './src/renderer/app.tsx',
|
||||
name: 'main_window'
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
|
||||
function notarizeMaybe() {
|
||||
if (process.platform !== 'darwin') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.npm_lifecycle_event !== 'publish') {
|
||||
console.log(`Not in "publish" mode, skipping notarization`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.env.CI) {
|
||||
console.log(`Not in CI, skipping notarization`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.env.TRAVIS_TAG && !process.env.NOTARIZE_WITHOUT_TAG) {
|
||||
console.log(`Not a tag, not notarizing`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.env.APPLE_ID || !process.env.APPLE_ID_PASSWORD) {
|
||||
console.warn('Should be notarizing, but environment variables APPLE_ID or APPLE_ID_PASSWORD are missing!');
|
||||
return;
|
||||
}
|
||||
|
||||
config.packagerConfig.osxNotarize = {
|
||||
appBundleId: 'com.electron.fiddle',
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_ID_PASSWORD,
|
||||
ascProvider: 'LT94ZKYDCJ'
|
||||
}
|
||||
}
|
||||
|
||||
notarizeMaybe()
|
||||
|
||||
// Finally, export it
|
||||
module.exports = config
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
{
|
||||
"transform": {
|
||||
".(js|jsx|ts|tsx)": "ts-jest"
|
||||
"\\.(js|jsx|ts|tsx)$": "ts-jest"
|
||||
},
|
||||
"testURL": "http://localhost",
|
||||
"testRegex": "(-spec)\\.(ts|tsx)$",
|
||||
"bail": true,
|
||||
"resetMocks": true,
|
||||
"bail": true,
|
||||
"resetModules": true,
|
||||
"snapshotSerializers": [
|
||||
"enzyme-to-json/serializer"
|
||||
],
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/tests/setup.js"
|
||||
],
|
||||
|
||||
Generated
+10062
-5183
File diff suppressed because it is too large
Load Diff
+64
-72
@@ -1,26 +1,30 @@
|
||||
{
|
||||
"name": "electron-fiddle",
|
||||
"productName": "Electron Fiddle",
|
||||
"version": "0.10.0",
|
||||
"version": "0.13.1",
|
||||
"description": "The easiest way to get started with Electron",
|
||||
"repository": "https://github.com/electron/fiddle",
|
||||
"main": ".webpack/main",
|
||||
"main": "./dist/src/main/main",
|
||||
"scripts": {
|
||||
"contributors": "node ./tools/contributors.js",
|
||||
"lint:style": "stylelint ./src/less/*.less --fix",
|
||||
"less": "node ./tools/lessc.js",
|
||||
"lint:style": "stylelint \"./src/less/*.less\" --fix",
|
||||
"lint:ts": "tslint -c tslint.json -p tsconfig.json -e \"node_modules/**/*.ts\" --fix",
|
||||
"lint:tests": "tslint ./tests/**/*.ts{,x} -c tslint.json --fix",
|
||||
"lint:templates": "standard ./static/show-me/**/*.js",
|
||||
"lint": "npm-run-all lint:*",
|
||||
"make": "npm run contributors && electron-forge make",
|
||||
"package": "npm run contributors && electron-forge package",
|
||||
"publish": "npm run contributors && electron-forge publish",
|
||||
"start": "electron-forge start",
|
||||
"test": "jest --config=jest.json --coverage",
|
||||
"lint:tests": "tslint \"./tests/**/*.ts{,x}\" -c tslint.json --fix",
|
||||
"lint:templates": "standard \"./static/show-me/**/*.js\"",
|
||||
"lint": "npm-run-all \"lint:*\"",
|
||||
"make": "electron-forge make",
|
||||
"package": "electron-forge package",
|
||||
"parcel:build": "node ./tools/parcel-build.js",
|
||||
"parcel:watch": "node ./tools/parcel-watch.js",
|
||||
"publish": "electron-forge publish",
|
||||
"start": "rimraf ./dist && electron-forge start",
|
||||
"test": "jest --config=jest.json",
|
||||
"test:ci": "jest --config=jest.json --coverage --runInBand",
|
||||
"test:coverage": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
|
||||
"tsc": "node ./tools/tsc.js",
|
||||
"electron-releases": "node ./tools/fetch-releases.js"
|
||||
"test:report": "jest --config=jest.json --json --bail=false --outputFile=report.json | true",
|
||||
"tsc": "tsc --noEmit -p .",
|
||||
"electron-releases": "node --unhandled-rejections=strict ./tools/fetch-releases.js"
|
||||
},
|
||||
"keywords": [
|
||||
"Electron",
|
||||
@@ -33,85 +37,73 @@
|
||||
"forge": "./forge.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.19.1",
|
||||
"@blueprintjs/select": "^3.11.1",
|
||||
"@octokit/rest": "^16.34.0",
|
||||
"builtin-modules": "^3.1.0",
|
||||
"@blueprintjs/core": "^3.24.0",
|
||||
"@blueprintjs/select": "^3.12.0",
|
||||
"@electron/get": "^1.9.0",
|
||||
"@octokit/rest": "^16.43.1",
|
||||
"@sentry/electron": "^1.2.1",
|
||||
"classnames": "^2.2.6",
|
||||
"electron-default-menu": "^1.0.1",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-download": "^4.1.1",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"extract-zip": "^1.6.7",
|
||||
"fix-path": "^2.1.0",
|
||||
"fix-path": "^3.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"mobx": "^5.14.2",
|
||||
"mobx-react": "^6.1.4",
|
||||
"monaco-editor": "^0.18.1",
|
||||
"mobx": "^5.15.4",
|
||||
"mobx-react": "^6.1.8",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"monaco-loader": "1.0.0",
|
||||
"namor": "^1.1.3",
|
||||
"react": "^16.11.0",
|
||||
"react-dom": "^16.11.0",
|
||||
"namor": "^2.0.2",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-mosaic-component": "^3.2.0",
|
||||
"semver": "^6.3.0",
|
||||
"semver": "^7.1.3",
|
||||
"tmp": "0.1.0",
|
||||
"tslib": "^1.10.0",
|
||||
"tslib": "^1.11.1",
|
||||
"update-electron-app": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.6.4",
|
||||
"@electron-forge/cli": "^6.0.0-beta.45",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.45",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.45",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.45",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.45",
|
||||
"@electron-forge/plugin-webpack": "^6.0.0-beta.45",
|
||||
"@electron-forge/publisher-github": "^6.0.0-beta.45",
|
||||
"@marshallofsound/webpack-asset-relocator-loader": "^0.5.0",
|
||||
"@types/builtin-modules": "^3.1.1",
|
||||
"@babel/core": "^7.8.6",
|
||||
"@electron-forge/cli": "^6.0.0-beta.50",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.50",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.50",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.50",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.50",
|
||||
"@electron-forge/publisher-github": "^6.0.0-beta.50",
|
||||
"@octokit/action": "^2.0.0",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/enzyme": "^3.10.3",
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@types/jest": "^24.0.19",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
"@types/jest": "^25.1.3",
|
||||
"@types/log-symbols": "^3.0.0",
|
||||
"@types/node": "^12.11.7",
|
||||
"@types/react": "^16.9.11",
|
||||
"@types/react-dom": "^16.9.3",
|
||||
"@types/semver": "^6.0.2",
|
||||
"@types/node": "^13.7.6",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/semver": "^7.1.0",
|
||||
"@types/tmp": "0.1.0",
|
||||
"chokidar": "^3.2.2",
|
||||
"coveralls": "^3.0.7",
|
||||
"css-loader": "^3.2.0",
|
||||
"electron": "7.0.0",
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.15.1",
|
||||
"enzyme-to-json": "^3.4.3",
|
||||
"file-loader": "^4.2.0",
|
||||
"jest": "^24.9.0",
|
||||
"jest-fetch-mock": "^2.1.2",
|
||||
"less": "^3.10.3",
|
||||
"less-loader": "^5.0.0",
|
||||
"loader-utils": "^1.2.3",
|
||||
"chokidar": "^3.3.1",
|
||||
"coveralls": "^3.0.9",
|
||||
"electron": "8.0.2",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.2",
|
||||
"enzyme-to-json": "^3.4.4",
|
||||
"fetch-mock-jest": "^1.1.0-beta.3",
|
||||
"jest": "^25.1.0",
|
||||
"less": "^3.11.1",
|
||||
"log-symbols": "^3.0.0",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"monaco-editor-webpack-plugin": "^1.7.0",
|
||||
"node-abi": "^2.12.0",
|
||||
"node-abi": "^2.15.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"node-loader": "^0.6.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"react-test-renderer": "^16.11.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"react-test-renderer": "^16.13.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"standard": "^14.3.1",
|
||||
"style-loader": "^1.0.0",
|
||||
"stylelint": "^11.1.1",
|
||||
"stylelint-config-standard": "^19.0.0",
|
||||
"ts-jest": "^24.1.0",
|
||||
"ts-loader": "^6.2.1",
|
||||
"tslint": "^5.20.0",
|
||||
"stylelint": "^13.2.0",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"ts-jest": "^25.2.1",
|
||||
"tslint": "^6.0.0",
|
||||
"tslint-microsoft-contrib": "^6.2.0",
|
||||
"tslint-react": "^4.1.0",
|
||||
"typescript": "^3.6.4",
|
||||
"url-loader": "^2.2.0",
|
||||
"webpack-bundle-analyzer": "^3.6.0"
|
||||
"tslint-react": "^4.2.0",
|
||||
"typescript": "^3.8.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ export const html = `<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<link rel="stylesheet" type="text/css" href="./styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
|
||||
@@ -3,13 +3,9 @@ const {app, BrowserWindow} = require('electron')
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow
|
||||
|
||||
function createWindow () {
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({width: 800, height: 600})
|
||||
const mainWindow = new BrowserWindow({width: 800, height: 600})
|
||||
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadURL(url.format({
|
||||
@@ -20,14 +16,6 @@ function createWindow () {
|
||||
|
||||
// Open the DevTools.
|
||||
// mainWindow.webContents.openDevTools()
|
||||
|
||||
// Emitted when the window is closed.
|
||||
mainWindow.on('closed', function () {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
mainWindow = null
|
||||
})
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
@@ -47,7 +35,7 @@ app.on('window-all-closed', function () {
|
||||
app.on('activate', function () {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
+2
-14
@@ -1,13 +1,9 @@
|
||||
export const main = `// Modules to control application life and create native browser window
|
||||
const {app, BrowserWindow} = require('electron')
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow
|
||||
|
||||
function createWindow () {
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
@@ -20,14 +16,6 @@ function createWindow () {
|
||||
|
||||
// Open the DevTools.
|
||||
// mainWindow.webContents.openDevTools()
|
||||
|
||||
// Emitted when the window is closed.
|
||||
mainWindow.on('closed', function () {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
mainWindow = null
|
||||
})
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
@@ -47,7 +35,7 @@ app.on('window-all-closed', function () {
|
||||
app.on('activate', function () {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
+22
-11
@@ -4,17 +4,18 @@ export type Files = Map<string, string>;
|
||||
|
||||
export type FileTransform = (files: Files) => Promise<Files>;
|
||||
|
||||
export enum ElectronVersionState {
|
||||
export enum VersionState {
|
||||
ready = 'ready',
|
||||
downloading = 'downloading',
|
||||
unzipping = 'unzipping',
|
||||
unknown = 'unknown'
|
||||
}
|
||||
|
||||
export enum ElectronVersionSource {
|
||||
export enum VersionSource {
|
||||
remote = 'remote',
|
||||
local = 'local'
|
||||
}
|
||||
export interface NpmVersion {
|
||||
export interface Version {
|
||||
version: string;
|
||||
name?: string;
|
||||
localPath?: string;
|
||||
@@ -25,12 +26,14 @@ export interface EditorValues {
|
||||
renderer: string;
|
||||
html: string;
|
||||
preload: string;
|
||||
css: string;
|
||||
package?: string;
|
||||
}
|
||||
|
||||
export interface ElectronVersion extends NpmVersion {
|
||||
state: ElectronVersionState;
|
||||
source: ElectronVersionSource;
|
||||
export interface RunnableVersion extends Version {
|
||||
state: VersionState;
|
||||
source: VersionSource;
|
||||
downloadProgress?: number;
|
||||
}
|
||||
|
||||
export interface SetFiddleOptions {
|
||||
@@ -50,7 +53,8 @@ export interface OutputOptions {
|
||||
isNotPre?: boolean;
|
||||
}
|
||||
|
||||
export interface WarningDialogTexts {
|
||||
export interface GenericDialogOptions {
|
||||
type: GenericDialogType;
|
||||
ok?: string;
|
||||
cancel?: string;
|
||||
label: string;
|
||||
@@ -60,12 +64,19 @@ export interface Templates {
|
||||
[index: string]: string | Templates;
|
||||
}
|
||||
|
||||
export const enum GenericDialogType {
|
||||
'confirm' = 'confirm',
|
||||
'warning' = 'warning',
|
||||
'success' = 'success',
|
||||
}
|
||||
|
||||
// Editors
|
||||
export const enum EditorId {
|
||||
'main' = 'main',
|
||||
'renderer' = 'renderer',
|
||||
'html' = 'html',
|
||||
'preload' = 'preload'
|
||||
'preload' = 'preload',
|
||||
'css' = 'css'
|
||||
}
|
||||
|
||||
// Panels that can show up as a mosaic
|
||||
@@ -75,9 +86,9 @@ export const enum PanelId {
|
||||
|
||||
export type MosaicId = EditorId | PanelId;
|
||||
|
||||
export const ALL_EDITORS = [ EditorId.main, EditorId.renderer, EditorId.preload, EditorId.html ];
|
||||
export const ALL_PANELS = [ PanelId.docsDemo ];
|
||||
export const ALL_MOSAICS = [ ...ALL_EDITORS, ...ALL_PANELS ];
|
||||
export const ALL_EDITORS = [EditorId.main, EditorId.renderer, EditorId.preload, EditorId.html, EditorId.css];
|
||||
export const ALL_PANELS = [PanelId.docsDemo];
|
||||
export const ALL_MOSAICS = [...ALL_EDITORS, ...ALL_PANELS];
|
||||
|
||||
export type ArrowPosition = 'top' | 'left' | 'bottom' | 'right';
|
||||
|
||||
|
||||
+11
-3
@@ -19,14 +19,20 @@ export enum IpcEvents {
|
||||
SHOW_WARNING_DIALOG = 'SHOW_WARNING_DIALOG',
|
||||
SHOW_CONFIRMATION_DIALOG = 'SHOW_CONFIRMATION_DIALOG',
|
||||
SHOW_WELCOME_TOUR = 'SHOW_WELCOME_TOUR',
|
||||
CLEAR_CONSOLE = 'CLEAR_CONSOLE'
|
||||
CLEAR_CONSOLE = 'CLEAR_CONSOLE',
|
||||
LOAD_LOCAL_VERSION_FOLDER = 'LOAD_LOCAL_VERSION_FOLDER',
|
||||
SHOW_LOCAL_VERSION_FOLDER_DIALOG = 'SHOW_LOCAL_VERSION_FOLDER_DIALOG',
|
||||
BISECT_COMMANDS_TOGGLE = 'BISECT_COMMANDS_TOGGLE',
|
||||
SHOW_INACTIVE = 'SHOW_INACTIVE'
|
||||
}
|
||||
|
||||
export const ipcMainEvents = [
|
||||
IpcEvents.FS_SAVE_FIDDLE_DIALOG,
|
||||
IpcEvents.FS_SAVE_FIDDLE,
|
||||
IpcEvents.SHOW_WARNING_DIALOG,
|
||||
IpcEvents.SHOW_CONFIRMATION_DIALOG
|
||||
IpcEvents.SHOW_CONFIRMATION_DIALOG,
|
||||
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG,
|
||||
IpcEvents.SHOW_INACTIVE
|
||||
];
|
||||
|
||||
export const ipcRendererEvents = [
|
||||
@@ -46,7 +52,9 @@ export const ipcRendererEvents = [
|
||||
IpcEvents.FS_SAVE_FIDDLE_FORGE,
|
||||
IpcEvents.FS_SAVE_FIDDLE_GIST,
|
||||
IpcEvents.SHOW_WELCOME_TOUR,
|
||||
IpcEvents.CLEAR_CONSOLE
|
||||
IpcEvents.CLEAR_CONSOLE,
|
||||
IpcEvents.LOAD_LOCAL_VERSION_FOLDER,
|
||||
IpcEvents.BISECT_COMMANDS_TOGGLE
|
||||
];
|
||||
|
||||
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL = 'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import "~@blueprintjs/core/lib/css/blueprint.css";
|
||||
@import "~@blueprintjs/select/lib/css/blueprint-select.css";
|
||||
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
|
||||
@import "~@blueprintjs/core/lib/less/variables.less";
|
||||
@import (inline) "../../node_modules/@blueprintjs/core/lib/css/blueprint.css";
|
||||
@import (inline) "../../node_modules/@blueprintjs/select/lib/css/blueprint-select.css";
|
||||
@import (inline) "../../node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css";
|
||||
@import (inline) "../../node_modules/@blueprintjs/core/lib/less/variables.less";
|
||||
|
||||
// Override some of the colors
|
||||
.fiddle.bp3-dark {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
button.version-chooser.bp3-button {
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
header {
|
||||
box-shadow: 0 0 0 1px rgba(16, 22, 26, 0.2), 0 0 0 rgba(16, 22, 26, 0), 0 1px 1px rgba(16, 22, 26, 0.4);
|
||||
//border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background-color: @background-3;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
@@ -20,6 +19,7 @@ header {
|
||||
select {
|
||||
-webkit-app-region: no-drag;
|
||||
margin-bottom: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
select {
|
||||
|
||||
@@ -24,6 +24,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
// update if list of Editor ID changes
|
||||
@editor-ids: main, renderer, html, preload;
|
||||
|
||||
// takes each editor ID and increase its z-index if the parent Mosiac root
|
||||
// has focused__id class for that specific id.
|
||||
each(@editor-ids, {
|
||||
.focused__@{value}.mosaic .mosaic-window.@{value} {
|
||||
z-index: 2;
|
||||
}
|
||||
});
|
||||
|
||||
.mosaic-window {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
|
||||
@@ -52,6 +52,10 @@
|
||||
.action {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.bp3-progress-bar {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.bp3-fill {
|
||||
.version-chooser {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -16,5 +16,5 @@
|
||||
@import "components/chrome-mac.less";
|
||||
@import "components/editors.less";
|
||||
@import "components/tour.less";
|
||||
@import "components/commands-version-chooser.less";
|
||||
@import "components/show-me.less";
|
||||
@import "components/version-select.less";
|
||||
|
||||
@@ -7,7 +7,7 @@ import { isDevMode } from '../utils/devmode';
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function setupDevTools(): Promise<void> {
|
||||
if (!isDevMode) return;
|
||||
if (!isDevMode()) return;
|
||||
|
||||
const {
|
||||
default: installExtension,
|
||||
|
||||
+18
-1
@@ -1,4 +1,4 @@
|
||||
import { dialog } from 'electron';
|
||||
import { dialog, IpcMainEvent } from 'electron';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { ipcMainManager } from './ipc';
|
||||
import { getOrCreateMainWindow } from './windows';
|
||||
@@ -17,6 +17,10 @@ export function setupDialogs() {
|
||||
showConfirmationDialog(args);
|
||||
});
|
||||
|
||||
ipcMainManager.on(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, async (event) => {
|
||||
await showOpenDialog(event);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,3 +46,16 @@ function showConfirmationDialog(args: Electron.MessageBoxOptions) {
|
||||
...args
|
||||
});
|
||||
}
|
||||
|
||||
async function showOpenDialog(event: IpcMainEvent) {
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
title: 'Open Folder',
|
||||
properties: ['openDirectory']
|
||||
});
|
||||
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.reply(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, [filePaths[0]]);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export class IpcMainManager extends EventEmitter {
|
||||
super();
|
||||
|
||||
ipcMainEvents.forEach((name) => {
|
||||
ipcMain.removeAllListeners(name);
|
||||
ipcMain.on(name, (...args: Array<any>) => this.emit(name, ...args));
|
||||
});
|
||||
|
||||
|
||||
+5
-2
@@ -1,5 +1,9 @@
|
||||
import { initSentry } from '../sentry';
|
||||
initSentry();
|
||||
|
||||
import { app } from 'electron';
|
||||
|
||||
import { isDevMode } from '../utils/devmode';
|
||||
import { setupAboutPanel } from '../utils/set-about-panel';
|
||||
import { setupDevTools } from './devtools';
|
||||
import { setupDialogs } from './dialogs';
|
||||
@@ -15,6 +19,7 @@ import { getOrCreateMainWindow } from './windows';
|
||||
*/
|
||||
export async function onReady() {
|
||||
await onFirstRunMaybe();
|
||||
if (!isDevMode()) process.env.NODE_ENV = 'production';
|
||||
|
||||
getOrCreateMainWindow();
|
||||
setupAboutPanel();
|
||||
@@ -58,8 +63,6 @@ export function onWindowsAllClosed() {
|
||||
* Exported for testing purposes.
|
||||
*/
|
||||
export function main() {
|
||||
console.log(`Welcome to Fiddle ${app.getVersion()}`);
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when
|
||||
// installing/uninstalling.
|
||||
if (shouldQuit()) {
|
||||
|
||||
+15
-8
@@ -144,7 +144,7 @@ function getShowMeMenuItem(key: string, item: string | Templates): MenuItemConst
|
||||
if (typeof item === 'string') {
|
||||
return {
|
||||
label: key,
|
||||
click: () => ipcMainManager.send(IpcEvents.FS_OPEN_TEMPLATE, [ key ])
|
||||
click: () => ipcMainManager.send(IpcEvents.FS_OPEN_TEMPLATE, [key])
|
||||
};
|
||||
}
|
||||
|
||||
@@ -160,10 +160,10 @@ function getShowMeMenu(): MenuItemConstructorOptions {
|
||||
const showMeMenu: Array<MenuItemConstructorOptions> = Object.keys(SHOW_ME_TEMPLATES)
|
||||
.map((key) => getShowMeMenuItem(key, SHOW_ME_TEMPLATES[key]));
|
||||
|
||||
return {
|
||||
label: 'Show Me',
|
||||
submenu: showMeMenu
|
||||
};
|
||||
return {
|
||||
label: 'Show Me',
|
||||
submenu: showMeMenu
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,15 +247,22 @@ export function setupMenu() {
|
||||
|
||||
// Tweak "View" menu
|
||||
if (label === 'View' && isSubmenu(item.submenu)) {
|
||||
item.submenu = item.submenu.filter((subItem) => subItem.label !== 'Toggle Developer Tools'); // Remove "Toggle Developer Tools"
|
||||
// remove "Reload" (has weird behaviour) and "Toggle Developer Tools"
|
||||
item.submenu = item.submenu
|
||||
.filter((subItem) => subItem.label !== 'Toggle Developer Tools' && subItem.label !== 'Reload');
|
||||
item.submenu.push({ type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' }); // Add zooming actions
|
||||
item.submenu.push({ type: 'separator' }, {
|
||||
label: 'Toggle Soft Wrap',
|
||||
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, [ 'wordWrap' ]),
|
||||
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, ['wordWrap']),
|
||||
});
|
||||
item.submenu.push({ type: 'separator' }, {
|
||||
label: 'Toggle Mini Map',
|
||||
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, [ 'minimap.enabled' ]),
|
||||
click: () => ipcMainManager.send(IpcEvents.MONACO_TOGGLE_OPTION, ['minimap.enabled']),
|
||||
});
|
||||
item.submenu.push({ type: 'separator' }, {
|
||||
label: 'Toggle Bisect Helper',
|
||||
click: () => ipcMainManager.send(IpcEvents.BISECT_COMMANDS_TOGGLE),
|
||||
accelerator: 'CommandorControl+Shift+B',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ export const listenForProtocolHandler = () => {
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) app.quit();
|
||||
|
||||
app.removeAllListeners('open-url');
|
||||
app.on('open-url', (_, url) => {
|
||||
if (url.startsWith(`${PROTOCOL}://`)) {
|
||||
handlePotentialProtocolLaunch(url);
|
||||
|
||||
+7
-2
@@ -1,5 +1,7 @@
|
||||
import { BrowserWindow, shell } from 'electron';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { createContextMenu } from './context-menu';
|
||||
import { ipcMainManager } from './ipc';
|
||||
|
||||
// Keep a global reference of the window objects, if we don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
@@ -16,7 +18,6 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
|
||||
height: 900,
|
||||
minHeight: 600,
|
||||
minWidth: 600,
|
||||
show: true,
|
||||
titleBarStyle: process.platform === 'darwin' ? 'hidden' : undefined,
|
||||
acceptFirstMouse: true,
|
||||
backgroundColor: '#1d2427',
|
||||
@@ -37,7 +38,7 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
|
||||
export function createMainWindow(): Electron.BrowserWindow {
|
||||
console.log(`Creating main window`);
|
||||
const browserWindow = new BrowserWindow(getMainWindowOptions());
|
||||
browserWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
|
||||
browserWindow.loadFile('./dist/static/index.html');
|
||||
|
||||
browserWindow.webContents.once('dom-ready', () => {
|
||||
browserWindow.show();
|
||||
@@ -62,6 +63,10 @@ export function createMainWindow(): Electron.BrowserWindow {
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
ipcMainManager.on(IpcEvents.SHOW_INACTIVE, () => {
|
||||
browserWindow.showInactive();
|
||||
});
|
||||
|
||||
browserWindows.push(browserWindow);
|
||||
|
||||
return browserWindow;
|
||||
|
||||
+22
-35
@@ -1,3 +1,6 @@
|
||||
import { initSentry } from '../sentry';
|
||||
initSentry();
|
||||
|
||||
import { when } from 'mobx';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
|
||||
@@ -6,6 +9,7 @@ import {
|
||||
ALL_EDITORS,
|
||||
EditorId,
|
||||
EditorValues,
|
||||
GenericDialogType,
|
||||
SetFiddleOptions
|
||||
} from '../interfaces';
|
||||
import { WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
|
||||
@@ -21,9 +25,6 @@ import { appState } from './state';
|
||||
import { getTheme } from './themes';
|
||||
import { TouchBarManager } from './touch-bar-manager';
|
||||
|
||||
// Import style
|
||||
require('../less/root.less');
|
||||
|
||||
/**
|
||||
* The top-level class controlling the whole app. This is *not* a React component,
|
||||
* but it does eventually render all components.
|
||||
@@ -54,14 +55,15 @@ export class App {
|
||||
) {
|
||||
// if unsaved, prompt user to make sure they're okay with overwriting and changing directory
|
||||
if (this.state.isUnsaved) {
|
||||
this.state.setWarningDialogTexts({
|
||||
this.state.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `Opening this Fiddle will replace your unsaved changes. Do you want to proceed?`,
|
||||
ok: 'Yes'
|
||||
});
|
||||
this.state.isWarningDialogShowing = true;
|
||||
await when(() => !this.state.isWarningDialogShowing);
|
||||
this.state.isGenericDialogShowing = true;
|
||||
await when(() => !this.state.isGenericDialogShowing);
|
||||
|
||||
if (!this.state.warningDialogLastResult) {
|
||||
if (!this.state.genericDialogLastResult) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -76,16 +78,14 @@ export class App {
|
||||
|
||||
// once loaded, we have a "saved" state
|
||||
this.state.isUnsaved = false;
|
||||
this.setupUnsavedOnChangeListener();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values on all three editors.
|
||||
* Sets the contents of all editor panes.
|
||||
*
|
||||
* @param {EditorValues} values
|
||||
* @param {warn} warn - Should we warn before overwriting unsaved data?
|
||||
*/
|
||||
public async setEditorValues(values: Partial<EditorValues>): Promise<void> {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
@@ -99,9 +99,16 @@ export class App {
|
||||
const backup = this.state.closedPanels[name];
|
||||
|
||||
if (typeof values[name] !== 'undefined') {
|
||||
if (isEditorBackup(backup) && backup.model) {
|
||||
// The editor does not exist, attempt to set it on the backup
|
||||
backup.model.setValue(values[name]!);
|
||||
if (isEditorBackup(backup)) {
|
||||
// The editor does not exist, attempt to set it on the backup.
|
||||
// If there's a model, we'll do it on the model. Else, we'll
|
||||
// set the value.
|
||||
|
||||
if (backup.model) {
|
||||
backup.model.setValue(values[name]!);
|
||||
} else {
|
||||
backup.value = values[name]!;
|
||||
}
|
||||
} else if (editor && editor.setValue) {
|
||||
// The editor exists, set the value directly
|
||||
editor.setValue(values[name]!);
|
||||
@@ -111,7 +118,7 @@ export class App {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values on all three editors.
|
||||
* Retrieves the contents of all editor panes.
|
||||
*
|
||||
* @returns {EditorValues}
|
||||
*/
|
||||
@@ -125,6 +132,7 @@ export class App {
|
||||
}
|
||||
|
||||
const values: EditorValues = {
|
||||
css: getEditorValue(EditorId.css),
|
||||
html: getEditorValue(EditorId.html),
|
||||
main: getEditorValue(EditorId.main),
|
||||
preload: getEditorValue(EditorId.preload),
|
||||
@@ -168,30 +176,9 @@ export class App {
|
||||
|
||||
ipcRenderer.send(WEBCONTENTS_READY_FOR_IPC_SIGNAL);
|
||||
|
||||
// TODO: A timer here is terrible. Let's fix this
|
||||
// and ensure we actually do it once Editors have mounted.
|
||||
setTimeout(() => {
|
||||
this.setupUnsavedOnChangeListener();
|
||||
}, 1500);
|
||||
|
||||
return rendered;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the editor is changed for the first time, we'll
|
||||
* set `isUnsaved` to true. That way, the app can warn you
|
||||
* if you're about to throw things away.
|
||||
*/
|
||||
public setupUnsavedOnChangeListener() {
|
||||
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
|
||||
const editor = window.ElectronFiddle.editors[key];
|
||||
const disposable = editor.onDidChangeModelContent(() => {
|
||||
this.state.isUnsaved = true;
|
||||
disposable.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads theme CSS into the HTML document.
|
||||
*
|
||||
|
||||
+229
-200
@@ -1,230 +1,259 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as fsType from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
import { VersionState } from '../interfaces';
|
||||
import { fancyImport } from '../utils/import';
|
||||
import { normalizeVersion } from '../utils/normalize-version';
|
||||
import { USER_DATA_PATH } from './constants';
|
||||
import { getOfflineTypeDefinitionPath } from './fetch-types';
|
||||
import { removeTypeDefsForVersion } from './fetch-types';
|
||||
import { AppState } from './state';
|
||||
|
||||
/**
|
||||
* The binary manager takes care of downloading Electron versions
|
||||
* General setup, called with a version. Is called during construction
|
||||
* to ensure that we always have or download at least one version.
|
||||
*
|
||||
* @export
|
||||
* @class BinaryManager
|
||||
* @param {string} iVersion
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export class BinaryManager {
|
||||
public state: Record<string, 'ready' | 'downloading'> = {};
|
||||
export async function setupBinary(appState: AppState, iVersion: string): Promise<void> {
|
||||
const version = normalizeVersion(iVersion);
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
|
||||
/**
|
||||
* Remove a version from disk. Does not update state. We'll try up to
|
||||
* four times before giving up if an error occurs.
|
||||
*
|
||||
* @param {string} iVersion
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async remove(iVersion: string): Promise<void> {
|
||||
const version = normalizeVersion(iVersion);
|
||||
let isDeleted = false;
|
||||
await fs.mkdirp(getDownloadPath(version));
|
||||
|
||||
// utility to re-run removal functions upon failure
|
||||
// due to windows filesystem lockfile jank
|
||||
const rerunner = async (func: () => Promise<void>, counter: number = 1) => {
|
||||
try {
|
||||
await func();
|
||||
} catch (error) {
|
||||
console.warn(`Binary Manager: failed to run ${func.name} for ${version}, but failed`, error);
|
||||
if (counter < 4) {
|
||||
console.log(`Binary Manager: Trying again to run ${func.name}`);
|
||||
await rerunner(func, counter + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const binaryCleaner = async () => {
|
||||
if (await this.getIsDownloaded(version)) {
|
||||
// This is necessary since we're messing with .asar files inside
|
||||
// the Electron binaries. Electron, powering Fiddle, will try to
|
||||
// "correct" our calls, but we don't want that right here.
|
||||
process.noAsar = true;
|
||||
await fs.remove(this.getDownloadPath(version));
|
||||
process.noAsar = false;
|
||||
|
||||
isDeleted = true;
|
||||
}
|
||||
};
|
||||
|
||||
const typeDefsCleaner = async () => {
|
||||
await this.removeTypeDefsForVersion(version);
|
||||
};
|
||||
|
||||
await rerunner(binaryCleaner);
|
||||
|
||||
if (isDeleted) {
|
||||
await rerunner(typeDefsCleaner);
|
||||
}
|
||||
const { state } = appState.versions[version];
|
||||
if (state === VersionState.downloading || state === VersionState.unzipping) {
|
||||
console.log(`Binary: Electron ${version} already downloading.`);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* General setup, called with a version. Is called during construction
|
||||
* to ensure that we always have or download at least one version.
|
||||
*
|
||||
* @param {string} iVersion
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async setup(iVersion: string): Promise<void> {
|
||||
const version = normalizeVersion(iVersion);
|
||||
const { promisify } = await import('util');
|
||||
const eDownload = promisify(require('electron-download'));
|
||||
if (await getIsDownloaded(version)) {
|
||||
console.log(`Binary: Electron ${version} already downloaded.`);
|
||||
appState.versions[version].state = VersionState.ready;
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.mkdirp(this.getDownloadPath(version));
|
||||
console.log(`Binary: Electron ${version} not present, downloading`);
|
||||
appState.versions[version].state = VersionState.downloading;
|
||||
|
||||
if (this.state[version] === 'downloading') {
|
||||
console.log(`BinaryManager: Electron ${version} already downloading.`);
|
||||
return;
|
||||
}
|
||||
const zipPath = await download(appState, version);
|
||||
const extractPath = getDownloadPath(version);
|
||||
console.log(`Binary: Electron ${version} downloaded, now unpacking to ${extractPath}`);
|
||||
|
||||
if (await this.getIsDownloaded(version)) {
|
||||
console.log(`BinaryManager: Electron ${version} already downloaded.`);
|
||||
this.state[version] = 'ready';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
appState.versions[version].state = VersionState.unzipping;
|
||||
|
||||
console.log(`BinaryManager: Electron ${version} not present, downloading`);
|
||||
this.state[version] = 'downloading';
|
||||
// Ensure the target path is empty
|
||||
await fs.emptyDir(extractPath);
|
||||
|
||||
const zipPath = await eDownload({ version });
|
||||
const extractPath = this.getDownloadPath(version);
|
||||
console.log(`BinaryManager: Electron ${version} downloaded, now unpacking to ${extractPath}`);
|
||||
const electronFiles = await unzip(zipPath, extractPath);
|
||||
console.log(`Unzipped ${version}`, electronFiles);
|
||||
} catch (error) {
|
||||
console.warn(`Failure while unzipping ${version}`, error);
|
||||
appState.versions[version].state = VersionState.unknown;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
appState.versions[version].state = VersionState.ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a version from disk. Does not update state. We'll try up to
|
||||
* four times before giving up if an error occurs.
|
||||
*
|
||||
* @param {string} iVersion
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function removeBinary(iVersion: string) {
|
||||
const version = normalizeVersion(iVersion);
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
let isDeleted = false;
|
||||
|
||||
// utility to re-run removal functions upon failure
|
||||
// due to windows filesystem lockfile jank
|
||||
const rerunner = async (func: () => Promise<void>, counter: number = 1) => {
|
||||
try {
|
||||
// Ensure the target path is empty
|
||||
await fs.emptyDir(extractPath);
|
||||
|
||||
const electronFiles = await this.unzip(zipPath, extractPath);
|
||||
console.log(`Unzipped ${version}`, electronFiles);
|
||||
await func();
|
||||
} catch (error) {
|
||||
console.warn(`Failure while unzipping ${version}`, error);
|
||||
|
||||
// TODO: Handle this case
|
||||
}
|
||||
|
||||
this.state[version] = 'ready';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expected path for the binary of a given Electron version
|
||||
*
|
||||
* @param {string} version
|
||||
* @param {string} dir
|
||||
* @returns {string}
|
||||
*/
|
||||
public getElectronBinaryPath(
|
||||
version: string,
|
||||
dir: string = this.getDownloadPath(version),
|
||||
): string {
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return path.join(dir, 'Electron.app/Contents/MacOS/Electron');
|
||||
case 'freebsd':
|
||||
case 'linux':
|
||||
return path.join(dir, 'electron');
|
||||
case 'win32':
|
||||
return path.join(dir, 'electron.exe');
|
||||
default:
|
||||
throw new Error(`Electron builds are not available for ${process.platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all versions downloaded to disk
|
||||
*
|
||||
* @returns {Promise<Array<string>>}
|
||||
*/
|
||||
public async getDownloadedVersions(): Promise<Array<string>> {
|
||||
const downloadPath = path.join(USER_DATA_PATH, 'electron-bin');
|
||||
console.log(`BinaryManager: Checking for downloaded versions`);
|
||||
|
||||
try {
|
||||
const directories = await fs.readdir(downloadPath);
|
||||
const knownVersions: Array<string> = [];
|
||||
|
||||
for (const directory of directories) {
|
||||
if (await this.getIsDownloaded(directory)) {
|
||||
knownVersions.push(directory);
|
||||
}
|
||||
}
|
||||
|
||||
return knownVersions;
|
||||
} catch (error) {
|
||||
console.warn(`Could not read known Electron versions`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Did we already download a given version?
|
||||
*
|
||||
* @param {string} version
|
||||
* @param {string} dir
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public async getIsDownloaded(version: string, dir?: string): Promise<boolean> {
|
||||
const expectedPath = this.getElectronBinaryPath(version, dir);
|
||||
|
||||
return fs.existsSync(expectedPath);
|
||||
}
|
||||
|
||||
public async removeTypeDefsForVersion(version: string) {
|
||||
const _version = normalizeVersion(version);
|
||||
const typeDefsDir = path.dirname(getOfflineTypeDefinitionPath(_version));
|
||||
|
||||
if (fs.existsSync(typeDefsDir)) {
|
||||
try {
|
||||
await fs.remove(typeDefsDir);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
console.warn(`Binary Manager: failed to run ${func.name} for ${version}, but failed`, error);
|
||||
if (counter < 4) {
|
||||
console.log(`Binary Manager: Trying again to run ${func.name}`);
|
||||
await rerunner(func, counter + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expected path for a given Electron version
|
||||
*
|
||||
* @param {string} version
|
||||
* @returns {string}
|
||||
*/
|
||||
private getDownloadPath(version: string): string {
|
||||
return path.join(USER_DATA_PATH, 'electron-bin', version);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unzips an electron package so that we can actually use it.
|
||||
*
|
||||
* @param {string} zipPath
|
||||
* @param {string} extractPath
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private unzip(zipPath: string, extractPath: string): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const extract = require('extract-zip');
|
||||
};
|
||||
|
||||
const binaryCleaner = async () => {
|
||||
if (await getIsDownloaded(version)) {
|
||||
// This is necessary since we're messing with .asar files inside
|
||||
// the Electron binaries. Electron, powering Fiddle, will try to
|
||||
// "correct" our calls, but we don't want that right here.
|
||||
process.noAsar = true;
|
||||
await fs.remove(getDownloadPath(version));
|
||||
process.noAsar = false;
|
||||
|
||||
const options = {
|
||||
dir: extractPath,
|
||||
};
|
||||
isDeleted = true;
|
||||
}
|
||||
};
|
||||
|
||||
extract(zipPath, options, (error: Error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
const typeDefsCleaner = async () => {
|
||||
await removeTypeDefsForVersion(version);
|
||||
};
|
||||
|
||||
console.log(`BinaryManager: Unpacked!`);
|
||||
process.noAsar = false;
|
||||
await rerunner(binaryCleaner);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
if (isDeleted) {
|
||||
await rerunner(typeDefsCleaner);
|
||||
}
|
||||
}
|
||||
|
||||
/* Did we already download a given version?
|
||||
*
|
||||
* @param {string} version
|
||||
* @param {string} dir
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export async function getIsDownloaded(version: string, dir?: string): Promise<boolean> {
|
||||
const expectedPath = getElectronBinaryPath(version, dir);
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
|
||||
return fs.existsSync(expectedPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expected path for the binary of a given Electron version
|
||||
*
|
||||
* @param {string} version
|
||||
* @param {string} dir
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getElectronBinaryPath(
|
||||
version: string,
|
||||
dir: string = getDownloadPath(version),
|
||||
): string {
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return path.join(dir, 'Electron.app/Contents/MacOS/Electron');
|
||||
case 'freebsd':
|
||||
case 'linux':
|
||||
return path.join(dir, 'electron');
|
||||
case 'win32':
|
||||
return path.join(dir, 'electron.exe');
|
||||
default:
|
||||
throw new Error(`Electron builds are not available for ${process.platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getDownloadingVersions(appState: AppState) {
|
||||
return Object.entries(appState.versions)
|
||||
.filter(([_, { state }]) => state === 'downloading')
|
||||
.map(([version, _]) => version);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of all versions downloaded to disk
|
||||
*
|
||||
* @returns {Promise<Array<string>>}
|
||||
*/
|
||||
export async function getDownloadedVersions(): Promise<Array<string>> {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const downloadPath = path.join(USER_DATA_PATH, 'electron-bin');
|
||||
console.log(`Binary: Checking for downloaded versions`);
|
||||
|
||||
try {
|
||||
const directories = await fs.readdir(downloadPath);
|
||||
const knownVersions: Array<string> = [];
|
||||
|
||||
for (const directory of directories) {
|
||||
if (await getIsDownloaded(directory)) {
|
||||
knownVersions.push(directory);
|
||||
}
|
||||
}
|
||||
|
||||
return knownVersions;
|
||||
} catch (error) {
|
||||
console.warn(`Could not read known Electron versions`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an Electron version.
|
||||
*
|
||||
* @param {AppState} appState
|
||||
* @param {string} version
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function download(appState: AppState, version: string): Promise<string> {
|
||||
const { download: electronDownload } = await import('@electron/get');
|
||||
const getProgressCallback = (progress: Progress) => {
|
||||
const roundedProgress = Math.round(progress.percent * 100) / 100;
|
||||
|
||||
if (roundedProgress !== appState.versions[version].downloadProgress) {
|
||||
console.debug(`Binary: Version ${version} download progress: ${progress.percent}`);
|
||||
appState.versions[version].downloadProgress = roundedProgress;
|
||||
}
|
||||
};
|
||||
|
||||
if (!appState.versions[version]) {
|
||||
throw new Error(`Version ${version} does not exist in state, cannot download`);
|
||||
}
|
||||
|
||||
const zipFilePath = await electronDownload(version, {
|
||||
downloadOptions: {
|
||||
quiet: true,
|
||||
getProgressCallback
|
||||
}
|
||||
});
|
||||
|
||||
return zipFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expected path for a given Electron version
|
||||
*
|
||||
* @param {string} version
|
||||
* @returns {string}
|
||||
*/
|
||||
function getDownloadPath(version: string): string {
|
||||
return path.join(USER_DATA_PATH, 'electron-bin', version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzips an electron package so that we can actually use it.
|
||||
*
|
||||
* @param {string} zipPath
|
||||
* @param {string} extractPath
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function unzip(zipPath: string, extractPath: string): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const extract = (await fancyImport<any>('extract-zip')).default;
|
||||
|
||||
process.noAsar = true;
|
||||
|
||||
const options = {
|
||||
dir: extractPath,
|
||||
};
|
||||
|
||||
extract(zipPath, options, (error: Error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Binary: Unpacked!`);
|
||||
process.noAsar = false;
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface Progress {
|
||||
percent: number;
|
||||
transferred: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { RunnableVersion } from '../interfaces';
|
||||
|
||||
export class Bisector {
|
||||
public revList: Array<RunnableVersion>;
|
||||
public minRev: number;
|
||||
public maxRev: number;
|
||||
private pivot: number;
|
||||
|
||||
constructor(revList: Array<RunnableVersion>) {
|
||||
this.getCurrentVersion = this.getCurrentVersion.bind(this);
|
||||
this.continue = this.continue.bind(this);
|
||||
this.calculatePivot = this.calculatePivot.bind(this);
|
||||
|
||||
this.revList = revList;
|
||||
this.minRev = 0;
|
||||
this.maxRev = revList.length - 1;
|
||||
this.calculatePivot();
|
||||
}
|
||||
|
||||
public getCurrentVersion() {
|
||||
return this.revList[this.pivot];
|
||||
}
|
||||
|
||||
public continue(isGoodVersion: boolean) {
|
||||
let isBisectOver = false;
|
||||
if (this.maxRev - this.minRev <= 1) {
|
||||
isBisectOver = true;
|
||||
}
|
||||
|
||||
if (isGoodVersion) {
|
||||
const upPivot = Math.floor((this.maxRev - this.pivot) / 2) + this.pivot;
|
||||
this.minRev = this.pivot;
|
||||
if (upPivot !== this.maxRev && upPivot !== this.pivot) {
|
||||
this.pivot = upPivot;
|
||||
} else {
|
||||
isBisectOver = true;
|
||||
}
|
||||
} else {
|
||||
const downPivot = Math.floor((this.pivot - this.minRev) / 2) + this.minRev;
|
||||
this.maxRev = this.pivot;
|
||||
if (downPivot !== this.minRev && downPivot !== this.pivot) {
|
||||
this.pivot = downPivot;
|
||||
} else {
|
||||
isBisectOver = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBisectOver) {
|
||||
return [this.revList[this.minRev], this.revList[this.maxRev]];
|
||||
} else {
|
||||
return this.revList[this.pivot];
|
||||
}
|
||||
}
|
||||
|
||||
private calculatePivot() {
|
||||
this.pivot = Math.floor((this.maxRev - this.minRev) / 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { GenericDialogType, VersionState } from '../../../src/interfaces';
|
||||
import { AppState } from '../state';
|
||||
|
||||
interface BisectHandlerProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class BisectHandler extends React.Component<BisectHandlerProps> {
|
||||
constructor(props: BisectHandlerProps) {
|
||||
super(props);
|
||||
|
||||
this.continueBisect = this.continueBisect.bind(this);
|
||||
this.terminateBisect = this.terminateBisect.bind(this);
|
||||
}
|
||||
|
||||
public continueBisect(isGood: boolean) {
|
||||
window.ElectronFiddle.app.runner.stop();
|
||||
|
||||
const { appState } = this.props;
|
||||
const response = appState.Bisector!.continue(isGood);
|
||||
|
||||
if (Array.isArray(response)) {
|
||||
this.terminateBisect();
|
||||
|
||||
const [minRev, maxRev] = response;
|
||||
const [minVer, maxVer] = [minRev.version, maxRev.version];
|
||||
const message = `Check between versions ${minVer} and ${maxVer}.`;
|
||||
|
||||
appState.pushOutput(`[BISECT] Complete: ${message}`);
|
||||
appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.success,
|
||||
label: `Bisect complete. ${message}`,
|
||||
cancel: undefined
|
||||
});
|
||||
appState.isGenericDialogShowing = true;
|
||||
} else {
|
||||
appState.setVersion(response.version);
|
||||
}
|
||||
}
|
||||
|
||||
public terminateBisect() {
|
||||
const { appState } = this.props;
|
||||
appState.Bisector = undefined;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
if (!!appState.Bisector) {
|
||||
const isDownloading = appState.currentElectronVersion.state === VersionState.downloading;
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon={'thumbs-up'}
|
||||
onClick={() => this.continueBisect(true)}
|
||||
disabled={isDownloading}
|
||||
/>
|
||||
<Button
|
||||
icon={'thumbs-down'}
|
||||
onClick={() => this.continueBisect(false)}
|
||||
disabled={isDownloading}
|
||||
/>
|
||||
<Button
|
||||
icon={'cross'}
|
||||
onClick={this.terminateBisect}
|
||||
>
|
||||
Cancel Bisect
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Button
|
||||
icon='git-branch'
|
||||
text='Bisect'
|
||||
onClick={appState.toggleBisectDialog}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,7 @@ export class EditorDropdown extends React.Component<EditorDropdownProps, EditorD
|
||||
text={TITLE_MAP[id]}
|
||||
id={id}
|
||||
onClick={this.onItemClick}
|
||||
disabled={appState.mosaicArrangement === id} // can't hide last editor panel
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as React from 'react';
|
||||
|
||||
import { when } from 'mobx';
|
||||
import { IpcEvents } from '../../ipc-events';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, RENDERER_JS_NAME } from '../../shared-constants';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME, STYLES_CSS_NAME } from '../../shared-constants';
|
||||
import { getOctokit } from '../../utils/octokit';
|
||||
import { ipcRendererManager } from '../ipc';
|
||||
import { AppState } from '../state';
|
||||
@@ -34,6 +34,10 @@ export class PublishButton extends React.Component<PublishButtonProps> {
|
||||
ipcRendererManager.on(IpcEvents.FS_SAVE_FIDDLE_GIST, this.handleClick);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
ipcRendererManager.off(IpcEvents.FS_SAVE_FIDDLE_GIST, this.handleClick);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the user clicks the publish button, we either show the
|
||||
* authentication dialog or publish right away.
|
||||
@@ -87,6 +91,12 @@ export class PublishButton extends React.Component<PublishButtonProps> {
|
||||
[RENDERER_JS_NAME]: {
|
||||
content: values.renderer || '// Empty',
|
||||
},
|
||||
[PRELOAD_JS_NAME]: {
|
||||
content: values.preload || '// Empty',
|
||||
},
|
||||
[STYLES_CSS_NAME]: {
|
||||
content: values.css || '/* Empty */',
|
||||
},
|
||||
},
|
||||
} as any); // Note: GitHub messed up, GistsCreateParamsFiles is an incorrect interface
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Button, IButtonProps, Spinner } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ElectronVersionState } from '../../interfaces';
|
||||
import { VersionState } from '../../interfaces';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface RunnerState {
|
||||
@@ -25,13 +25,17 @@ export class Runner extends React.Component<RunnerProps, RunnerState> {
|
||||
const { isRunning, currentElectronVersion } = this.props.appState;
|
||||
|
||||
const state = currentElectronVersion && currentElectronVersion.state;
|
||||
const props: IButtonProps = { className: 'button-run' };
|
||||
const props: IButtonProps = { className: 'button-run', disabled: true };
|
||||
|
||||
if (state === ElectronVersionState.downloading) {
|
||||
if (state === VersionState.downloading) {
|
||||
props.text = 'Downloading';
|
||||
props.disabled = true;
|
||||
props.icon = <Spinner size={16} value={currentElectronVersion?.downloadProgress} />;
|
||||
} else if (state === VersionState.unzipping) {
|
||||
props.text = 'Unzipping';
|
||||
props.icon = <Spinner size={16} />;
|
||||
} else if (state === ElectronVersionState.ready) {
|
||||
} else if (state === VersionState.ready) {
|
||||
props.disabled = false;
|
||||
|
||||
if (isRunning) {
|
||||
props.active = true;
|
||||
props.text = 'Stop';
|
||||
@@ -44,7 +48,6 @@ export class Runner extends React.Component<RunnerProps, RunnerState> {
|
||||
}
|
||||
} else {
|
||||
props.text = 'Checking status';
|
||||
props.disabled = true;
|
||||
props.icon = <Spinner size={16} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,95 +1,9 @@
|
||||
import { Button, MenuItem } from '@blueprintjs/core';
|
||||
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
|
||||
import { ButtonGroup } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../interfaces';
|
||||
import { highlightText } from '../../utils/highlight-text';
|
||||
import { sortedElectronMap } from '../../utils/sorted-electron-map';
|
||||
import { AppState } from '../state';
|
||||
import { getReleaseChannel } from '../versions';
|
||||
|
||||
const ElectronVersionSelect = Select.ofType<ElectronVersion>();
|
||||
|
||||
/**
|
||||
* Helper method: Returns the <Select /> label for an Electron
|
||||
* version.
|
||||
*
|
||||
* @param {ElectronVersion} { source, state }
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getItemLabel({ source, state, name }: ElectronVersion): string {
|
||||
let label = '';
|
||||
|
||||
if (source === ElectronVersionSource.local) {
|
||||
label = name || 'Local';
|
||||
} else {
|
||||
if (state === ElectronVersionState.unknown) {
|
||||
label = `Not downloaded`;
|
||||
} else if (state === ElectronVersionState.ready) {
|
||||
label = `Downloaded`;
|
||||
} else if (state === ElectronVersionState.downloading) {
|
||||
label = `Downloading`;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method: Returns the <Select /> icon for an Electron
|
||||
* version.
|
||||
*
|
||||
* @param {ElectronVersion} { state }
|
||||
* @returns
|
||||
*/
|
||||
export function getItemIcon({ state }: ElectronVersion) {
|
||||
return state === 'ready'
|
||||
? 'saved'
|
||||
: state === 'downloading' ? 'cloud-download' : 'cloud';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method: Returns the <Select /> predicate for an Electron
|
||||
* version.
|
||||
*
|
||||
* @param {string} query
|
||||
* @param {ElectronVersion} { version }
|
||||
* @returns
|
||||
*/
|
||||
export const filterItem: ItemPredicate<ElectronVersion> = (query, { version }) => {
|
||||
return version.toLowerCase().includes(query.toLowerCase());
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper method: Returns the <Select /> <MenuItem /> for Electron
|
||||
* versions.
|
||||
*
|
||||
* @param {ElectronVersion} item
|
||||
* @param {IItemRendererProps} { handleClick, modifiers, query }
|
||||
* @returns
|
||||
*/
|
||||
export const renderItem: ItemRenderer<ElectronVersion> = (item, { handleClick, modifiers, query }) => {
|
||||
if (!modifiers.matchesPredicate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
text={highlightText(item.version, query)}
|
||||
key={item.version}
|
||||
onClick={handleClick}
|
||||
label={getItemLabel(item)}
|
||||
icon={getItemIcon(item)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export interface VersionChooserState {
|
||||
value: string;
|
||||
}
|
||||
import { VersionSelect } from './version-select';
|
||||
|
||||
export interface VersionChooserProps {
|
||||
appState: AppState;
|
||||
@@ -98,70 +12,18 @@ export interface VersionChooserProps {
|
||||
/**
|
||||
* A dropdown allowing the selection of Electron versions. The actual
|
||||
* download is managed in the state.
|
||||
*
|
||||
* @class VersionChooser
|
||||
* @extends {React.Component<VersionChooserProps, VersionChooserState>}
|
||||
*/
|
||||
@observer
|
||||
export class VersionChooser extends React.Component<VersionChooserProps, VersionChooserState> {
|
||||
constructor(props: VersionChooserProps) {
|
||||
super(props);
|
||||
export const VersionChooser = observer((props: VersionChooserProps) => {
|
||||
const { currentElectronVersion, Bisector, setVersion } = props.appState;
|
||||
|
||||
this.onItemSelect = this.onItemSelect.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle change, which usually means that we'd like update
|
||||
* the selection version.
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLSelectElement>} event
|
||||
*/
|
||||
public onItemSelect({ version }: ElectronVersion) {
|
||||
this.props.appState.setVersion(version);
|
||||
}
|
||||
|
||||
public getItems(): Array<ElectronVersion> {
|
||||
const { versions, versionsToShow, statesToShow } = this.props.appState;
|
||||
|
||||
return sortedElectronMap<ElectronVersion>(versions, (_key, item) => item)
|
||||
.filter((item) => {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we want to show the version
|
||||
if (!versionsToShow.includes(getReleaseChannel(item))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we want to show the state
|
||||
if (!statesToShow.includes(item.state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { currentElectronVersion } = this.props.appState;
|
||||
const { version } = currentElectronVersion;
|
||||
|
||||
return (
|
||||
<ElectronVersionSelect
|
||||
filterable={true}
|
||||
items={this.getItems()}
|
||||
itemRenderer={renderItem}
|
||||
itemPredicate={filterItem}
|
||||
onItemSelect={this.onItemSelect}
|
||||
noResults={<MenuItem disabled={true} text='No results.' />}
|
||||
>
|
||||
<Button
|
||||
className='version-chooser'
|
||||
text={`Electron v${version}`}
|
||||
icon={getItemIcon(currentElectronVersion)}
|
||||
/>
|
||||
</ElectronVersionSelect>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<VersionSelect
|
||||
appState={props.appState}
|
||||
onVersionSelect={({ version }) => setVersion(version)}
|
||||
currentVersion={currentElectronVersion}
|
||||
disabled={!!Bisector}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from 'react';
|
||||
|
||||
import { AppState } from '../state';
|
||||
import { AddressBar } from './commands-address-bar';
|
||||
import { BisectHandler } from './commands-bisect';
|
||||
import { EditorDropdown } from './commands-editors';
|
||||
import { PublishButton } from './commands-publish-button';
|
||||
import { Runner } from './commands-runner';
|
||||
@@ -28,6 +29,7 @@ export class Commands extends React.Component<CommandsProps, {}> {
|
||||
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
const { isBisectCommandShowing: isBisectCommandShowing } = appState;
|
||||
|
||||
return (
|
||||
<div className='commands'>
|
||||
@@ -36,6 +38,15 @@ export class Commands extends React.Component<CommandsProps, {}> {
|
||||
<VersionChooser appState={appState} />
|
||||
<Runner appState={appState} />
|
||||
</ControlGroup>
|
||||
{
|
||||
// tslint:disable-next-line jsx-no-multiline-js
|
||||
isBisectCommandShowing &&
|
||||
(
|
||||
<ControlGroup fill={true} vertical={false}>
|
||||
<BisectHandler appState={appState} />
|
||||
</ControlGroup>
|
||||
)
|
||||
}
|
||||
<ControlGroup fill={true} vertical={false}>
|
||||
<Button
|
||||
active={appState.isConsoleShowing}
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
import { getTheme, THEMES_PATH } from '../themes';
|
||||
|
||||
import { GenericDialogType } from '../../../src/interfaces';
|
||||
import { AppState } from '../state';
|
||||
import { defaultDark, LoadedFiddleTheme } from '../themes-defaults';
|
||||
|
||||
@@ -68,8 +69,11 @@ export class AddThemeDialog extends React.Component<AddThemeDialogProps, AddThem
|
||||
const name = editor.name ? editor.name : file.name;
|
||||
await this.createNewThemeFromMonaco(name, newTheme);
|
||||
} catch (error) {
|
||||
this.props.appState.setWarningDialogTexts({label: `Error: ${error}, please pick a different file.`});
|
||||
this.props.appState.isWarningDialogShowing = true;
|
||||
this.props.appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `Error: ${error}, please pick a different file.`
|
||||
});
|
||||
this.props.appState.isGenericDialogShowing = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@ import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
import * as semver from 'semver';
|
||||
|
||||
import { NpmVersion } from '../../interfaces';
|
||||
import { Version } from '../../interfaces';
|
||||
import { IpcEvents } from '../../ipc-events';
|
||||
import { getElectronNameForPlatform } from '../../utils/electron-name';
|
||||
import { getIsDownloaded } from '../binary';
|
||||
import { ipcRendererManager } from '../ipc';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface AddVersionDialogProps {
|
||||
@@ -16,7 +19,7 @@ export interface AddVersionDialogProps {
|
||||
export interface AddVersionDialogState {
|
||||
isValidElectron: boolean;
|
||||
isValidVersion: boolean;
|
||||
file?: File;
|
||||
folderPath?: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
@@ -39,8 +42,15 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
|
||||
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.onChangeFile = this.onChangeFile.bind(this);
|
||||
this.onChangeVersion = this.onChangeVersion.bind(this);
|
||||
|
||||
ipcRendererManager.on(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, (_event, [file]) => {
|
||||
this.setFolderPath(file);
|
||||
});
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.LOAD_LOCAL_VERSION_FOLDER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,16 +58,10 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public async onChangeFile(event: React.FormEvent<HTMLInputElement>) {
|
||||
const { files } = event.target as any;
|
||||
const { binaryManager } = this.props.appState;
|
||||
const file = files && files[0] ? files[0] : undefined;
|
||||
public async setFolderPath(folderPath: string) {
|
||||
const isValidElectron = !!await getIsDownloaded('custom', folderPath);
|
||||
|
||||
const isValidElectron = !!(file
|
||||
&& file.path
|
||||
&& await binaryManager.getIsDownloaded('custom', file.path));
|
||||
|
||||
this.setState({ file, isValidElectron });
|
||||
this.setState({ folderPath, isValidElectron });
|
||||
}
|
||||
|
||||
public onChangeVersion(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
@@ -67,7 +71,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
|
||||
this.setState({
|
||||
version,
|
||||
isValidVersion,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,18 +80,18 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async onSubmit(): Promise<void> {
|
||||
const { file, version } = this.state;
|
||||
const { folderPath, version } = this.state;
|
||||
|
||||
if (!file) return;
|
||||
if (!folderPath) return;
|
||||
|
||||
const name = file.path
|
||||
const name = folderPath
|
||||
.slice(-20)
|
||||
.split(path.sep)
|
||||
.slice(1)
|
||||
.join(path.sep);
|
||||
|
||||
const toAdd: NpmVersion = {
|
||||
localPath: file.path,
|
||||
const toAdd: Version = {
|
||||
localPath: folderPath,
|
||||
version,
|
||||
name
|
||||
};
|
||||
@@ -129,12 +133,16 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
|
||||
|
||||
public render() {
|
||||
const { isAddVersionDialogShowing } = this.props.appState;
|
||||
const inputProps = { webkitdirectory: 'true' };
|
||||
const { file } = this.state;
|
||||
const inputProps = {
|
||||
onClick: (e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
ipcRendererManager.send(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG);
|
||||
}
|
||||
};
|
||||
const { folderPath } = this.state;
|
||||
|
||||
const text = file && file.path
|
||||
? file.path
|
||||
: `Select the folder containing ${getElectronNameForPlatform()}...`;
|
||||
const text = folderPath ||
|
||||
`Select the folder containing ${getElectronNameForPlatform()}...`;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -145,7 +153,6 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
|
||||
>
|
||||
<div className='bp3-dialog-body'>
|
||||
<FileInput
|
||||
onInputChange={this.onChangeFile}
|
||||
id='custom-electron-version'
|
||||
inputProps={inputProps as any}
|
||||
text={text}
|
||||
@@ -163,9 +170,9 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
|
||||
}
|
||||
|
||||
private renderPath(): JSX.Element | null {
|
||||
const { file, isValidElectron } = this.state;
|
||||
const { folderPath, isValidElectron } = this.state;
|
||||
|
||||
if (!file || !file.path) return null;
|
||||
if (!folderPath) return null;
|
||||
|
||||
const info = isValidElectron
|
||||
? `We found an ${getElectronNameForPlatform()} in this folder.`
|
||||
@@ -208,7 +215,7 @@ export class AddVersionDialog extends React.Component<AddVersionDialogProps, Add
|
||||
isValidElectron: false,
|
||||
isValidVersion: false,
|
||||
version: '',
|
||||
file: undefined
|
||||
folderPath: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
import { Button, ButtonGroup, Callout, Dialog, Label } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { RunnableVersion } from '../../interfaces';
|
||||
import { Bisector } from '../bisect';
|
||||
import { AppState } from '../state';
|
||||
import { VersionSelect } from './version-select';
|
||||
|
||||
export interface BisectDialogProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface BisectDialogState {
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
allVersions: Array<RunnableVersion>;
|
||||
showHelp?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "add version" dialog allows users to add custom builds of Electron.
|
||||
*
|
||||
* @class BisectDialog
|
||||
* @extends {React.Component<BisectDialogProps, BisectDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class BisectDialog extends React.Component<BisectDialogProps, BisectDialogState> {
|
||||
constructor(props: BisectDialogProps) {
|
||||
super(props);
|
||||
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.onBeginSelect = this.onBeginSelect.bind(this);
|
||||
this.onEndSelect = this.onEndSelect.bind(this);
|
||||
this.showHelp = this.showHelp.bind(this);
|
||||
this.isEarliestItemDisabled = this.isEarliestItemDisabled.bind(this);
|
||||
this.isLatestItemDisabled = this.isLatestItemDisabled.bind(this);
|
||||
|
||||
this.state = {
|
||||
allVersions: this.props.appState.versionsToShow,
|
||||
startIndex: 10,
|
||||
endIndex: 0
|
||||
};
|
||||
}
|
||||
|
||||
public onBeginSelect(version: RunnableVersion) {
|
||||
this.setState({ startIndex: this.state.allVersions.indexOf(version) });
|
||||
}
|
||||
|
||||
public onEndSelect(version: RunnableVersion) {
|
||||
this.setState({ endIndex: this.state.allVersions.indexOf(version) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submission of the dialog
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async onSubmit(): Promise<void> {
|
||||
const { endIndex, startIndex, allVersions } = this.state;
|
||||
const { appState } = this.props;
|
||||
|
||||
if (endIndex === undefined || startIndex === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bisectRange = allVersions
|
||||
.slice(endIndex, startIndex + 1);
|
||||
|
||||
appState.Bisector = new Bisector(bisectRange);
|
||||
const initialBisectPivot = appState.Bisector.getCurrentVersion().version;
|
||||
appState.setVersion(initialBisectPivot);
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the dialog
|
||||
*/
|
||||
public onClose() {
|
||||
this.props.appState.isBisectDialogShowing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the additional help
|
||||
*/
|
||||
public showHelp() {
|
||||
this.setState({ showHelp: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Can we get this show on the road?
|
||||
*/
|
||||
get canSubmit(): boolean {
|
||||
return this.state.startIndex > this.state.endIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the buttons
|
||||
*/
|
||||
get buttons() {
|
||||
return [
|
||||
(
|
||||
<Button
|
||||
icon='play'
|
||||
key='submit'
|
||||
disabled={!this.canSubmit}
|
||||
onClick={this.onSubmit}
|
||||
text='Begin'
|
||||
/>
|
||||
), (
|
||||
<Button
|
||||
icon='cross'
|
||||
key='cancel'
|
||||
onClick={this.onClose}
|
||||
text='Cancel'
|
||||
/>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the help
|
||||
*/
|
||||
get help() {
|
||||
let moreHelp = (
|
||||
<Button
|
||||
icon='help'
|
||||
text='Show help'
|
||||
onClick={this.showHelp}
|
||||
/>
|
||||
);
|
||||
|
||||
if (this.state.showHelp) {
|
||||
moreHelp = (
|
||||
<>
|
||||
<p>
|
||||
First, write a fiddle that reproduces a bug or an issue. Then, select the earliest version to
|
||||
start your search with. Typically, that's the "last known good" version that did not have the bug.
|
||||
Then, select that latest version to end the search with, usually the "first known bad" version.
|
||||
</p>
|
||||
<p>
|
||||
Once you begin your bisect, Fiddle will run your fiddle with a number of Electron versions, closing
|
||||
in on the version that introduced the bug. Once completed, you will know which Electron version
|
||||
introduced your issue.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Callout style={{ marginTop: 0, marginBottom: '1rem' }}>
|
||||
<p>
|
||||
A "bisect" is a popular method <a href='https://git-scm.com/docs/git-bisect' target='_blank'>
|
||||
borrowed from <code>git</code></a> for learning which version of Electron introduced a bug. This
|
||||
tool helps you perform a bisect.
|
||||
</p>
|
||||
{moreHelp}
|
||||
</Callout>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isBisectDialogShowing } = this.props.appState;
|
||||
const { startIndex, endIndex, allVersions } = this.state;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={isBisectDialogShowing}
|
||||
onClose={this.onClose}
|
||||
title='Start a bisect session'
|
||||
className='dialog-add-version'
|
||||
>
|
||||
<div className='bp3-dialog-body'>
|
||||
{this.help}
|
||||
<Label>
|
||||
Earliest Version (Last "known good" version)
|
||||
<ButtonGroup fill={true}>
|
||||
<VersionSelect
|
||||
currentVersion={allVersions[startIndex]}
|
||||
appState={this.props.appState}
|
||||
onVersionSelect={this.onBeginSelect}
|
||||
itemDisabled={this.isEarliestItemDisabled}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Label>
|
||||
<Label>
|
||||
Latest Version (First "known bad" version)
|
||||
<ButtonGroup fill={true}>
|
||||
<VersionSelect
|
||||
currentVersion={allVersions[endIndex]}
|
||||
appState={this.props.appState}
|
||||
onVersionSelect={this.onEndSelect}
|
||||
itemDisabled={this.isLatestItemDisabled}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Label>
|
||||
</div>
|
||||
<div className='bp3-dialog-footer'>
|
||||
<div className='bp3-dialog-footer-actions'>
|
||||
{this.buttons}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should an item in the "earliest version" dropdown be disabled?
|
||||
*
|
||||
* @param {RunnableVersion} version
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isEarliestItemDisabled(version: RunnableVersion): boolean {
|
||||
const { allVersions, endIndex } = this.state;
|
||||
|
||||
// In the array, "newer" versions will have a lower index.
|
||||
// 0: 5.0.0
|
||||
// 1: 4.0.0
|
||||
// 2: 3.0.0
|
||||
// ...
|
||||
return allVersions.indexOf(version) < endIndex + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should an item in the "latest version" dropdown be disabled?
|
||||
*
|
||||
* @param {RunnableVersion} version
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isLatestItemDisabled(version: RunnableVersion): boolean {
|
||||
const { allVersions, startIndex } = this.state;
|
||||
|
||||
return allVersions.indexOf(version) > startIndex - 1;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
import { Alert, Intent } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface ConfirmDialogProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface ConfirmDialogState {
|
||||
}
|
||||
|
||||
/**
|
||||
* The token dialog prompts the user to either continue or cancel the operation.
|
||||
*
|
||||
* @export
|
||||
* @class ConfirmDialog
|
||||
* @extends {React.Component<ConfirmDialogProps, ConfirmDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class ConfirmDialog extends React.Component<ConfirmDialogProps, ConfirmDialogState> {
|
||||
constructor(props: ConfirmDialogProps) {
|
||||
super(props);
|
||||
|
||||
this.onClose = this.onClose.bind(this);
|
||||
}
|
||||
|
||||
public onClose(result: boolean) {
|
||||
this.props.appState.confirmationPromptLastResult = result;
|
||||
this.props.appState.toggleConfirmationPromptDialog();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isConfirmationPromptShowing, confirmationDialogTexts } = this.props.appState;
|
||||
|
||||
return (
|
||||
<Alert
|
||||
isOpen={isConfirmationPromptShowing}
|
||||
onClose={this.onClose}
|
||||
icon='help'
|
||||
confirmButtonText={confirmationDialogTexts.ok}
|
||||
cancelButtonText={confirmationDialogTexts.cancel}
|
||||
intent={Intent.PRIMARY}
|
||||
>
|
||||
<p>{confirmationDialogTexts.label}</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
import { Alert, IconName, Intent} from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { GenericDialogType } from '../../../src/interfaces';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface GenericDialogProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface GenericDialogState {
|
||||
}
|
||||
|
||||
/**
|
||||
* The token dialog prompts the user to either continue or cancel the operation.
|
||||
*
|
||||
* @export
|
||||
* @class GenericDialog
|
||||
* @extends {React.Component<GenericDialogProps, GenericDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class GenericDialog extends React.Component<GenericDialogProps, GenericDialogState> {
|
||||
constructor(props: GenericDialogProps) {
|
||||
super(props);
|
||||
|
||||
this.onClose = this.onClose.bind(this);
|
||||
}
|
||||
|
||||
public onClose(result: boolean) {
|
||||
this.props.appState.genericDialogLastResult = result;
|
||||
this.props.appState.toggleGenericDialog();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isGenericDialogShowing, genericDialogOptions } = this.props.appState;
|
||||
const {type, ok, cancel, label} = genericDialogOptions;
|
||||
|
||||
let intent: Intent;
|
||||
let icon: IconName;
|
||||
switch (type) {
|
||||
case GenericDialogType.warning:
|
||||
intent = Intent.DANGER;
|
||||
icon = 'warning-sign';
|
||||
break;
|
||||
case GenericDialogType.confirm:
|
||||
intent = Intent.PRIMARY;
|
||||
icon = 'help';
|
||||
break;
|
||||
case GenericDialogType.success:
|
||||
intent = Intent.SUCCESS;
|
||||
icon = 'info-sign';
|
||||
break;
|
||||
default:
|
||||
intent = Intent.NONE;
|
||||
icon = 'help';
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<Alert
|
||||
isOpen={isGenericDialogShowing}
|
||||
onClose={this.onClose}
|
||||
icon={icon}
|
||||
confirmButtonText={ok}
|
||||
cancelButtonText={cancel}
|
||||
intent={intent}
|
||||
>
|
||||
<p>{label}</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
import { Alert, Intent } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface WarningDialogProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface WarningDialogState {
|
||||
}
|
||||
|
||||
/**
|
||||
* The token dialog prompts the user to either continue or cancel the operation.
|
||||
*
|
||||
* @export
|
||||
* @class WarningDialog
|
||||
* @extends {React.Component<WarningDialogProps, WarningDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class WarningDialog extends React.Component<WarningDialogProps, WarningDialogState> {
|
||||
constructor(props: WarningDialogProps) {
|
||||
super(props);
|
||||
|
||||
this.onClose = this.onClose.bind(this);
|
||||
}
|
||||
|
||||
public onClose(result: boolean) {
|
||||
this.props.appState.warningDialogLastResult = result;
|
||||
this.props.appState.toggleWarningDialog();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isWarningDialogShowing, warningDialogTexts } = this.props.appState;
|
||||
return (
|
||||
<Alert
|
||||
isOpen={isWarningDialogShowing}
|
||||
onClose={this.onClose}
|
||||
icon='warning-sign'
|
||||
confirmButtonText={warningDialogTexts.ok}
|
||||
cancelButtonText={warningDialogTexts.cancel}
|
||||
intent={Intent.DANGER}
|
||||
>
|
||||
<p>{warningDialogTexts.label}</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ import * as React from 'react';
|
||||
import { AppState } from '../state';
|
||||
import { AddThemeDialog } from './dialog-add-theme';
|
||||
import { AddVersionDialog } from './dialog-add-version';
|
||||
import { ConfirmDialog } from './dialog-confirm';
|
||||
import { BisectDialog } from './dialog-bisect';
|
||||
import { GenericDialog } from './dialog-generic';
|
||||
import { TokenDialog } from './dialog-token';
|
||||
import { WarningDialog } from './dialog-warning';
|
||||
import { Settings } from './settings';
|
||||
|
||||
export interface DialogsProps {
|
||||
@@ -23,7 +23,14 @@ export interface DialogsProps {
|
||||
export class Dialogs extends React.Component<DialogsProps, {}> {
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
const { isTokenDialogShowing, isSettingsShowing, isAddVersionDialogShowing, isThemeDialogShowing } = appState;
|
||||
const {
|
||||
isTokenDialogShowing,
|
||||
isSettingsShowing,
|
||||
isAddVersionDialogShowing,
|
||||
isThemeDialogShowing,
|
||||
isBisectDialogShowing,
|
||||
isGenericDialogShowing
|
||||
} = appState;
|
||||
const maybeToken = isTokenDialogShowing
|
||||
? <TokenDialog key='dialogs' appState={appState} />
|
||||
: null;
|
||||
@@ -35,9 +42,12 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
|
||||
: null;
|
||||
const maybeMonaco = isThemeDialogShowing ? <AddThemeDialog appState={appState} />
|
||||
: null;
|
||||
const eitherWarningOrPrompt = appState.isWarningDialogShowing
|
||||
? <WarningDialog appState={appState} />
|
||||
: <ConfirmDialog appState={appState}/>;
|
||||
const maybeBisect = isBisectDialogShowing
|
||||
? <BisectDialog key='bisect-dialog' appState={appState} />
|
||||
: null;
|
||||
const genericDialog = isGenericDialogShowing
|
||||
? <GenericDialog appState={appState} />
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div key='dialogs' className='dialogs'>
|
||||
@@ -45,7 +55,8 @@ export class Dialogs extends React.Component<DialogsProps, {}> {
|
||||
{maybeSettings}
|
||||
{maybeAddLocalVersion}
|
||||
{maybeMonaco}
|
||||
{eitherWarningOrPrompt}
|
||||
{maybeBisect}
|
||||
{genericDialog}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface EditorProps {
|
||||
options?: Partial<MonacoType.editor.IEditorConstructionOptions>;
|
||||
editorDidMount?: (editor: MonacoType.editor.IStandaloneCodeEditor) => void;
|
||||
onChange?: (value: string, event: MonacoType.editor.IModelContentChangedEvent) => void;
|
||||
setFocused: (id: EditorId) => void;
|
||||
}
|
||||
|
||||
export class Editor extends React.Component<EditorProps> {
|
||||
@@ -28,7 +29,16 @@ export class Editor extends React.Component<EditorProps> {
|
||||
constructor(props: EditorProps) {
|
||||
super(props);
|
||||
|
||||
this.language = props.id === 'html' ? 'html' : 'javascript';
|
||||
switch (props.id) {
|
||||
case 'html':
|
||||
this.language = 'html';
|
||||
break;
|
||||
case 'css':
|
||||
this.language = 'css';
|
||||
break;
|
||||
default:
|
||||
this.language = 'javascript';
|
||||
}
|
||||
}
|
||||
|
||||
public shouldComponentUpdate() {
|
||||
@@ -80,6 +90,12 @@ export class Editor extends React.Component<EditorProps> {
|
||||
...monacoOptions
|
||||
});
|
||||
|
||||
// mark this editor as focused whenever it is
|
||||
this.editor.onDidFocusEditorText(() => {
|
||||
const { id, setFocused } = this.props;
|
||||
setFocused(id);
|
||||
});
|
||||
|
||||
await this.editorDidMount(this.editor);
|
||||
}
|
||||
}
|
||||
@@ -98,6 +114,23 @@ export class Editor extends React.Component<EditorProps> {
|
||||
return <div className='editorContainer' ref={this.containerRef} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a model and attach it to the editor
|
||||
*
|
||||
* @private
|
||||
* @param {string} value
|
||||
*/
|
||||
private async createModel(value: string) {
|
||||
const { monaco } = this.props;
|
||||
|
||||
const model = monaco.editor.createModel(value, this.language);
|
||||
model.updateOptions({
|
||||
tabSize: 2
|
||||
});
|
||||
|
||||
this.editor.setModel(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content on the editor, including the model and the view state.
|
||||
*
|
||||
@@ -105,27 +138,27 @@ export class Editor extends React.Component<EditorProps> {
|
||||
* @memberof Editor
|
||||
*/
|
||||
private async setContent() {
|
||||
const { appState, id, monaco } = this.props;
|
||||
const { appState, id } = this.props;
|
||||
const { version } = appState;
|
||||
|
||||
const backup = appState.getAndRemoveEditorValueBackup(id);
|
||||
|
||||
if (backup && backup.model) {
|
||||
if (backup) {
|
||||
console.log(`Editor: Backup found, restoring state`);
|
||||
|
||||
if (backup.viewState) {
|
||||
this.editor.restoreViewState(backup.viewState);
|
||||
}
|
||||
|
||||
this.editor.setModel(backup.model);
|
||||
// If there's a model, use the model. No model? Use the value
|
||||
if (backup.model) {
|
||||
this.editor.setModel(backup.model);
|
||||
} else {
|
||||
this.createModel(backup.value ?? '');
|
||||
}
|
||||
} else {
|
||||
const value = await getContent(id, version);
|
||||
const model = monaco.editor.createModel(value, this.language);
|
||||
model.updateOptions({
|
||||
tabSize: 2
|
||||
});
|
||||
|
||||
this.editor.setModel(model);
|
||||
await this.createModel(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { reaction } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
// tslint:disable-next-line:no-submodule-imports
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
import * as React from 'react';
|
||||
import { Mosaic, MosaicBranch, MosaicNode, MosaicWindow, MosaicWindowProps } from 'react-mosaic-component';
|
||||
|
||||
@@ -21,7 +20,7 @@ import { renderNonIdealState } from './editors-non-ideal-state';
|
||||
import { DocsDemoGoHomeButton, MaximizeButton, RemoveButton } from './editors-toolbar-button';
|
||||
import { ShowMe } from './show-me';
|
||||
|
||||
const defaultMonacoOptions: monaco.editor.IEditorOptions = {
|
||||
const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
@@ -33,6 +32,7 @@ export const TITLE_MAP: Record<MosaicId, string> = {
|
||||
renderer: 'Renderer Process (renderer.js)',
|
||||
preload: 'Preload (preload.js)',
|
||||
html: 'HTML (index.html)',
|
||||
css: 'Stylesheet (styles.css)',
|
||||
docsDemo: 'Docs & Demos',
|
||||
};
|
||||
|
||||
@@ -41,9 +41,10 @@ export interface EditorsProps {
|
||||
}
|
||||
|
||||
export interface EditorsState {
|
||||
monaco?: typeof monaco;
|
||||
monaco?: typeof MonacoType;
|
||||
isMounted?: boolean;
|
||||
monacoOptions: monaco.editor.IEditorOptions;
|
||||
monacoOptions: MonacoType.editor.IEditorOptions;
|
||||
focused?: EditorId;
|
||||
}
|
||||
|
||||
@observer
|
||||
@@ -62,10 +63,9 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
this.renderEditor = this.renderEditor.bind(this);
|
||||
this.renderTile = this.renderTile.bind(this);
|
||||
this.renderGenericPanel = this.renderGenericPanel.bind(this);
|
||||
this.setFocused = this.setFocused.bind(this);
|
||||
|
||||
this.state = { monacoOptions: defaultMonacoOptions };
|
||||
|
||||
this.loadMonaco();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
*
|
||||
* @memberof Editors
|
||||
*/
|
||||
public componentDidMount() {
|
||||
public async componentDidMount() {
|
||||
ipcRendererManager.on(IpcEvents.MONACO_EXECUTE_COMMAND, (_event, cmd: string) => {
|
||||
this.executeCommand(cmd);
|
||||
});
|
||||
@@ -93,10 +93,16 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
});
|
||||
|
||||
this.setState({ isMounted: true });
|
||||
await this.loadMonaco();
|
||||
this.props.appState.isUnsaved = false;
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.disposeLayoutAutorun();
|
||||
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.MONACO_EXECUTE_COMMAND);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.FS_NEW_FIDDLE);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.MONACO_TOGGLE_OPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,7 +139,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
|
||||
Object.keys(window.ElectronFiddle.editors)
|
||||
.forEach((key) => {
|
||||
const editor: monaco.editor.IStandaloneCodeEditor | null
|
||||
const editor: MonacoType.editor.IStandaloneCodeEditor | null
|
||||
= window.ElectronFiddle.editors[key];
|
||||
|
||||
if (editor) {
|
||||
@@ -161,10 +167,22 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
public renderToolbar(
|
||||
{ title }: MosaicWindowProps<MosaicId>, id: MosaicId
|
||||
): JSX.Element {
|
||||
const { appState } = this.props;
|
||||
const docsDemoGoHomeMaybe = id === PanelId.docsDemo
|
||||
? <DocsDemoGoHomeButton id={id} appState={this.props.appState} />
|
||||
? <DocsDemoGoHomeButton id={id} appState={appState} />
|
||||
: null;
|
||||
|
||||
// only show toolbar controls if we have more than 1 visible editor
|
||||
// Mosaic arrangement is type string if 1 editor, object otherwise
|
||||
const toolbarControlsMaybe =
|
||||
(typeof appState.mosaicArrangement !== 'string') &&
|
||||
(
|
||||
<>
|
||||
<MaximizeButton id={id} appState={appState} />
|
||||
<RemoveButton id={id} appState={appState} />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Left */}
|
||||
@@ -178,8 +196,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
{/* Right */}
|
||||
<div className='mosaic-controls'>
|
||||
{docsDemoGoHomeMaybe}
|
||||
<MaximizeButton id={id} appState={this.props.appState} />
|
||||
<RemoveButton id={id} appState={this.props.appState} />
|
||||
{toolbarControlsMaybe}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -230,6 +247,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
*/
|
||||
public renderEditor(id: EditorId): JSX.Element | null {
|
||||
const { appState } = this.props;
|
||||
const { monaco } = this.state;
|
||||
|
||||
return (
|
||||
<Editor
|
||||
@@ -237,17 +255,20 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
monaco={monaco!}
|
||||
appState={appState}
|
||||
monacoOptions={defaultMonacoOptions}
|
||||
setFocused={this.setFocused}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
const { monaco } = this.state;
|
||||
|
||||
if (!monaco) return null;
|
||||
|
||||
return (
|
||||
<Mosaic<EditorId | PanelId>
|
||||
className={`focused__${this.state.focused}`}
|
||||
onChange={this.onChange}
|
||||
value={appState.mosaicArrangement}
|
||||
zeroStateView={renderNonIdealState(appState)}
|
||||
@@ -270,8 +291,10 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
* We're doing things a bit roundabout to ensure that we're not overloading the
|
||||
* mobx state with a gigantic Monaco tree.
|
||||
*/
|
||||
public async loadMonaco(): Promise<void> {
|
||||
public async loadMonaco() {
|
||||
const { app } = window.ElectronFiddle;
|
||||
const loader = require('monaco-loader');
|
||||
const monaco = app.monaco || await loader();
|
||||
|
||||
if (!app.monaco) {
|
||||
app.monaco = monaco;
|
||||
@@ -286,6 +309,16 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
this.setState({ monaco });
|
||||
}
|
||||
|
||||
activateTheme(monaco, undefined, this.props.appState.theme);
|
||||
await activateTheme(monaco, undefined, this.props.appState.theme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the currently-focused editor. This will impact the editor's
|
||||
* z-index, ensuring that its intellisense menus don't get clipped
|
||||
* by the other editor windows.
|
||||
* @param id Editor ID
|
||||
*/
|
||||
public setFocused(id: EditorId): void {
|
||||
this.setState({ focused: id });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ import {
|
||||
HTMLTable,
|
||||
IButtonProps,
|
||||
Icon,
|
||||
IconName
|
||||
IconName,
|
||||
Tooltip,
|
||||
ProgressBar
|
||||
} from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ElectronVersion, ElectronVersionSource, ElectronVersionState } from '../../interfaces';
|
||||
import { RunnableVersion, VersionSource, VersionState } from '../../interfaces';
|
||||
import { normalizeVersion } from '../../utils/normalize-version';
|
||||
import { sortedElectronMap } from '../../utils/sorted-electron-map';
|
||||
import { AppState } from '../state';
|
||||
@@ -69,7 +71,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
if (!checked) {
|
||||
appState.statesToShow = appState.statesToShow.filter((s) => s !== id);
|
||||
} else {
|
||||
appState.statesToShow.push(id as ElectronVersionState);
|
||||
appState.statesToShow.push(id as VersionState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +87,9 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
const { appState } = this.props;
|
||||
|
||||
if (!checked) {
|
||||
appState.versionsToShow = appState.versionsToShow.filter((c) => c !== id);
|
||||
appState.channelsToShow = appState.channelsToShow.filter((c) => c !== id);
|
||||
} else {
|
||||
appState.versionsToShow.push(id as ElectronReleaseChannel);
|
||||
appState.channelsToShow.push(id as ElectronReleaseChannel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +204,7 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
*/
|
||||
private renderVersionStateOptions(): JSX.Element {
|
||||
const { appState } = this.props;
|
||||
const getIsChecked = (state: ElectronVersionState) => {
|
||||
const getIsChecked = (state: VersionState) => {
|
||||
return appState.statesToShow.includes(state);
|
||||
};
|
||||
|
||||
@@ -210,22 +212,29 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
<FormGroup
|
||||
label='Include Electron versions that are:'
|
||||
>
|
||||
<Tooltip
|
||||
content='Always enabled'
|
||||
position='bottom'
|
||||
intent='primary'
|
||||
>
|
||||
<Checkbox
|
||||
checked={getIsChecked(VersionState.ready)}
|
||||
label='Ready'
|
||||
id='ready'
|
||||
onChange={this.handleStateChange}
|
||||
inline={true}
|
||||
disabled={true}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronVersionState.downloading)}
|
||||
checked={getIsChecked(VersionState.downloading)}
|
||||
label='Downloading'
|
||||
id='downloading'
|
||||
onChange={this.handleStateChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronVersionState.ready)}
|
||||
label='Downloaded'
|
||||
id='ready'
|
||||
onChange={this.handleStateChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronVersionState.unknown)}
|
||||
checked={getIsChecked(VersionState.unknown)}
|
||||
label='Not Downloaded'
|
||||
id='unknown'
|
||||
onChange={this.handleStateChange}
|
||||
@@ -243,42 +252,47 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
*/
|
||||
private renderVersionChannelOptions(): JSX.Element {
|
||||
const { appState } = this.props;
|
||||
|
||||
const getIsChecked = (channel: ElectronReleaseChannel) => {
|
||||
return appState.versionsToShow.includes(channel);
|
||||
return appState.channelsToShow.includes(channel);
|
||||
};
|
||||
|
||||
const getIsCurrentVersionReleaseChannel = (channel: ElectronReleaseChannel) => {
|
||||
return getReleaseChannel(appState.version) === channel;
|
||||
};
|
||||
|
||||
const channels = {
|
||||
stable: ElectronReleaseChannel.stable,
|
||||
beta: ElectronReleaseChannel.beta,
|
||||
nightly: ElectronReleaseChannel.nightly,
|
||||
unsupported: ElectronReleaseChannel.unsupported
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
label='Include Electron versions from these release channels:'
|
||||
>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.stable)}
|
||||
label='Stable'
|
||||
id='Stable'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.beta)}
|
||||
label='Beta'
|
||||
id='Beta'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.nightly)}
|
||||
label='Nightly'
|
||||
id='Nightly'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(ElectronReleaseChannel.unsupported)}
|
||||
label='Unsupported'
|
||||
id='Unsupported'
|
||||
onChange={this.handleChannelChange}
|
||||
inline={true}
|
||||
/>
|
||||
{
|
||||
// tslint:disable-next-line:jsx-no-multiline-js
|
||||
Object.entries(channels).map(([_, channel]) => (
|
||||
<Tooltip
|
||||
content={`Can't disable channel of selected version (${appState.version})`}
|
||||
disabled={!getIsCurrentVersionReleaseChannel(channel)}
|
||||
position='bottom'
|
||||
intent='primary'
|
||||
key={channel}
|
||||
>
|
||||
<Checkbox
|
||||
checked={getIsChecked(channel)}
|
||||
label={channel}
|
||||
id={channel}
|
||||
onChange={this.handleChannelChange}
|
||||
disabled={getIsCurrentVersionReleaseChannel(channel)}
|
||||
inline={true}
|
||||
/>
|
||||
</Tooltip>
|
||||
))
|
||||
}
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
@@ -316,11 +330,11 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
* @returns {Array<JSX.Element>}
|
||||
*/
|
||||
private renderTableRows(): Array<JSX.Element | null> {
|
||||
const { versions, versionsToShow, statesToShow } = this.props.appState;
|
||||
const { versions, channelsToShow, statesToShow } = this.props.appState;
|
||||
|
||||
return sortedElectronMap<JSX.Element | null>(versions, (key, item) => {
|
||||
// Check if we want to show the version
|
||||
if (!versionsToShow.includes(getReleaseChannel(item))) {
|
||||
if (!channelsToShow.includes(getReleaseChannel(item))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -342,25 +356,25 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
/**
|
||||
* Returns a human-readable state indicator for an Electron version
|
||||
*
|
||||
* @param {ElectronVersion} item
|
||||
* @param {RunnableVersion} item
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
private renderHumanState(item: ElectronVersion): JSX.Element {
|
||||
private renderHumanState(item: RunnableVersion): JSX.Element {
|
||||
const { state } = item;
|
||||
let icon: IconName = 'box';
|
||||
|
||||
if (state === VersionState.downloading || state === VersionState.unzipping) {
|
||||
return <ProgressBar value={item.downloadProgress} />;
|
||||
}
|
||||
|
||||
let humanState = 'Downloaded';
|
||||
|
||||
if (state === ElectronVersionState.downloading) {
|
||||
icon = 'cloud-download';
|
||||
humanState = 'Downloading';
|
||||
} else if (state === ElectronVersionState.unknown) {
|
||||
icon = 'cloud';
|
||||
if (state === VersionState.unknown) {
|
||||
humanState = 'Not downloaded';
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Icon icon={icon} /> {humanState}
|
||||
{humanState}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -370,10 +384,10 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
*
|
||||
* @private
|
||||
* @param {string} key
|
||||
* @param {ElectronVersion} item
|
||||
* @param {RunnableVersion} item
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
private renderAction(key: string, item: ElectronVersion): JSX.Element {
|
||||
private renderAction(key: string, item: RunnableVersion): JSX.Element {
|
||||
const { state, source } = item;
|
||||
const { appState } = this.props;
|
||||
const buttonProps: IButtonProps = {
|
||||
@@ -382,17 +396,23 @@ export class ElectronSettings extends React.Component<ElectronSettingsProps, Ele
|
||||
};
|
||||
|
||||
// Already downloaded
|
||||
if (state === 'ready') {
|
||||
if (state === VersionState.ready) {
|
||||
buttonProps.disabled = false;
|
||||
buttonProps.onClick = () => appState.removeVersion(key);
|
||||
buttonProps.icon = 'trash';
|
||||
buttonProps.text = source === ElectronVersionSource.local
|
||||
buttonProps.text = source === VersionSource.local
|
||||
? 'Remove'
|
||||
: 'Delete';
|
||||
} else if (state === 'downloading') {
|
||||
} else if (state === VersionState.downloading) {
|
||||
buttonProps.disabled = true;
|
||||
buttonProps.loading = true;
|
||||
buttonProps.text = 'Downloading';
|
||||
buttonProps.icon = 'cloud-download';
|
||||
} else if (state === VersionState.unzipping) {
|
||||
buttonProps.disabled = true;
|
||||
buttonProps.loading = true;
|
||||
buttonProps.text = 'Unzipping';
|
||||
buttonProps.icon = 'cloud-download';
|
||||
} else {
|
||||
buttonProps.disabled = false;
|
||||
buttonProps.loading = false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Callout, Checkbox, FormGroup } from '@blueprintjs/core';
|
||||
import { Callout, Checkbox, FormGroup, InputGroup } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -21,6 +21,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
|
||||
|
||||
this.handleDeleteDataChange = this.handleDeleteDataChange.bind(this);
|
||||
this.handleElectronLoggingChange = this.handleElectronLoggingChange.bind(this);
|
||||
this.handleExecutionFlagChange = this.handleExecutionFlagChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,8 +49,25 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
|
||||
this.props.appState.isEnablingElectronLogging = checked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a change in the execution flags run with the Electron executable
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public handleExecutionFlagChange(
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) {
|
||||
const { value } = event.currentTarget;
|
||||
const flags = value.split('|');
|
||||
this.props.appState.executionFlags = flags;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isKeepingUserDataDirs, isEnablingElectronLogging } = this.props.appState;
|
||||
const {
|
||||
isKeepingUserDataDirs,
|
||||
isEnablingElectronLogging,
|
||||
executionFlags = []
|
||||
} = this.props.appState;
|
||||
|
||||
const deleteUserDirLabel = `
|
||||
Whenever Electron runs, it creates a user data directory for cookies, the cache,
|
||||
@@ -87,6 +105,26 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {
|
||||
/>
|
||||
</FormGroup>
|
||||
</Callout>
|
||||
<br />
|
||||
<Callout>
|
||||
<FormGroup>
|
||||
<p>
|
||||
Electron allows starting the executable with <a
|
||||
href='https://electronjs.org/docs/api/chrome-command-line-switches'
|
||||
>
|
||||
user-provided flags
|
||||
</a>
|
||||
, such as '--js-flags=--expose-gc'. Those can be added here as bar-separated (|)
|
||||
flags to run when you start your Fiddles.
|
||||
</p>
|
||||
<br />
|
||||
<InputGroup
|
||||
placeholder='--js-flags=--expose-gc|--lang=es'
|
||||
value={executionFlags.join('|')}
|
||||
onChange={this.handleExecutionFlagChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Callout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Button, Callout, FormGroup, MenuItem } from '@blueprintjs/core';
|
||||
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
|
||||
import { shell } from 'electron';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as fsType from 'fs-extra';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
@@ -19,7 +19,7 @@ const ThemeSelect = Select.ofType<LoadedFiddleTheme>();
|
||||
* version.
|
||||
*
|
||||
* @param {string} query
|
||||
* @param {ElectronVersion} { version }
|
||||
* @param {RunnableVersion} { version }
|
||||
* @returns
|
||||
*/
|
||||
export const filterItem: ItemPredicate<LoadedFiddleTheme> = (query, { name }) => {
|
||||
@@ -31,7 +31,7 @@ export const filterItem: ItemPredicate<LoadedFiddleTheme> = (query, { name }) =>
|
||||
* Helper method: Returns the <Select /> <MenuItem /> for Electron
|
||||
* versions.
|
||||
*
|
||||
* @param {ElectronVersion} item
|
||||
* @param {RunnableVersion} item
|
||||
* @param {IItemRendererProps} { handleClick, modifiers, query }
|
||||
* @returns
|
||||
*/
|
||||
@@ -113,6 +113,7 @@ export class AppearanceSettings extends React.Component<
|
||||
*/
|
||||
public async createNewThemeFromCurrent(): Promise<boolean> {
|
||||
const { appState } = this.props;
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const theme = await getTheme(appState.theme);
|
||||
|
||||
try {
|
||||
@@ -146,6 +147,8 @@ export class AppearanceSettings extends React.Component<
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async openThemeFolder(): Promise<boolean> {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
|
||||
try {
|
||||
await fs.ensureDir(THEMES_PATH);
|
||||
await shell.showItemInFolder(THEMES_PATH);
|
||||
|
||||
@@ -29,8 +29,8 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
<>
|
||||
<p>
|
||||
Electron Fiddle allows you to build little experiments and mini-apps with
|
||||
Electron. Each Fiddle has three files: A main script, a renderer script,
|
||||
and an HTML file.
|
||||
Electron. Each Fiddle has at least three of these files: A main script, a
|
||||
renderer script, a preload script, and an HTML file.
|
||||
</p>
|
||||
<p>
|
||||
If you <code>require()</code> a module, Fiddle will install
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { Button, IButtonGroupProps, MenuItem } from '@blueprintjs/core';
|
||||
import { ItemPredicate, ItemRenderer, Select } from '@blueprintjs/select';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { RunnableVersion, VersionSource, VersionState } from '../../interfaces';
|
||||
import { highlightText } from '../../utils/highlight-text';
|
||||
import { AppState } from '../state';
|
||||
|
||||
const ElectronVersionSelect = Select.ofType<RunnableVersion>();
|
||||
|
||||
/**
|
||||
* Helper method: Returns the <Select /> label for an Electron
|
||||
* version.
|
||||
*
|
||||
* @param {RunnableVersion} { source, state }
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getItemLabel({ source, state, name }: RunnableVersion): string {
|
||||
let label = '';
|
||||
|
||||
if (source === VersionSource.local) {
|
||||
label = name || 'Local';
|
||||
} else {
|
||||
if (state === VersionState.unknown) {
|
||||
label = `Not downloaded`;
|
||||
} else if (state === VersionState.ready) {
|
||||
label = `Downloaded`;
|
||||
} else if (state === VersionState.downloading) {
|
||||
label = `Downloading`;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method: Returns the <Select /> icon for an Electron
|
||||
* version.
|
||||
*
|
||||
* @param {RunnableVersion} { state }
|
||||
* @returns
|
||||
*/
|
||||
export function getItemIcon({ state }: RunnableVersion) {
|
||||
return state === 'ready'
|
||||
? 'saved'
|
||||
: state === 'downloading' ? 'cloud-download' : 'cloud';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method: Returns the <Select /> predicate for an Electron
|
||||
* version.
|
||||
*
|
||||
* @param {string} query
|
||||
* @param {RunnableVersion} { version }
|
||||
* @returns
|
||||
*/
|
||||
export const filterItem: ItemPredicate<RunnableVersion> = (query, { version }) => {
|
||||
return version.toLowerCase().includes(query.toLowerCase());
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper method: Returns the <Select /> <MenuItem /> for Electron
|
||||
* versions.
|
||||
*
|
||||
* @param {RunnableVersion} item
|
||||
* @param {IItemRendererProps} { handleClick, modifiers, query }
|
||||
* @returns
|
||||
*/
|
||||
export const renderItem: ItemRenderer<RunnableVersion> = (item, { handleClick, modifiers, query }) => {
|
||||
if (!modifiers.matchesPredicate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
text={highlightText(item.version, query)}
|
||||
key={item.version}
|
||||
onClick={handleClick}
|
||||
label={getItemLabel(item)}
|
||||
icon={getItemIcon(item)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export interface VersionSelectState {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface VersionSelectProps {
|
||||
appState: AppState;
|
||||
disabled?: boolean;
|
||||
currentVersion: RunnableVersion;
|
||||
onVersionSelect: (version: RunnableVersion) => void;
|
||||
buttonGroupProps?: IButtonGroupProps;
|
||||
itemDisabled?: keyof RunnableVersion | ((item: RunnableVersion, index: number) => boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* A dropdown allowing the selection of Electron versions. The actual
|
||||
* download is managed in the state.
|
||||
*
|
||||
* @class VersionSelect
|
||||
* @extends {React.Component<VersionSelectProps, VersionSelectState>}
|
||||
*/
|
||||
@observer
|
||||
export class VersionSelect extends React.Component<VersionSelectProps, VersionSelectState> {
|
||||
public render() {
|
||||
const { currentVersion, itemDisabled } = this.props;
|
||||
const { version } = currentVersion;
|
||||
|
||||
return (
|
||||
<ElectronVersionSelect
|
||||
filterable={true}
|
||||
items={this.props.appState.versionsToShow}
|
||||
itemRenderer={renderItem}
|
||||
itemPredicate={filterItem}
|
||||
itemDisabled={itemDisabled}
|
||||
onItemSelect={this.props.onVersionSelect}
|
||||
noResults={<MenuItem disabled={true} text='No results.' />}
|
||||
disabled={!!this.props.disabled}
|
||||
>
|
||||
<Button
|
||||
className='version-chooser'
|
||||
text={`Electron v${version}`}
|
||||
icon={getItemIcon(currentVersion)}
|
||||
disabled={!!this.props.disabled}
|
||||
/>
|
||||
</ElectronVersionSelect>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import * as path from 'path';
|
||||
import { MosaicNode } from 'react-mosaic-component';
|
||||
|
||||
import { EditorId, MosaicId } from '../interfaces';
|
||||
import { EditorBackup } from '../utils/editor-backup';
|
||||
|
||||
// Reminder: When testing, this file is mocked in tests/setup.js
|
||||
|
||||
@@ -19,5 +20,9 @@ export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
|
||||
}
|
||||
};
|
||||
|
||||
export const DEFAULT_CLOSED_PANELS: Partial<Record<MosaicId, EditorBackup | true>> = {
|
||||
docsDemo: true
|
||||
};
|
||||
|
||||
export const ELECTRON_ORG = 'electron';
|
||||
export const ELECTRON_REPO = 'electron';
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as fsType from 'fs-extra';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ElectronVersion, ElectronVersionSource } from '../interfaces';
|
||||
import { RunnableVersion, VersionSource } from '../interfaces';
|
||||
import { callIn } from '../utils/call-in';
|
||||
import { fancyImport } from '../utils/import';
|
||||
import { normalizeVersion } from '../utils/normalize-version';
|
||||
import { USER_DATA_PATH } from './constants';
|
||||
|
||||
const definitionPath = path.join(USER_DATA_PATH, 'electron-typedef');
|
||||
@@ -35,6 +37,26 @@ export async function fetchTypeDefinitions(version: string): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the type definition for a given version
|
||||
*
|
||||
* @param version
|
||||
*/
|
||||
export async function removeTypeDefsForVersion(version: string) {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const _version = normalizeVersion(version);
|
||||
const typeDefsDir = path.dirname(getOfflineTypeDefinitionPath(_version));
|
||||
|
||||
if (fs.existsSync(typeDefsDir)) {
|
||||
try {
|
||||
await fs.remove(typeDefsDir);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the path for offline TypeScript definitions
|
||||
*
|
||||
@@ -52,6 +74,7 @@ export function getOfflineTypeDefinitionPath(version: string): string {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export async function getOfflineTypeDefinitions(version: string): Promise<boolean> {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
return fs.existsSync(getOfflineTypeDefinitionPath(version));
|
||||
}
|
||||
|
||||
@@ -62,7 +85,8 @@ export async function getOfflineTypeDefinitions(version: string): Promise<boolea
|
||||
* @param {string} version
|
||||
* @returns {void}
|
||||
*/
|
||||
export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Promise<string | null> {
|
||||
export async function getDownloadedVersionTypeDefs(version: RunnableVersion): Promise<string | null> {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
await fs.mkdirp(definitionPath);
|
||||
const offlinePath = getOfflineTypeDefinitionPath(version.version);
|
||||
|
||||
@@ -88,8 +112,9 @@ export async function getDownloadedVersionTypeDefs(version: ElectronVersion): Pr
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLocalVersionTypeDefs(version: ElectronVersion) {
|
||||
if (version.source === ElectronVersionSource.local && !!version.localPath) {
|
||||
export async function getLocalVersionTypeDefs(version: RunnableVersion) {
|
||||
if (version.source === VersionSource.local && !!version.localPath) {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const typesPath = getLocalTypePathForVersion(version);
|
||||
if (!!typesPath && fs.existsSync(typesPath)) {
|
||||
return fs.readFile(typesPath, 'utf-8');
|
||||
@@ -103,7 +128,7 @@ export async function getLocalVersionTypeDefs(version: ElectronVersion) {
|
||||
*
|
||||
* @param {string} version
|
||||
*/
|
||||
export async function updateEditorTypeDefinitions(version: ElectronVersion, i: number = 0): Promise<void> {
|
||||
export async function updateEditorTypeDefinitions(version: RunnableVersion, i: number = 0): Promise<void> {
|
||||
const defer = async (): Promise<void> => {
|
||||
if (i > 10) {
|
||||
console.warn(`Fetch Types: Failed, dependencies do not exist`);
|
||||
@@ -121,7 +146,7 @@ export async function updateEditorTypeDefinitions(version: ElectronVersion, i: n
|
||||
const monaco: typeof MonacoType = app.monaco!;
|
||||
const typeDefDisposable: MonacoType.IDisposable = app.typeDefDisposable!;
|
||||
|
||||
const getTypeDefs = (version.source === ElectronVersionSource.local) ?
|
||||
const getTypeDefs = (version.source === VersionSource.local) ?
|
||||
getLocalVersionTypeDefs : getDownloadedVersionTypeDefs;
|
||||
|
||||
const typeDefs = await getTypeDefs(version);
|
||||
@@ -139,7 +164,7 @@ export async function updateEditorTypeDefinitions(version: ElectronVersion, i: n
|
||||
}
|
||||
}
|
||||
|
||||
export function getLocalTypePathForVersion(version: ElectronVersion) {
|
||||
export function getLocalTypePathForVersion(version: RunnableVersion) {
|
||||
if (version.localPath) {
|
||||
return path.join(
|
||||
version.localPath,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as fsType from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
import { EditorValues, Files, FileTransform } from '../interfaces';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME, STYLES_CSS_NAME } from '../shared-constants';
|
||||
import { DEFAULT_OPTIONS, PackageJsonOptions } from '../utils/get-package';
|
||||
import { fancyImport } from '../utils/import';
|
||||
import { ipcRendererManager } from './ipc';
|
||||
import { AppState } from './state';
|
||||
import { getTemplateValues } from './templates';
|
||||
@@ -16,6 +17,11 @@ export class FileManager {
|
||||
this.openFiddle = this.openFiddle.bind(this);
|
||||
this.saveFiddle = this.saveFiddle.bind(this);
|
||||
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.FS_OPEN_FIDDLE);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.FS_OPEN_TEMPLATE);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.FS_SAVE_FIDDLE);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.FS_SAVE_FIDDLE_FORGE);
|
||||
|
||||
ipcRendererManager.on(IpcEvents.FS_OPEN_FIDDLE, (_event, filePath) => {
|
||||
this.openFiddle(filePath);
|
||||
});
|
||||
@@ -62,6 +68,7 @@ export class FileManager {
|
||||
main: await this.readFile(path.join(filePath, MAIN_JS_NAME)),
|
||||
renderer: await this.readFile(path.join(filePath, RENDERER_JS_NAME)),
|
||||
preload: await this.readFile(path.join(filePath, PRELOAD_JS_NAME)),
|
||||
css: await this.readFile(path.join(filePath, STYLES_CSS_NAME))
|
||||
};
|
||||
|
||||
|
||||
@@ -103,7 +110,6 @@ export class FileManager {
|
||||
}
|
||||
|
||||
this.appState.isUnsaved = false;
|
||||
window.ElectronFiddle.app.setupUnsavedOnChangeListener();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +131,7 @@ export class FileManager {
|
||||
output.set(MAIN_JS_NAME, values.main);
|
||||
output.set(INDEX_HTML_NAME, values.html);
|
||||
output.set(PRELOAD_JS_NAME, values.preload);
|
||||
output.set(STYLES_CSS_NAME, values.css);
|
||||
output.set(PACKAGE_NAME, values.package!);
|
||||
|
||||
for (const transform of transforms) {
|
||||
@@ -148,6 +155,8 @@ export class FileManager {
|
||||
*/
|
||||
public async cleanup(dir?: string): Promise<boolean> {
|
||||
if (dir) {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
|
||||
if (fs.existsSync(dir)) {
|
||||
try {
|
||||
await fs.remove(dir);
|
||||
@@ -172,6 +181,7 @@ export class FileManager {
|
||||
public async saveToTemp(
|
||||
options: PackageJsonOptions, ...transforms: Array<FileTransform>
|
||||
): Promise<string> {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const tmp = await import('tmp');
|
||||
const files = await this.getFiles(options, ...transforms);
|
||||
const dir = tmp.dirSync();
|
||||
@@ -199,6 +209,7 @@ export class FileManager {
|
||||
*/
|
||||
private async readFile(filePath: string): Promise<string> {
|
||||
try {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
return await fs.readFile(filePath, 'utf-8');
|
||||
} catch (error) {
|
||||
console.log(`FileManager: Could not read ${filePath}`, error);
|
||||
@@ -216,6 +227,7 @@ export class FileManager {
|
||||
*/
|
||||
private async saveFile(filePath: string, content: string): Promise<void> {
|
||||
try {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
return await fs.outputFile(filePath, content, { encoding: 'utf-8' });
|
||||
} catch (error) {
|
||||
console.log(`FileManager: Could not save ${filePath}`, error);
|
||||
@@ -233,6 +245,7 @@ export class FileManager {
|
||||
*/
|
||||
private async removeFile(filePath: string): Promise<void> {
|
||||
try {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
return await fs.remove(filePath);
|
||||
} catch (error) {
|
||||
console.log(`FileManager: Could not remove ${filePath}`, error);
|
||||
|
||||
@@ -15,6 +15,7 @@ export class IpcRendererManager extends EventEmitter {
|
||||
super();
|
||||
|
||||
ipcRendererEvents.forEach((name) => {
|
||||
ipcRenderer.removeAllListeners(name);
|
||||
ipcRenderer.on(name, (...args: Array<any>) => this.emit(name, ...args));
|
||||
});
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,6 +1,8 @@
|
||||
import { EditorValues } from '../interfaces';
|
||||
import { exec } from '../utils/exec';
|
||||
|
||||
const { builtinModules } = require('module');
|
||||
|
||||
export interface NpmOperationOptions {
|
||||
dir: string;
|
||||
}
|
||||
@@ -11,8 +13,8 @@ export let isInstalled: boolean | null = null;
|
||||
/* perhaps we can expose this to the settings module?*/
|
||||
const ignoredModules: Array<string> = [
|
||||
'electron',
|
||||
// tslint:disable-next-line:no-submodule-imports
|
||||
...require('builtin-modules/static')
|
||||
'original-fs',
|
||||
...builtinModules
|
||||
];
|
||||
|
||||
/* regular expression to both match and extract module names */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as Octokit from '@octokit/rest';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { when } from 'mobx';
|
||||
import { EditorId, EditorValues } from '../interfaces';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { EditorId, EditorValues, GenericDialogType } from '../interfaces';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME, STYLES_CSS_NAME } from '../shared-constants';
|
||||
import { getOctokit } from '../utils/octokit';
|
||||
import { sortedElectronMap } from '../utils/sorted-electron-map';
|
||||
import { ELECTRON_ORG, ELECTRON_REPO } from './constants';
|
||||
@@ -25,17 +25,20 @@ export class RemoteLoader {
|
||||
|
||||
public async loadFiddleFromElectronExample(_: any, exampleInfo: { path: string; ref: string }) {
|
||||
console.log(`Loading fiddle from Electron example`, _, exampleInfo);
|
||||
const ok = await this.verifyRemoteLoad('example from the Electron docs', exampleInfo.ref);
|
||||
const { path, ref } = exampleInfo;
|
||||
const prettyName = path.replace('docs/fiddles/', '');
|
||||
const ok = await this.verifyRemoteLoad(`'${prettyName}' example from the Electron docs for version ${ref}`);
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchExampleAndLoad(exampleInfo.ref, exampleInfo.path);
|
||||
this.fetchExampleAndLoad(ref, path);
|
||||
}
|
||||
|
||||
public async loadFiddleFromGist(_: any, gistInfo: { id: string }) {
|
||||
const ok = await this.verifyRemoteLoad('gist');
|
||||
const { id } = gistInfo;
|
||||
const ok = await this.verifyRemoteLoad(`gist`);
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchGistAndLoad(gistInfo.id);
|
||||
this.fetchGistAndLoad(id);
|
||||
}
|
||||
|
||||
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
|
||||
@@ -57,6 +60,7 @@ export class RemoteLoader {
|
||||
renderer: await getContent(EditorId.renderer, this.appState.version),
|
||||
main: await getContent(EditorId.main, this.appState.version),
|
||||
preload: await getContent(EditorId.preload, this.appState.version),
|
||||
css: await getContent(EditorId.css, this.appState.version)
|
||||
};
|
||||
|
||||
const loaders: Array<Promise<void>> = [];
|
||||
@@ -95,6 +99,11 @@ export class RemoteLoader {
|
||||
.then((r) => r.text()).then((t) => { values.preload = t; })
|
||||
);
|
||||
|
||||
case STYLES_CSS_NAME:
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.css = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -140,7 +149,8 @@ export class RemoteLoader {
|
||||
html: this.getContentOrEmpty(gist, INDEX_HTML_NAME),
|
||||
main: this.getContentOrEmpty(gist, MAIN_JS_NAME),
|
||||
renderer: this.getContentOrEmpty(gist, RENDERER_JS_NAME),
|
||||
preload: this.getContentOrEmpty(gist, PRELOAD_JS_NAME)
|
||||
preload: this.getContentOrEmpty(gist, PRELOAD_JS_NAME),
|
||||
css: this.getContentOrEmpty(gist, STYLES_CSS_NAME)
|
||||
}, gistId);
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
@@ -159,11 +169,11 @@ export class RemoteLoader {
|
||||
// check if version is part of release channel
|
||||
const versionReleaseChannel: ElectronReleaseChannel = getReleaseChannel(version);
|
||||
|
||||
if (!this.appState.versionsToShow.includes(versionReleaseChannel)) {
|
||||
if (!this.appState.channelsToShow.includes(versionReleaseChannel)) {
|
||||
const ok = await this.verifyReleaseChannelEnabled(versionReleaseChannel);
|
||||
if (!ok) return false;
|
||||
|
||||
this.appState.versionsToShow.push(versionReleaseChannel);
|
||||
this.appState.channelsToShow.push(versionReleaseChannel);
|
||||
}
|
||||
|
||||
this.appState.setVersion(version);
|
||||
@@ -197,26 +207,28 @@ export class RemoteLoader {
|
||||
*
|
||||
* @param what What are we loading from (gist, example, etc.)
|
||||
*/
|
||||
public async verifyRemoteLoad(what: string, fiddlePath?: string): Promise<boolean> {
|
||||
this.appState.setConfirmationPromptTexts({
|
||||
label: `Are you sure you sure you want to load this '${what}' from fiddle path '${fiddlePath}'? Only load and run it if you trust the source.`
|
||||
public async verifyRemoteLoad(what: string): Promise<boolean> {
|
||||
this.appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.confirm,
|
||||
label: `Are you sure you want to load this ${what}? Only load and run it if you trust the source.`
|
||||
});
|
||||
this.appState.isConfirmationPromptShowing = true;
|
||||
await when(() => !this.appState.isConfirmationPromptShowing);
|
||||
this.appState.isGenericDialogShowing = true;
|
||||
await when(() => !this.appState.isGenericDialogShowing);
|
||||
|
||||
return !!this.appState.confirmationPromptLastResult;
|
||||
return !!this.appState.genericDialogLastResult;
|
||||
}
|
||||
|
||||
public async verifyReleaseChannelEnabled(channel: string): Promise<boolean> {
|
||||
this.appState.setWarningDialogTexts({
|
||||
this.appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `You're loading an example with a version of Electron with an unincluded release
|
||||
channel (${channel}). Do you want to enable the release channel to load the
|
||||
version of Electron from the example?`
|
||||
});
|
||||
this.appState.isWarningDialogShowing = true;
|
||||
await when(() => !this.appState.isWarningDialogShowing);
|
||||
this.appState.isGenericDialogShowing = true;
|
||||
await when(() => !this.appState.isGenericDialogShowing);
|
||||
|
||||
return !!this.appState.warningDialogLastResult;
|
||||
return !!this.appState.genericDialogLastResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,7 +239,7 @@ export class RemoteLoader {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
|
||||
await window.ElectronFiddle.app.replaceFiddle(values, {gistId});
|
||||
await window.ElectronFiddle.app.replaceFiddle(values, { gistId });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -240,18 +252,20 @@ export class RemoteLoader {
|
||||
*/
|
||||
private handleLoadingFailed(error: Error): false {
|
||||
if (navigator.onLine) {
|
||||
this.appState.setWarningDialogTexts({
|
||||
this.appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `Loading the fiddle failed: ${error}`,
|
||||
cancel: undefined
|
||||
});
|
||||
} else {
|
||||
this.appState.setWarningDialogTexts({
|
||||
this.appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `Loading the fiddle failed. Your computer seems to be offline. Error: ${error}`,
|
||||
cancel: undefined
|
||||
});
|
||||
}
|
||||
|
||||
this.appState.toggleWarningDialog();
|
||||
this.appState.toggleGenericDialog();
|
||||
|
||||
console.warn(`Loading Fiddle failed`, error);
|
||||
return false;
|
||||
|
||||
+14
-8
@@ -6,6 +6,7 @@ import { IpcEvents } from '../ipc-events';
|
||||
import { getAppDataDir } from '../utils/app-data-dir';
|
||||
import { PackageJsonOptions } from '../utils/get-package';
|
||||
import { maybePlural } from '../utils/plural-maybe';
|
||||
import { getElectronBinaryPath, getIsDownloaded } from './binary';
|
||||
import { ipcRendererManager } from './ipc';
|
||||
import { findModulesInEditors, getIsNpmInstalled, installModules, npmRun } from './npm';
|
||||
import { AppState } from './state';
|
||||
@@ -22,6 +23,11 @@ export class Runner {
|
||||
this.run = this.run.bind(this);
|
||||
this.stop = this.stop.bind(this);
|
||||
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.FIDDLE_RUN);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.FIDDLE_PACKAGE);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.FIDDLE_MAKE);
|
||||
|
||||
|
||||
ipcRendererManager.on(IpcEvents.FIDDLE_RUN, this.run);
|
||||
ipcRendererManager.on(IpcEvents.FIDDLE_PACKAGE, () => {
|
||||
this.performForgeOperation(ForgeCommands.PACKAGE);
|
||||
@@ -39,7 +45,7 @@ export class Runner {
|
||||
public async run(): Promise<boolean> {
|
||||
const { fileManager, getEditorValues } = window.ElectronFiddle.app;
|
||||
const options = { includeDependencies: false, includeElectron: false };
|
||||
const { binaryManager, currentElectronVersion } = this.appState;
|
||||
const { currentElectronVersion } = this.appState;
|
||||
const { version, localPath } = currentElectronVersion;
|
||||
|
||||
if (this.appState.isClearingConsoleOnRun) {
|
||||
@@ -60,7 +66,7 @@ export class Runner {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isReady = await binaryManager.getIsDownloaded(version, localPath);
|
||||
const isReady = await getIsDownloaded(version, localPath);
|
||||
|
||||
if (!isReady) {
|
||||
console.warn(`Runner: Binary ${version} not ready`);
|
||||
@@ -179,9 +185,9 @@ export class Runner {
|
||||
* @memberof Runner
|
||||
*/
|
||||
public async execute(dir: string): Promise<void> {
|
||||
const { currentElectronVersion, pushOutput, binaryManager } = this.appState;
|
||||
const { currentElectronVersion, pushOutput } = this.appState;
|
||||
const { version, localPath } = currentElectronVersion;
|
||||
const binaryPath = binaryManager.getElectronBinaryPath(version, localPath);
|
||||
const binaryPath = getElectronBinaryPath(version, localPath);
|
||||
console.log(`Runner: Binary ${binaryPath} ready, launching`);
|
||||
|
||||
const env = { ...process.env };
|
||||
@@ -193,10 +199,10 @@ export class Runner {
|
||||
delete env.ELECTRON_ENABLE_STACK_DUMPING;
|
||||
}
|
||||
|
||||
this.child = spawn(binaryPath, [ dir, '--inspect' ], {
|
||||
cwd: dir,
|
||||
env,
|
||||
});
|
||||
// Add user-specified cli flags if any have been set.
|
||||
const options = [ dir, '--inspect' ].concat(this.appState.executionFlags);
|
||||
|
||||
this.child = spawn(binaryPath, options, { cwd: dir, env });
|
||||
this.appState.isRunning = true;
|
||||
pushOutput(`Electron v${version} started.`);
|
||||
|
||||
|
||||
+153
-96
@@ -1,4 +1,4 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as fsType from 'fs-extra';
|
||||
import { action, autorun, computed, observable, when } from 'mobx';
|
||||
import { MosaicNode } from 'react-mosaic-component';
|
||||
|
||||
@@ -6,34 +6,39 @@ import {
|
||||
ALL_MOSAICS,
|
||||
DocsDemoPage,
|
||||
EditorId,
|
||||
ElectronVersion,
|
||||
ElectronVersionSource,
|
||||
ElectronVersionState,
|
||||
GenericDialogOptions,
|
||||
GenericDialogType,
|
||||
MosaicId,
|
||||
NpmVersion,
|
||||
OutputEntry,
|
||||
OutputOptions,
|
||||
WarningDialogTexts
|
||||
RunnableVersion,
|
||||
Version,
|
||||
VersionSource,
|
||||
VersionState
|
||||
} from '../interfaces';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { arrayToStringMap } from '../utils/array-to-stringmap';
|
||||
import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
|
||||
import { createMosaicArrangement, getVisibleMosaics } from '../utils/editors-mosaic-arrangement';
|
||||
import { getName } from '../utils/get-title';
|
||||
import { fancyImport } from '../utils/import';
|
||||
import { normalizeVersion } from '../utils/normalize-version';
|
||||
import { isEditorBackup, isEditorId, isPanelId } from '../utils/type-checks';
|
||||
import { BinaryManager } from './binary';
|
||||
import { DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
|
||||
import { getDownloadedVersions, getDownloadingVersions, removeBinary, setupBinary } from './binary';
|
||||
import { Bisector } from './bisect';
|
||||
import { DEFAULT_CLOSED_PANELS, DEFAULT_MOSAIC_ARRANGEMENT } from './constants';
|
||||
import { getContent, isContentUnchanged } from './content';
|
||||
import { getLocalTypePathForVersion, updateEditorTypeDefinitions } from './fetch-types';
|
||||
import { ipcRendererManager } from './ipc';
|
||||
import { activateTheme } from './themes';
|
||||
|
||||
import { sortedElectronMap } from '../utils/sorted-electron-map';
|
||||
import {
|
||||
addLocalVersion,
|
||||
ElectronReleaseChannel,
|
||||
getDefaultVersion,
|
||||
getElectronVersions,
|
||||
getReleaseChannel,
|
||||
getUpdatedElectronVersions,
|
||||
saveLocalVersions
|
||||
} from './versions';
|
||||
@@ -65,55 +70,55 @@ export class AppState {
|
||||
// -- Persisted settings ------------------
|
||||
@observable public version: string = defaultVersion;
|
||||
@observable public theme: string | null = localStorage.getItem('theme');
|
||||
@observable public isClearingConsoleOnRun: boolean = !!this.retrieve('isClearingConsoleOnRun');
|
||||
@observable public gitHubAvatarUrl: string | null = localStorage.getItem('gitHubAvatarUrl');
|
||||
@observable public gitHubName: string | null = localStorage.getItem('gitHubName');
|
||||
@observable public gitHubLogin: string | null = localStorage.getItem('gitHubLogin');
|
||||
@observable public gitHubToken: string | null = localStorage.getItem('gitHubToken') || null;
|
||||
@observable public gitHubPublishAsPublic: boolean = !!this.retrieve('gitHubPublishAsPublic');
|
||||
@observable public versionsToShow: Array<ElectronReleaseChannel> =
|
||||
this.retrieve('versionsToShow') as Array<ElectronReleaseChannel>
|
||||
|| [ ElectronReleaseChannel.stable, ElectronReleaseChannel.beta ];
|
||||
@observable public statesToShow: Array<ElectronVersionState> =
|
||||
this.retrieve('statesToShow') as Array<ElectronVersionState>
|
||||
|| [ ElectronVersionState.downloading, ElectronVersionState.ready, ElectronVersionState.unknown ];
|
||||
@observable public channelsToShow: Array<ElectronReleaseChannel> =
|
||||
this.retrieve('channelsToShow') as Array<ElectronReleaseChannel>
|
||||
|| [ElectronReleaseChannel.stable, ElectronReleaseChannel.beta];
|
||||
@observable public statesToShow: Array<VersionState> =
|
||||
this.retrieve('statesToShow') as Array<VersionState>
|
||||
|| [VersionState.downloading, VersionState.ready, VersionState.unknown];
|
||||
@observable public isKeepingUserDataDirs: boolean = !!this.retrieve('isKeepingUserDataDirs');
|
||||
@observable public isEnablingElectronLogging: boolean = !!this.retrieve('isEnablingElectronLogging');
|
||||
|
||||
@observable public binaryManager: BinaryManager = new BinaryManager();
|
||||
@observable public isClearingConsoleOnRun: boolean = !!this.retrieve('isClearingConsoleOnRun');
|
||||
@observable public executionFlags: Array<string> =
|
||||
this.retrieve('executionFlags') as Array<string> === null ?
|
||||
[] : this.retrieve('executionFlags') as Array<string>;
|
||||
|
||||
// -- Various session-only state ------------------
|
||||
@observable public gistId: string = '';
|
||||
@observable public isPublishing: boolean = false;
|
||||
@observable public versions: Record<string, ElectronVersion> = arrayToStringMap(knownVersions);
|
||||
@observable public versions: Record<string, RunnableVersion> = arrayToStringMap(knownVersions);
|
||||
@observable public output: Array<OutputEntry> = [];
|
||||
@observable public localPath: string | undefined;
|
||||
@observable public isUpdatingElectronVersions = false;
|
||||
@observable public warningDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
|
||||
@observable public confirmationDialogTexts = { label: '', ok: 'Okay', cancel: 'Cancel' };
|
||||
@observable public warningDialogLastResult: boolean | null = null;
|
||||
@observable public confirmationPromptLastResult: boolean | null = null;
|
||||
@observable public isRunning = false;
|
||||
@observable public genericDialogOptions = { type: GenericDialogType.warning, label: '', ok: 'Okay', cancel: 'Cancel' };
|
||||
@observable public genericDialogLastResult: boolean | null = null;
|
||||
@observable public mosaicArrangement: MosaicNode<MosaicId> | null = DEFAULT_MOSAIC_ARRANGEMENT;
|
||||
@observable public templateName: string | undefined;
|
||||
@observable public currentDocsDemoPage: DocsDemoPage = DocsDemoPage.DEFAULT;
|
||||
@observable public localTypeWatcher: fsType.FSWatcher | undefined;
|
||||
@observable public Bisector: Bisector | undefined;
|
||||
|
||||
@observable public isPublishing: boolean = false;
|
||||
@observable public isRunning: boolean = false;
|
||||
@observable public isUnsaved: boolean;
|
||||
@observable public isUpdatingElectronVersions: boolean = false;
|
||||
|
||||
// -- Various "isShowing" settings ------------------
|
||||
@observable public isBisectCommandShowing: boolean;
|
||||
@observable public isConsoleShowing: boolean = false;
|
||||
@observable public isTokenDialogShowing: boolean = false;
|
||||
@observable public isWarningDialogShowing: boolean = false;
|
||||
@observable public isConfirmationPromptShowing: boolean = false;
|
||||
@observable public isGenericDialogShowing: boolean = false;
|
||||
@observable public isSettingsShowing: boolean = false;
|
||||
@observable public isUnsaved: boolean = false;
|
||||
@observable public isBisectDialogShowing: boolean = false;
|
||||
@observable public isAddVersionDialogShowing: boolean = false;
|
||||
@observable public isThemeDialogShowing: boolean = false;
|
||||
@observable public isTourShowing: boolean = !localStorage.getItem('hasShownTour');
|
||||
|
||||
// -- Editor Values stored when we close the editor ------------------
|
||||
@observable public closedPanels: Partial<Record<MosaicId, EditorBackup | true>> = {
|
||||
docsDemo: true // Closed by default
|
||||
};
|
||||
@observable public closedPanels: Partial<Record<MosaicId, EditorBackup | true>> = DEFAULT_CLOSED_PANELS;
|
||||
|
||||
private outputBuffer: string = '';
|
||||
private name: string;
|
||||
@@ -127,15 +132,23 @@ export class AppState {
|
||||
this.setVersion = this.setVersion.bind(this);
|
||||
this.showTour = this.showTour.bind(this);
|
||||
this.signOutGitHub = this.signOutGitHub.bind(this);
|
||||
this.toggleBisectCommands = this.toggleBisectCommands.bind(this);
|
||||
this.toggleAuthDialog = this.toggleAuthDialog.bind(this);
|
||||
this.toggleConsole = this.toggleConsole.bind(this);
|
||||
this.clearConsole = this.clearConsole.bind(this);
|
||||
this.toggleSettings = this.toggleSettings.bind(this);
|
||||
this.toggleBisectDialog = this.toggleBisectDialog.bind(this);
|
||||
this.updateElectronVersions = this.updateElectronVersions.bind(this);
|
||||
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.OPEN_SETTINGS);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.SHOW_WELCOME_TOUR);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.CLEAR_CONSOLE);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.BISECT_COMMANDS_TOGGLE);
|
||||
|
||||
ipcRendererManager.on(IpcEvents.OPEN_SETTINGS, this.toggleSettings);
|
||||
ipcRendererManager.on(IpcEvents.SHOW_WELCOME_TOUR, this.showTour);
|
||||
ipcRendererManager.on(IpcEvents.CLEAR_CONSOLE, this.clearConsole);
|
||||
ipcRendererManager.on(IpcEvents.BISECT_COMMANDS_TOGGLE, this.toggleBisectCommands);
|
||||
|
||||
// Setup auto-runs
|
||||
autorun(() => this.save('theme', this.theme));
|
||||
@@ -147,24 +160,29 @@ export class AppState {
|
||||
autorun(() => this.save('gitHubPublishAsPublic', this.gitHubPublishAsPublic));
|
||||
autorun(() => this.save('isKeepingUserDataDirs', this.isKeepingUserDataDirs));
|
||||
autorun(() => this.save('isEnablingElectronLogging', this.isEnablingElectronLogging));
|
||||
autorun(() => this.save('executionFlags', this.executionFlags));
|
||||
autorun(() => this.save('version', this.version));
|
||||
autorun(() => this.save('versionsToShow', this.versionsToShow));
|
||||
autorun(() => this.save('channelsToShow', this.channelsToShow));
|
||||
autorun(() => this.save('statesToShow', this.statesToShow));
|
||||
|
||||
autorun(() => {
|
||||
if (this.isUnsaved) {
|
||||
if (typeof this.isUnsaved === 'undefined') return;
|
||||
|
||||
if (!!this.isUnsaved) {
|
||||
window.onbeforeunload = () => {
|
||||
this.setWarningDialogTexts({
|
||||
ipcRendererManager.send(IpcEvents.SHOW_INACTIVE);
|
||||
this.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `The current Fiddle is unsaved. Do you want to exit anyway?`,
|
||||
ok: 'Quit'
|
||||
});
|
||||
|
||||
this.isWarningDialogShowing = true;
|
||||
this.isGenericDialogShowing = true;
|
||||
|
||||
// We'll wait until the warning dialog was closed
|
||||
when(() => !this.isWarningDialogShowing).then(() => {
|
||||
when(() => !this.isGenericDialogShowing).then(() => {
|
||||
// The user confirmed, let's close for real.
|
||||
if (this.warningDialogLastResult) {
|
||||
if (this.genericDialogLastResult) {
|
||||
window.onbeforeunload = null;
|
||||
|
||||
// Should we just close or quit?
|
||||
@@ -183,6 +201,15 @@ export class AppState {
|
||||
};
|
||||
} else {
|
||||
window.onbeforeunload = null;
|
||||
|
||||
// set up editor listeners to verify if unsaved
|
||||
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
|
||||
const editor = window.ElectronFiddle.editors[key];
|
||||
const disposable = editor.onDidChangeModelContent(() => {
|
||||
this.isUnsaved = true;
|
||||
disposable.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -194,10 +221,10 @@ export class AppState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current ElectronVersion or the first
|
||||
* Returns the current RunnableVersion or the first
|
||||
* one that can be found.
|
||||
*/
|
||||
@computed get currentElectronVersion(): ElectronVersion {
|
||||
@computed get currentElectronVersion(): RunnableVersion {
|
||||
if (this.versions[this.version]) {
|
||||
return this.versions[this.version];
|
||||
} else {
|
||||
@@ -205,6 +232,31 @@ export class AppState {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Electron versions to show given the
|
||||
* current settings for states and channels to display
|
||||
*/
|
||||
@computed get versionsToShow(): Array<RunnableVersion> {
|
||||
return sortedElectronMap<RunnableVersion>(this.versions, (_key, item) => item)
|
||||
.filter((item) => {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we want to show the version
|
||||
if (!this.channelsToShow.includes(getReleaseChannel(item))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we want to show the state
|
||||
if (!this.statesToShow.includes(item.state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the Electron versions: First, fetch them from GitHub,
|
||||
* then update their respective downloaded state.
|
||||
@@ -241,6 +293,13 @@ export class AppState {
|
||||
this.output = [];
|
||||
}
|
||||
|
||||
@action public toggleBisectCommands() {
|
||||
// guard against hiding the commands when executing a bisect
|
||||
if (!this.Bisector && !this.isBisectDialogShowing) {
|
||||
this.isBisectCommandShowing = !this.isBisectCommandShowing;
|
||||
}
|
||||
}
|
||||
|
||||
@action public toggleAddVersionDialog() {
|
||||
this.isAddVersionDialogShowing = !this.isAddVersionDialogShowing;
|
||||
}
|
||||
@@ -253,20 +312,16 @@ export class AppState {
|
||||
this.isTokenDialogShowing = !this.isTokenDialogShowing;
|
||||
}
|
||||
|
||||
@action public toggleWarningDialog() {
|
||||
this.isWarningDialogShowing = !this.isWarningDialogShowing;
|
||||
@action public toggleGenericDialog() {
|
||||
this.isGenericDialogShowing = !this.isGenericDialogShowing;
|
||||
|
||||
if (this.isWarningDialogShowing) {
|
||||
this.warningDialogLastResult = null;
|
||||
if (this.isGenericDialogShowing) {
|
||||
this.genericDialogLastResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
@action public toggleConfirmationPromptDialog() {
|
||||
this.isConfirmationPromptShowing = !this.isConfirmationPromptShowing;
|
||||
|
||||
if (this.isConfirmationPromptShowing) {
|
||||
this.confirmationPromptLastResult = null;
|
||||
}
|
||||
@action public toggleBisectDialog() {
|
||||
this.isBisectDialogShowing = !this.isBisectDialogShowing;
|
||||
}
|
||||
|
||||
@action public toggleSettings() {
|
||||
@@ -294,35 +349,28 @@ export class AppState {
|
||||
window.ElectronFiddle.app.setupTheme();
|
||||
}
|
||||
|
||||
@action public setWarningDialogTexts(input: WarningDialogTexts) {
|
||||
this.warningDialogTexts = {
|
||||
@action public setGenericDialogOptions(opts: GenericDialogOptions) {
|
||||
this.genericDialogOptions = {
|
||||
type: GenericDialogType.warning,
|
||||
ok: 'Okay',
|
||||
cancel: 'Cancel',
|
||||
...input
|
||||
...opts
|
||||
};
|
||||
}
|
||||
|
||||
@action public setConfirmationPromptTexts(input: WarningDialogTexts) {
|
||||
this.confirmationDialogTexts = {
|
||||
ok: 'Okay',
|
||||
cancel: 'Cancel',
|
||||
...input
|
||||
};
|
||||
}
|
||||
|
||||
@action public addLocalVersion(input: NpmVersion) {
|
||||
@action public addLocalVersion(input: Version) {
|
||||
addLocalVersion(input);
|
||||
|
||||
this.versions = arrayToStringMap(getElectronVersions());
|
||||
this.updateDownloadedVersionState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a version of Electron
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
/**
|
||||
* Remove a version of Electron
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@action public async removeVersion(input: string) {
|
||||
const version = normalizeVersion(input);
|
||||
const release = this.versions[version];
|
||||
@@ -339,7 +387,7 @@ export class AppState {
|
||||
const updatedVersions = { ...this.versions };
|
||||
|
||||
// Actually remove
|
||||
if (release && release.source === ElectronVersionSource.local) {
|
||||
if (release && release.source === VersionSource.local) {
|
||||
delete updatedVersions[version];
|
||||
|
||||
const versionsAsArray = Object
|
||||
@@ -348,49 +396,48 @@ export class AppState {
|
||||
|
||||
saveLocalVersions(versionsAsArray);
|
||||
} else {
|
||||
await this.binaryManager.remove(version);
|
||||
updatedVersions[version].state = ElectronVersionState.unknown;
|
||||
await removeBinary(version);
|
||||
updatedVersions[version].state = VersionState.unknown;
|
||||
}
|
||||
|
||||
this.versions = updatedVersions;
|
||||
this.updateDownloadedVersionState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a version of Electron.
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
/**
|
||||
* Download a version of Electron.
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@action public async downloadVersion(input: string) {
|
||||
const version = normalizeVersion(input);
|
||||
console.log(`State: Downloading Electron ${version}`);
|
||||
|
||||
const release = this.versions[version] || { state: '', source: '' };
|
||||
const isLocal = release.source === ElectronVersionSource.local;
|
||||
const isLocal = release.source === VersionSource.local;
|
||||
const isReady = release.state === 'ready';
|
||||
|
||||
// Fetch new binaries, maybe?
|
||||
if (!isLocal && !isReady) {
|
||||
console.log(`State: Instructing BinaryManager to fetch v${version}`);
|
||||
console.log(`State: Fetching v${version}`);
|
||||
const updatedVersions = { ...this.versions };
|
||||
updatedVersions[version] = updatedVersions[version] || {};
|
||||
updatedVersions[version].state = ElectronVersionState.downloading;
|
||||
this.versions = updatedVersions;
|
||||
|
||||
await this.binaryManager.setup(version);
|
||||
await setupBinary(this, version);
|
||||
this.updateDownloadedVersionState();
|
||||
} else {
|
||||
console.log(`State: Version ${version} already downloaded, doing nothing.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a version of Electron (and download it if necessary).
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
/**
|
||||
* Select a version of Electron (and download it if necessary).
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@action public async setVersion(input: string) {
|
||||
const version = normalizeVersion(input);
|
||||
|
||||
@@ -414,7 +461,8 @@ export class AppState {
|
||||
// Update TypeScript definitions
|
||||
const versionObject = this.versions[version];
|
||||
|
||||
if (versionObject.source === ElectronVersionSource.local) {
|
||||
if (versionObject.source === VersionSource.local) {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const typePath = getLocalTypePathForVersion(versionObject);
|
||||
console.info(`TypeDefs: Watching file for local version ${version} at path ${typePath}`);
|
||||
this.localTypeWatcher = fs.watch(typePath!, async () => {
|
||||
@@ -434,20 +482,29 @@ export class AppState {
|
||||
await this.downloadVersion(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go and check which versions have already been downloaded.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
/**
|
||||
* Go and check which versions have already been downloaded.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@action public async updateDownloadedVersionState(): Promise<void> {
|
||||
const downloadedVersions = await this.binaryManager.getDownloadedVersions();
|
||||
const updatedVersions = { ...this.versions };
|
||||
|
||||
(downloadedVersions || []).forEach((version) => {
|
||||
// Keep state of currently downloading binaries first
|
||||
const downloadingVersions = getDownloadingVersions(this);
|
||||
(downloadingVersions || []).forEach((version) => {
|
||||
if (updatedVersions[version]) {
|
||||
updatedVersions[version].state = ElectronVersionState.ready;
|
||||
updatedVersions[version].state = VersionState.downloading;
|
||||
}
|
||||
});
|
||||
|
||||
const downloadedVersions = await getDownloadedVersions();
|
||||
(downloadedVersions || []).forEach((version) => {
|
||||
if (updatedVersions[version]) {
|
||||
updatedVersions[version].state = VersionState.ready;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`State: Updated version state`, updatedVersions);
|
||||
|
||||
this.versions = updatedVersions;
|
||||
@@ -575,7 +632,7 @@ export class AppState {
|
||||
*/
|
||||
@action public showMosaic(id: MosaicId) {
|
||||
const currentlyVisible = getVisibleMosaics(this.mosaicArrangement);
|
||||
this.setVisibleMosaics([ ...currentlyVisible, id ]);
|
||||
this.setVisibleMosaics([...currentlyVisible, id]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import * as fsExtraType from 'fs-extra';
|
||||
import * as pathType from 'path';
|
||||
|
||||
import { EditorValues } from '../interfaces';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME, STYLES_CSS_NAME } from '../shared-constants';
|
||||
import { fancyImport } from '../utils/import';
|
||||
|
||||
/**
|
||||
* Returns expected content for a given name.
|
||||
@@ -11,6 +12,8 @@ import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME } from
|
||||
* @returns {Promise<EditorValues>}
|
||||
*/
|
||||
export async function getTemplateValues(name: string): Promise<EditorValues> {
|
||||
const path = await fancyImport<typeof pathType>('path');
|
||||
const fs = await fancyImport<typeof fsExtraType>('fs-extra');
|
||||
const templatesPath = path.join(__dirname, '../../static/show-me');
|
||||
const templatePath = path.join(templatesPath, name.toLowerCase());
|
||||
|
||||
@@ -38,6 +41,7 @@ export async function getTemplateValues(name: string): Promise<EditorValues> {
|
||||
renderer: await getFile(RENDERER_JS_NAME),
|
||||
main: await getFile(MAIN_JS_NAME),
|
||||
html: await getFile(INDEX_HTML_NAME),
|
||||
preload: await getFile(PRELOAD_JS_NAME)
|
||||
preload: await getFile(PRELOAD_JS_NAME),
|
||||
css: await getFile(STYLES_CSS_NAME)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from 'electron';
|
||||
import { autorun } from 'mobx';
|
||||
|
||||
import { ElectronVersion, ElectronVersionState } from '../interfaces';
|
||||
import { RunnableVersion, VersionState } from '../interfaces';
|
||||
import { getNiceGreeting } from '../utils/nice-greeting';
|
||||
import { sortedElectronMap } from '../utils/sorted-electron-map';
|
||||
import { AppState } from './state';
|
||||
@@ -22,11 +22,11 @@ const {
|
||||
* Helper method: Returns an icon (emoji, lol) for an Electron
|
||||
* version.
|
||||
*
|
||||
* @param {Partial<ElectronVersion>} { state }
|
||||
* @param {Partial<RunnableVersion>} { state }
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getItemIcon(
|
||||
{ state }: Partial<ElectronVersion> = { state: ElectronVersionState.unknown }
|
||||
{ state }: Partial<RunnableVersion> = { state: VersionState.unknown }
|
||||
) {
|
||||
return state === 'ready'
|
||||
? '💾'
|
||||
|
||||
+53
-55
@@ -1,4 +1,5 @@
|
||||
import { ElectronVersion, ElectronVersionSource, ElectronVersionState, NpmVersion } from '../interfaces';
|
||||
import semver from 'semver';
|
||||
import { RunnableVersion, Version, VersionSource, VersionState } from '../interfaces';
|
||||
import { normalizeVersion } from '../utils/normalize-version';
|
||||
|
||||
export const enum ElectronReleaseChannel {
|
||||
@@ -11,11 +12,11 @@ export const enum ElectronReleaseChannel {
|
||||
/**
|
||||
* Returns a sensible default version string.
|
||||
*
|
||||
* @param {Array<ElectronVersion>} knownVersions
|
||||
* @param {Array<RunnableVersion>} knownVersions
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDefaultVersion(
|
||||
knownVersions: Array<ElectronVersion> = []
|
||||
knownVersions: Array<RunnableVersion> = []
|
||||
): string {
|
||||
const ls = localStorage.getItem('version');
|
||||
|
||||
@@ -45,13 +46,12 @@ export function getDefaultVersion(
|
||||
* Return the release channel for a given input
|
||||
* version.
|
||||
*
|
||||
* @param {NpmVersion | string} input
|
||||
* @param {Version | string} input
|
||||
* @returns {ElectronReleaseChannel}
|
||||
*/
|
||||
export function getReleaseChannel(
|
||||
input: NpmVersion | string
|
||||
input: Version | string
|
||||
): ElectronReleaseChannel {
|
||||
|
||||
const tag = (typeof input === 'string') ? input : (input.version || '');
|
||||
|
||||
if (tag.includes('beta')) {
|
||||
@@ -79,17 +79,17 @@ export const enum VersionKeys {
|
||||
* Retrieve Electron versions from localStorage.
|
||||
*
|
||||
* @param {VersionKeys} key
|
||||
* @param {() => Array<NpmVersion>} fallbackMethod
|
||||
* @returns {Array<NpmVersion>}
|
||||
* @param {() => Array<Version>} fallbackMethod
|
||||
* @returns {Array<Version>}
|
||||
*/
|
||||
function getVersions(
|
||||
key: VersionKeys, fallbackMethod: () => Array<NpmVersion>
|
||||
): Array<NpmVersion> {
|
||||
key: VersionKeys, fallbackMethod: () => Array<Version>
|
||||
): Array<Version> {
|
||||
const fromLs = window.localStorage.getItem(key);
|
||||
|
||||
if (fromLs) {
|
||||
try {
|
||||
let result: Array<NpmVersion> = JSON.parse(fromLs);
|
||||
let result: Array<Version> = JSON.parse(fromLs);
|
||||
|
||||
if (!isExpectedFormat(result)) {
|
||||
// Known versions can just be downloaded again.
|
||||
@@ -115,9 +115,9 @@ function getVersions(
|
||||
* Save an array of GitHubVersions to localStorage.
|
||||
*
|
||||
* @param {VersionKeys} key
|
||||
* @param {Array<NpmVersion} versions
|
||||
* @param {Array<Version} versions
|
||||
*/
|
||||
function saveVersions(key: VersionKeys, versions: Array<NpmVersion>) {
|
||||
function saveVersions(key: VersionKeys, versions: Array<Version>) {
|
||||
const stringified = JSON.stringify(versions);
|
||||
window.localStorage.setItem(key, stringified);
|
||||
}
|
||||
@@ -125,35 +125,35 @@ function saveVersions(key: VersionKeys, versions: Array<NpmVersion>) {
|
||||
/**
|
||||
* Return both known as well as local versions.
|
||||
*
|
||||
* @returns {Array<NpmVersion>}
|
||||
* @returns {Array<Version>}
|
||||
*/
|
||||
export function getElectronVersions(): Array<ElectronVersion> {
|
||||
const known: Array<ElectronVersion> = getKnownVersions().map((version) => {
|
||||
export function getElectronVersions(): Array<RunnableVersion> {
|
||||
const known: Array<RunnableVersion> = getKnownVersions().map((version) => {
|
||||
return {
|
||||
...version,
|
||||
source: ElectronVersionSource.remote,
|
||||
state: ElectronVersionState.unknown
|
||||
source: VersionSource.remote,
|
||||
state: VersionState.unknown
|
||||
};
|
||||
});
|
||||
|
||||
const local: Array<ElectronVersion> = getLocalVersions().map((version) => {
|
||||
const local: Array<RunnableVersion> = getLocalVersions().map((version) => {
|
||||
return {
|
||||
...version,
|
||||
source: ElectronVersionSource.local,
|
||||
state: ElectronVersionState.ready
|
||||
source: VersionSource.local,
|
||||
state: VersionState.ready
|
||||
};
|
||||
});
|
||||
|
||||
return [ ...known, ...local ];
|
||||
return [...known, ...local];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a version to the local versions
|
||||
*
|
||||
* @param {NpmVersion} input
|
||||
* @returns {Array<NpmVersion>}
|
||||
* @param {Version} input
|
||||
* @returns {Array<Version>}
|
||||
*/
|
||||
export function addLocalVersion(input: NpmVersion): Array<NpmVersion> {
|
||||
export function addLocalVersion(input: Version): Array<Version> {
|
||||
const versions = getLocalVersions();
|
||||
|
||||
if (!versions.find((v) => v.localPath === input.localPath)) {
|
||||
@@ -168,9 +168,9 @@ export function addLocalVersion(input: NpmVersion): Array<NpmVersion> {
|
||||
/**
|
||||
* Retrieves local Electron versions, configured by the user.
|
||||
*
|
||||
* @returns {Array<NpmVersion>}
|
||||
* @returns {Array<Version>}
|
||||
*/
|
||||
export function getLocalVersions(): Array<NpmVersion> {
|
||||
export function getLocalVersions(): Array<Version> {
|
||||
const versions = getVersions(VersionKeys.local, () => []);
|
||||
|
||||
return versions;
|
||||
@@ -179,12 +179,12 @@ export function getLocalVersions(): Array<NpmVersion> {
|
||||
/**
|
||||
* Saves local versions to localStorage.
|
||||
*
|
||||
* @param {Array<NpmVersion>} versions
|
||||
* @param {Array<Version>} versions
|
||||
*/
|
||||
export function saveLocalVersions(versions: Array<NpmVersion | ElectronVersion>) {
|
||||
export function saveLocalVersions(versions: Array<Version | RunnableVersion>) {
|
||||
const filteredVersions = versions.filter((v) => {
|
||||
if (isElectronVersion(v)) {
|
||||
return v.source === ElectronVersionSource.local;
|
||||
return v.source === VersionSource.local;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -197,18 +197,18 @@ export function saveLocalVersions(versions: Array<NpmVersion | ElectronVersion>)
|
||||
* Retrieves our best guess regarding the latest Electron versions. Tries to
|
||||
* fetch them from localStorage, then from a static releases.json file.
|
||||
*
|
||||
* @returns {Array<NpmVersion>}
|
||||
* @returns {Array<Version>}
|
||||
*/
|
||||
export function getKnownVersions(): Array<NpmVersion> {
|
||||
export function getKnownVersions(): Array<Version> {
|
||||
return getVersions(VersionKeys.known, () => require('../../static/releases.json'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves known versions to localStorage.
|
||||
*
|
||||
* @param {Array<NpmVersion>} versions
|
||||
* @param {Array<Version>} versions
|
||||
*/
|
||||
export function saveKnownVersions(versions: Array<NpmVersion>) {
|
||||
export function saveKnownVersions(versions: Array<Version>) {
|
||||
return saveVersions(VersionKeys.known, versions);
|
||||
}
|
||||
|
||||
@@ -217,10 +217,10 @@ export function saveKnownVersions(versions: Array<NpmVersion>) {
|
||||
* saved after.
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<Array<ElectronVersion>>}
|
||||
* @returns {Promise<Array<RunnableVersion>>}
|
||||
*/
|
||||
export async function getUpdatedElectronVersions(
|
||||
): Promise<Array<ElectronVersion>> {
|
||||
): Promise<Array<RunnableVersion>> {
|
||||
try {
|
||||
await fetchVersions();
|
||||
} catch (error) {
|
||||
@@ -233,23 +233,21 @@ export async function getUpdatedElectronVersions(
|
||||
/**
|
||||
* Fetch the latest known versions directly from npm.
|
||||
*
|
||||
* @returns {Promise<Array<NpmVersion>>}
|
||||
* @returns {Promise<Array<Version>>}
|
||||
*/
|
||||
export async function fetchVersions() {
|
||||
const channels = [
|
||||
`https://registry.npmjs.org/electron`, // stable, beta
|
||||
`https://registry.npmjs.org/electron-nightly` // nightly
|
||||
];
|
||||
const response = await window.fetch('https://unpkg.com/electron-releases/lite.json');
|
||||
const data = await response.json();
|
||||
|
||||
const output: Array<NpmVersion> = [];
|
||||
for (const channelUrl of channels) {
|
||||
const response = await window.fetch(channelUrl);
|
||||
const data = await response.json();
|
||||
const versions: Record<string, any> = data.versions;
|
||||
output.push(...Object.keys(versions).map((version) => ({ version })));
|
||||
}
|
||||
// pre-0.24.0 versions were technically 'atom-shell' and cannot
|
||||
// be downloaded with @electron/get
|
||||
const MIN_DOWNLOAD_VERSION = '0.24.0';
|
||||
|
||||
if (output && output.length > 0 && isExpectedFormat(output)) {
|
||||
const output = data
|
||||
.map(({ version }: any) => ({ version }))
|
||||
.filter(({ version }: any) => semver.gte(version, MIN_DOWNLOAD_VERSION));
|
||||
|
||||
if (output?.length > 0 && isExpectedFormat(output)) {
|
||||
console.log(`Fetched new Electron versions (Count: ${output.length})`);
|
||||
saveKnownVersions(output);
|
||||
}
|
||||
@@ -271,9 +269,9 @@ export function isExpectedFormat(input: Array<any>): boolean {
|
||||
* Migrates old versions, if necessary
|
||||
*
|
||||
* @param {Array<any>} input
|
||||
* @returns {Array<NpmVersion>}
|
||||
* @returns {Array<Version>}
|
||||
*/
|
||||
export function migrateVersions(input: Array<any> = []): Array<NpmVersion> {
|
||||
export function migrateVersions(input: Array<any> = []): Array<Version> {
|
||||
return input
|
||||
.filter((item) => !!item)
|
||||
.map((item) => {
|
||||
@@ -287,11 +285,11 @@ export function migrateVersions(input: Array<any> = []): Array<NpmVersion> {
|
||||
localPath: url
|
||||
};
|
||||
})
|
||||
.filter((item) => !!item) as Array<NpmVersion>;
|
||||
.filter((item) => !!item) as Array<Version>;
|
||||
}
|
||||
|
||||
export function isElectronVersion(
|
||||
input: NpmVersion | ElectronVersion
|
||||
): input is ElectronVersion {
|
||||
return (input as ElectronVersion).source !== undefined;
|
||||
input: Version | RunnableVersion
|
||||
): input is RunnableVersion {
|
||||
return (input as RunnableVersion).source !== undefined;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import * as Sentry from '@sentry/electron';
|
||||
|
||||
export function initSentry() {
|
||||
if (!(global as any).__JEST__) {
|
||||
Sentry.init({dsn: 'https://966a5b01ac8d4941b81e4ebd0ab4c991@sentry.io/1882540'});
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,5 @@ export const INDEX_HTML_NAME = 'index.html';
|
||||
export const MAIN_JS_NAME = 'main.js';
|
||||
export const RENDERER_JS_NAME = 'renderer.js';
|
||||
export const PRELOAD_JS_NAME = 'preload.js';
|
||||
export const STYLES_CSS_NAME = 'styles.css';
|
||||
export const PACKAGE_NAME = 'package.json';
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { ElectronVersion } from '../interfaces';
|
||||
import { RunnableVersion } from '../interfaces';
|
||||
import { normalizeVersion } from './normalize-version';
|
||||
|
||||
/**
|
||||
* Takes an array of GitHub releases and returns a StringMap of
|
||||
* Electron releases.
|
||||
*
|
||||
* @param {Array<ElectronVersion>} input
|
||||
* @returns {Record<string, ElectronVersion>}
|
||||
* @param {Array<RunnableVersion>} input
|
||||
* @returns {Record<string, RunnableVersion>}
|
||||
*/
|
||||
export function arrayToStringMap(
|
||||
input: Array<ElectronVersion>
|
||||
): Record<string, ElectronVersion> {
|
||||
input: Array<RunnableVersion>
|
||||
): Record<string, RunnableVersion> {
|
||||
const output = {};
|
||||
|
||||
input.forEach((version) => {
|
||||
|
||||
@@ -6,9 +6,9 @@ import { getEditorValue } from './editor-value';
|
||||
import { getEditorViewState } from './editor-viewstate';
|
||||
|
||||
export interface EditorBackup {
|
||||
value: string;
|
||||
model: editor.ITextModel | null;
|
||||
viewState: editor.ICodeEditorViewState | null;
|
||||
value?: string;
|
||||
model?: editor.ITextModel | null;
|
||||
viewState?: editor.ICodeEditorViewState | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as GitHubType from '@octokit/rest';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { AppState } from '../renderer/state';
|
||||
|
||||
let _Octokit: typeof GitHubType;
|
||||
let _octo: GitHubType;
|
||||
let _Octokit: typeof Octokit;
|
||||
let _octo: Octokit;
|
||||
|
||||
/**
|
||||
* Returns a loaded Octokit. If state is passed and authentication
|
||||
@@ -13,7 +13,7 @@ let _octo: GitHubType;
|
||||
*/
|
||||
export async function getOctokit(
|
||||
appState?: AppState
|
||||
): Promise<GitHubType> {
|
||||
): Promise<Octokit> {
|
||||
_Octokit = _Octokit || (await import('@octokit/rest') as any).default;
|
||||
_octo = _octo || new _Octokit();
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import * as semver from 'semver';
|
||||
|
||||
import { ElectronVersion } from '../interfaces';
|
||||
import { RunnableVersion } from '../interfaces';
|
||||
|
||||
/**
|
||||
* Sorts Electron versions and returns the result of a
|
||||
* map function.
|
||||
*
|
||||
* @param {Record<string, ElectronVersion>} versions
|
||||
* @param {(key: string, version: ElectronVersion) => void} mapFn
|
||||
* @param {Record<string, RunnableVersion>} versions
|
||||
* @param {(key: string, version: RunnableVersion) => void} mapFn
|
||||
* @returns {Array<T>}
|
||||
*/
|
||||
export function sortedElectronMap<T>(
|
||||
versions: Record<string, ElectronVersion>,
|
||||
mapFn: (key: string, version: ElectronVersion) => T
|
||||
versions: Record<string, RunnableVersion>,
|
||||
mapFn: (key: string, version: RunnableVersion) => T
|
||||
) {
|
||||
return Object.keys(versions)
|
||||
.sort((a, b) => {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.debugger</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -6,9 +6,11 @@
|
||||
<title>Electron Fiddle</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../src/less/root.less">
|
||||
<style id="fiddle-theme"></style>
|
||||
</head>
|
||||
<body class="fiddle">
|
||||
<div id="app"></div>
|
||||
<script src="../src/renderer/app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -25,10 +25,6 @@ app.on('ready', () => {
|
||||
console.log('The autoUpdater is checking for an update')
|
||||
})
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
console.log('The autoUpdater has found an update!')
|
||||
})
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
console.log('The autoUpdater has found an update and is now downloading it!')
|
||||
})
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
const { app, BrowserWindow, session } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
const mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
|
||||
@@ -5,20 +5,21 @@
|
||||
|
||||
const { app, BrowserWindow, dialog } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
const mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
|
||||
// Show an "Open File" dialog and attempt to open
|
||||
// the chosen file in our window.
|
||||
//
|
||||
// If you provide a callback, the method will be asynchronous,
|
||||
// but in thise case, we'll just use the synchronous variant.
|
||||
const file = dialog.showOpenDialog({
|
||||
title: 'Hello!',
|
||||
dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile']
|
||||
}).then(result => {
|
||||
if (result.canceled) {
|
||||
console.log('Dialog was canceled')
|
||||
} else {
|
||||
const file = result.filePaths[0]
|
||||
mainWindow.loadURL(`file://${file}`)
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
mainWindow.loadURL(`file://${file}`)
|
||||
})
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
|
||||
const { app, BrowserWindow, ipcMain } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
|
||||
@@ -4,6 +4,6 @@ const { ipcRenderer } = require('electron')
|
||||
console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
|
||||
|
||||
// prints "pong"
|
||||
ipcRenderer.on('asynchronous-reply', (...args) => console.log(args))
|
||||
ipcRenderer.on('asynchronous-reply', (_, ...args) => console.log(...args))
|
||||
|
||||
ipcRenderer.send('asynchronous-message', 'ping')
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
const { app, BrowserWindow, Menu } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
const mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
const template = [
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
|
||||
const { app, BrowserWindow, Notification } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 600,
|
||||
webPreferences: {
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
// We cannot require the screen module until the
|
||||
// app is ready
|
||||
@@ -17,7 +15,7 @@ app.on('ready', () => {
|
||||
const primaryDisplay = screen.getPrimaryDisplay()
|
||||
const { width, height } = primaryDisplay.workAreaSize
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
width,
|
||||
height,
|
||||
webPreferences: {
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 600,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
const { app, BrowserWindow, TouchBar } = require('electron')
|
||||
const { TouchBarLabel, TouchBarButton, TouchBarSpacer } = TouchBar
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
const mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
// This API only works on macOS devices with a TouchBar.
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
const { app, BrowserWindow, webContents } = require('electron')
|
||||
|
||||
let mainWindow
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
const mainWindow = new BrowserWindow({ height: 600, width: 600 })
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let mainWindow = null
|
||||
|
||||
app.on('ready', () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 600,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
import { EditorId } from '../../src/interfaces';
|
||||
import { App as AppType } from '../../src/renderer/app';
|
||||
import { EditorMock } from './mocks/editors';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
ElectronFiddle: {
|
||||
app: AppType;
|
||||
editors: Record<EditorId, EditorMock>;
|
||||
};
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"expectedVersionCount": 325,
|
||||
"lastElectronVersion": "9.0.0-beta.9"
|
||||
}
|
||||
Vendored
+7
@@ -10,3 +10,10 @@ declare global {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface Window {
|
||||
ElectronFiddle: {
|
||||
app: AppType;
|
||||
editors: Record<EditorId, EditorMock>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import { createContextMenu, getInspectItems, getMonacoItems, getRunItems } from '../../src/main/context-menu';
|
||||
import { ipcMainManager } from '../../src/main/ipc';
|
||||
import { isDevMode } from '../../src/utils/devmode';
|
||||
|
||||
+23
-11
@@ -1,12 +1,11 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import { setupDevTools } from '../../src/main/devtools';
|
||||
import { isDevMode } from '../../src/utils/devmode';
|
||||
|
||||
let mockIsDevMode = false;
|
||||
|
||||
jest.mock('../../src/utils/devmode', () => ({
|
||||
get isDevMode() {
|
||||
return mockIsDevMode;
|
||||
}
|
||||
}));
|
||||
jest.mock('../../src/utils/devmode');
|
||||
|
||||
jest.mock('electron-devtools-installer', () => ({
|
||||
default: jest.fn(),
|
||||
@@ -17,18 +16,31 @@ jest.mock('electron-devtools-installer', () => ({
|
||||
describe('devtools', () => {
|
||||
it('does not set up developer tools if not in dev mode', () => {
|
||||
const devtools = require('electron-devtools-installer');
|
||||
|
||||
(isDevMode as jest.Mock).mockReturnValue(false);
|
||||
setupDevTools();
|
||||
|
||||
expect(devtools.default).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('sets up developer tools if not in dev mode', () => {
|
||||
it('sets up developer tools if in dev mode', () => {
|
||||
const devtools = require('electron-devtools-installer');
|
||||
|
||||
mockIsDevMode = true;
|
||||
(isDevMode as jest.Mock).mockReturnValue(true);
|
||||
setupDevTools();
|
||||
|
||||
expect(devtools.default).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('catch error in setting up developer tools', async (done) => {
|
||||
const devtools = require('electron-devtools-installer');
|
||||
// throw devtool error
|
||||
devtools.default.mockRejectedValue(new Error('devtool error'));
|
||||
(isDevMode as jest.Mock).mockReturnValue(true);
|
||||
|
||||
try {
|
||||
await setupDevTools();
|
||||
done();
|
||||
} catch (e) {
|
||||
expect(e).toMatch('error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,28 +1,102 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import { IpcEvents } from '../../src/ipc-events';
|
||||
import { setupDialogs } from '../../src/main/dialogs';
|
||||
import { ipcMainManager } from '../../src/main/ipc';
|
||||
|
||||
import { dialog } from 'electron';
|
||||
import { flushPromises } from '../utils';
|
||||
|
||||
jest.mock('../../src/main/windows');
|
||||
|
||||
describe('dialogs', () => {
|
||||
it('sets up dialogs', () => {
|
||||
setupDialogs();
|
||||
|
||||
beforeEach(() => {
|
||||
setupDialogs();
|
||||
});
|
||||
|
||||
it('sets up dialogs', () => {
|
||||
expect(ipcMainManager.eventNames()).toEqual([
|
||||
IpcEvents.SHOW_WARNING_DIALOG,
|
||||
IpcEvents.SHOW_CONFIRMATION_DIALOG
|
||||
IpcEvents.SHOW_CONFIRMATION_DIALOG,
|
||||
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG
|
||||
]);
|
||||
});
|
||||
|
||||
it('tries to show a dialog if triggered', () => {
|
||||
setupDialogs();
|
||||
describe('warning dialog', () => {
|
||||
it('shows dialog when triggering IPC event', () => {
|
||||
ipcMainManager.emit(IpcEvents.SHOW_WARNING_DIALOG, {}, { hi: 'hello' });
|
||||
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
|
||||
type: 'warning',
|
||||
hi: 'hello'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ipcMainManager.emit(IpcEvents.SHOW_WARNING_DIALOG, {}, { hi: 'hello' });
|
||||
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
|
||||
type: 'warning',
|
||||
hi: 'hello'
|
||||
describe('confirmation dialog', () => {
|
||||
it('shows dialog when triggering IPC event', () => {
|
||||
ipcMainManager.emit(IpcEvents.SHOW_CONFIRMATION_DIALOG, {}, { hi: 'hello' });
|
||||
expect(dialog.showMessageBox).toHaveBeenCalledWith(undefined, {
|
||||
type: 'warning',
|
||||
hi: 'hello'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('local version folder dialog', () => {
|
||||
it('shows dialog when triggering IPC event', () => {
|
||||
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
|
||||
filePaths: []
|
||||
});
|
||||
|
||||
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
|
||||
reply: jest.fn()
|
||||
});
|
||||
expect(dialog.showOpenDialog).toHaveBeenCalledWith(expect.objectContaining({
|
||||
properties: ['openDirectory']
|
||||
}));
|
||||
});
|
||||
|
||||
it('triggers IPC load local version event', async () => {
|
||||
const replyFn = jest.fn();
|
||||
const paths = ['/test/path/'];
|
||||
|
||||
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
|
||||
filePaths: paths
|
||||
});
|
||||
|
||||
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
|
||||
reply: replyFn
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
expect(replyFn).toHaveBeenCalledWith(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, paths);
|
||||
});
|
||||
|
||||
it('does nothing if not given a path', async () => {
|
||||
const replyFn = jest.fn();
|
||||
|
||||
// empty array
|
||||
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({
|
||||
filePaths: []
|
||||
});
|
||||
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
|
||||
reply: replyFn
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
expect(replyFn).not.toHaveBeenCalled();
|
||||
|
||||
// nothing in response
|
||||
(dialog.showOpenDialog as jest.Mock).mockResolvedValue({});
|
||||
ipcMainManager.emit(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, {
|
||||
reply: replyFn
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
expect(replyFn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import { IpcEvents } from '../../src/ipc-events';
|
||||
import {
|
||||
setupFileListeners,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import { app, dialog } from 'electron';
|
||||
|
||||
import { onFirstRunMaybe } from '../../src/main/first-run';
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import * as electron from 'electron';
|
||||
import { IpcEvents } from '../../src/ipc-events';
|
||||
import { ipcMainManager } from '../../src/main/ipc';
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import { app } from 'electron';
|
||||
|
||||
import { main, onBeforeQuit, onReady, onWindowsAllClosed } from '../../src/main/main';
|
||||
|
||||
+20
-2
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import * as electron from 'electron';
|
||||
|
||||
import { IpcEvents } from '../../src/ipc-events';
|
||||
@@ -78,6 +82,20 @@ describe('menu', () => {
|
||||
(toggleMap as any).click();
|
||||
expect(ipcMainManager.send).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('adds Bisect toggle', () => {
|
||||
overridePlatform('linux');
|
||||
|
||||
setupMenu();
|
||||
|
||||
const result = (electron.Menu.buildFromTemplate as any).mock.calls[0][0];
|
||||
const submenu = result[2].submenu as Array<Electron.MenuItemConstructorOptions>;
|
||||
|
||||
const toggleSoftWrap = submenu.find(({ label }) => label === 'Toggle Bisect Helper');
|
||||
(toggleSoftWrap as any).click();
|
||||
expect(ipcMainManager.send).toHaveBeenCalledWith(IpcEvents.BISECT_COMMANDS_TOGGLE);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('menu groups', () => {
|
||||
@@ -174,7 +192,7 @@ describe('menu', () => {
|
||||
it('attempts to open a template on click', () => {
|
||||
showMe.submenu[0].submenu[0].click();
|
||||
expect(ipcMainManager.send)
|
||||
.toHaveBeenCalledWith(IpcEvents.FS_OPEN_TEMPLATE, [ 'App' ]);
|
||||
.toHaveBeenCalledWith(IpcEvents.FS_OPEN_TEMPLATE, ['App']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -182,7 +200,7 @@ describe('menu', () => {
|
||||
let tasks: any;
|
||||
|
||||
beforeEach(() => {
|
||||
const mock = (electron.Menu.buildFromTemplate as any).mock;
|
||||
const mock = (electron.Menu.buildFromTemplate as any).mock;
|
||||
const menu = mock.calls[0][0];
|
||||
tasks = menu[menu.length - 3];
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user