Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c06a33018 | ||
|
|
af4155b2df | ||
|
|
548b68e208 | ||
|
|
683def4215 | ||
|
|
5596c12f19 | ||
|
|
db9c1d5105 | ||
|
|
b52e70ec61 | ||
|
|
305bb9afbe | ||
|
|
d121954018 | ||
|
|
e51b1648cb | ||
|
|
ccc72ee70a | ||
|
|
48c62f4e02 |
@@ -0,0 +1,210 @@
|
||||
step-restore-cache: &step-restore-cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- v1-modules-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
- v1-modules-{{ arch }}
|
||||
step-install: &step-install
|
||||
run: npx yarn --frozen-lockfile && rm -rf node_modules/electron/dist
|
||||
step-save-cache: &step-save-cache
|
||||
save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
key: v1-modules-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
step-install-electron: &step-install-electron
|
||||
run: cd node_modules/electron && node install.js
|
||||
|
||||
steps-test: &steps-test
|
||||
steps:
|
||||
- checkout
|
||||
- *step-restore-cache
|
||||
- *step-install
|
||||
- *step-save-cache
|
||||
- *step-install-electron
|
||||
- run: yarn lint
|
||||
- run: yarn test --maxWorkers=4
|
||||
|
||||
steps-build: &steps-build
|
||||
steps:
|
||||
- checkout
|
||||
- *step-restore-cache
|
||||
- *step-install
|
||||
- *step-save-cache
|
||||
- *step-install-electron
|
||||
- when:
|
||||
condition: <<parameters.on_linux>>
|
||||
steps:
|
||||
- run:
|
||||
name: Install Linux Dependencies
|
||||
command: sudo apt install -y rpm fakeroot
|
||||
- when:
|
||||
condition: <<parameters.on_mac>>
|
||||
steps:
|
||||
- run:
|
||||
name: Import macOS Certificate
|
||||
command: .circleci/import-macos-cert.sh
|
||||
- when:
|
||||
condition: <<parameters.on_win>>
|
||||
steps:
|
||||
- run:
|
||||
name: Import Windows Certificate
|
||||
command: |
|
||||
if (Test-Path Env:\WINDOWS_CERTIFICATE_P12) {
|
||||
$workingDirectory = Convert-Path (Resolve-Path -path ".")
|
||||
$filename = "$workingDirectory\cert.p12"
|
||||
$bytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_P12)
|
||||
[IO.File]::WriteAllBytes($filename, $bytes)
|
||||
}
|
||||
shell: powershell.exe
|
||||
- run:
|
||||
name: Publish dry run
|
||||
command: yarn run publish --dry-run --arch=$TARGET_ARCH
|
||||
- store_artifacts:
|
||||
path: out/make
|
||||
destination: dist
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- out/make
|
||||
- out/publish-dry-run
|
||||
|
||||
version: 2.1
|
||||
orbs:
|
||||
win: circleci/windows@1.0.0
|
||||
jobs:
|
||||
test-linux:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
<<: *steps-test
|
||||
build-linux:
|
||||
docker:
|
||||
- image: circleci/node:10-buster
|
||||
environment:
|
||||
TARGET_ARCH: x64
|
||||
parameters:
|
||||
on_linux:
|
||||
type: boolean
|
||||
default: true
|
||||
on_mac:
|
||||
type: boolean
|
||||
default: false
|
||||
on_win:
|
||||
type: boolean
|
||||
default: false
|
||||
<<: *steps-build
|
||||
|
||||
test-mac:
|
||||
macos:
|
||||
xcode: "10.2.0"
|
||||
<<: *steps-test
|
||||
build-mac:
|
||||
macos:
|
||||
xcode: "10.2.0"
|
||||
environment:
|
||||
TARGET_ARCH: x64
|
||||
parameters:
|
||||
on_linux:
|
||||
type: boolean
|
||||
default: false
|
||||
on_mac:
|
||||
type: boolean
|
||||
default: true
|
||||
on_win:
|
||||
type: boolean
|
||||
default: false
|
||||
<<: *steps-build
|
||||
|
||||
test-windows:
|
||||
executor:
|
||||
name: win/vs2019
|
||||
shell: bash.exe
|
||||
<<: *steps-test
|
||||
build-windows-ia32:
|
||||
executor:
|
||||
name: win/vs2019
|
||||
shell: bash.exe
|
||||
environment:
|
||||
TARGET_ARCH: ia32
|
||||
parameters:
|
||||
on_linux:
|
||||
type: boolean
|
||||
default: false
|
||||
on_mac:
|
||||
type: boolean
|
||||
default: false
|
||||
on_win:
|
||||
type: boolean
|
||||
default: true
|
||||
<<: *steps-build
|
||||
build-windows-x64:
|
||||
executor:
|
||||
name: win/vs2019
|
||||
shell: bash.exe
|
||||
environment:
|
||||
TARGET_ARCH: x64
|
||||
parameters:
|
||||
on_linux:
|
||||
type: boolean
|
||||
default: false
|
||||
on_mac:
|
||||
type: boolean
|
||||
default: false
|
||||
on_win:
|
||||
type: boolean
|
||||
default: true
|
||||
<<: *steps-build
|
||||
|
||||
finalize:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
steps:
|
||||
- checkout
|
||||
- *step-restore-cache
|
||||
- *step-install
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- store_artifacts:
|
||||
path: out/make
|
||||
destination: dist
|
||||
|
||||
release:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
steps:
|
||||
- checkout
|
||||
- *step-restore-cache
|
||||
- *step-install
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: yarn publish --from-dry-run
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build:
|
||||
jobs:
|
||||
- test-linux
|
||||
- test-mac
|
||||
- test-windows
|
||||
|
||||
- build-linux
|
||||
- build-mac
|
||||
- build-windows-ia32
|
||||
- build-windows-x64
|
||||
|
||||
- finalize:
|
||||
requires:
|
||||
- build-linux
|
||||
- build-mac
|
||||
- build-windows-ia32
|
||||
- build-windows-x64
|
||||
- release:
|
||||
requires:
|
||||
- build-linux
|
||||
- build-mac
|
||||
- build-windows-ia32
|
||||
- build-windows-x64
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v.*/
|
||||
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [[ ! -z $MACOS_CERT_P12 ]]
|
||||
then
|
||||
|
||||
export CERTIFICATE_P12=cert.p12;
|
||||
echo $MACOS_CERT_P12 | base64 --decode > $CERTIFICATE_P12;
|
||||
export KEYCHAIN=build.keychain;
|
||||
|
||||
# Create the keychain with a password
|
||||
security create-keychain -p travis $KEYCHAIN;
|
||||
|
||||
# Make the custom keychain default, so xcodebuild will use it for signing
|
||||
security default-keychain -s $KEYCHAIN;
|
||||
|
||||
# Unlock the keychain
|
||||
security unlock-keychain -p travis $KEYCHAIN;
|
||||
|
||||
# Add certificates to keychain and allow codesign to access them
|
||||
# Apple Worldwide Developer Relations Certification Authority
|
||||
security import ./tools/certs/apple.cer -k ~/Library/Keychains/$KEYCHAIN -T /usr/bin/codesign
|
||||
# Developer Authentication Certification Authority
|
||||
security import ./tools/certs/dac.cer -k ~/Library/Keychains/$KEYCHAIN -T /usr/bin/codesign
|
||||
# Developer ID Felix
|
||||
security import $CERTIFICATE_P12 -k $KEYCHAIN -P $MACOS_CERT_PASSWORD -T /usr/bin/codesign 2>&1 >/dev/null;
|
||||
rm $CERTIFICATE_P12;
|
||||
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k travis $KEYCHAIN
|
||||
|
||||
# Echo the identity
|
||||
security find-identity -v -p codesigning
|
||||
fi
|
||||
@@ -1,4 +0,0 @@
|
||||
/out
|
||||
/dist
|
||||
/coverage
|
||||
/static
|
||||
@@ -1,30 +0,0 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
|
||||
sourceType: 'module', // Allows for the use of imports
|
||||
ecmaFeatures: {
|
||||
jsx: true, // Allows for the parsing of JSX
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
|
||||
},
|
||||
},
|
||||
extends: [
|
||||
'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react
|
||||
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||
'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
|
||||
'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
|
||||
],
|
||||
rules: {
|
||||
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
|
||||
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
||||
},
|
||||
};
|
||||
@@ -21,7 +21,6 @@
|
||||
/out/
|
||||
node_modules/
|
||||
SHASUMS256.txt
|
||||
**/yarn.lock
|
||||
compile_commands.json
|
||||
.envrc
|
||||
.cache
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
trailingComma: "all",
|
||||
singleQuote: true,
|
||||
};
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
language: node_js
|
||||
node_js: "12"
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
dist: bionic
|
||||
osx_image: xcode10
|
||||
|
||||
cache:
|
||||
npm: true
|
||||
directories:
|
||||
- $HOME/.cache/electron
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- fakeroot
|
||||
- rpm
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v\d+\.\d+\.\d+/
|
||||
|
||||
install:
|
||||
- npm ci
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" && "$TRAVIS_SECURE_ENV_VARS" == "true" ]]; then
|
||||
export CERTIFICATE_P12=cert.p12;
|
||||
echo $MACOS_CERT_P12 | base64 --decode > $CERTIFICATE_P12;
|
||||
export KEYCHAIN=build.keychain;
|
||||
|
||||
# Create the keychain with a password
|
||||
security create-keychain -p travis $KEYCHAIN;
|
||||
|
||||
# Make the custom keychain default, so xcodebuild will use it for signing
|
||||
security default-keychain -s $KEYCHAIN;
|
||||
|
||||
# Unlock the keychain
|
||||
security unlock-keychain -p travis $KEYCHAIN;
|
||||
|
||||
# Add certificates to keychain and allow codesign to access them
|
||||
# Apple Worldwide Developer Relations Certification Authority
|
||||
security import ./tools/certs/apple.cer -k ~/Library/Keychains/$KEYCHAIN -T /usr/bin/codesign
|
||||
# Developer Authentication Certification Authority
|
||||
security import ./tools/certs/dac.cer -k ~/Library/Keychains/$KEYCHAIN -T /usr/bin/codesign
|
||||
# Developer ID Felix
|
||||
security import $CERTIFICATE_P12 -k $KEYCHAIN -P $MACOS_CERT_PASSWORD -T /usr/bin/codesign 2>&1 >/dev/null;
|
||||
rm $CERTIFICATE_P12;
|
||||
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k travis $KEYCHAIN
|
||||
|
||||
# Echo the identity
|
||||
security find-identity -v -p codesigning
|
||||
fi
|
||||
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run test:ci
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then npm run test:coverage; fi
|
||||
- if test -z "$TRAVIS_TAG"; then npm run make; fi
|
||||
|
||||
after_success: if test -n "$TRAVIS_TAG"; then npm run publish; fi
|
||||
@@ -1,8 +1,6 @@
|
||||
# <img src="https://user-images.githubusercontent.com/378023/49785546-4b7f7000-fd64-11e8-8033-a52c73a07fbf.png" width="60px" align="center" alt="Electron Fiddle icon"> Electron Fiddle
|
||||
|
||||
[](https://travis-ci.org/electron/fiddle)
|
||||
[](https://coveralls.io/github/electron/fiddle?branch=master)
|
||||
[](https://discord.com/invite/electron)
|
||||
[](https://travis-ci.org/electron/fiddle) [](https://coveralls.io/github/electron/fiddle?branch=master)
|
||||
|
||||
Electron Fiddle lets you create and play with small Electron experiments. It
|
||||
greets you with a quick-start template after opening – change a few things,
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "12"
|
||||
|
||||
init:
|
||||
- git config --global core.symlinks true
|
||||
|
||||
install:
|
||||
# Setup the code signing certificate
|
||||
- ps: >-
|
||||
if (Test-Path Env:\WINDOWS_CERTIFICATE_P12) {
|
||||
$workingDirectory = Convert-Path (Resolve-Path -path ".")
|
||||
$filename = "$workingDirectory\cert.p12"
|
||||
$bytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_P12)
|
||||
[IO.File]::WriteAllBytes($filename, $bytes)
|
||||
}
|
||||
- ps: Install-Product node $env:nodejs_version x64
|
||||
- node --version
|
||||
- npm ci
|
||||
|
||||
cache:
|
||||
- '%APPDATA%\npm-cache -> appveyor.yml'
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- npm run lint
|
||||
- npm run test
|
||||
|
||||
artifacts:
|
||||
- path: 'out\make\squirrel.windows\**\*.exe'
|
||||
|
||||
build_script:
|
||||
- if %APPVEYOR_REPO_TAG% EQU false npm run make
|
||||
- if %APPVEYOR_REPO_TAG% EQU true npm run publish
|
||||
- if %APPVEYOR_REPO_TAG% EQU true npm run publish -- --arch=ia32
|
||||
- ps: Tree ./out/make /F
|
||||
+39
-46
@@ -1,13 +1,15 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const packageJson = require('./package.json');
|
||||
/* tslint:disable */
|
||||
|
||||
const { version } = packageJson;
|
||||
const iconDir = path.resolve(__dirname, 'assets', 'icons');
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const packageJson = require('./package.json')
|
||||
|
||||
const { version } = packageJson
|
||||
const iconDir = path.resolve(__dirname, 'assets', 'icons')
|
||||
|
||||
const config = {
|
||||
hooks: {
|
||||
generateAssets: require('./tools/generateAssets'),
|
||||
generateAssets: require('./tools/generateAssets')
|
||||
},
|
||||
packagerConfig: {
|
||||
name: 'Electron Fiddle',
|
||||
@@ -16,18 +18,14 @@ const config = {
|
||||
icon: path.resolve(__dirname, 'assets', 'icons', 'fiddle'),
|
||||
appBundleId: 'com.electron.fiddle',
|
||||
usageDescription: {
|
||||
Camera:
|
||||
'Access is needed by certain built-in fiddles in addition to any custom fiddles that use the Camera',
|
||||
Microphone:
|
||||
'Access is needed by certain built-in fiddles in addition to any custom fiddles that use the Microphone',
|
||||
Camera: 'Access is needed by certain built-in fiddles in addition to any custom fiddles that use the Camera',
|
||||
Microphone: 'Access is needed by certain built-in fiddles in addition to any custom fiddles that use the Microphone'
|
||||
},
|
||||
appCategoryType: 'public.app-category.developer-tools',
|
||||
protocols: [
|
||||
{
|
||||
name: 'Electron Fiddle Launch Protocol',
|
||||
schemes: ['electron-fiddle'],
|
||||
},
|
||||
],
|
||||
protocols: [{
|
||||
name: 'Electron Fiddle Launch Protocol',
|
||||
schemes: ['electron-fiddle']
|
||||
}],
|
||||
win32metadata: {
|
||||
CompanyName: 'Electron Community',
|
||||
OriginalFilename: 'Electron Fiddle',
|
||||
@@ -36,10 +34,10 @@ const config = {
|
||||
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)',
|
||||
'hardened-runtime': true,
|
||||
'gatekeeper-assess': false,
|
||||
entitlements: 'static/entitlements.plist',
|
||||
'entitlements': 'static/entitlements.plist',
|
||||
'entitlements-inherit': 'static/entitlements.plist',
|
||||
'signature-flags': 'library',
|
||||
},
|
||||
'signature-flags': 'library'
|
||||
}
|
||||
},
|
||||
makers: [
|
||||
{
|
||||
@@ -51,44 +49,41 @@ const config = {
|
||||
: process.env.WINDOWS_CERTIFICATE_FILE;
|
||||
|
||||
if (!certificateFile || !fs.existsSync(certificateFile)) {
|
||||
console.warn(
|
||||
`Warning: Could not find certificate file at ${certificateFile}`,
|
||||
);
|
||||
console.warn(`Warning: Could not find certificate file at ${certificateFile}`)
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'electron-fiddle',
|
||||
authors: 'Electron Community',
|
||||
exe: 'electron-fiddle.exe',
|
||||
iconUrl:
|
||||
'https://raw.githubusercontent.com/electron/fiddle/0119f0ce697f5ff7dec4fe51f17620c78cfd488b/assets/icons/fiddle.ico',
|
||||
iconUrl: 'https://raw.githubusercontent.com/electron/fiddle/0119f0ce697f5ff7dec4fe51f17620c78cfd488b/assets/icons/fiddle.ico',
|
||||
loadingGif: './assets/loading.gif',
|
||||
noMsi: true,
|
||||
remoteReleases: '',
|
||||
setupExe: `electron-fiddle-${version}-${arch}-setup.exe`,
|
||||
setupIcon: path.resolve(iconDir, 'fiddle.ico'),
|
||||
certificatePassword: process.env.WINDOWS_CERTIFICATE_PASSWORD,
|
||||
certificateFile,
|
||||
};
|
||||
},
|
||||
certificateFile
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-zip',
|
||||
platforms: ['darwin'],
|
||||
platforms: ['darwin']
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-deb',
|
||||
platforms: ['linux'],
|
||||
config: {
|
||||
icon: {
|
||||
scalable: path.resolve(iconDir, 'fiddle.svg'),
|
||||
},
|
||||
},
|
||||
scalable: path.resolve(iconDir, 'fiddle.svg')
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-rpm',
|
||||
platforms: ['linux'],
|
||||
},
|
||||
platforms: ['linux']
|
||||
}
|
||||
],
|
||||
publishers: [
|
||||
{
|
||||
@@ -96,14 +91,14 @@ const config = {
|
||||
config: {
|
||||
repository: {
|
||||
owner: 'electron',
|
||||
name: 'fiddle',
|
||||
name: 'fiddle'
|
||||
},
|
||||
draft: true,
|
||||
prerelease: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
prerelease: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function notarizeMaybe() {
|
||||
if (process.platform !== 'darwin') {
|
||||
@@ -126,9 +121,7 @@ function notarizeMaybe() {
|
||||
}
|
||||
|
||||
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!',
|
||||
);
|
||||
console.warn('Should be notarizing, but environment variables APPLE_ID or APPLE_ID_PASSWORD are missing!');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,11 +129,11 @@ function notarizeMaybe() {
|
||||
appBundleId: 'com.electron.fiddle',
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_ID_PASSWORD,
|
||||
ascProvider: 'LT94ZKYDCJ',
|
||||
};
|
||||
ascProvider: 'LT94ZKYDCJ'
|
||||
}
|
||||
}
|
||||
|
||||
notarizeMaybe();
|
||||
notarizeMaybe()
|
||||
|
||||
// Finally, export it
|
||||
module.exports = config;
|
||||
module.exports = config
|
||||
|
||||
Generated
-20287
File diff suppressed because it is too large
Load Diff
+13
-34
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "electron-fiddle",
|
||||
"productName": "Electron Fiddle",
|
||||
"version": "0.16.0",
|
||||
"version": "0.15.1",
|
||||
"description": "The easiest way to get started with Electron",
|
||||
"repository": "https://github.com/electron/fiddle",
|
||||
"main": "./dist/src/main/main",
|
||||
@@ -9,8 +9,9 @@
|
||||
"contributors": "node ./tools/contributors.js",
|
||||
"less": "node ./tools/lessc.js",
|
||||
"lint:style": "stylelint \"./src/less/*.less\" --fix",
|
||||
"lint:ts": "eslint \"./**/*.{js,ts,tsx}\" --fix",
|
||||
"lint:templates": "standard \"./static/show-me/**/*.js\" --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": "electron-forge make",
|
||||
"package": "electron-forge package",
|
||||
@@ -42,8 +43,8 @@
|
||||
"@octokit/rest": "^16.43.1",
|
||||
"@sentry/electron": "^1.2.1",
|
||||
"classnames": "^2.2.6",
|
||||
"electron-default-menu": "^1.0.2",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-default-menu": "^1.0.1",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"extract-zip": "^1.6.7",
|
||||
"fix-path": "^3.0.0",
|
||||
@@ -59,7 +60,7 @@
|
||||
"semver": "^7.1.3",
|
||||
"tmp": "0.1.0",
|
||||
"tslib": "^1.11.1",
|
||||
"update-electron-app": "^2.0.1"
|
||||
"update-electron-app": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.6",
|
||||
@@ -80,51 +81,29 @@
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/semver": "^7.1.0",
|
||||
"@types/tmp": "0.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
||||
"@typescript-eslint/parser": "^3.7.0",
|
||||
"chokidar": "^3.3.1",
|
||||
"coveralls": "^3.0.9",
|
||||
"electron": "^9.3.1",
|
||||
"electron": "8.0.2",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.2",
|
||||
"enzyme-to-json": "^3.4.4",
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-plugin-react": "^7.20.3",
|
||||
"fetch-mock-jest": "^1.1.0-beta.3",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "^25.1.0",
|
||||
"less": "^3.11.1",
|
||||
"lint-staged": "^10.2.11",
|
||||
"log-symbols": "^3.0.0",
|
||||
"node-abi": "^2.18.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-abi": "^2.15.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"prettier": "^2.0.5",
|
||||
"react-test-renderer": "^16.13.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"standard": "^14.3.1",
|
||||
"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.2.0",
|
||||
"typescript": "^3.8.2"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"./**/*.{js,ts,tsx}": [
|
||||
"eslint --fix"
|
||||
],
|
||||
"./static/show-me/**/*.js": [
|
||||
"standard --fix"
|
||||
],
|
||||
"./src/less/*.less": [
|
||||
"stylelint --fix"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -8,7 +8,7 @@ declare global {
|
||||
interface Window {
|
||||
ElectronFiddle: {
|
||||
app: AppType;
|
||||
contentChangeListeners: Array<any>;
|
||||
contentChangeListeners: Array<any>,
|
||||
editors: Record<EditorId, MonacoType.editor.IStandaloneCodeEditor | null>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,3 +18,4 @@ export const html = `<!DOCTYPE html>
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ function createWindow () {
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(createWindow)
|
||||
app.on('ready', createWindow)
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
|
||||
+7
-29
@@ -8,27 +8,13 @@ export enum VersionState {
|
||||
ready = 'ready',
|
||||
downloading = 'downloading',
|
||||
unzipping = 'unzipping',
|
||||
unknown = 'unknown',
|
||||
unknown = 'unknown'
|
||||
}
|
||||
|
||||
export enum VersionSource {
|
||||
remote = 'remote',
|
||||
local = 'local',
|
||||
local = 'local'
|
||||
}
|
||||
|
||||
export enum GistActionType {
|
||||
publish = 'Publish',
|
||||
update = 'Update',
|
||||
delete = 'Delete',
|
||||
}
|
||||
|
||||
export enum GistActionState {
|
||||
publishing = 'publishing',
|
||||
updating = 'updating',
|
||||
deleting = 'deleting',
|
||||
none = 'none',
|
||||
}
|
||||
|
||||
export interface Version {
|
||||
version: string;
|
||||
name?: string;
|
||||
@@ -71,9 +57,7 @@ export interface GenericDialogOptions {
|
||||
type: GenericDialogType;
|
||||
ok?: string;
|
||||
cancel?: string;
|
||||
wantsInput?: boolean;
|
||||
label: string | JSX.Element;
|
||||
placeholder?: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface Templates {
|
||||
@@ -92,23 +76,17 @@ export const enum EditorId {
|
||||
'renderer' = 'renderer',
|
||||
'html' = 'html',
|
||||
'preload' = 'preload',
|
||||
'css' = 'css',
|
||||
'css' = 'css'
|
||||
}
|
||||
|
||||
// Panels that can show up as a mosaic
|
||||
export const enum PanelId {
|
||||
'docsDemo' = 'docsDemo',
|
||||
'docsDemo' = 'docsDemo'
|
||||
}
|
||||
|
||||
export type MosaicId = EditorId | PanelId;
|
||||
|
||||
export const ALL_EDITORS = [
|
||||
EditorId.main,
|
||||
EditorId.renderer,
|
||||
EditorId.preload,
|
||||
EditorId.html,
|
||||
EditorId.css,
|
||||
];
|
||||
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];
|
||||
|
||||
@@ -116,5 +94,5 @@ export type ArrowPosition = 'top' | 'left' | 'bottom' | 'right';
|
||||
|
||||
export const enum DocsDemoPage {
|
||||
DEFAULT = 'DEFAULT',
|
||||
DEMO_APP = 'DEMO_APP',
|
||||
DEMO_APP = 'DEMO_APP'
|
||||
}
|
||||
|
||||
+1
-4
@@ -27,7 +27,6 @@ export enum IpcEvents {
|
||||
BEFORE_QUIT = 'BEFORE_QUIT',
|
||||
CONFIRM_QUIT = 'CONFIRM_QUIT',
|
||||
SET_APPDATA_DIR = 'SET_APPDATA_DIR',
|
||||
SELECT_ALL_IN_EDITOR = 'SELECT_ALL_IN_EDITOR',
|
||||
}
|
||||
|
||||
export const ipcMainEvents = [
|
||||
@@ -62,8 +61,6 @@ export const ipcRendererEvents = [
|
||||
IpcEvents.BISECT_COMMANDS_TOGGLE,
|
||||
IpcEvents.BEFORE_QUIT,
|
||||
IpcEvents.SET_APPDATA_DIR,
|
||||
IpcEvents.SELECT_ALL_IN_EDITOR,
|
||||
];
|
||||
|
||||
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL =
|
||||
'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
|
||||
export const WEBCONTENTS_READY_FOR_IPC_SIGNAL = 'WEBCONTENTS_READY_FOR_IPC_SIGNAL';
|
||||
|
||||
+42
-54
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
BrowserWindow,
|
||||
ContextMenuParams,
|
||||
Menu,
|
||||
MenuItemConstructorOptions,
|
||||
} from 'electron';
|
||||
import { BrowserWindow, ContextMenuParams, Menu, MenuItemConstructorOptions } from 'electron';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { isDevMode } from '../utils/devmode';
|
||||
import { ipcMainManager } from './ipc';
|
||||
@@ -18,16 +13,16 @@ export function getRunItems(): Array<MenuItemConstructorOptions> {
|
||||
{
|
||||
id: 'run',
|
||||
label: 'Run Fiddle',
|
||||
click: () => ipcMainManager.send(IpcEvents.FIDDLE_RUN),
|
||||
click: () => ipcMainManager.send(IpcEvents.FIDDLE_RUN)
|
||||
},
|
||||
{
|
||||
id: 'clear_console',
|
||||
label: 'Clear Console',
|
||||
click: () => ipcMainManager.send(IpcEvents.CLEAR_CONSOLE),
|
||||
click: () => ipcMainManager.send(IpcEvents.CLEAR_CONSOLE)
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
type: 'separator'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -40,10 +35,9 @@ export function getRunItems(): Array<MenuItemConstructorOptions> {
|
||||
* @param {ContextMenuParams} { x, y }
|
||||
* @returns {Array<MenuItemConstructorOptions>}
|
||||
*/
|
||||
export function getMonacoItems({
|
||||
pageURL,
|
||||
editFlags,
|
||||
}: ContextMenuParams): Array<MenuItemConstructorOptions> {
|
||||
export function getMonacoItems(
|
||||
{ pageURL, editFlags }: ContextMenuParams
|
||||
): Array<MenuItemConstructorOptions> {
|
||||
if (!editFlags.canPaste || !/.*index\.html(#?)$/.test(pageURL || '')) {
|
||||
return [];
|
||||
}
|
||||
@@ -53,51 +47,51 @@ export function getMonacoItems({
|
||||
id: 'go_to_definition',
|
||||
label: 'Go to Definition',
|
||||
click() {
|
||||
const cmd = ['editor.action.goToDeclaration'];
|
||||
const cmd = [ 'editor.action.goToDeclaration' ];
|
||||
ipcMainManager.send(IpcEvents.MONACO_EXECUTE_COMMAND, cmd);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'peek_definition',
|
||||
label: 'Peek Definition',
|
||||
click() {
|
||||
const cmd = ['editor.action.previewDeclaration'];
|
||||
const cmd = [ 'editor.action.previewDeclaration' ];
|
||||
ipcMainManager.send(IpcEvents.MONACO_EXECUTE_COMMAND, cmd);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'references',
|
||||
label: 'Find References',
|
||||
click() {
|
||||
const cmd = ['editor.action.referenceSearch.trigger'];
|
||||
const cmd = [ 'editor.action.referenceSearch.trigger' ];
|
||||
ipcMainManager.send(IpcEvents.MONACO_EXECUTE_COMMAND, cmd);
|
||||
},
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
id: 'palette',
|
||||
label: 'Command Palette',
|
||||
click() {
|
||||
const cmd = ['editor.action.quickCommand'];
|
||||
const cmd = [ 'editor.action.quickCommand' ];
|
||||
ipcMainManager.send(IpcEvents.MONACO_EXECUTE_COMMAND, cmd);
|
||||
},
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
id: 'format_document',
|
||||
label: 'Format Document',
|
||||
click() {
|
||||
const cmd = ['editor.action.formatDocument'];
|
||||
const cmd = [ 'editor.action.formatDocument' ];
|
||||
ipcMainManager.send(IpcEvents.MONACO_EXECUTE_COMMAND, cmd);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'format_selection',
|
||||
label: 'Format Selection',
|
||||
click() {
|
||||
const cmd = ['editor.action.formatSelection'];
|
||||
const cmd = [ 'editor.action.formatSelection' ];
|
||||
ipcMainManager.send(IpcEvents.MONACO_EXECUTE_COMMAND, cmd);
|
||||
},
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
];
|
||||
@@ -111,28 +105,25 @@ export function getMonacoItems({
|
||||
* @returns {Array<MenuItemConstructorOptions>}
|
||||
*/
|
||||
export function getInspectItems(
|
||||
browserWindow: BrowserWindow,
|
||||
{ x, y }: ContextMenuParams,
|
||||
browserWindow: BrowserWindow, { x, y }: ContextMenuParams
|
||||
): Array<MenuItemConstructorOptions> {
|
||||
if (!isDevMode()) return [];
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'inspect',
|
||||
label: 'Inspect Element',
|
||||
click: () => {
|
||||
browserWindow.webContents.inspectElement(x, y);
|
||||
return [{
|
||||
id: 'inspect',
|
||||
label: 'Inspect Element',
|
||||
click: () => {
|
||||
browserWindow.webContents.inspectElement(x, y);
|
||||
|
||||
try {
|
||||
if (browserWindow.webContents.isDevToolsOpened()) {
|
||||
browserWindow.webContents.devToolsWebContents?.focus();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Tried to focus dev tools, but failed`, { error });
|
||||
try {
|
||||
if (browserWindow.webContents.isDevToolsOpened()) {
|
||||
browserWindow.webContents.devToolsWebContents.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
} catch (error) {
|
||||
console.warn(`Tried to focus dev tools, but failed`, { error });
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,24 +142,21 @@ export function createContextMenu(browserWindow: BrowserWindow) {
|
||||
id: 'cut',
|
||||
label: 'Cut',
|
||||
role: 'cut',
|
||||
enabled: editFlags.canCut,
|
||||
},
|
||||
{
|
||||
enabled: editFlags.canCut
|
||||
}, {
|
||||
id: 'copy',
|
||||
label: 'Copy',
|
||||
role: 'copy',
|
||||
enabled: editFlags.canCopy,
|
||||
},
|
||||
{
|
||||
enabled: editFlags.canCopy
|
||||
}, {
|
||||
id: 'paste',
|
||||
label: 'Paste',
|
||||
role: 'paste',
|
||||
enabled: editFlags.canPaste,
|
||||
enabled: editFlags.canPaste
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
...getInspectItems(browserWindow, props),
|
||||
...getInspectItems(browserWindow, props)
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function setupDevTools(): Promise<void> {
|
||||
const {
|
||||
default: installExtension,
|
||||
REACT_DEVELOPER_TOOLS,
|
||||
REACT_PERF,
|
||||
REACT_PERF
|
||||
} = require('electron-devtools-installer');
|
||||
|
||||
try {
|
||||
|
||||
+7
-9
@@ -17,12 +17,10 @@ export function setupDialogs() {
|
||||
showConfirmationDialog(args);
|
||||
});
|
||||
|
||||
ipcMainManager.on(
|
||||
IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG,
|
||||
async (event) => {
|
||||
await showOpenDialog(event);
|
||||
},
|
||||
);
|
||||
ipcMainManager.on(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG, async (event) => {
|
||||
await showOpenDialog(event);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,7 +31,7 @@ export function setupDialogs() {
|
||||
function showWarningDialog(args: Electron.MessageBoxOptions) {
|
||||
dialog.showMessageBox(getOrCreateMainWindow(), {
|
||||
type: 'warning',
|
||||
...args,
|
||||
...args
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,14 +43,14 @@ function showWarningDialog(args: Electron.MessageBoxOptions) {
|
||||
function showConfirmationDialog(args: Electron.MessageBoxOptions) {
|
||||
dialog.showMessageBox(getOrCreateMainWindow(), {
|
||||
type: 'warning',
|
||||
...args,
|
||||
...args
|
||||
});
|
||||
}
|
||||
|
||||
async function showOpenDialog(event: IpcMainEvent) {
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
title: 'Open Folder',
|
||||
properties: ['openDirectory'],
|
||||
properties: ['openDirectory']
|
||||
});
|
||||
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
|
||||
+7
-12
@@ -3,12 +3,7 @@ import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import {
|
||||
INDEX_HTML_NAME,
|
||||
MAIN_JS_NAME,
|
||||
PACKAGE_NAME,
|
||||
RENDERER_JS_NAME,
|
||||
} from '../shared-constants';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PACKAGE_NAME, RENDERER_JS_NAME } from '../shared-constants';
|
||||
import { ipcMainManager } from './ipc';
|
||||
|
||||
/**
|
||||
@@ -27,14 +22,14 @@ export function setupFileListeners() {
|
||||
export async function showOpenDialog() {
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
title: 'Open Fiddle',
|
||||
properties: ['openDirectory'],
|
||||
properties: ['openDirectory']
|
||||
});
|
||||
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ipcMainManager.send(IpcEvents.FS_OPEN_FIDDLE, [filePaths[0]]);
|
||||
ipcMainManager.send(IpcEvents.FS_OPEN_FIDDLE, [ filePaths[0] ]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +41,7 @@ export async function showSaveDialog(event?: IpcEvents, as?: string) {
|
||||
const { filePaths } = await dialog.showOpenDialog({
|
||||
buttonLabel: 'Save here',
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
title: `Save Fiddle${as ? ` as ${as}` : ''}`,
|
||||
title: `Save Fiddle${as ? ` as ${as}` : ''}`
|
||||
});
|
||||
|
||||
if (!filePaths || filePaths.length < 1) {
|
||||
@@ -57,7 +52,7 @@ export async function showSaveDialog(event?: IpcEvents, as?: string) {
|
||||
|
||||
// Let's confirm real quick if we want this
|
||||
if (await ensureSaveTargetEmpty(filePaths[0])) {
|
||||
ipcMainManager.send(event || IpcEvents.FS_SAVE_FIDDLE, [filePaths[0]]);
|
||||
ipcMainManager.send(event || IpcEvents.FS_SAVE_FIDDLE, [ filePaths[0] ]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +67,7 @@ async function ensureSaveTargetEmpty(filePath: string): Promise<boolean> {
|
||||
path.join(filePath, INDEX_HTML_NAME),
|
||||
path.join(filePath, RENDERER_JS_NAME),
|
||||
path.join(filePath, MAIN_JS_NAME),
|
||||
path.join(filePath, PACKAGE_NAME),
|
||||
path.join(filePath, PACKAGE_NAME)
|
||||
];
|
||||
|
||||
let noFilesOrOverwriteGranted = true;
|
||||
@@ -97,7 +92,7 @@ async function confirmFileOverwrite(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
const result = await dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
buttons: ['Cancel', 'Yes'],
|
||||
buttons: [ 'Cancel', 'Yes' ],
|
||||
message: 'Overwrite files?',
|
||||
detail: `The file ${filePath} already exists. Do you want to overwrite it?`,
|
||||
});
|
||||
|
||||
+12
-26
@@ -1,11 +1,7 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import {
|
||||
IpcEvents,
|
||||
ipcMainEvents,
|
||||
WEBCONTENTS_READY_FOR_IPC_SIGNAL,
|
||||
} from '../ipc-events';
|
||||
import { IpcEvents, ipcMainEvents, WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
|
||||
import { getOrCreateMainWindow } from './windows';
|
||||
|
||||
/**
|
||||
@@ -18,10 +14,7 @@ import { getOrCreateMainWindow } from './windows';
|
||||
*/
|
||||
export class IpcMainManager extends EventEmitter {
|
||||
public readyWebContents = new WeakSet<Electron.WebContents>();
|
||||
private messageQueue = new WeakMap<
|
||||
Electron.WebContents,
|
||||
Array<[IpcEvents, Array<any> | undefined]>
|
||||
>();
|
||||
private messageQueue = new WeakMap<Electron.WebContents, Array<[IpcEvents, Array<any> | undefined]>>();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -31,19 +24,16 @@ export class IpcMainManager extends EventEmitter {
|
||||
ipcMain.on(name, (...args: Array<any>) => this.emit(name, ...args));
|
||||
});
|
||||
|
||||
ipcMain.on(
|
||||
WEBCONTENTS_READY_FOR_IPC_SIGNAL,
|
||||
(event: Electron.IpcMainEvent) => {
|
||||
this.readyWebContents.add(event.sender);
|
||||
ipcMain.on(WEBCONTENTS_READY_FOR_IPC_SIGNAL, (event: Electron.IpcMainEvent) => {
|
||||
this.readyWebContents.add(event.sender);
|
||||
|
||||
const queue = this.messageQueue.get(event.sender);
|
||||
this.messageQueue.delete(event.sender);
|
||||
if (!queue) return;
|
||||
for (const item of queue) {
|
||||
this.send(item[0], item[1], event.sender);
|
||||
}
|
||||
},
|
||||
);
|
||||
const queue = this.messageQueue.get(event.sender);
|
||||
this.messageQueue.delete(event.sender);
|
||||
if (!queue) return;
|
||||
for (const item of queue) {
|
||||
this.send(item[0], item[1], event.sender);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,11 +44,7 @@ export class IpcMainManager extends EventEmitter {
|
||||
* @param {Array<any>} args
|
||||
* @param {Electron.WebContents} [target]
|
||||
*/
|
||||
public send(
|
||||
channel: IpcEvents,
|
||||
args?: Array<any>,
|
||||
target?: Electron.WebContents,
|
||||
) {
|
||||
public send(channel: IpcEvents, args?: Array<any>, target?: Electron.WebContents) {
|
||||
const _target = target || getOrCreateMainWindow().webContents;
|
||||
const _args = args || [];
|
||||
if (!this.readyWebContents.has(_target)) {
|
||||
|
||||
+3
-6
@@ -5,7 +5,7 @@ import { app } from 'electron';
|
||||
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import { isDevMode } from '../utils/devmode';
|
||||
import { setupAboutPanel } from './about-panel';
|
||||
import { setupAboutPanel } from '../utils/set-about-panel';
|
||||
import { setupDevTools } from './devtools';
|
||||
import { setupDialogs } from './dialogs';
|
||||
import { onFirstRunMaybe } from './first-run';
|
||||
@@ -47,10 +47,7 @@ export function onBeforeQuit() {
|
||||
ipcMainManager.on(IpcEvents.CONFIRM_QUIT, quitAppIfConfirmed);
|
||||
}
|
||||
|
||||
export function quitAppIfConfirmed(
|
||||
_: Electron.IpcMainEvent,
|
||||
quitConfirmed: boolean,
|
||||
) {
|
||||
export function quitAppIfConfirmed(_: Electron.IpcMainEvent, quitConfirmed: boolean) {
|
||||
if (quitConfirmed) {
|
||||
app.quit();
|
||||
}
|
||||
@@ -89,7 +86,7 @@ export function main() {
|
||||
listenForProtocolHandler();
|
||||
|
||||
// Launch
|
||||
app.whenReady().then(onReady);
|
||||
app.on('ready', onReady);
|
||||
app.on('before-quit', onBeforeQuit);
|
||||
app.on('window-all-closed', onWindowsAllClosed);
|
||||
app.on('activate', getOrCreateMainWindow);
|
||||
|
||||
+83
-136
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
Menu,
|
||||
MenuItemConstructorOptions,
|
||||
shell,
|
||||
} from 'electron';
|
||||
import { app, BrowserWindow, Menu, MenuItemConstructorOptions, shell } from 'electron';
|
||||
|
||||
import { Templates } from '../interfaces';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
@@ -20,7 +14,7 @@ import { createMainWindow } from './windows';
|
||||
* @returns {submenu is Array<Electron.MenuItemConstructorOptions>}
|
||||
*/
|
||||
function isSubmenu(
|
||||
submenu?: Array<MenuItemConstructorOptions> | Menu,
|
||||
submenu?: Array<MenuItemConstructorOptions> | Menu
|
||||
): submenu is Array<MenuItemConstructorOptions> {
|
||||
return !!submenu && Array.isArray(submenu);
|
||||
}
|
||||
@@ -33,16 +27,16 @@ function isSubmenu(
|
||||
function getHelpItems(): Array<MenuItemConstructorOptions> {
|
||||
return [
|
||||
{
|
||||
type: 'separator',
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Show Welcome Tour',
|
||||
click() {
|
||||
ipcMainManager.send(IpcEvents.SHOW_WELCOME_TOUR);
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
@@ -51,30 +45,30 @@ function getHelpItems(): Array<MenuItemConstructorOptions> {
|
||||
const browserWindow = BrowserWindow.getFocusedWindow();
|
||||
|
||||
if (browserWindow && !browserWindow.isDestroyed()) {
|
||||
browserWindow.webContents.toggleDevTools();
|
||||
browserWindow.webContents.openDevTools({ mode: 'bottom' });
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Open Fiddle Repository...',
|
||||
click() {
|
||||
shell.openExternal('https://github.com/electron/fiddle');
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Open Electron Repository...',
|
||||
click() {
|
||||
shell.openExternal('https://github.com/electron/electron');
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Open Electron Issue Tracker...',
|
||||
click() {
|
||||
shell.openExternal('https://github.com/electron/electron/issues');
|
||||
},
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -88,18 +82,16 @@ function getHelpItems(): Array<MenuItemConstructorOptions> {
|
||||
function getPreferencesItems(): Array<MenuItemConstructorOptions> {
|
||||
return [
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: 'Preferences',
|
||||
accelerator: 'CmdOrCtrl+,',
|
||||
click() {
|
||||
ipcMainManager.send(IpcEvents.OPEN_SETTINGS);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -111,11 +103,10 @@ function getPreferencesItems(): Array<MenuItemConstructorOptions> {
|
||||
function getQuitItems(): Array<MenuItemConstructorOptions> {
|
||||
return [
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
role: 'quit',
|
||||
},
|
||||
type: 'separator'
|
||||
}, {
|
||||
role: 'quit'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -129,32 +120,29 @@ function getTasksMenu(): MenuItemConstructorOptions {
|
||||
{
|
||||
label: 'Run Fiddle...',
|
||||
accelerator: 'F5',
|
||||
click: () => ipcMainManager.send(IpcEvents.FIDDLE_RUN),
|
||||
click: () => ipcMainManager.send(IpcEvents.FIDDLE_RUN)
|
||||
},
|
||||
{
|
||||
label: 'Package Fiddle...',
|
||||
click: () => ipcMainManager.send(IpcEvents.FIDDLE_PACKAGE),
|
||||
click: () => ipcMainManager.send(IpcEvents.FIDDLE_PACKAGE)
|
||||
},
|
||||
{
|
||||
label: 'Make installers for Fiddle...',
|
||||
click: () => ipcMainManager.send(IpcEvents.FIDDLE_MAKE),
|
||||
},
|
||||
click: () => ipcMainManager.send(IpcEvents.FIDDLE_MAKE)
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
label: 'Tasks',
|
||||
submenu: tasksMenu,
|
||||
submenu: tasksMenu
|
||||
};
|
||||
}
|
||||
|
||||
function getShowMeMenuItem(
|
||||
key: string,
|
||||
item: string | Templates,
|
||||
): MenuItemConstructorOptions {
|
||||
function getShowMeMenuItem(key: string, item: string | Templates): MenuItemConstructorOptions {
|
||||
if (typeof item === 'string') {
|
||||
return {
|
||||
label: key,
|
||||
click: () => ipcMainManager.send(IpcEvents.FS_OPEN_TEMPLATE, [key]),
|
||||
click: () => ipcMainManager.send(IpcEvents.FS_OPEN_TEMPLATE, [key])
|
||||
};
|
||||
}
|
||||
|
||||
@@ -162,18 +150,17 @@ function getShowMeMenuItem(
|
||||
label: key,
|
||||
submenu: Object.keys(item).map((subkey) => {
|
||||
return getShowMeMenuItem(subkey, item[subkey]);
|
||||
}),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function getShowMeMenu(): MenuItemConstructorOptions {
|
||||
const showMeMenu: Array<MenuItemConstructorOptions> = Object.keys(
|
||||
SHOW_ME_TEMPLATES,
|
||||
).map((key) => getShowMeMenuItem(key, SHOW_ME_TEMPLATES[key]));
|
||||
const showMeMenu: Array<MenuItemConstructorOptions> = Object.keys(SHOW_ME_TEMPLATES)
|
||||
.map((key) => getShowMeMenuItem(key, SHOW_ME_TEMPLATES[key]));
|
||||
|
||||
return {
|
||||
label: 'Show Me',
|
||||
submenu: showMeMenu,
|
||||
submenu: showMeMenu
|
||||
};
|
||||
}
|
||||
|
||||
@@ -187,36 +174,34 @@ function getFileMenu(): MenuItemConstructorOptions {
|
||||
{
|
||||
label: 'New Fiddle',
|
||||
click: () => ipcMainManager.send(IpcEvents.FS_NEW_FIDDLE),
|
||||
accelerator: 'CmdOrCtrl+N',
|
||||
},
|
||||
{
|
||||
accelerator: 'CmdOrCtrl+N'
|
||||
}, {
|
||||
label: 'New Window',
|
||||
click: () => createMainWindow(),
|
||||
accelerator: 'CmdOrCtrl+Shift+N',
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
accelerator: 'CmdOrCtrl+Shift+N'
|
||||
}, {
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Open',
|
||||
click: showOpenDialog,
|
||||
accelerator: 'CmdOrCtrl+O',
|
||||
accelerator: 'CmdOrCtrl+O'
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Save',
|
||||
click: () => ipcMainManager.send(IpcEvents.FS_SAVE_FIDDLE),
|
||||
accelerator: 'CmdOrCtrl+S',
|
||||
accelerator: 'CmdOrCtrl+S'
|
||||
},
|
||||
{
|
||||
label: 'Save as',
|
||||
click: () => showSaveDialog(IpcEvents.FS_SAVE_FIDDLE),
|
||||
accelerator: 'CmdOrCtrl+Shift+S',
|
||||
accelerator: 'CmdOrCtrl+Shift+S'
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Publish to Gist',
|
||||
@@ -224,24 +209,18 @@ function getFileMenu(): MenuItemConstructorOptions {
|
||||
},
|
||||
{
|
||||
label: 'Save as Forge Project',
|
||||
click: () =>
|
||||
showSaveDialog(IpcEvents.FS_SAVE_FIDDLE_FORGE, 'Forge Project'),
|
||||
},
|
||||
click: () => showSaveDialog(IpcEvents.FS_SAVE_FIDDLE_FORGE, 'Forge Project')
|
||||
}
|
||||
];
|
||||
|
||||
// macOS has these items in the "Fiddle" menu
|
||||
if (process.platform !== 'darwin') {
|
||||
fileMenu.splice(
|
||||
fileMenu.length,
|
||||
0,
|
||||
...getPreferencesItems(),
|
||||
...getQuitItems(),
|
||||
);
|
||||
fileMenu.splice(fileMenu.length, 0, ...getPreferencesItems(), ...getQuitItems());
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'File',
|
||||
submenu: fileMenu,
|
||||
submenu: fileMenu
|
||||
};
|
||||
}
|
||||
|
||||
@@ -251,85 +230,53 @@ function getFileMenu(): MenuItemConstructorOptions {
|
||||
export function setupMenu() {
|
||||
// Get template for default menu
|
||||
const defaultMenu = require('electron-default-menu');
|
||||
const menu = (defaultMenu(app, shell) as Array<
|
||||
MenuItemConstructorOptions
|
||||
>).map((item) => {
|
||||
const { label } = item;
|
||||
const menu = (defaultMenu(app, shell) as Array<MenuItemConstructorOptions>)
|
||||
.map((item) => {
|
||||
const { label } = item;
|
||||
|
||||
// Append the "Settings" item
|
||||
if (
|
||||
process.platform === 'darwin' &&
|
||||
label === app.name &&
|
||||
isSubmenu(item.submenu)
|
||||
) {
|
||||
item.submenu.splice(2, 0, ...getPreferencesItems());
|
||||
}
|
||||
// Append the "Settings" item
|
||||
if (
|
||||
process.platform === 'darwin'
|
||||
&& label === app.name
|
||||
&& isSubmenu(item.submenu)
|
||||
) {
|
||||
item.submenu.splice(2, 0, ...getPreferencesItems());
|
||||
}
|
||||
|
||||
// Custom handler for "Select All" for Monaco
|
||||
if (label === 'Edit' && isSubmenu(item.submenu)) {
|
||||
const selectAll = item.submenu.find((i) => i.label === 'Select All')!;
|
||||
delete selectAll.role; // override default role
|
||||
selectAll.click = () => {
|
||||
ipcMainManager.send(IpcEvents.SELECT_ALL_IN_EDITOR);
|
||||
|
||||
// Allow selection to occur in text fields outside the editors.
|
||||
if (process.platform === 'darwin') {
|
||||
Menu.sendActionToFirstResponder('selectAll:');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Tweak "View" menu
|
||||
if (label === 'View' && isSubmenu(item.submenu)) {
|
||||
// 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' },
|
||||
{
|
||||
// Tweak "View" menu
|
||||
if (label === 'View' && isSubmenu(item.submenu)) {
|
||||
// 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']),
|
||||
},
|
||||
);
|
||||
item.submenu.push(
|
||||
{ type: 'separator' },
|
||||
{
|
||||
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',
|
||||
]),
|
||||
},
|
||||
);
|
||||
item.submenu.push(
|
||||
{ type: 'separator' },
|
||||
{
|
||||
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',
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Append items to "Help"
|
||||
if (label === 'Help' && isSubmenu(item.submenu)) {
|
||||
item.submenu = getHelpItems();
|
||||
}
|
||||
// Append items to "Help"
|
||||
if (label === 'Help' && isSubmenu(item.submenu)) {
|
||||
item.submenu = getHelpItems();
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
return item;
|
||||
});
|
||||
|
||||
menu.splice(process.platform === 'darwin' ? 1 : 0, 0, getFileMenu());
|
||||
menu.splice(
|
||||
process.platform === 'darwin' ? 1 : 0,
|
||||
0,
|
||||
getFileMenu()
|
||||
);
|
||||
|
||||
menu.splice(menu.length - 1, 0, getTasksMenu(), getShowMeMenu());
|
||||
|
||||
|
||||
+11
-21
@@ -8,11 +8,7 @@ import { isDevMode } from '../utils/devmode';
|
||||
import { ipcMainManager } from './ipc';
|
||||
|
||||
const PROTOCOL = 'electron-fiddle';
|
||||
const squirrelPath = path.resolve(
|
||||
path.dirname(process.execPath),
|
||||
'..',
|
||||
'electron-fiddle.exe',
|
||||
);
|
||||
const squirrelPath = path.resolve(path.dirname(process.execPath), '..', 'electron-fiddle.exe');
|
||||
|
||||
const handlePotentialProtocolLaunch = (url: string) => {
|
||||
if (!app.isReady()) {
|
||||
@@ -30,18 +26,14 @@ const handlePotentialProtocolLaunch = (url: string) => {
|
||||
case 'gist':
|
||||
if (pathParts.length === 1) {
|
||||
// We only have a gist ID
|
||||
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [
|
||||
{
|
||||
id: pathParts[0],
|
||||
},
|
||||
]);
|
||||
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [{
|
||||
id: pathParts[0],
|
||||
}]);
|
||||
} else if (pathParts.length === 2) {
|
||||
// We have a gist owner and gist ID, we can ignore the owner
|
||||
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [
|
||||
{
|
||||
id: pathParts[1],
|
||||
},
|
||||
]);
|
||||
ipcMainManager.send(IpcEvents.LOAD_GIST_REQUEST, [{
|
||||
id: pathParts[1],
|
||||
}]);
|
||||
} else {
|
||||
// This is a super invalid gist launch
|
||||
return;
|
||||
@@ -52,12 +44,10 @@ const handlePotentialProtocolLaunch = (url: string) => {
|
||||
if (pathParts.length > 1) {
|
||||
// First part of the commit HASH / ref / branch
|
||||
// Rest is the path to the example
|
||||
ipcMainManager.send(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, [
|
||||
{
|
||||
ref: pathParts[0],
|
||||
path: pathParts.slice(1).join('/'),
|
||||
},
|
||||
]);
|
||||
ipcMainManager.send(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, [{
|
||||
ref: pathParts[0],
|
||||
path: pathParts.slice(1).join('/'),
|
||||
}]);
|
||||
} else {
|
||||
// This is an invalid electron launch
|
||||
return;
|
||||
|
||||
+7
-11
@@ -23,11 +23,12 @@ export function getMainWindowOptions(): Electron.BrowserWindowConstructorOptions
|
||||
backgroundColor: '#1d2427',
|
||||
webPreferences: {
|
||||
webviewTag: false,
|
||||
nodeIntegration: true,
|
||||
},
|
||||
nodeIntegration: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new main window.
|
||||
*
|
||||
@@ -49,7 +50,8 @@ export function createMainWindow(): Electron.BrowserWindow {
|
||||
});
|
||||
|
||||
browserWindow.on('closed', () => {
|
||||
browserWindows = browserWindows.filter((bw) => browserWindow !== bw);
|
||||
browserWindows = browserWindows
|
||||
.filter((bw) => browserWindow !== bw);
|
||||
|
||||
browserWindow = null;
|
||||
});
|
||||
@@ -71,11 +73,7 @@ export function createMainWindow(): Electron.BrowserWindow {
|
||||
});
|
||||
|
||||
const appData = app.getPath('appData');
|
||||
ipcMainManager.send(
|
||||
IpcEvents.SET_APPDATA_DIR,
|
||||
[appData],
|
||||
browserWindow.webContents,
|
||||
);
|
||||
ipcMainManager.send(IpcEvents.SET_APPDATA_DIR, [appData], browserWindow.webContents);
|
||||
|
||||
browserWindows.push(browserWindow);
|
||||
|
||||
@@ -88,7 +86,5 @@ export function createMainWindow(): Electron.BrowserWindow {
|
||||
* @returns {Electron.BrowserWindow}
|
||||
*/
|
||||
export function getOrCreateMainWindow(): Electron.BrowserWindow {
|
||||
return (
|
||||
BrowserWindow.getFocusedWindow() || browserWindows[0] || createMainWindow()
|
||||
);
|
||||
return BrowserWindow.getFocusedWindow() || browserWindows[0] || createMainWindow();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
EditorId,
|
||||
EditorValues,
|
||||
GenericDialogType,
|
||||
SetFiddleOptions,
|
||||
SetFiddleOptions
|
||||
} from '../interfaces';
|
||||
import { WEBCONTENTS_READY_FOR_IPC_SIGNAL } from '../ipc-events';
|
||||
import { updateEditorLayout } from '../utils/editor-layout';
|
||||
@@ -52,14 +52,14 @@ export class App {
|
||||
|
||||
public async replaceFiddle(
|
||||
editorValues: Partial<EditorValues>,
|
||||
{ filePath, gistId, templateName }: Partial<SetFiddleOptions>,
|
||||
{ filePath, gistId, templateName }: Partial<SetFiddleOptions>
|
||||
) {
|
||||
// if unsaved, prompt user to make sure they're okay with overwriting and changing directory
|
||||
if (this.state.isUnsaved) {
|
||||
this.state.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `Opening this Fiddle will replace your unsaved changes. Do you want to proceed?`,
|
||||
ok: 'Yes',
|
||||
ok: 'Yes'
|
||||
});
|
||||
this.state.isGenericDialogShowing = true;
|
||||
await when(() => !this.state.isGenericDialogShowing);
|
||||
@@ -80,6 +80,8 @@ export class App {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.title = getTitle(this.state);
|
||||
this.state.gistId = gistId || '';
|
||||
this.state.localPath = filePath;
|
||||
this.state.templateName = templateName;
|
||||
@@ -89,8 +91,6 @@ export class App {
|
||||
await this.setEditorValues(editorValues);
|
||||
this.state.isUnsaved = false;
|
||||
|
||||
document.title = getTitle(this.state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export class App {
|
||||
* @returns {EditorValues}
|
||||
*/
|
||||
public async getEditorValues(
|
||||
options?: PackageJsonOptions,
|
||||
options?: PackageJsonOptions
|
||||
): Promise<EditorValues> {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
@@ -148,7 +148,7 @@ export class App {
|
||||
html: getEditorValue(EditorId.html),
|
||||
main: getEditorValue(EditorId.main),
|
||||
preload: getEditorValue(EditorId.preload),
|
||||
renderer: getEditorValue(EditorId.renderer),
|
||||
renderer: getEditorValue(EditorId.renderer)
|
||||
};
|
||||
|
||||
if (options && options.include !== false) {
|
||||
@@ -198,7 +198,7 @@ export class App {
|
||||
*/
|
||||
public async setupTheme(): Promise<void> {
|
||||
const tag: HTMLStyleElement | null = document.querySelector(
|
||||
'style#fiddle-theme',
|
||||
'style#fiddle-theme'
|
||||
);
|
||||
const theme = await getTheme(this.state.theme);
|
||||
|
||||
|
||||
+19
-35
@@ -15,10 +15,7 @@ import { AppState } from './state';
|
||||
* @param {string} iVersion
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function setupBinary(
|
||||
appState: AppState,
|
||||
iVersion: string,
|
||||
): Promise<void> {
|
||||
export async function setupBinary(appState: AppState, iVersion: string): Promise<void> {
|
||||
const version = normalizeVersion(iVersion);
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
|
||||
@@ -41,9 +38,7 @@ export async function setupBinary(
|
||||
|
||||
const zipPath = await download(appState, version);
|
||||
const extractPath = getDownloadPath(version);
|
||||
console.log(
|
||||
`Binary: Electron ${version} downloaded, now unpacking to ${extractPath}`,
|
||||
);
|
||||
console.log(`Binary: Electron ${version} downloaded, now unpacking to ${extractPath}`);
|
||||
|
||||
try {
|
||||
appState.versions[version].state = VersionState.unzipping;
|
||||
@@ -77,14 +72,11 @@ export async function removeBinary(iVersion: string) {
|
||||
|
||||
// utility to re-run removal functions upon failure
|
||||
// due to windows filesystem lockfile jank
|
||||
const rerunner = async (func: () => Promise<void>, counter = 1) => {
|
||||
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,
|
||||
);
|
||||
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);
|
||||
@@ -117,19 +109,16 @@ export async function removeBinary(iVersion: string) {
|
||||
}
|
||||
|
||||
/* 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');
|
||||
*
|
||||
* @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);
|
||||
return fs.existsSync(expectedPath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,9 +141,7 @@ export function getElectronBinaryPath(
|
||||
case 'win32':
|
||||
return path.join(dir, 'electron.exe');
|
||||
default:
|
||||
throw new Error(
|
||||
`Electron builds are not available for ${process.platform}`,
|
||||
);
|
||||
throw new Error(`Electron builds are not available for ${process.platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +151,7 @@ export function getDownloadingVersions(appState: AppState) {
|
||||
.map(([version, _]) => version);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of all versions downloaded to disk
|
||||
*
|
||||
@@ -204,24 +192,20 @@ async function download(appState: AppState, version: string): Promise<string> {
|
||||
const roundedProgress = Math.round(progress.percent * 100) / 100;
|
||||
|
||||
if (roundedProgress !== appState.versions[version].downloadProgress) {
|
||||
console.debug(
|
||||
`Binary: Version ${version} download progress: ${progress.percent}`,
|
||||
);
|
||||
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`,
|
||||
);
|
||||
throw new Error(`Version ${version} does not exist in state, cannot download`);
|
||||
}
|
||||
|
||||
const zipFilePath = await electronDownload(version, {
|
||||
downloadOptions: {
|
||||
quiet: true,
|
||||
getProgressCallback,
|
||||
},
|
||||
getProgressCallback
|
||||
}
|
||||
});
|
||||
|
||||
return zipFilePath;
|
||||
|
||||
@@ -36,8 +36,7 @@ export class Bisector {
|
||||
isBisectOver = true;
|
||||
}
|
||||
} else {
|
||||
const downPivot =
|
||||
Math.floor((this.pivot - this.minRev) / 2) + this.minRev;
|
||||
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;
|
||||
|
||||
@@ -12,10 +12,7 @@ export interface ChromeMacProps {
|
||||
@observer
|
||||
export class ChromeMac extends React.Component<ChromeMacProps> {
|
||||
public handleDoubleClick = () => {
|
||||
const doubleClickAction = remote.systemPreferences.getUserDefault(
|
||||
'AppleActionOnDoubleClick',
|
||||
'string',
|
||||
);
|
||||
const doubleClickAction = remote.systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
|
||||
const win = remote.getCurrentWindow();
|
||||
if (doubleClickAction === 'Minimize') {
|
||||
win.minimize();
|
||||
@@ -26,13 +23,13 @@ export class ChromeMac extends React.Component<ChromeMacProps> {
|
||||
win.unmaximize();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (process.platform !== 'darwin') return null;
|
||||
|
||||
return (
|
||||
<div className="chrome drag" onDoubleClick={this.handleDoubleClick}>
|
||||
<div className='chrome drag' onDoubleClick={this.handleDoubleClick}>
|
||||
<small>{getTitle(this.props.appState)}</small>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,440 +0,0 @@
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
IToastProps,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Popover,
|
||||
Position,
|
||||
Toaster,
|
||||
} from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { when } from 'mobx';
|
||||
import {
|
||||
EditorValues,
|
||||
GenericDialogType,
|
||||
GistActionState,
|
||||
GistActionType,
|
||||
} from '../../interfaces';
|
||||
import { IpcEvents } from '../../ipc-events';
|
||||
import {
|
||||
INDEX_HTML_NAME,
|
||||
MAIN_JS_NAME,
|
||||
PRELOAD_JS_NAME,
|
||||
RENDERER_JS_NAME,
|
||||
STYLES_CSS_NAME,
|
||||
} from '../../shared-constants';
|
||||
import { getOctokit } from '../../utils/octokit';
|
||||
import { EMPTY_EDITOR_CONTENT } from '../constants';
|
||||
import { ipcRendererManager } from '../ipc';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface GistActionButtonProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
interface IGistActionButtonState {
|
||||
readonly isUpdating: boolean;
|
||||
readonly isDeleting: boolean;
|
||||
readonly actionType: GistActionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "publish" button takes care of logging you in.
|
||||
*
|
||||
* @export
|
||||
* @class GistActionButton
|
||||
* @extends {React.Component<GistActionButtonProps, GistActionButtonState>}
|
||||
*/
|
||||
@observer
|
||||
export class GistActionButton extends React.Component<
|
||||
GistActionButtonProps,
|
||||
IGistActionButtonState
|
||||
> {
|
||||
public constructor(props: GistActionButtonProps) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.performGistAction = this.performGistAction.bind(this);
|
||||
this.setPrivate = this.setPrivate.bind(this);
|
||||
this.setPublic = this.setPublic.bind(this);
|
||||
|
||||
this.state = {
|
||||
isUpdating: false,
|
||||
isDeleting: false,
|
||||
actionType: GistActionType.publish,
|
||||
};
|
||||
}
|
||||
|
||||
private toaster: Toaster;
|
||||
private refHandlers = {
|
||||
toaster: (ref: Toaster) => (this.toaster = ref),
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
ipcRendererManager.on(IpcEvents.FS_SAVE_FIDDLE_GIST, this.handleClick);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
ipcRendererManager.off(IpcEvents.FS_SAVE_FIDDLE_GIST, this.handleClick);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the user clicks the publish button, we either show the
|
||||
* authentication dialog or publish right away.
|
||||
*
|
||||
* If we're showing the authentication dialog, we wait for it
|
||||
* to be closed again (or a GitHub token to show up) before
|
||||
* we publish
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* @memberof GistActionButton
|
||||
*/
|
||||
public async handleClick(): Promise<void> {
|
||||
const { appState } = this.props;
|
||||
|
||||
if (!appState.gitHubToken) {
|
||||
appState.toggleAuthDialog();
|
||||
}
|
||||
|
||||
// Wait for the dialog to be closed again
|
||||
await when(() => !!appState.gitHubToken || !appState.isTokenDialogShowing);
|
||||
|
||||
if (appState.gitHubToken) {
|
||||
return this.performGistAction();
|
||||
}
|
||||
}
|
||||
|
||||
public async getFiddleDescriptionFromUser(): Promise<string> {
|
||||
const { appState } = this.props;
|
||||
|
||||
// Reset potentially non-null last description.
|
||||
appState.genericDialogLastInput = null;
|
||||
|
||||
appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.confirm,
|
||||
label: 'Please provide a brief description for your Fiddle Gist',
|
||||
wantsInput: true,
|
||||
ok: 'Publish',
|
||||
cancel: undefined,
|
||||
placeholder: 'Electron Fiddle Gist',
|
||||
});
|
||||
|
||||
appState.isGenericDialogShowing = true;
|
||||
await when(() => !appState.isGenericDialogShowing);
|
||||
|
||||
return appState.genericDialogLastInput ?? 'Electron Fiddle Gist';
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a new GitHub gist.
|
||||
*/
|
||||
public async handlePublish() {
|
||||
const { appState } = this.props;
|
||||
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
const { gitHubPublishAsPublic } = this.props.appState;
|
||||
const options = { includeDependencies: true, includeElectron: true };
|
||||
const values = await window.ElectronFiddle.app.getEditorValues(options);
|
||||
|
||||
appState.activeGistAction = GistActionState.publishing;
|
||||
|
||||
try {
|
||||
const description = await this.getFiddleDescriptionFromUser();
|
||||
|
||||
const gist = await octo.gists.create({
|
||||
public: !!gitHubPublishAsPublic,
|
||||
description,
|
||||
files: this.gistFilesList(values) as any, // Note: GitHub messed up, GistsCreateParamsFiles is an incorrect interface
|
||||
});
|
||||
|
||||
appState.gistId = gist.data.id;
|
||||
appState.localPath = undefined;
|
||||
|
||||
console.log(`Publish Button: Publishing complete`, { gist });
|
||||
this.renderToast({ message: 'Publishing completed successfully!' });
|
||||
|
||||
// Only set action type to update if publish completed successfully.
|
||||
this.setActionType(GistActionType.update);
|
||||
} catch (error) {
|
||||
console.warn(`Could not publish gist`, { error });
|
||||
|
||||
const messageBoxOptions: Electron.MessageBoxOptions = {
|
||||
message:
|
||||
'Publishing Fiddle to GitHub failed. Are you connected to the Internet?',
|
||||
detail: `GitHub encountered the following error: ${error.message}`,
|
||||
};
|
||||
|
||||
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
|
||||
}
|
||||
|
||||
// Ensure any previous input is reset.
|
||||
appState.genericDialogLastInput = null;
|
||||
appState.activeGistAction = GistActionState.none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing GitHub gist.
|
||||
*/
|
||||
public async handleUpdate() {
|
||||
const { appState } = this.props;
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
const options = { includeDependencies: true, includeElectron: true };
|
||||
const values = await window.ElectronFiddle.app.getEditorValues(options);
|
||||
|
||||
appState.activeGistAction = GistActionState.updating;
|
||||
|
||||
try {
|
||||
const gist = await octo.gists.update({
|
||||
gist_id: appState.gistId!,
|
||||
files: this.gistFilesList(values) as any,
|
||||
});
|
||||
|
||||
console.log('Updating: Updating done', { gist });
|
||||
this.renderToast({ message: 'Successfully updated gist!' });
|
||||
} catch (error) {
|
||||
console.warn(`Could not update gist`, { error });
|
||||
|
||||
const messageBoxOptions: Electron.MessageBoxOptions = {
|
||||
message:
|
||||
'Updating Fiddle Gist failed. Are you connected to the Internet and is this your Gist?',
|
||||
detail: `GitHub encountered the following error: ${error.message}`,
|
||||
};
|
||||
|
||||
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
|
||||
}
|
||||
|
||||
appState.activeGistAction = GistActionState.none;
|
||||
this.setActionType(GistActionType.update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing GitHub gist.
|
||||
*/
|
||||
public async handleDelete() {
|
||||
const { appState } = this.props;
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
|
||||
appState.activeGistAction = GistActionState.deleting;
|
||||
|
||||
try {
|
||||
const gist = await octo.gists.delete({
|
||||
gist_id: appState.gistId!,
|
||||
});
|
||||
|
||||
console.log('Deleting: Deleting done', { gist });
|
||||
this.renderToast({ message: 'Successfully deleted gist!' });
|
||||
} catch (error) {
|
||||
console.warn(`Could not delete gist`, { error });
|
||||
|
||||
const messageBoxOptions: Electron.MessageBoxOptions = {
|
||||
message:
|
||||
'Deleting Fiddle Gist failed. Are you connected to the Internet, is this your Gist, and have you loaded it?',
|
||||
detail: `GitHub encountered the following error: ${error.message}`,
|
||||
};
|
||||
|
||||
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
|
||||
}
|
||||
|
||||
appState.gistId = undefined;
|
||||
appState.activeGistAction = GistActionState.none;
|
||||
this.setActionType(GistActionType.publish);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect with GitHub, perform a publish/update/delete action,
|
||||
* and update all related properties in the app state.
|
||||
*/
|
||||
public async performGistAction(): Promise<void> {
|
||||
const { gistId } = this.props.appState;
|
||||
const { actionType } = this.state;
|
||||
|
||||
if (gistId) {
|
||||
switch (actionType) {
|
||||
case GistActionType.publish:
|
||||
await this.handlePublish();
|
||||
break;
|
||||
case GistActionType.update:
|
||||
await this.handleUpdate();
|
||||
break;
|
||||
case GistActionType.delete:
|
||||
await this.handleDelete();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
await this.handlePublish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish fiddles as private.
|
||||
*
|
||||
* @memberof GistActionButton
|
||||
*/
|
||||
public setPrivate() {
|
||||
this.setPrivacy(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish fiddles as public.
|
||||
*
|
||||
* @memberof GistActionButton
|
||||
*/
|
||||
public setPublic() {
|
||||
this.setPrivacy(true);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { gistId, activeGistAction } = this.props.appState;
|
||||
const { actionType } = this.state;
|
||||
|
||||
const getTextForButton = () => {
|
||||
let text;
|
||||
if (gistId) {
|
||||
text = actionType;
|
||||
} else if (activeGistAction === GistActionState.updating) {
|
||||
text = 'Updating...';
|
||||
} else if (activeGistAction === GistActionState.publishing) {
|
||||
text = 'Publishing...';
|
||||
} else if (activeGistAction === GistActionState.deleting) {
|
||||
text = 'Deleting...';
|
||||
} else {
|
||||
text = 'Publish';
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
const getActionIcon = () => {
|
||||
switch (actionType) {
|
||||
case GistActionType.publish:
|
||||
return 'upload';
|
||||
case GistActionType.update:
|
||||
return 'refresh';
|
||||
case GistActionType.delete:
|
||||
return 'delete';
|
||||
}
|
||||
};
|
||||
|
||||
const isPerformingAction = activeGistAction !== GistActionState.none;
|
||||
return (
|
||||
<>
|
||||
<fieldset disabled={isPerformingAction}>
|
||||
<ButtonGroup className="button-gist-action">
|
||||
{this.renderPrivacyMenu()}
|
||||
<Button
|
||||
onClick={this.handleClick}
|
||||
loading={isPerformingAction}
|
||||
icon={getActionIcon()}
|
||||
text={getTextForButton()}
|
||||
/>
|
||||
{this.renderGistActionMenu()}
|
||||
</ButtonGroup>
|
||||
</fieldset>
|
||||
<Toaster
|
||||
position={Position.BOTTOM_RIGHT}
|
||||
ref={this.refHandlers.toaster}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderGistActionMenu = () => {
|
||||
const { gistId } = this.props.appState;
|
||||
const { actionType } = this.state;
|
||||
|
||||
if (!gistId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text="Publish"
|
||||
active={actionType === GistActionType.publish}
|
||||
onClick={() => this.setActionType(GistActionType.publish)}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Update"
|
||||
active={actionType === GistActionType.update}
|
||||
onClick={() => this.setActionType(GistActionType.update)}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Delete"
|
||||
active={actionType === GistActionType.delete}
|
||||
onClick={() => this.setActionType(GistActionType.delete)}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={menu} position={Position.BOTTOM}>
|
||||
<Button icon="wrench" />
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
private renderPrivacyMenu = () => {
|
||||
const { gitHubPublishAsPublic, gistId } = this.props.appState;
|
||||
|
||||
if (gistId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const privacyIcon = gitHubPublishAsPublic ? 'unlock' : 'lock';
|
||||
const privacyMenu = (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text="Private"
|
||||
icon="lock"
|
||||
active={!gitHubPublishAsPublic}
|
||||
onClick={this.setPrivate}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Public"
|
||||
icon="unlock"
|
||||
active={gitHubPublishAsPublic}
|
||||
onClick={this.setPublic}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={privacyMenu} position={Position.BOTTOM}>
|
||||
<Button icon={privacyIcon} />
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
private setActionType = (actionType: GistActionType) => {
|
||||
this.setState({ actionType });
|
||||
};
|
||||
|
||||
private setPrivacy(publishAsPublic: boolean) {
|
||||
this.props.appState.gitHubPublishAsPublic = publishAsPublic;
|
||||
}
|
||||
|
||||
private renderToast = (toast: IToastProps) => {
|
||||
this.toaster.show(toast);
|
||||
};
|
||||
|
||||
private gistFilesList = (values: EditorValues) => {
|
||||
return {
|
||||
[INDEX_HTML_NAME]: {
|
||||
content: values.html || EMPTY_EDITOR_CONTENT.html,
|
||||
},
|
||||
[MAIN_JS_NAME]: {
|
||||
content: values.main || EMPTY_EDITOR_CONTENT.js,
|
||||
},
|
||||
[RENDERER_JS_NAME]: {
|
||||
content: values.renderer || EMPTY_EDITOR_CONTENT.js,
|
||||
},
|
||||
[PRELOAD_JS_NAME]: {
|
||||
content: values.preload || EMPTY_EDITOR_CONTENT.js,
|
||||
},
|
||||
[STYLES_CSS_NAME]: {
|
||||
content: values.css || EMPTY_EDITOR_CONTENT.css,
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { IpcEvents } from '../../ipc-events';
|
||||
import { GistActionState } from '../../interfaces';
|
||||
import { idFromUrl, urlFromId } from '../../utils/gist';
|
||||
import { ipcRendererManager } from '../ipc';
|
||||
import { AppState } from '../state';
|
||||
@@ -23,10 +22,7 @@ export interface AddressBarState {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class AddressBar extends React.Component<
|
||||
AddressBarProps,
|
||||
AddressBarState
|
||||
> {
|
||||
export class AddressBar extends React.Component<AddressBarProps, AddressBarState> {
|
||||
constructor(props: AddressBarProps) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
@@ -34,7 +30,7 @@ export class AddressBar extends React.Component<
|
||||
this.submit = this.submit.bind(this);
|
||||
|
||||
const { gistId } = this.props.appState;
|
||||
const value = urlFromId(gistId);
|
||||
const value = gistId ? urlFromId(gistId) : '';
|
||||
|
||||
const { remoteLoader } = window.ElectronFiddle.app;
|
||||
|
||||
@@ -42,8 +38,8 @@ export class AddressBar extends React.Component<
|
||||
value,
|
||||
loaders: {
|
||||
gist: remoteLoader.loadFiddleFromGist.bind(remoteLoader),
|
||||
example: remoteLoader.loadFiddleFromElectronExample.bind(remoteLoader),
|
||||
},
|
||||
example: remoteLoader.loadFiddleFromElectronExample.bind(remoteLoader)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,9 +62,7 @@ export class AddressBar extends React.Component<
|
||||
public submit() {
|
||||
const { remoteLoader } = window.ElectronFiddle.app;
|
||||
if (this.state.value) {
|
||||
remoteLoader.fetchGistAndLoad(
|
||||
idFromUrl(this.state.value) || this.state.value,
|
||||
);
|
||||
remoteLoader.fetchGistAndLoad(idFromUrl(this.state.value) || this.state.value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,25 +74,16 @@ export class AddressBar extends React.Component<
|
||||
const { loaders } = this.state;
|
||||
reaction(
|
||||
() => appState.gistId,
|
||||
(gistId: string) => this.setState({ value: urlFromId(gistId) }),
|
||||
(gistId: string) => this.setState({ value: urlFromId(gistId) })
|
||||
);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_GIST_REQUEST, loaders.gist);
|
||||
ipcRendererManager.on(
|
||||
IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST,
|
||||
loaders.example,
|
||||
);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, loaders.example);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
const { loaders } = this.state;
|
||||
ipcRendererManager.removeListener(
|
||||
IpcEvents.LOAD_GIST_REQUEST,
|
||||
loaders.gist,
|
||||
);
|
||||
ipcRendererManager.removeListener(
|
||||
IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST,
|
||||
loaders.example,
|
||||
);
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_GIST_REQUEST, loaders.gist);
|
||||
ipcRendererManager.removeListener(IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST, loaders.example);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,29 +99,28 @@ export class AddressBar extends React.Component<
|
||||
return (
|
||||
<Button
|
||||
disabled={!isValueCorrect}
|
||||
icon="cloud-download"
|
||||
text="Load Fiddle"
|
||||
icon='cloud-download'
|
||||
text='Load Fiddle'
|
||||
onClick={this.submit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isUnsaved, activeGistAction } = this.props.appState;
|
||||
const { isUnsaved, isPublishing } = this.props.appState;
|
||||
const { value } = this.state;
|
||||
const isCorrect = /https:\/\/gist\.github\.com\/(.+)$/.test(value);
|
||||
const className = classnames('address-bar', isUnsaved, { empty: !value });
|
||||
|
||||
const isPerformingAction = activeGistAction !== GistActionState.none;
|
||||
return (
|
||||
<form className={className} onSubmit={this.handleSubmit}>
|
||||
<fieldset disabled={isPerformingAction}>
|
||||
<fieldset disabled={isPublishing}>
|
||||
<InputGroup
|
||||
key="addressbar"
|
||||
leftIcon="geosearch"
|
||||
key='addressbar'
|
||||
leftIcon='geosearch'
|
||||
intent={isCorrect || !value ? undefined : Intent.DANGER}
|
||||
onChange={this.handleChange}
|
||||
placeholder="https://gist.github.com/..."
|
||||
placeholder='https://gist.github.com/...'
|
||||
value={value}
|
||||
rightElement={this.renderLoadButton(isCorrect)}
|
||||
/>
|
||||
|
||||
@@ -29,25 +29,13 @@ export class BisectHandler extends React.Component<BisectHandlerProps> {
|
||||
|
||||
const [minRev, maxRev] = response;
|
||||
const [minVer, maxVer] = [minRev.version, maxRev.version];
|
||||
const label = (
|
||||
<>
|
||||
Bisect complete. Check the range{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={`https://github.com/electron/electron/compare/v${minVer}...v${maxVer}`}
|
||||
>
|
||||
{minVer}...{maxVer}
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
);
|
||||
const message = `Check between versions ${minVer} and ${maxVer}.`;
|
||||
|
||||
appState.pushOutput(`[BISECT] Complete: ${minVer}...${maxVer}`);
|
||||
appState.pushOutput(`[BISECT] Complete: ${message}`);
|
||||
appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.success,
|
||||
label,
|
||||
cancel: undefined,
|
||||
label: `Bisect complete. ${message}`,
|
||||
cancel: undefined
|
||||
});
|
||||
appState.isGenericDialogShowing = true;
|
||||
} else {
|
||||
@@ -63,8 +51,7 @@ export class BisectHandler extends React.Component<BisectHandlerProps> {
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
if (!!appState.Bisector) {
|
||||
const isDownloading =
|
||||
appState.currentElectronVersion.state === VersionState.downloading;
|
||||
const isDownloading = appState.currentElectronVersion.state === VersionState.downloading;
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
@@ -77,7 +64,10 @@ export class BisectHandler extends React.Component<BisectHandlerProps> {
|
||||
onClick={() => this.continueBisect(false)}
|
||||
disabled={isDownloading}
|
||||
/>
|
||||
<Button icon={'cross'} onClick={this.terminateBisect}>
|
||||
<Button
|
||||
icon={'cross'}
|
||||
onClick={this.terminateBisect}
|
||||
>
|
||||
Cancel Bisect
|
||||
</Button>
|
||||
</>
|
||||
@@ -85,8 +75,8 @@ export class BisectHandler extends React.Component<BisectHandlerProps> {
|
||||
} else {
|
||||
return (
|
||||
<Button
|
||||
icon="git-branch"
|
||||
text="Bisect"
|
||||
icon='git-branch'
|
||||
text='Bisect'
|
||||
onClick={appState.toggleBisectDialog}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
Button,
|
||||
Menu,
|
||||
MenuDivider,
|
||||
MenuItem,
|
||||
Popover,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { Button, Menu, MenuDivider, MenuItem, Popover, Position } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -29,10 +22,7 @@ export interface EditorDropdownProps {
|
||||
* @extends {React.Component<EditorDropdownProps, EditorDropdownState>}
|
||||
*/
|
||||
@observer
|
||||
export class EditorDropdown extends React.Component<
|
||||
EditorDropdownProps,
|
||||
EditorDropdownState
|
||||
> {
|
||||
export class EditorDropdown extends React.Component<EditorDropdownProps, EditorDropdownState> {
|
||||
constructor(props: EditorDropdownProps) {
|
||||
super(props);
|
||||
|
||||
@@ -43,7 +33,7 @@ export class EditorDropdown extends React.Component<
|
||||
return (
|
||||
<>
|
||||
<Popover content={this.renderMenu()} position={Position.BOTTOM}>
|
||||
<Button icon="applications" text="Editors" />
|
||||
<Button icon='applications' text='Editors' />
|
||||
</Popover>
|
||||
{this.renderDocsDemos()}
|
||||
</>
|
||||
@@ -57,8 +47,8 @@ export class EditorDropdown extends React.Component<
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon="help"
|
||||
text="Docs & Demos"
|
||||
icon='help'
|
||||
text='Docs & Demos'
|
||||
id={PanelId.docsDemo}
|
||||
onClick={this.onItemClick}
|
||||
active={!this.props.appState.closedPanels.docsDemo}
|
||||
@@ -67,7 +57,11 @@ export class EditorDropdown extends React.Component<
|
||||
}
|
||||
|
||||
public renderMenu() {
|
||||
return <Menu>{...this.renderMenuItems()}</Menu>;
|
||||
return (
|
||||
<Menu>
|
||||
{...this.renderMenuItems()}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
public renderMenuItems() {
|
||||
@@ -84,20 +78,20 @@ export class EditorDropdown extends React.Component<
|
||||
id={id}
|
||||
onClick={this.onItemClick}
|
||||
disabled={appState.mosaicArrangement === id} // can't hide last editor panel
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
result.push(
|
||||
<React.Fragment key={'fragment-reset-layout'}>
|
||||
<MenuDivider />
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
icon="grid-view"
|
||||
key="reset-layout"
|
||||
text="Reset Layout"
|
||||
icon='grid-view'
|
||||
key='reset-layout'
|
||||
text='Reset Layout'
|
||||
onClick={appState.resetEditorLayout}
|
||||
/>
|
||||
</React.Fragment>,
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return result;
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
import { Button, ButtonGroup, IToastProps, Menu, MenuItem, Popover, Position, Toaster } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { when } from 'mobx';
|
||||
import { EditorValues } from '../../interfaces';
|
||||
import { IpcEvents } from '../../ipc-events';
|
||||
import { INDEX_HTML_NAME, MAIN_JS_NAME, PRELOAD_JS_NAME, RENDERER_JS_NAME, STYLES_CSS_NAME } from '../../shared-constants';
|
||||
import { getOctokit } from '../../utils/octokit';
|
||||
import { EMPTY_EDITOR_CONTENT } from '../constants';
|
||||
import { ipcRendererManager } from '../ipc';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface PublishButtonProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
interface IPublishButtonState {
|
||||
readonly isUpdating: boolean;
|
||||
readonly wouldPublish: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "publish" button takes care of logging you in.
|
||||
*
|
||||
* @export
|
||||
* @class PublishButton
|
||||
* @extends {React.Component<PublishButtonProps, PublishButtonState>}
|
||||
*/
|
||||
@observer
|
||||
export class PublishButton extends React.Component<PublishButtonProps, IPublishButtonState> {
|
||||
public constructor(props: PublishButtonProps) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.publishOrUpdateFiddle = this.publishOrUpdateFiddle.bind(this);
|
||||
this.setPrivate = this.setPrivate.bind(this);
|
||||
this.setPublic = this.setPublic.bind(this);
|
||||
|
||||
this.state = {
|
||||
isUpdating: false,
|
||||
wouldPublish: false,
|
||||
};
|
||||
}
|
||||
|
||||
private toaster: Toaster;
|
||||
private refHandlers = {
|
||||
toaster: (ref: Toaster) => this.toaster = ref,
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
ipcRendererManager.on(IpcEvents.FS_SAVE_FIDDLE_GIST, this.handleClick);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
ipcRendererManager.off(IpcEvents.FS_SAVE_FIDDLE_GIST, this.handleClick);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the user clicks the publish button, we either show the
|
||||
* authentication dialog or publish right away.
|
||||
*
|
||||
* If we're showing the authentication dialog, we wait for it
|
||||
* to be closed again (or a GitHub token to show up) before
|
||||
* we publish
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* @memberof PublishButton
|
||||
*/
|
||||
public async handleClick(): Promise<void> {
|
||||
const { appState } = this.props;
|
||||
|
||||
if (!appState.gitHubToken) {
|
||||
appState.toggleAuthDialog();
|
||||
}
|
||||
|
||||
// Wait for the dialog to be closed again
|
||||
await when(() => !!appState.gitHubToken || !appState.isTokenDialogShowing);
|
||||
|
||||
if (appState.gitHubToken) {
|
||||
return this.publishOrUpdateFiddle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect with GitHub, publish the current Fiddle as a gist,
|
||||
* and update all related properties in the app state.
|
||||
*/
|
||||
public async publishOrUpdateFiddle(): Promise<void> {
|
||||
const { appState } = this.props;
|
||||
const { wouldPublish } = this.state;
|
||||
appState.isPublishing = true;
|
||||
|
||||
const octo = await getOctokit(this.props.appState);
|
||||
const { gitHubPublishAsPublic, gistId } = this.props.appState;
|
||||
const options = { includeDependencies: true, includeElectron: true };
|
||||
const values = await window.ElectronFiddle.app.getEditorValues(options);
|
||||
|
||||
if (gistId && !wouldPublish) {
|
||||
this.setState({
|
||||
isUpdating: true
|
||||
});
|
||||
const gist = await octo.gists.update({
|
||||
gist_id: appState.gistId,
|
||||
files: this.gistFilesList(values) as any,
|
||||
});
|
||||
|
||||
console.log('Updating: Updating done', { gist });
|
||||
this.renderToast({ message: 'Successfully updated gist!' });
|
||||
this.setState({
|
||||
isUpdating: false
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
const gist = await octo.gists.create({
|
||||
public: !!gitHubPublishAsPublic,
|
||||
description: 'Electron Fiddle Gist',
|
||||
files: this.gistFilesList(values) as any, // Note: GitHub messed up, GistsCreateParamsFiles is an incorrect interface
|
||||
});
|
||||
|
||||
appState.gistId = gist.data.id;
|
||||
|
||||
console.log(`Publish Button: Publishing done`, { gist });
|
||||
this.renderToast({ message: 'Publishing done successfully!' });
|
||||
} catch (error) {
|
||||
console.warn(`Could not publish gist`, { error });
|
||||
|
||||
const messageBoxOptions: Electron.MessageBoxOptions = {
|
||||
message: 'Publishing Fiddle to GitHub failed. Are you connected to the Internet?',
|
||||
detail: `GitHub encountered the following error: ${error.message}`
|
||||
};
|
||||
|
||||
ipcRendererManager.send(IpcEvents.SHOW_WARNING_DIALOG, messageBoxOptions);
|
||||
}
|
||||
}
|
||||
|
||||
appState.isPublishing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish fiddles as private.
|
||||
*
|
||||
* @memberof PublishButton
|
||||
*/
|
||||
public setPrivate() {
|
||||
this.setPrivacy(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish fiddles as public.
|
||||
*
|
||||
* @memberof PublishButton
|
||||
*/
|
||||
public setPublic() {
|
||||
this.setPrivacy(true);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isPublishing, gistId } = this.props.appState;
|
||||
const { isUpdating, wouldPublish } = this.state;
|
||||
|
||||
const getTextForButton = gistId && !wouldPublish
|
||||
? 'Update'
|
||||
: isUpdating
|
||||
? 'Updating...'
|
||||
: isPublishing
|
||||
? 'Publishing...'
|
||||
: 'Publish';
|
||||
|
||||
return (
|
||||
<>
|
||||
<fieldset disabled={isPublishing}>
|
||||
<ButtonGroup className='button-publish'>
|
||||
{this.renderPrivaryMenu()}
|
||||
<Button
|
||||
onClick={this.handleClick}
|
||||
loading={isPublishing}
|
||||
icon='upload'
|
||||
text={getTextForButton}
|
||||
/>
|
||||
{this.renderMaybePublishMenu()}
|
||||
</ButtonGroup>
|
||||
</fieldset>
|
||||
<Toaster position={Position.BOTTOM_RIGHT} ref={this.refHandlers.toaster} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderMaybePublishMenu = () => {
|
||||
const { gistId } = this.props.appState;
|
||||
const { wouldPublish } = this.state;
|
||||
|
||||
if (!gistId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text='Publish'
|
||||
active={wouldPublish}
|
||||
onClick={() => this.setWouldPublish(true)}
|
||||
/>
|
||||
<MenuItem
|
||||
text='Update'
|
||||
active={!wouldPublish}
|
||||
onClick={() => this.setWouldPublish(false)}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={menu}
|
||||
position={Position.BOTTOM}
|
||||
>
|
||||
<Button
|
||||
icon='wrench'
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
private renderPrivaryMenu = () => {
|
||||
const { gitHubPublishAsPublic, gistId } = this.props.appState;
|
||||
|
||||
if (gistId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const privacyIcon = gitHubPublishAsPublic ? 'unlock' : 'lock';
|
||||
const privacyMenu = (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text='Private'
|
||||
icon='lock'
|
||||
active={!gitHubPublishAsPublic}
|
||||
onClick={this.setPrivate}
|
||||
/>
|
||||
<MenuItem
|
||||
text='Public'
|
||||
icon='unlock'
|
||||
active={gitHubPublishAsPublic}
|
||||
onClick={this.setPublic}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={privacyMenu}
|
||||
position={Position.BOTTOM}
|
||||
>
|
||||
<Button
|
||||
icon={privacyIcon}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
private setWouldPublish = (wouldPublish: boolean) => {
|
||||
this.setState({
|
||||
wouldPublish,
|
||||
});
|
||||
}
|
||||
|
||||
private setPrivacy(publishAsPublic: boolean) {
|
||||
this.props.appState.gitHubPublishAsPublic = publishAsPublic;
|
||||
}
|
||||
|
||||
private renderToast = (toast: IToastProps) => {
|
||||
this.toaster.show(toast);
|
||||
}
|
||||
|
||||
private gistFilesList = (values: EditorValues) => {
|
||||
return {
|
||||
[INDEX_HTML_NAME]: {
|
||||
content: values.html || EMPTY_EDITOR_CONTENT.html,
|
||||
},
|
||||
[MAIN_JS_NAME]: {
|
||||
content: values.main || EMPTY_EDITOR_CONTENT.js,
|
||||
},
|
||||
[RENDERER_JS_NAME]: {
|
||||
content: values.renderer || EMPTY_EDITOR_CONTENT.js,
|
||||
},
|
||||
[PRELOAD_JS_NAME]: {
|
||||
content: values.preload || EMPTY_EDITOR_CONTENT.js,
|
||||
},
|
||||
[STYLES_CSS_NAME]: {
|
||||
content: values.css || EMPTY_EDITOR_CONTENT.css,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ import * as React from 'react';
|
||||
import { VersionState } from '../../interfaces';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface RunnerState {
|
||||
}
|
||||
|
||||
export interface RunnerProps {
|
||||
appState: AppState;
|
||||
}
|
||||
@@ -14,10 +17,10 @@ export interface RunnerProps {
|
||||
* with Electron. It also renders the button that does so.
|
||||
*
|
||||
* @class Runner
|
||||
* @extends {React.Component<RunnerProps>}
|
||||
* @extends {React.Component<RunnerProps, RunnerState>}
|
||||
*/
|
||||
@observer
|
||||
export class Runner extends React.Component<RunnerProps> {
|
||||
export class Runner extends React.Component<RunnerProps, RunnerState> {
|
||||
public render() {
|
||||
const { isRunning, currentElectronVersion } = this.props.appState;
|
||||
|
||||
@@ -26,9 +29,7 @@ export class Runner extends React.Component<RunnerProps> {
|
||||
|
||||
if (state === VersionState.downloading) {
|
||||
props.text = 'Downloading';
|
||||
props.icon = (
|
||||
<Spinner size={16} value={currentElectronVersion?.downloadProgress} />
|
||||
);
|
||||
props.icon = <Spinner size={16} value={currentElectronVersion?.downloadProgress} />;
|
||||
} else if (state === VersionState.unzipping) {
|
||||
props.text = 'Unzipping';
|
||||
props.icon = <Spinner size={16} />;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AppState } from '../state';
|
||||
import { AddressBar } from './commands-address-bar';
|
||||
import { BisectHandler } from './commands-bisect';
|
||||
import { EditorDropdown } from './commands-editors';
|
||||
import { GistActionButton } from './commands-action-button';
|
||||
import { PublishButton } from './commands-publish-button';
|
||||
import { Runner } from './commands-runner';
|
||||
import { VersionChooser } from './commands-version-chooser';
|
||||
|
||||
@@ -19,10 +19,10 @@ export interface CommandsProps {
|
||||
* all the things
|
||||
*
|
||||
* @class Commands
|
||||
* @extends {React.Component<CommandsProps>}
|
||||
* @extends {React.Component<CommandsProps, {}>}
|
||||
*/
|
||||
@observer
|
||||
export class Commands extends React.Component<CommandsProps> {
|
||||
export class Commands extends React.Component<CommandsProps, {}> {
|
||||
constructor(props: CommandsProps) {
|
||||
super(props);
|
||||
}
|
||||
@@ -32,22 +32,26 @@ export class Commands extends React.Component<CommandsProps> {
|
||||
const { isBisectCommandShowing: isBisectCommandShowing } = appState;
|
||||
|
||||
return (
|
||||
<div className="commands">
|
||||
<div className='commands'>
|
||||
<div>
|
||||
<ControlGroup fill={true} vertical={false}>
|
||||
<VersionChooser appState={appState} />
|
||||
<Runner appState={appState} />
|
||||
</ControlGroup>
|
||||
{isBisectCommandShowing && (
|
||||
<ControlGroup fill={true} vertical={false}>
|
||||
<BisectHandler appState={appState} />
|
||||
</ControlGroup>
|
||||
)}
|
||||
{
|
||||
// tslint:disable-next-line jsx-no-multiline-js
|
||||
isBisectCommandShowing &&
|
||||
(
|
||||
<ControlGroup fill={true} vertical={false}>
|
||||
<BisectHandler appState={appState} />
|
||||
</ControlGroup>
|
||||
)
|
||||
}
|
||||
<ControlGroup fill={true} vertical={false}>
|
||||
<Button
|
||||
active={appState.isConsoleShowing}
|
||||
icon="console"
|
||||
text="Console"
|
||||
icon='console'
|
||||
text='Console'
|
||||
onClick={appState.toggleConsole}
|
||||
/>
|
||||
<EditorDropdown appState={appState} />
|
||||
@@ -55,7 +59,7 @@ export class Commands extends React.Component<CommandsProps> {
|
||||
</div>
|
||||
<div>
|
||||
<AddressBar appState={appState} />
|
||||
<GistActionButton appState={appState} />
|
||||
<PublishButton appState={appState} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -26,10 +26,7 @@ export interface AddThemeDialogState {
|
||||
* @extends {React.Component<AddThemeDialogProps, AddThemeDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class AddThemeDialog extends React.Component<
|
||||
AddThemeDialogProps,
|
||||
AddThemeDialogState
|
||||
> {
|
||||
export class AddThemeDialog extends React.Component<AddThemeDialogProps, AddThemeDialogState> {
|
||||
public resetState = { file: undefined };
|
||||
|
||||
constructor(props: AddThemeDialogProps) {
|
||||
@@ -61,25 +58,20 @@ export class AddThemeDialog extends React.Component<
|
||||
*/
|
||||
public async onSubmit(): Promise<void> {
|
||||
const { file } = this.state;
|
||||
const defaultTheme = !!this.props.appState.theme
|
||||
? await getTheme(this.props.appState.theme)
|
||||
: defaultDark;
|
||||
const defaultTheme = !!this.props.appState.theme ? await getTheme(this.props.appState.theme) : defaultDark;
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
const editor = fsType.readJSONSync(file.path);
|
||||
if (!editor.base && !editor.rules)
|
||||
throw Error('File does not match specifications'); // has to have these attributes
|
||||
defaultTheme.editor = editor as Partial<
|
||||
MonacoType.editor.IStandaloneThemeData
|
||||
>;
|
||||
if (!editor.base && !editor.rules) throw Error('File does not match specifications'); // has to have these attributes
|
||||
defaultTheme.editor = editor as Partial<MonacoType.editor.IStandaloneThemeData>;
|
||||
const newTheme = defaultTheme;
|
||||
const name = editor.name ? editor.name : file.name;
|
||||
await this.createNewThemeFromMonaco(name, newTheme);
|
||||
} catch (error) {
|
||||
this.props.appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `Error: ${error}, please pick a different file.`,
|
||||
label: `Error: ${error}, please pick a different file.`
|
||||
});
|
||||
this.props.appState.isGenericDialogShowing = true;
|
||||
return;
|
||||
@@ -89,39 +81,40 @@ export class AddThemeDialog extends React.Component<
|
||||
return;
|
||||
}
|
||||
|
||||
public async createNewThemeFromMonaco(
|
||||
name: string,
|
||||
newTheme: LoadedFiddleTheme,
|
||||
): Promise<boolean> {
|
||||
if (!name) return false;
|
||||
const themePath = path.join(THEMES_PATH, `${name}`);
|
||||
public async createNewThemeFromMonaco(name: string, newTheme: LoadedFiddleTheme): Promise<boolean> {
|
||||
if (!name) return false;
|
||||
const themePath = path.join(THEMES_PATH, `${name}`);
|
||||
|
||||
await fsType.outputJSON(
|
||||
themePath,
|
||||
{
|
||||
await fsType.outputJSON(themePath, {
|
||||
...newTheme,
|
||||
name,
|
||||
},
|
||||
{ spaces: 2 },
|
||||
);
|
||||
}, {spaces: 2});
|
||||
|
||||
this.props.appState.setTheme(themePath);
|
||||
shell.showItemInFolder(themePath);
|
||||
return true;
|
||||
this.props.appState.setTheme(themePath);
|
||||
shell.showItemInFolder(themePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
get buttons() {
|
||||
const canSubmit = !!this.state.file;
|
||||
|
||||
return [
|
||||
<Button
|
||||
icon="add"
|
||||
key="submit"
|
||||
disabled={!canSubmit}
|
||||
onClick={this.onSubmit}
|
||||
text="Add"
|
||||
/>,
|
||||
<Button icon="cross" key="cancel" onClick={this.onClose} text="Cancel" />,
|
||||
(
|
||||
<Button
|
||||
icon='add'
|
||||
key='submit'
|
||||
disabled={!canSubmit}
|
||||
onClick={this.onSubmit}
|
||||
text='Add'
|
||||
/>
|
||||
), (
|
||||
<Button
|
||||
icon='cross'
|
||||
key='cancel'
|
||||
onClick={this.onClose}
|
||||
text='Cancel'
|
||||
/>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -140,10 +133,10 @@ export class AddThemeDialog extends React.Component<
|
||||
<Dialog
|
||||
isOpen={isThemeDialogShowing}
|
||||
onClose={this.onClose}
|
||||
title="Add theme"
|
||||
className="dialog-add-version"
|
||||
title='Add theme'
|
||||
className='dialog-add-version'
|
||||
>
|
||||
<div className="bp3-dialog-body">
|
||||
<div className='bp3-dialog-body'>
|
||||
<FileInput
|
||||
onInputChange={this.onChangeFile}
|
||||
inputProps={inputProps as any}
|
||||
@@ -151,8 +144,10 @@ export class AddThemeDialog extends React.Component<
|
||||
/>
|
||||
<br />
|
||||
</div>
|
||||
<div className="bp3-dialog-footer">
|
||||
<div className="bp3-dialog-footer-actions">{this.buttons}</div>
|
||||
<div className='bp3-dialog-footer'>
|
||||
<div className='bp3-dialog-footer-actions'>
|
||||
{this.buttons}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -165,4 +160,5 @@ export class AddThemeDialog extends React.Component<
|
||||
this.setState(this.resetState);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
Button,
|
||||
Callout,
|
||||
Dialog,
|
||||
FileInput,
|
||||
InputGroup,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
|
||||
import { Button, Callout, Dialog, FileInput, InputGroup, Intent } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
@@ -36,29 +30,23 @@ export interface AddVersionDialogState {
|
||||
* @extends {React.Component<AddVersionDialogProps, AddVersionDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class AddVersionDialog extends React.Component<
|
||||
AddVersionDialogProps,
|
||||
AddVersionDialogState
|
||||
> {
|
||||
export class AddVersionDialog extends React.Component<AddVersionDialogProps, AddVersionDialogState> {
|
||||
constructor(props: AddVersionDialogProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isValidVersion: false,
|
||||
isValidElectron: false,
|
||||
version: '',
|
||||
version: ''
|
||||
};
|
||||
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.onChangeVersion = this.onChangeVersion.bind(this);
|
||||
|
||||
ipcRendererManager.on(
|
||||
IpcEvents.LOAD_LOCAL_VERSION_FOLDER,
|
||||
(_event, [file]) => {
|
||||
this.setFolderPath(file);
|
||||
},
|
||||
);
|
||||
ipcRendererManager.on(IpcEvents.LOAD_LOCAL_VERSION_FOLDER, (_event, [file]) => {
|
||||
this.setFolderPath(file);
|
||||
});
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
@@ -71,7 +59,7 @@ export class AddVersionDialog extends React.Component<
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public async setFolderPath(folderPath: string) {
|
||||
const isValidElectron = !!(await getIsDownloaded('custom', folderPath));
|
||||
const isValidElectron = !!await getIsDownloaded('custom', folderPath);
|
||||
|
||||
this.setState({ folderPath, isValidElectron });
|
||||
}
|
||||
@@ -96,12 +84,16 @@ export class AddVersionDialog extends React.Component<
|
||||
|
||||
if (!folderPath) return;
|
||||
|
||||
const name = folderPath.slice(-20).split(path.sep).slice(1).join(path.sep);
|
||||
const name = folderPath
|
||||
.slice(-20)
|
||||
.split(path.sep)
|
||||
.slice(1)
|
||||
.join(path.sep);
|
||||
|
||||
const toAdd: Version = {
|
||||
localPath: folderPath,
|
||||
version,
|
||||
name,
|
||||
name
|
||||
};
|
||||
|
||||
this.props.appState.addLocalVersion(toAdd);
|
||||
@@ -120,14 +112,22 @@ export class AddVersionDialog extends React.Component<
|
||||
const canSubmit = this.state.isValidElectron && this.state.isValidVersion;
|
||||
|
||||
return [
|
||||
<Button
|
||||
icon="add"
|
||||
key="submit"
|
||||
disabled={!canSubmit}
|
||||
onClick={this.onSubmit}
|
||||
text="Add"
|
||||
/>,
|
||||
<Button icon="cross" key="cancel" onClick={this.onClose} text="Cancel" />,
|
||||
(
|
||||
<Button
|
||||
icon='add'
|
||||
key='submit'
|
||||
disabled={!canSubmit}
|
||||
onClick={this.onSubmit}
|
||||
text='Add'
|
||||
/>
|
||||
), (
|
||||
<Button
|
||||
icon='cross'
|
||||
key='cancel'
|
||||
onClick={this.onClose}
|
||||
text='Cancel'
|
||||
/>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -137,32 +137,33 @@ export class AddVersionDialog extends React.Component<
|
||||
onClick: (e: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
ipcRendererManager.send(IpcEvents.SHOW_LOCAL_VERSION_FOLDER_DIALOG);
|
||||
},
|
||||
}
|
||||
};
|
||||
const { folderPath } = this.state;
|
||||
|
||||
const text =
|
||||
folderPath ||
|
||||
const text = folderPath ||
|
||||
`Select the folder containing ${getElectronNameForPlatform()}...`;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={isAddVersionDialogShowing}
|
||||
onClose={this.onClose}
|
||||
title="Add local Electron build"
|
||||
className="dialog-add-version"
|
||||
title='Add local Electron build'
|
||||
className='dialog-add-version'
|
||||
>
|
||||
<div className="bp3-dialog-body">
|
||||
<div className='bp3-dialog-body'>
|
||||
<FileInput
|
||||
id="custom-electron-version"
|
||||
id='custom-electron-version'
|
||||
inputProps={inputProps as any}
|
||||
text={text}
|
||||
/>
|
||||
<br />
|
||||
{this.renderPath()}
|
||||
</div>
|
||||
<div className="bp3-dialog-footer">
|
||||
<div className="bp3-dialog-footer-actions">{this.buttons}</div>
|
||||
<div className='bp3-dialog-footer'>
|
||||
<div className='bp3-dialog-footer-actions'>
|
||||
{this.buttons}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -193,14 +194,14 @@ export class AddVersionDialog extends React.Component<
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
Please specify a version, used for typings and the name. Must be{' '}
|
||||
<code>semver</code> compliant.
|
||||
Please specify a version, used for typings and the name.
|
||||
Must be <code>semver</code> compliant.
|
||||
</p>
|
||||
<InputGroup
|
||||
intent={isValidVersion ? undefined : Intent.DANGER}
|
||||
value={version}
|
||||
onChange={this.onChangeVersion}
|
||||
placeholder="4.0.0"
|
||||
placeholder='4.0.0'
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -214,7 +215,7 @@ export class AddVersionDialog extends React.Component<
|
||||
isValidElectron: false,
|
||||
isValidVersion: false,
|
||||
version: '',
|
||||
folderPath: undefined,
|
||||
folderPath: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ export interface BisectDialogState {
|
||||
* @extends {React.Component<BisectDialogProps, BisectDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class BisectDialog extends React.Component<
|
||||
BisectDialogProps,
|
||||
BisectDialogState
|
||||
> {
|
||||
export class BisectDialog extends React.Component<BisectDialogProps, BisectDialogState> {
|
||||
constructor(props: BisectDialogProps) {
|
||||
super(props);
|
||||
|
||||
@@ -43,7 +40,7 @@ export class BisectDialog extends React.Component<
|
||||
this.state = {
|
||||
allVersions: this.props.appState.versionsToShow,
|
||||
startIndex: 10,
|
||||
endIndex: 0,
|
||||
endIndex: 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -68,7 +65,9 @@ export class BisectDialog extends React.Component<
|
||||
return;
|
||||
}
|
||||
|
||||
const bisectRange = allVersions.slice(endIndex, startIndex + 1).reverse();
|
||||
const bisectRange = allVersions
|
||||
.slice(endIndex, startIndex + 1)
|
||||
.reverse();
|
||||
|
||||
appState.Bisector = new Bisector(bisectRange);
|
||||
const initialBisectPivot = appState.Bisector.getCurrentVersion().version;
|
||||
@@ -102,14 +101,22 @@ export class BisectDialog extends React.Component<
|
||||
*/
|
||||
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" />,
|
||||
(
|
||||
<Button
|
||||
icon='play'
|
||||
key='submit'
|
||||
disabled={!this.canSubmit}
|
||||
onClick={this.onSubmit}
|
||||
text='Begin'
|
||||
/>
|
||||
), (
|
||||
<Button
|
||||
icon='cross'
|
||||
key='cancel'
|
||||
onClick={this.onClose}
|
||||
text='Cancel'
|
||||
/>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -118,24 +125,25 @@ export class BisectDialog extends React.Component<
|
||||
*/
|
||||
get help() {
|
||||
let moreHelp = (
|
||||
<Button icon="help" text="Show help" onClick={this.showHelp} />
|
||||
<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.
|
||||
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.
|
||||
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>
|
||||
</>
|
||||
);
|
||||
@@ -144,16 +152,9 @@ export class BisectDialog extends React.Component<
|
||||
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"
|
||||
rel="noreferrer"
|
||||
>
|
||||
borrowed from <code>git</code>
|
||||
</a>{' '}
|
||||
for learning which version of Electron introduced a bug. This tool
|
||||
helps you perform a bisect.
|
||||
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>
|
||||
@@ -168,13 +169,13 @@ export class BisectDialog extends React.Component<
|
||||
<Dialog
|
||||
isOpen={isBisectDialogShowing}
|
||||
onClose={this.onClose}
|
||||
title="Start a bisect session"
|
||||
className="dialog-add-version"
|
||||
title='Start a bisect session'
|
||||
className='dialog-add-version'
|
||||
>
|
||||
<div className="bp3-dialog-body">
|
||||
<div className='bp3-dialog-body'>
|
||||
{this.help}
|
||||
<Label>
|
||||
Earliest Version (Last "known good" version)
|
||||
Earliest Version (Last "known good" version)
|
||||
<ButtonGroup fill={true}>
|
||||
<VersionSelect
|
||||
currentVersion={allVersions[startIndex]}
|
||||
@@ -185,7 +186,7 @@ export class BisectDialog extends React.Component<
|
||||
</ButtonGroup>
|
||||
</Label>
|
||||
<Label>
|
||||
Latest Version (First "known bad" version)
|
||||
Latest Version (First "known bad" version)
|
||||
<ButtonGroup fill={true}>
|
||||
<VersionSelect
|
||||
currentVersion={allVersions[endIndex]}
|
||||
@@ -196,8 +197,10 @@ export class BisectDialog extends React.Component<
|
||||
</ButtonGroup>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="bp3-dialog-footer">
|
||||
<div className="bp3-dialog-footer-actions">{this.buttons}</div>
|
||||
<div className='bp3-dialog-footer'>
|
||||
<div className='bp3-dialog-footer-actions'>
|
||||
{this.buttons}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Alert, IconName, InputGroup, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { Alert, IconName, Intent} from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -9,6 +10,9 @@ export interface GenericDialogProps {
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
export interface GenericDialogState {
|
||||
}
|
||||
|
||||
/**
|
||||
* The token dialog prompts the user to either continue or cancel the operation.
|
||||
*
|
||||
@@ -17,7 +21,7 @@ export interface GenericDialogProps {
|
||||
* @extends {React.Component<GenericDialogProps, GenericDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class GenericDialog extends React.Component<GenericDialogProps> {
|
||||
export class GenericDialog extends React.Component<GenericDialogProps, GenericDialogState> {
|
||||
constructor(props: GenericDialogProps) {
|
||||
super(props);
|
||||
|
||||
@@ -25,27 +29,13 @@ export class GenericDialog extends React.Component<GenericDialogProps> {
|
||||
}
|
||||
|
||||
public onClose(result: boolean) {
|
||||
const input = document.getElementById('input') as HTMLInputElement;
|
||||
|
||||
this.props.appState.genericDialogLastInput =
|
||||
input && input.value !== '' ? input.value : null;
|
||||
this.props.appState.genericDialogLastResult = result;
|
||||
this.props.appState.toggleGenericDialog();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
isGenericDialogShowing,
|
||||
genericDialogOptions,
|
||||
} = this.props.appState;
|
||||
const {
|
||||
type,
|
||||
ok,
|
||||
cancel,
|
||||
label,
|
||||
wantsInput,
|
||||
placeholder,
|
||||
} = genericDialogOptions;
|
||||
const { isGenericDialogShowing, genericDialogOptions } = this.props.appState;
|
||||
const {type, ok, cancel, label} = genericDialogOptions;
|
||||
|
||||
let intent: Intent;
|
||||
let icon: IconName;
|
||||
@@ -67,16 +57,6 @@ export class GenericDialog extends React.Component<GenericDialogProps> {
|
||||
icon = 'help';
|
||||
break;
|
||||
}
|
||||
|
||||
let dialogInput;
|
||||
if (wantsInput) {
|
||||
dialogInput = placeholder ? (
|
||||
<InputGroup id="input" placeholder={placeholder} />
|
||||
) : (
|
||||
<InputGroup id="input" />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert
|
||||
isOpen={isGenericDialogShowing}
|
||||
@@ -87,7 +67,6 @@ export class GenericDialog extends React.Component<GenericDialogProps> {
|
||||
intent={intent}
|
||||
>
|
||||
<p>{label}</p>
|
||||
{wantsInput && dialogInput}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
import { Button, Callout, Dialog, InputGroup, Intent } from '@blueprintjs/core';
|
||||
import { clipboard, shell } from 'electron';
|
||||
import { observer } from 'mobx-react';
|
||||
@@ -16,7 +17,7 @@ export interface TokenDialogState {
|
||||
error: boolean;
|
||||
}
|
||||
|
||||
const TOKEN_SCOPES = ['gist'].join();
|
||||
const TOKEN_SCOPES = [ 'gist' ].join();
|
||||
const TOKEN_DESCRIPTION = encodeURIComponent('Fiddle Gist Token');
|
||||
const GENERATE_TOKEN_URL = `https://github.com/settings/tokens/new?scopes=${TOKEN_SCOPES}&description=${TOKEN_DESCRIPTION}`;
|
||||
|
||||
@@ -29,17 +30,14 @@ const GENERATE_TOKEN_URL = `https://github.com/settings/tokens/new?scopes=${TOKE
|
||||
* @extends {React.Component<TokenDialogProps, TokenDialogState>}
|
||||
*/
|
||||
@observer
|
||||
export class TokenDialog extends React.Component<
|
||||
TokenDialogProps,
|
||||
TokenDialogState
|
||||
> {
|
||||
export class TokenDialog extends React.Component<TokenDialogProps, TokenDialogState> {
|
||||
constructor(props: TokenDialogProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
verifying: false,
|
||||
error: false,
|
||||
tokenInput: '',
|
||||
tokenInput: ''
|
||||
};
|
||||
|
||||
this.onSubmitToken = this.onSubmitToken.bind(this);
|
||||
@@ -93,7 +91,7 @@ export class TokenDialog extends React.Component<
|
||||
this.setState({
|
||||
verifying: false,
|
||||
error: false,
|
||||
tokenInput: '',
|
||||
tokenInput: ''
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,20 +134,23 @@ export class TokenDialog extends React.Component<
|
||||
const canSubmit = !!this.state.tokenInput;
|
||||
|
||||
return [
|
||||
<Button
|
||||
key="done"
|
||||
disabled={!canSubmit}
|
||||
onClick={this.onSubmitToken}
|
||||
loading={this.state.verifying}
|
||||
text="Done"
|
||||
icon="log-in"
|
||||
/>,
|
||||
<Button
|
||||
key="cancel"
|
||||
text="Cancel"
|
||||
icon="log-out"
|
||||
onClick={this.onClose}
|
||||
/>,
|
||||
(
|
||||
<Button
|
||||
key='done'
|
||||
disabled={!canSubmit}
|
||||
onClick={this.onSubmitToken}
|
||||
loading={this.state.verifying}
|
||||
text='Done'
|
||||
icon='log-in'
|
||||
/>
|
||||
), (
|
||||
<Button
|
||||
key='cancel'
|
||||
text='Cancel'
|
||||
icon='log-out'
|
||||
onClick={this.onClose}
|
||||
/>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -171,15 +172,11 @@ export class TokenDialog extends React.Component<
|
||||
<Dialog
|
||||
isOpen={isTokenDialogShowing}
|
||||
onClose={this.onClose}
|
||||
title="GitHub Token"
|
||||
title='GitHub Token'
|
||||
>
|
||||
<div className="bp3-dialog-body">
|
||||
<div className='bp3-dialog-body'>
|
||||
<p>
|
||||
Generate a{' '}
|
||||
<a onClick={this.openGenerateTokenExternal}>
|
||||
GitHub Personal Access Token
|
||||
</a>{' '}
|
||||
and paste it here:
|
||||
Generate a <a onClick={this.openGenerateTokenExternal}>GitHub Personal Access Token</a> and paste it here:
|
||||
</p>
|
||||
|
||||
{this.state.error ? this.invalidWarning : null}
|
||||
@@ -190,8 +187,10 @@ export class TokenDialog extends React.Component<
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="bp3-dialog-footer">
|
||||
<div className="bp3-dialog-footer-actions">{this.buttons}</div>
|
||||
<div className='bp3-dialog-footer'>
|
||||
<div className='bp3-dialog-footer-actions'>
|
||||
{this.buttons}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface DialogsProps {
|
||||
* @extends {React.Component<DialogsProps, {}>}
|
||||
*/
|
||||
@observer
|
||||
export class Dialogs extends React.Component<DialogsProps> {
|
||||
export class Dialogs extends React.Component<DialogsProps, {}> {
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
const {
|
||||
@@ -29,29 +29,28 @@ export class Dialogs extends React.Component<DialogsProps> {
|
||||
isAddVersionDialogShowing,
|
||||
isThemeDialogShowing,
|
||||
isBisectDialogShowing,
|
||||
isGenericDialogShowing,
|
||||
isGenericDialogShowing
|
||||
} = appState;
|
||||
const maybeToken = isTokenDialogShowing ? (
|
||||
<TokenDialog key="dialogs" appState={appState} />
|
||||
) : null;
|
||||
const maybeSettings = isSettingsShowing ? (
|
||||
<Settings key="settings" appState={appState} />
|
||||
) : null;
|
||||
const maybeAddLocalVersion = isAddVersionDialogShowing ? (
|
||||
<AddVersionDialog key="add-version-dialog" appState={appState} />
|
||||
) : null;
|
||||
const maybeMonaco = isThemeDialogShowing ? (
|
||||
<AddThemeDialog appState={appState} />
|
||||
) : null;
|
||||
const maybeBisect = isBisectDialogShowing ? (
|
||||
<BisectDialog key="bisect-dialog" appState={appState} />
|
||||
) : null;
|
||||
const genericDialog = isGenericDialogShowing ? (
|
||||
<GenericDialog appState={appState} />
|
||||
) : null;
|
||||
const maybeToken = isTokenDialogShowing
|
||||
? <TokenDialog key='dialogs' appState={appState} />
|
||||
: null;
|
||||
const maybeSettings = isSettingsShowing
|
||||
? <Settings key='settings' appState={appState} />
|
||||
: null;
|
||||
const maybeAddLocalVersion = isAddVersionDialogShowing
|
||||
? <AddVersionDialog key='add-version-dialog' appState={appState} />
|
||||
: null;
|
||||
const maybeMonaco = isThemeDialogShowing ? <AddThemeDialog appState={appState} />
|
||||
: null;
|
||||
const maybeBisect = isBisectDialogShowing
|
||||
? <BisectDialog key='bisect-dialog' appState={appState} />
|
||||
: null;
|
||||
const genericDialog = isGenericDialogShowing
|
||||
? <GenericDialog appState={appState} />
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div key="dialogs" className="dialogs">
|
||||
<div key='dialogs' className='dialogs'>
|
||||
{maybeToken}
|
||||
{maybeSettings}
|
||||
{maybeAddLocalVersion}
|
||||
|
||||
@@ -15,17 +15,14 @@ export interface EditorProps {
|
||||
id: EditorId;
|
||||
options?: Partial<MonacoType.editor.IEditorConstructionOptions>;
|
||||
editorDidMount?: (editor: MonacoType.editor.IStandaloneCodeEditor) => void;
|
||||
onChange?: (
|
||||
value: string,
|
||||
event: MonacoType.editor.IModelContentChangedEvent,
|
||||
) => void;
|
||||
onChange?: (value: string, event: MonacoType.editor.IModelContentChangedEvent) => void;
|
||||
setFocused: (id: EditorId) => void;
|
||||
}
|
||||
|
||||
export class Editor extends React.Component<EditorProps> {
|
||||
public editor: MonacoType.editor.IStandaloneCodeEditor;
|
||||
public language = 'javascript';
|
||||
public value = '';
|
||||
public language: string = 'javascript';
|
||||
public value: string = '';
|
||||
|
||||
private containerRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
@@ -90,7 +87,7 @@ export class Editor extends React.Component<EditorProps> {
|
||||
theme: 'main',
|
||||
contextmenu: false,
|
||||
model: null,
|
||||
...monacoOptions,
|
||||
...monacoOptions
|
||||
});
|
||||
|
||||
// mark this editor as focused whenever it is
|
||||
@@ -114,7 +111,7 @@ export class Editor extends React.Component<EditorProps> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
return <div className="editorContainer" ref={this.containerRef} />;
|
||||
return <div className='editorContainer' ref={this.containerRef} />;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,7 +125,7 @@ export class Editor extends React.Component<EditorProps> {
|
||||
|
||||
const model = monaco.editor.createModel(value, this.language);
|
||||
model.updateOptions({
|
||||
tabSize: 2,
|
||||
tabSize: 2
|
||||
});
|
||||
|
||||
this.editor.setModel(model);
|
||||
|
||||
@@ -6,10 +6,10 @@ import { EditorId } from '../../interfaces';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export function renderNonIdealState(appState: AppState) {
|
||||
const allEditors = [EditorId.html, EditorId.main, EditorId.renderer];
|
||||
const allEditors = [ EditorId.html, EditorId.main, EditorId.renderer ];
|
||||
const resolveButton = (
|
||||
<Button
|
||||
text="Open all editors"
|
||||
text='Open all editors'
|
||||
onClick={() => appState.setVisibleMosaics(allEditors)}
|
||||
/>
|
||||
);
|
||||
@@ -17,7 +17,7 @@ export function renderNonIdealState(appState: AppState) {
|
||||
return (
|
||||
<NonIdealState
|
||||
action={resolveButton}
|
||||
icon="applications"
|
||||
icon='applications'
|
||||
description='You have closed all editors. You can open them again with the button below or the "Editors" button above!'
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import {
|
||||
MosaicContext,
|
||||
MosaicRootActions,
|
||||
MosaicWindowContext,
|
||||
} from 'react-mosaic-component';
|
||||
import { MosaicContext, MosaicRootActions, MosaicWindowContext } from 'react-mosaic-component';
|
||||
|
||||
import { DocsDemoPage, MosaicId } from '../../interfaces';
|
||||
import { AppState } from '../state';
|
||||
@@ -15,9 +11,7 @@ export interface ToolbarButtonProps {
|
||||
id: MosaicId;
|
||||
}
|
||||
|
||||
export abstract class ToolbarButton extends React.PureComponent<
|
||||
ToolbarButtonProps
|
||||
> {
|
||||
export abstract class ToolbarButton extends React.PureComponent<ToolbarButtonProps> {
|
||||
public static contextType = MosaicWindowContext;
|
||||
public context: MosaicWindowContext;
|
||||
|
||||
@@ -36,9 +30,7 @@ export abstract class ToolbarButton extends React.PureComponent<
|
||||
/**
|
||||
* Create a button that performs the actual action
|
||||
*/
|
||||
public abstract createButton(
|
||||
_mosaicActions: MosaicRootActions<any>,
|
||||
): React.ReactNode;
|
||||
public abstract createButton(_mosaicActions: MosaicRootActions<any>): React.ReactNode;
|
||||
}
|
||||
|
||||
export class MaximizeButton extends ToolbarButton {
|
||||
@@ -50,7 +42,13 @@ export class MaximizeButton extends ToolbarButton {
|
||||
mosaicActions.expand(this.context.mosaicWindowActions.getPath());
|
||||
};
|
||||
|
||||
return <Button icon="maximize" className="bp3-small" onClick={onClick} />;
|
||||
return (
|
||||
<Button
|
||||
icon='maximize'
|
||||
className='bp3-small'
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +57,15 @@ export class RemoveButton extends ToolbarButton {
|
||||
* Create a button that can remove this panel
|
||||
*/
|
||||
public createButton(_mosaicActions: MosaicRootActions<any>) {
|
||||
const onClick = () =>
|
||||
this.props.appState.hideAndBackupMosaic(this.props.id);
|
||||
const onClick = () => this.props.appState.hideAndBackupMosaic(this.props.id);
|
||||
|
||||
return <Button icon="cross" className="bp3-small" onClick={onClick} />;
|
||||
return (
|
||||
<Button
|
||||
icon='cross'
|
||||
className='bp3-small'
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,15 +74,14 @@ export class DocsDemoGoHomeButton extends ToolbarButton {
|
||||
* Create a button that can remove this panel
|
||||
*/
|
||||
public createButton(_mosaicActions: MosaicRootActions<any>) {
|
||||
const onClick = () =>
|
||||
(this.props.appState.currentDocsDemoPage = DocsDemoPage.DEFAULT);
|
||||
const onClick = () => this.props.appState.currentDocsDemoPage = DocsDemoPage.DEFAULT;
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon="home"
|
||||
className="bp3-small"
|
||||
icon='home'
|
||||
className='bp3-small'
|
||||
onClick={onClick}
|
||||
text="Overview"
|
||||
text='Overview'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,7 @@ import { reaction } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as MonacoType from 'monaco-editor';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Mosaic,
|
||||
MosaicBranch,
|
||||
MosaicNode,
|
||||
MosaicWindow,
|
||||
MosaicWindowProps,
|
||||
} from 'react-mosaic-component';
|
||||
import { Mosaic, MosaicBranch, MosaicNode, MosaicWindow, MosaicWindowProps } from 'react-mosaic-component';
|
||||
|
||||
import { EditorId, MosaicId, PanelId } from '../../interfaces';
|
||||
import { IpcEvents } from '../../ipc-events';
|
||||
@@ -23,18 +17,14 @@ import { AppState } from '../state';
|
||||
import { activateTheme } from '../themes';
|
||||
import { Editor } from './editor';
|
||||
import { renderNonIdealState } from './editors-non-ideal-state';
|
||||
import {
|
||||
DocsDemoGoHomeButton,
|
||||
MaximizeButton,
|
||||
RemoveButton,
|
||||
} from './editors-toolbar-button';
|
||||
import { DocsDemoGoHomeButton, MaximizeButton, RemoveButton } from './editors-toolbar-button';
|
||||
import { ShowMe } from './show-me';
|
||||
|
||||
const defaultMonacoOptions: MonacoType.editor.IEditorOptions = {
|
||||
minimap: {
|
||||
enabled: false,
|
||||
enabled: false
|
||||
},
|
||||
wordWrap: 'on',
|
||||
wordWrap: 'on'
|
||||
};
|
||||
|
||||
export const TITLE_MAP: Record<MosaicId, string> = {
|
||||
@@ -63,7 +53,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
// the editor layout. That method is itself debounced.
|
||||
public disposeLayoutAutorun = reaction(
|
||||
() => this.props.appState.mosaicArrangement,
|
||||
() => updateEditorLayout(),
|
||||
() => updateEditorLayout()
|
||||
);
|
||||
|
||||
constructor(props: EditorsProps) {
|
||||
@@ -84,41 +74,22 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
* @memberof Editors
|
||||
*/
|
||||
public async componentDidMount() {
|
||||
ipcRendererManager.on(
|
||||
IpcEvents.MONACO_EXECUTE_COMMAND,
|
||||
(_event, cmd: string) => {
|
||||
this.executeCommand(cmd);
|
||||
},
|
||||
);
|
||||
ipcRendererManager.on(IpcEvents.MONACO_EXECUTE_COMMAND, (_event, cmd: string) => {
|
||||
this.executeCommand(cmd);
|
||||
});
|
||||
|
||||
ipcRendererManager.on(IpcEvents.FS_NEW_FIDDLE, async (_event) => {
|
||||
const { version } = this.props.appState;
|
||||
|
||||
await window.ElectronFiddle.app.replaceFiddle(
|
||||
{
|
||||
html: await getContent(EditorId.html, version),
|
||||
renderer: await getContent(EditorId.renderer, version),
|
||||
main: await getContent(EditorId.main, version),
|
||||
},
|
||||
{},
|
||||
);
|
||||
await window.ElectronFiddle.app.replaceFiddle({
|
||||
html: await getContent(EditorId.html, version),
|
||||
renderer: await getContent(EditorId.renderer, version),
|
||||
main: await getContent(EditorId.main, version),
|
||||
}, {});
|
||||
});
|
||||
|
||||
ipcRendererManager.on(
|
||||
IpcEvents.MONACO_TOGGLE_OPTION,
|
||||
(_event, cmd: string) => {
|
||||
this.toggleEditorOption(cmd);
|
||||
},
|
||||
);
|
||||
|
||||
ipcRendererManager.on(IpcEvents.SELECT_ALL_IN_EDITOR, (_event) => {
|
||||
// programmatically fetch all editor contents and set as selection
|
||||
const editor = getFocusedEditor();
|
||||
const range = editor?.getModel()?.getFullModelRange();
|
||||
|
||||
if (!!range) {
|
||||
editor?.setSelection(range);
|
||||
}
|
||||
ipcRendererManager.on(IpcEvents.MONACO_TOGGLE_OPTION, (_event, cmd: string) => {
|
||||
this.toggleEditorOption(cmd);
|
||||
});
|
||||
|
||||
this.setState({ isMounted: true });
|
||||
@@ -132,7 +103,6 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.MONACO_EXECUTE_COMMAND);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.FS_NEW_FIDDLE);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.MONACO_TOGGLE_OPTION);
|
||||
ipcRendererManager.removeAllListeners(IpcEvents.SELECT_ALL_IN_EDITOR);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,9 +117,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
if (editor) {
|
||||
const command = editor.getAction(commandId);
|
||||
|
||||
console.log(
|
||||
`Editors: Trying to run ${command.id}. Supported: ${command.isSupported}`,
|
||||
);
|
||||
console.log(`Editors: Trying to run ${command.id}. Supported: ${command.isSupported}`);
|
||||
|
||||
if (command && command.isSupported()) {
|
||||
command.run();
|
||||
@@ -169,14 +137,15 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
|
||||
setAtPath(path, newOptions, toggleMonaco(currentSetting));
|
||||
|
||||
Object.keys(window.ElectronFiddle.editors).forEach((key) => {
|
||||
const editor: MonacoType.editor.IStandaloneCodeEditor | null =
|
||||
window.ElectronFiddle.editors[key];
|
||||
Object.keys(window.ElectronFiddle.editors)
|
||||
.forEach((key) => {
|
||||
const editor: MonacoType.editor.IStandaloneCodeEditor | null
|
||||
= window.ElectronFiddle.editors[key];
|
||||
|
||||
if (editor) {
|
||||
editor.updateOptions(newOptions);
|
||||
}
|
||||
});
|
||||
if (editor) {
|
||||
editor.updateOptions(newOptions);
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({ monacoOptions: newOptions });
|
||||
|
||||
@@ -196,35 +165,36 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
public renderToolbar(
|
||||
{ title }: MosaicWindowProps<MosaicId>,
|
||||
id: MosaicId,
|
||||
{ title }: MosaicWindowProps<MosaicId>, id: MosaicId
|
||||
): JSX.Element {
|
||||
const { appState } = this.props;
|
||||
const docsDemoGoHomeMaybe =
|
||||
id === PanelId.docsDemo ? (
|
||||
<DocsDemoGoHomeButton id={id} appState={appState} />
|
||||
) : null;
|
||||
const docsDemoGoHomeMaybe = id === PanelId.docsDemo
|
||||
? <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} />
|
||||
</>
|
||||
);
|
||||
const toolbarControlsMaybe =
|
||||
(typeof appState.mosaicArrangement !== 'string') &&
|
||||
(
|
||||
<>
|
||||
<MaximizeButton id={id} appState={appState} />
|
||||
<RemoveButton id={id} appState={appState} />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Left */}
|
||||
<div>
|
||||
<h5>{title}</h5>
|
||||
<h5>
|
||||
{title}
|
||||
</h5>
|
||||
</div>
|
||||
{/* Middle */}
|
||||
<div />
|
||||
{/* Right */}
|
||||
<div className="mosaic-controls">
|
||||
<div className='mosaic-controls'>
|
||||
{docsDemoGoHomeMaybe}
|
||||
{toolbarControlsMaybe}
|
||||
</div>
|
||||
@@ -250,9 +220,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
className={id}
|
||||
path={path}
|
||||
title={TITLE_MAP[id]}
|
||||
renderToolbar={(props: MosaicWindowProps<MosaicId>) =>
|
||||
this.renderToolbar(props, id)
|
||||
}
|
||||
renderToolbar={(props: MosaicWindowProps<MosaicId>) => this.renderToolbar(props, id)}
|
||||
>
|
||||
{content}
|
||||
</MosaicWindow>
|
||||
@@ -326,7 +294,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
public async loadMonaco() {
|
||||
const { app } = window.ElectronFiddle;
|
||||
const loader = require('monaco-loader');
|
||||
const monaco = app.monaco || (await loader());
|
||||
const monaco = app.monaco || await loader();
|
||||
|
||||
if (!app.monaco) {
|
||||
app.monaco = monaco;
|
||||
@@ -335,7 +303,7 @@ export class Editors extends React.Component<EditorsProps, EditorsState> {
|
||||
if (!this.state || !this.state.isMounted) {
|
||||
this.setState({
|
||||
monaco,
|
||||
monacoOptions: defaultMonacoOptions,
|
||||
monacoOptions: defaultMonacoOptions
|
||||
});
|
||||
} else {
|
||||
this.setState({ monaco });
|
||||
|
||||
@@ -13,15 +13,15 @@ export interface HeaderProps {
|
||||
* Everything above the editors, so buttons and the address bar.
|
||||
*
|
||||
* @class Header
|
||||
* @extends {React.Component<HeaderProps>}
|
||||
* @extends {React.Component<HeaderProps, HeaderState>}
|
||||
*/
|
||||
export class Header extends React.Component<HeaderProps> {
|
||||
export class Header extends React.Component<HeaderProps, {}> {
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<ChromeMac appState={this.props.appState} />
|
||||
<header id="header">
|
||||
<Commands key="commands" appState={this.props.appState} />
|
||||
<header id='header'>
|
||||
<Commands key='commands' appState={this.props.appState} />
|
||||
</header>
|
||||
<WelcomeTour appState={this.props.appState} />
|
||||
</>
|
||||
|
||||
@@ -15,13 +15,11 @@ export interface WrapperState {
|
||||
|
||||
export type WrapperMosaicId = 'output' | 'editors';
|
||||
|
||||
export class OutputEditorsWrapper extends React.Component<
|
||||
WrapperProps,
|
||||
WrapperState
|
||||
> {
|
||||
export class OutputEditorsWrapper extends React.Component<WrapperProps, WrapperState> {
|
||||
|
||||
private MOSAIC_ELEMENTS = {
|
||||
output: <Output appState={this.props.appState} />,
|
||||
editors: <Editors appState={this.props.appState} />,
|
||||
editors: <Editors appState={this.props.appState} />
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
@@ -32,7 +30,7 @@ export class OutputEditorsWrapper extends React.Component<
|
||||
first: 'output',
|
||||
second: 'editors',
|
||||
splitPercentage: 25,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,7 +39,7 @@ export class OutputEditorsWrapper extends React.Component<
|
||||
<>
|
||||
<Mosaic<WrapperMosaicId>
|
||||
renderTile={(id: string) => this.MOSAIC_ELEMENTS[id]}
|
||||
resize={{ minimumPaneSizePercentage: 0 }}
|
||||
resize={{minimumPaneSizePercentage: 0}}
|
||||
value={this.state.mosaicArrangement}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
@@ -56,6 +54,6 @@ export class OutputEditorsWrapper extends React.Component<
|
||||
this.props.appState.isConsoleShowing = isConsoleShowing;
|
||||
}
|
||||
|
||||
this.setState({ mosaicArrangement: currentNode });
|
||||
};
|
||||
this.setState({mosaicArrangement: currentNode});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { OutputEntry } from '../../interfaces';
|
||||
import { AppState } from '../state';
|
||||
import { WrapperMosaicId } from './output-editors-wrapper';
|
||||
|
||||
|
||||
export interface CommandsProps {
|
||||
appState: AppState;
|
||||
// Used to keep testing conform
|
||||
@@ -18,10 +19,10 @@ export interface CommandsProps {
|
||||
* whenever a Fiddle is launched in Electron.
|
||||
*
|
||||
* @class Output
|
||||
* @extends {React.Component<CommandsProps>}
|
||||
* @extends {React.Component<CommandsProps, {}>}
|
||||
*/
|
||||
@observer
|
||||
export class Output extends React.Component<CommandsProps> {
|
||||
export class Output extends React.Component<CommandsProps, {}> {
|
||||
public static contextType = MosaicContext;
|
||||
public context: MosaicContext<WrapperMosaicId>;
|
||||
private outputRef = React.createRef<HTMLDivElement>();
|
||||
@@ -33,6 +34,7 @@ export class Output extends React.Component<CommandsProps> {
|
||||
this.renderEntry = this.renderEntry.bind(this);
|
||||
}
|
||||
|
||||
|
||||
public componentDidMount() {
|
||||
autorun(() => {
|
||||
const { isConsoleShowing } = this.props.appState;
|
||||
@@ -81,16 +83,13 @@ export class Output extends React.Component<CommandsProps> {
|
||||
*/
|
||||
public renderEntry(entry: OutputEntry, index: number): Array<JSX.Element> {
|
||||
const ts = this.renderTimestamp(entry.timestamp);
|
||||
const timestamp = <span className="timestamp">{ts}</span>;
|
||||
const timestamp = <span className='timestamp'>{ts}</span>;
|
||||
const lines = entry.text.split(/\r?\n/);
|
||||
const style: React.CSSProperties = entry.isNotPre
|
||||
? { whiteSpace: 'initial' }
|
||||
: {};
|
||||
const style: React.CSSProperties = entry.isNotPre ? { whiteSpace: 'initial' } : {};
|
||||
|
||||
return lines.map((text, lineIndex) => (
|
||||
<p style={style} key={`${entry.timestamp}--${index}--${lineIndex}`}>
|
||||
{timestamp}
|
||||
{text}
|
||||
{timestamp}{text}
|
||||
</p>
|
||||
));
|
||||
}
|
||||
@@ -108,7 +107,7 @@ export class Output extends React.Component<CommandsProps> {
|
||||
.map(this.renderEntry);
|
||||
|
||||
return (
|
||||
<div className="output" ref={this.outputRef}>
|
||||
<div className='output' ref={this.outputRef}>
|
||||
{lines}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,15 +30,12 @@ export interface Contributor {
|
||||
* @class CreditsSettings
|
||||
* @extends {React.Component<CreditsSettingsProps, CreditsSettingsState>}
|
||||
*/
|
||||
export class CreditsSettings extends React.Component<
|
||||
CreditsSettingsProps,
|
||||
CreditsSettingsState
|
||||
> {
|
||||
export class CreditsSettings extends React.Component<CreditsSettingsProps, CreditsSettingsState> {
|
||||
constructor(props: CreditsSettingsProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
contributors: [],
|
||||
contributors: []
|
||||
};
|
||||
|
||||
this.getContributors();
|
||||
@@ -53,25 +50,22 @@ export class CreditsSettings extends React.Component<
|
||||
const { contributors } = this.state;
|
||||
|
||||
return contributors.map(({ name, avatar, url, login, location, bio }) => {
|
||||
const maybeLocation = location ? (
|
||||
<p className="location">📍 {location}</p>
|
||||
) : null;
|
||||
const maybeBio = bio ? <small className="bio">{bio}</small> : null;
|
||||
const maybeLocation = location
|
||||
? <p className='location'>📍 {location}</p>
|
||||
: null;
|
||||
const maybeBio = bio
|
||||
? <small className='bio'>{bio}</small>
|
||||
: null;
|
||||
const style: React.CSSProperties = {
|
||||
backgroundImage: `url(${avatar})`,
|
||||
backgroundImage: `url(${avatar})`
|
||||
};
|
||||
const onClick = () => shell.openExternal(url);
|
||||
|
||||
return (
|
||||
<Card
|
||||
interactive={true}
|
||||
key={login}
|
||||
className="contributor"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="avatar" style={style} />
|
||||
<div className="details">
|
||||
<h5 className="name">{name || login}</h5>
|
||||
<Card interactive={true} key={login} className='contributor' onClick={onClick}>
|
||||
<div className='avatar' style={style} />
|
||||
<div className='details'>
|
||||
<h5 className='name'>{name || login}</h5>
|
||||
{maybeLocation}
|
||||
{maybeBio}
|
||||
</div>
|
||||
@@ -90,17 +84,16 @@ export class CreditsSettings extends React.Component<
|
||||
would like to thank those who helped to make Electron Fiddle:
|
||||
</Callout>
|
||||
<br />
|
||||
<div className="contributors">{this.renderContributors()}</div>
|
||||
<div className='contributors'>
|
||||
{this.renderContributors()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public async getContributors() {
|
||||
try {
|
||||
const contributorsFile = path.join(
|
||||
__dirname,
|
||||
'../../static/contributors.json',
|
||||
);
|
||||
const contributorsFile = path.join(__dirname, '../../static/contributors.json');
|
||||
const contributors = await fs.readJSON(contributorsFile);
|
||||
this.setState({ contributors });
|
||||
} catch (error) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
IButtonProps,
|
||||
Icon,
|
||||
IconName,
|
||||
Tooltip,
|
||||
Tooltip
|
||||
} from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
@@ -35,10 +35,7 @@ export interface ElectronSettingsState {
|
||||
* @extends {React.Component<ElectronSettingsProps, {}>}
|
||||
*/
|
||||
@observer
|
||||
export class ElectronSettings extends React.Component<
|
||||
ElectronSettingsProps,
|
||||
ElectronSettingsState
|
||||
> {
|
||||
export class ElectronSettings extends React.Component<ElectronSettingsProps, ElectronSettingsState> {
|
||||
constructor(props: ElectronSettingsProps) {
|
||||
super(props);
|
||||
|
||||
@@ -51,7 +48,7 @@ export class ElectronSettings extends React.Component<
|
||||
|
||||
this.state = {
|
||||
isDownloadingAll: false,
|
||||
isDeletingAll: false,
|
||||
isDeletingAll: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,7 +61,9 @@ export class ElectronSettings extends React.Component<
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public handleStateChange(event: React.FormEvent<HTMLInputElement>) {
|
||||
public handleStateChange(
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) {
|
||||
const { id, checked } = event.currentTarget;
|
||||
const { appState } = this.props;
|
||||
|
||||
@@ -80,7 +79,9 @@ export class ElectronSettings extends React.Component<
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public handleChannelChange(event: React.FormEvent<HTMLInputElement>) {
|
||||
public handleChannelChange(
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) {
|
||||
const { id, checked } = event.currentTarget;
|
||||
const { appState } = this.props;
|
||||
|
||||
@@ -137,7 +138,7 @@ export class ElectronSettings extends React.Component<
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="settings-electron">
|
||||
<div className='settings-electron'>
|
||||
<h2>Electron Settings</h2>
|
||||
<Callout>
|
||||
{this.renderVersionChannelOptions()}
|
||||
@@ -169,25 +170,25 @@ export class ElectronSettings extends React.Component<
|
||||
disabled={isUpdatingElectronVersions}
|
||||
onClick={this.handleDownloadClick}
|
||||
loading={isUpdatingElectronVersions}
|
||||
icon="numbered-list"
|
||||
text="Update Electron Release List"
|
||||
icon='numbered-list'
|
||||
text='Update Electron Release List'
|
||||
/>
|
||||
<Button
|
||||
disabled={isWorking}
|
||||
icon="download"
|
||||
icon='download'
|
||||
onClick={this.handleDownloadAll}
|
||||
text="Download All Versions"
|
||||
text='Download All Versions'
|
||||
/>
|
||||
<Button
|
||||
disabled={isWorking}
|
||||
icon="trash"
|
||||
icon='trash'
|
||||
onClick={this.handleDeleteAll}
|
||||
text="Delete All Downloads"
|
||||
text='Delete All Downloads'
|
||||
/>
|
||||
<Button
|
||||
icon="document-open"
|
||||
icon='document-open'
|
||||
onClick={this.handleAddVersion}
|
||||
text="Add Local Electron Build"
|
||||
text='Add Local Electron Build'
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
@@ -207,12 +208,18 @@ export class ElectronSettings extends React.Component<
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup label="Include Electron versions that are:">
|
||||
<Tooltip content="Always enabled" position="bottom" intent="primary">
|
||||
<FormGroup
|
||||
label='Include Electron versions that are:'
|
||||
>
|
||||
<Tooltip
|
||||
content='Always enabled'
|
||||
position='bottom'
|
||||
intent='primary'
|
||||
>
|
||||
<Checkbox
|
||||
checked={getIsChecked(VersionState.ready)}
|
||||
label="Ready"
|
||||
id="ready"
|
||||
label='Ready'
|
||||
id='ready'
|
||||
onChange={this.handleStateChange}
|
||||
inline={true}
|
||||
disabled={true}
|
||||
@@ -220,15 +227,15 @@ export class ElectronSettings extends React.Component<
|
||||
</Tooltip>
|
||||
<Checkbox
|
||||
checked={getIsChecked(VersionState.downloading)}
|
||||
label="Downloading"
|
||||
id="downloading"
|
||||
label='Downloading'
|
||||
id='downloading'
|
||||
onChange={this.handleStateChange}
|
||||
inline={true}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={getIsChecked(VersionState.unknown)}
|
||||
label="Not Downloaded"
|
||||
id="unknown"
|
||||
label='Not Downloaded'
|
||||
id='unknown'
|
||||
onChange={this.handleStateChange}
|
||||
inline={true}
|
||||
/>
|
||||
@@ -249,9 +256,7 @@ export class ElectronSettings extends React.Component<
|
||||
return appState.channelsToShow.includes(channel);
|
||||
};
|
||||
|
||||
const getIsCurrentVersionReleaseChannel = (
|
||||
channel: ElectronReleaseChannel,
|
||||
) => {
|
||||
const getIsCurrentVersionReleaseChannel = (channel: ElectronReleaseChannel) => {
|
||||
return getReleaseChannel(appState.version) === channel;
|
||||
};
|
||||
|
||||
@@ -259,29 +264,34 @@ export class ElectronSettings extends React.Component<
|
||||
stable: ElectronReleaseChannel.stable,
|
||||
beta: ElectronReleaseChannel.beta,
|
||||
nightly: ElectronReleaseChannel.nightly,
|
||||
unsupported: ElectronReleaseChannel.unsupported,
|
||||
unsupported: ElectronReleaseChannel.unsupported
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup label="Include Electron versions from these release channels:">
|
||||
{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
|
||||
label='Include Electron versions from these release channels:'
|
||||
>
|
||||
{
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
@@ -294,15 +304,20 @@ export class ElectronSettings extends React.Component<
|
||||
*/
|
||||
private renderTable(): JSX.Element {
|
||||
return (
|
||||
<HTMLTable className="electron-versions-table" striped={true}>
|
||||
<HTMLTable
|
||||
className='electron-versions-table'
|
||||
striped={true}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Status</th>
|
||||
<th className="action">Action</th>
|
||||
<th className='action'>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{this.renderTableRows()}</tbody>
|
||||
<tbody>
|
||||
{this.renderTableRows()}
|
||||
</tbody>
|
||||
</HTMLTable>
|
||||
);
|
||||
}
|
||||
@@ -331,7 +346,7 @@ export class ElectronSettings extends React.Component<
|
||||
<tr key={item.version}>
|
||||
<td>{item.version}</td>
|
||||
<td>{this.renderHumanState(item)}</td>
|
||||
<td className="action">{this.renderAction(key, item)}</td>
|
||||
<td className='action'>{this.renderAction(key, item)}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
@@ -376,14 +391,16 @@ export class ElectronSettings extends React.Component<
|
||||
const { appState } = this.props;
|
||||
const buttonProps: IButtonProps = {
|
||||
fill: true,
|
||||
small: true,
|
||||
small: true
|
||||
};
|
||||
|
||||
// Already downloaded
|
||||
if (state === 'ready') {
|
||||
buttonProps.onClick = () => appState.removeVersion(key);
|
||||
buttonProps.icon = 'trash';
|
||||
buttonProps.text = source === VersionSource.local ? 'Remove' : 'Delete';
|
||||
buttonProps.text = source === VersionSource.local
|
||||
? 'Remove'
|
||||
: 'Delete';
|
||||
} else if (state === 'downloading') {
|
||||
buttonProps.disabled = true;
|
||||
buttonProps.loading = true;
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import {
|
||||
Callout,
|
||||
Checkbox,
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
} from '@blueprintjs/core';
|
||||
import { Callout, Checkbox, FormGroup, InputGroup } from '@blueprintjs/core';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { IPackageManager } from '../npm';
|
||||
import { AppState } from '../state';
|
||||
|
||||
export interface ExecutionSettingsProps {
|
||||
@@ -23,14 +15,12 @@ export interface ExecutionSettingsProps {
|
||||
* @extends {React.Component<ExecutionSettingsProps, {}>}
|
||||
*/
|
||||
@observer
|
||||
export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
|
||||
export class ExecutionSettings extends React.Component<ExecutionSettingsProps, {}> {
|
||||
constructor(props: ExecutionSettingsProps) {
|
||||
super(props);
|
||||
|
||||
this.handleDeleteDataChange = this.handleDeleteDataChange.bind(this);
|
||||
this.handleElectronLoggingChange = this.handleElectronLoggingChange.bind(
|
||||
this,
|
||||
);
|
||||
this.handleElectronLoggingChange = this.handleElectronLoggingChange.bind(this);
|
||||
this.handleExecutionFlagChange = this.handleExecutionFlagChange.bind(this);
|
||||
}
|
||||
|
||||
@@ -40,7 +30,9 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public handleDeleteDataChange(event: React.FormEvent<HTMLInputElement>) {
|
||||
public handleDeleteDataChange(
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) {
|
||||
const { checked } = event.currentTarget;
|
||||
this.props.appState.isKeepingUserDataDirs = checked;
|
||||
}
|
||||
@@ -50,7 +42,9 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public handleElectronLoggingChange(event: React.FormEvent<HTMLInputElement>) {
|
||||
public handleElectronLoggingChange(
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) {
|
||||
const { checked } = event.currentTarget;
|
||||
this.props.appState.isEnablingElectronLogging = checked;
|
||||
}
|
||||
@@ -60,7 +54,9 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public handleExecutionFlagChange(event: React.FormEvent<HTMLInputElement>) {
|
||||
public handleExecutionFlagChange(
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) {
|
||||
const { value } = event.currentTarget;
|
||||
const flags = value.split('|');
|
||||
this.props.appState.executionFlags = flags;
|
||||
@@ -70,7 +66,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
|
||||
const {
|
||||
isKeepingUserDataDirs,
|
||||
isEnablingElectronLogging,
|
||||
executionFlags = [],
|
||||
executionFlags = []
|
||||
} = this.props.appState;
|
||||
|
||||
const deleteUserDirLabel = `
|
||||
@@ -87,15 +83,14 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
|
||||
<div>
|
||||
<h2>Execution</h2>
|
||||
<Callout>
|
||||
These advanced settings control how Electron Fiddle executes your
|
||||
fiddles.
|
||||
These advanced settings control how Electron Fiddle executes your fiddles.
|
||||
</Callout>
|
||||
<br />
|
||||
<Callout>
|
||||
<FormGroup label={deleteUserDirLabel}>
|
||||
<Checkbox
|
||||
checked={isKeepingUserDataDirs}
|
||||
label="Do not delete user data directories."
|
||||
label='Do not delete user data directories.'
|
||||
onChange={this.handleDeleteDataChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -105,7 +100,7 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
|
||||
<FormGroup label={electronLoggingLabel}>
|
||||
<Checkbox
|
||||
checked={isEnablingElectronLogging}
|
||||
label="Enable advanced Electron logging."
|
||||
label='Enable advanced Electron logging.'
|
||||
onChange={this.handleElectronLoggingChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -113,58 +108,24 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
|
||||
<br />
|
||||
<Callout>
|
||||
<FormGroup>
|
||||
<p>
|
||||
Electron allows starting the executable with{' '}
|
||||
<a href="https://www.electronjs.org/docs/api/command-line-switches">
|
||||
user-provided flags
|
||||
</a>
|
||||
, such as '--js-flags=--expose-gc'. Those can be added
|
||||
here as bar-separated (|) flags to run when you start your
|
||||
Fiddles.
|
||||
</p>
|
||||
<p>
|
||||
Electron allows starting the executable with <a
|
||||
href='https://www.electronjs.org/docs/api/command-line-switches'
|
||||
>
|
||||
user-provided flags
|
||||
</a>
|
||||
, such as '--js-flags=--expose-gc'. Those can be added here as bar-separated (|)
|
||||
flags to run when you start your Fiddles.
|
||||
</p>
|
||||
<br />
|
||||
<InputGroup
|
||||
placeholder="--js-flags=--expose-gc|--lang=es"
|
||||
placeholder='--js-flags=--expose-gc|--lang=es'
|
||||
value={executionFlags.join('|')}
|
||||
onChange={this.handleExecutionFlagChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Callout>
|
||||
<br />
|
||||
<Callout>
|
||||
<FormGroup>
|
||||
<span style={{ marginRight: 4 }}>
|
||||
Electron Fiddle will install packages on runtime if they are
|
||||
imported within your fiddle with <code>require</code>. It uses{' '}
|
||||
<a href="https://www.npmjs.com/" target="_blank" rel="noreferrer">
|
||||
npm
|
||||
</a>{' '}
|
||||
as its package manager by default, but{' '}
|
||||
<a
|
||||
href="https://classic.yarnpkg.com/lang/en/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Yarn
|
||||
</a>{' '}
|
||||
is also available.
|
||||
</span>
|
||||
<RadioGroup
|
||||
onChange={this.handlePMChange}
|
||||
selectedValue={this.props.appState.packageManager}
|
||||
inline={true}
|
||||
>
|
||||
<Radio label="npm" value="npm" />
|
||||
<Radio label="yarn" value="yarn" />
|
||||
</RadioGroup>
|
||||
</FormGroup>
|
||||
</Callout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private handlePMChange = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
this.props.appState.packageManager = event.currentTarget
|
||||
.value as IPackageManager;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,13 +22,11 @@ const ThemeSelect = Select.ofType<LoadedFiddleTheme>();
|
||||
* @param {RunnableVersion} { version }
|
||||
* @returns
|
||||
*/
|
||||
export const filterItem: ItemPredicate<LoadedFiddleTheme> = (
|
||||
query,
|
||||
{ name },
|
||||
) => {
|
||||
export const filterItem: ItemPredicate<LoadedFiddleTheme> = (query, { name }) => {
|
||||
return name.toLowerCase().includes(query.toLowerCase());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper method: Returns the <Select /> <MenuItem /> for Electron
|
||||
* versions.
|
||||
@@ -37,10 +35,7 @@ export const filterItem: ItemPredicate<LoadedFiddleTheme> = (
|
||||
* @param {IItemRendererProps} { handleClick, modifiers, query }
|
||||
* @returns
|
||||
*/
|
||||
export const renderItem: ItemRenderer<LoadedFiddleTheme> = (
|
||||
item,
|
||||
{ handleClick, modifiers, query },
|
||||
) => {
|
||||
export const renderItem: ItemRenderer<LoadedFiddleTheme> = (item, { handleClick, modifiers, query }) => {
|
||||
if (!modifiers.matchesPredicate) {
|
||||
return null;
|
||||
}
|
||||
@@ -52,14 +47,13 @@ export const renderItem: ItemRenderer<LoadedFiddleTheme> = (
|
||||
text={highlightText(item.name, query)}
|
||||
key={item.name}
|
||||
onClick={handleClick}
|
||||
icon="media"
|
||||
icon='media'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export interface AppearanceSettingsProps {
|
||||
appState: AppState;
|
||||
toggleHasPopoverOpen: () => void;
|
||||
}
|
||||
|
||||
export interface AppearanceSettingsState {
|
||||
@@ -75,8 +69,7 @@ export interface AppearanceSettingsState {
|
||||
*/
|
||||
@observer
|
||||
export class AppearanceSettings extends React.Component<
|
||||
AppearanceSettingsProps,
|
||||
AppearanceSettingsState
|
||||
AppearanceSettingsProps, AppearanceSettingsState
|
||||
> {
|
||||
public constructor(props: AppearanceSettingsProps) {
|
||||
super(props);
|
||||
@@ -86,13 +79,13 @@ export class AppearanceSettings extends React.Component<
|
||||
this.handleAddTheme = this.handleAddTheme.bind(this);
|
||||
|
||||
this.state = {
|
||||
themes: [],
|
||||
themes: []
|
||||
};
|
||||
|
||||
getAvailableThemes().then((themes) => {
|
||||
const { theme } = this.props.appState;
|
||||
const selectedTheme =
|
||||
(theme && themes.find(({ file }) => file === theme)) || themes[0];
|
||||
const selectedTheme = theme &&
|
||||
themes.find(({ file }) => file === theme) || themes[0];
|
||||
|
||||
this.setState({ themes, selectedTheme });
|
||||
});
|
||||
@@ -128,20 +121,16 @@ export class AppearanceSettings extends React.Component<
|
||||
const name = namor.generate({ words: 2, numbers: 0 });
|
||||
const themePath = path.join(THEMES_PATH, `${name}.json`);
|
||||
|
||||
await fs.outputJSON(
|
||||
themePath,
|
||||
{
|
||||
...theme,
|
||||
name,
|
||||
file: undefined,
|
||||
css: undefined,
|
||||
},
|
||||
{ spaces: 2 },
|
||||
);
|
||||
await fs.outputJSON(themePath, {
|
||||
...theme,
|
||||
name,
|
||||
file: undefined,
|
||||
css: undefined
|
||||
}, { spaces: 2 });
|
||||
|
||||
shell.showItemInFolder(themePath);
|
||||
|
||||
this.setState({ themes: await getAvailableThemes() });
|
||||
this.setState({themes: await getAvailableThemes()});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -172,54 +161,51 @@ export class AppearanceSettings extends React.Component<
|
||||
|
||||
public render() {
|
||||
const { selectedTheme } = this.state;
|
||||
const selectedName =
|
||||
(selectedTheme && selectedTheme.name) || 'Select a theme';
|
||||
const selectedName = selectedTheme && selectedTheme.name || 'Select a theme';
|
||||
|
||||
return (
|
||||
<div className="settings-appearance">
|
||||
<div className='settings-appearance'>
|
||||
<h4>Appearance</h4>
|
||||
<FormGroup label="Choose your theme" inline={true}>
|
||||
<FormGroup
|
||||
label='Choose your theme'
|
||||
inline={true}
|
||||
>
|
||||
<ThemeSelect
|
||||
filterable={true}
|
||||
items={this.state.themes}
|
||||
itemRenderer={renderItem}
|
||||
itemPredicate={filterItem}
|
||||
onItemSelect={this.handleChange}
|
||||
popoverProps={{
|
||||
onClosed: () => this.props.toggleHasPopoverOpen(),
|
||||
}}
|
||||
noResults={<MenuItem disabled={true} text="No results." />}
|
||||
noResults={<MenuItem disabled={true} text='No results.' />}
|
||||
>
|
||||
<Button
|
||||
id="open-theme-selector"
|
||||
text={selectedName}
|
||||
icon="tint"
|
||||
onClick={() => this.props.toggleHasPopoverOpen()}
|
||||
icon='tint'
|
||||
/>
|
||||
</ThemeSelect>
|
||||
</FormGroup>
|
||||
<Callout>
|
||||
<p>
|
||||
To add themes, add JSON theme files to{' '}
|
||||
<a id="open-theme-folder" onClick={this.openThemeFolder}>
|
||||
To add themes, add JSON theme files to <a
|
||||
id='open-theme-folder'
|
||||
onClick={this.openThemeFolder}
|
||||
>
|
||||
<code>{THEMES_PATH}</code>
|
||||
</a>
|
||||
. The easiest way to get started is to clone one of the two existing
|
||||
</a>. The easiest way to get started is to clone one of the two existing
|
||||
themes and to add your own colors.
|
||||
</p>
|
||||
<p>
|
||||
Additionally, if you wish to import a Monaco Editor theme, pick your
|
||||
JSON file and Fiddle will attempt to import it.
|
||||
Additionally, if you wish to import a Monaco Editor theme, pick your JSON file and Fiddle will attempt to import it.
|
||||
</p>
|
||||
<Button
|
||||
onClick={this.createNewThemeFromCurrent}
|
||||
text="Create theme from current selection"
|
||||
icon="duplicate"
|
||||
text='Create theme from current selection'
|
||||
icon='duplicate'
|
||||
/>
|
||||
<Button
|
||||
icon="document-open"
|
||||
icon='document-open'
|
||||
onClick={this.handleAddTheme}
|
||||
text="Add a Monaco Editor theme"
|
||||
text='Add a Monaco Editor theme'
|
||||
/>
|
||||
</Callout>
|
||||
</div>
|
||||
@@ -232,4 +218,5 @@ export class AppearanceSettings extends React.Component<
|
||||
public handleAddTheme(): void {
|
||||
this.props.appState.toggleAddMonacoThemeDialog();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ export interface ConsoleSettingsProps {
|
||||
* Settings content to manage console-related preferences.
|
||||
*
|
||||
* @class ConsoleSettings
|
||||
* @extends {React.Component<ConsoleSettingsProps>}
|
||||
* @extends {React.Component<ConsoleSettingsProps, {}>}
|
||||
*/
|
||||
@observer
|
||||
export class ConsoleSettings extends React.Component<ConsoleSettingsProps> {
|
||||
export class ConsoleSettings extends React.Component<ConsoleSettingsProps, {}> {
|
||||
constructor(props: ConsoleSettingsProps) {
|
||||
super(props);
|
||||
|
||||
@@ -28,7 +28,9 @@ export class ConsoleSettings extends React.Component<ConsoleSettingsProps> {
|
||||
*
|
||||
* @param {React.ChangeEvent<HTMLInputElement>} event
|
||||
*/
|
||||
public handleClearOnRunChange(event: React.FormEvent<HTMLInputElement>) {
|
||||
public handleClearOnRunChange(
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) {
|
||||
const { checked } = event.currentTarget;
|
||||
this.props.appState.isClearingConsoleOnRun = checked;
|
||||
}
|
||||
@@ -47,7 +49,7 @@ export class ConsoleSettings extends React.Component<ConsoleSettingsProps> {
|
||||
<FormGroup label={clearOnRunLabel}>
|
||||
<Checkbox
|
||||
checked={isClearingConsoleOnRun}
|
||||
label="Clear on run."
|
||||
label='Clear on run.'
|
||||
onChange={this.handleClearOnRunChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface GitHubSettingsProps {
|
||||
* @extends {React.Component<GitHubSettingsProps, {}>}
|
||||
*/
|
||||
@observer
|
||||
export class GitHubSettings extends React.Component<GitHubSettingsProps> {
|
||||
export class GitHubSettings extends React.Component<GitHubSettingsProps, {}> {
|
||||
constructor(props: GitHubSettingsProps) {
|
||||
super(props);
|
||||
|
||||
@@ -31,10 +31,10 @@ export class GitHubSettings extends React.Component<GitHubSettingsProps> {
|
||||
return (
|
||||
<Callout>
|
||||
<p>
|
||||
Your fiddles can be published as GitHub Gists - that way you can share
|
||||
your fiddles with the world!
|
||||
Your fiddles can be published as GitHub Gists -
|
||||
that way you can share your fiddles with the world!
|
||||
</p>
|
||||
<Button onClick={this.signIn} icon="log-in" text="Sign in" />
|
||||
<Button onClick={this.signIn} icon='log-in' text='Sign in'/>
|
||||
</Callout>
|
||||
);
|
||||
}
|
||||
@@ -51,11 +51,11 @@ export class GitHubSettings extends React.Component<GitHubSettingsProps> {
|
||||
return (
|
||||
<Callout>
|
||||
<p>
|
||||
Your fiddles can be published as public GitHub Gists. Using the
|
||||
personal access token you gave us, we logged you into GitHub as{' '}
|
||||
<code>{gitHubLogin}</code>.
|
||||
Your fiddles can be published as public GitHub Gists.
|
||||
Using the personal access token you gave us, we
|
||||
logged you into GitHub as <code>{gitHubLogin}</code>.
|
||||
</p>
|
||||
<Button onClick={signOut} icon="log-out" text="Sign out" />
|
||||
<Button onClick={signOut} icon='log-out' text='Sign out'/>
|
||||
</Callout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,25 +9,21 @@ import { GitHubSettings } from './settings-general-github';
|
||||
|
||||
export interface GeneralSettingsProps {
|
||||
appState: AppState;
|
||||
toggleHasPopoverOpen: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings content to manage GitHub-related preferences.
|
||||
*
|
||||
* @class GitHubSettings
|
||||
* @extends {React.Component<GeneralSettingsProps>}
|
||||
* @extends {React.Component<GeneralSettingsProps, {}>}
|
||||
*/
|
||||
@observer
|
||||
export class GeneralSettings extends React.Component<GeneralSettingsProps> {
|
||||
export class GeneralSettings extends React.Component<GeneralSettingsProps, {}> {
|
||||
public render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>General Settings</h2>
|
||||
<AppearanceSettings
|
||||
appState={this.props.appState}
|
||||
toggleHasPopoverOpen={() => this.props.toggleHasPopoverOpen()}
|
||||
/>
|
||||
<AppearanceSettings appState={this.props.appState} />
|
||||
<Divider />
|
||||
<ConsoleSettings appState={this.props.appState} />
|
||||
<Divider />
|
||||
|
||||
@@ -12,14 +12,14 @@ enum SettingsSections {
|
||||
General = 'General',
|
||||
Electron = 'Electron',
|
||||
Execution = 'Execution',
|
||||
Credits = 'Credits',
|
||||
Credits = 'Credits'
|
||||
}
|
||||
|
||||
const settingsSections = [
|
||||
SettingsSections.General,
|
||||
SettingsSections.Electron,
|
||||
SettingsSections.Execution,
|
||||
SettingsSections.Credits,
|
||||
SettingsSections.Credits
|
||||
];
|
||||
|
||||
export interface SettingsProps {
|
||||
@@ -28,7 +28,6 @@ export interface SettingsProps {
|
||||
|
||||
export interface SettingsState {
|
||||
section: SettingsSections;
|
||||
hasPopoverOpen: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,8 +42,7 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
section: SettingsSections.General,
|
||||
hasPopoverOpen: false,
|
||||
section: SettingsSections.General
|
||||
};
|
||||
|
||||
this.closeSettingsPanel = this.closeSettingsPanel.bind(this);
|
||||
@@ -55,7 +53,7 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
window.removeEventListener('keyup', this.closeSettingsPanel, true);
|
||||
window.removeEventListener('keyup', this.closeSettingsPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,12 +67,7 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
|
||||
const { appState } = this.props;
|
||||
|
||||
if (section === SettingsSections.General) {
|
||||
return (
|
||||
<GeneralSettings
|
||||
appState={appState}
|
||||
toggleHasPopoverOpen={() => this.toggleHasPopoverOpen()}
|
||||
/>
|
||||
);
|
||||
return <GeneralSettings appState={appState} />;
|
||||
}
|
||||
|
||||
if (section === SettingsSections.Electron) {
|
||||
@@ -124,13 +117,13 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
|
||||
if (!isSettingsShowing) return null;
|
||||
|
||||
return (
|
||||
<div className="settings">
|
||||
<div className="settings-menu">
|
||||
<div className='settings'>
|
||||
<div className='settings-menu'>
|
||||
<ul>{this.renderOptions()}</ul>
|
||||
</div>
|
||||
<div className="settings-content">
|
||||
<div className="settings-close" onClick={appState.toggleSettings}>
|
||||
<Icon icon="cross" />
|
||||
<div className='settings-content'>
|
||||
<div className='settings-close' onClick={appState.toggleSettings}>
|
||||
<Icon icon='cross' />
|
||||
</div>
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
@@ -158,22 +151,11 @@ export class Settings extends React.Component<SettingsProps, SettingsState> {
|
||||
|
||||
/**
|
||||
* Trigger closing of the settings panel upon Esc
|
||||
* If hasPopoverOpen is set to true, settings will not close as only the popover should close
|
||||
*/
|
||||
private closeSettingsPanel(event: KeyboardEvent) {
|
||||
const { appState } = this.props;
|
||||
if (event.code === 'Escape' && !this.state.hasPopoverOpen) {
|
||||
if (event.code === 'Escape') {
|
||||
appState.isSettingsShowing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles whether there is a popover open
|
||||
*/
|
||||
public toggleHasPopoverOpen(): void {
|
||||
this.setState({
|
||||
...this.state,
|
||||
hasPopoverOpen: !this.state.hasPopoverOpen,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,16 @@ export interface ShowMeProps {
|
||||
* panel.
|
||||
*
|
||||
* @class ShowMe
|
||||
* @extends {React.Component<ShowMeProps>}
|
||||
* @extends {React.Component<ShowMeProps, ShowMeState>}
|
||||
*/
|
||||
@observer
|
||||
export class ShowMe extends React.Component<ShowMeProps> {
|
||||
export class ShowMe extends React.Component<ShowMeProps, {}> {
|
||||
public render() {
|
||||
const { currentDocsDemoPage: showMeName } = this.props.appState;
|
||||
const Content =
|
||||
DOCS_DEMO_COMPONENTS[showMeName] || DOCS_DEMO_COMPONENTS.DEFAULT;
|
||||
const Content = DOCS_DEMO_COMPONENTS[showMeName] || DOCS_DEMO_COMPONENTS.DEFAULT;
|
||||
|
||||
return (
|
||||
<div className="show-me-panel">
|
||||
<div className='show-me-panel'>
|
||||
<Content appState={this.props.appState} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -24,21 +24,18 @@ function ShowHide() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
This button hides Electron Fiddle right away, showing it again in two
|
||||
seconds.
|
||||
</p>
|
||||
<p>This button hides Electron Fiddle right away, showing it again in two seconds.</p>
|
||||
<Button
|
||||
id="show-hide"
|
||||
icon="eye-off"
|
||||
text="Hide Electron Fiddle"
|
||||
id='show-hide'
|
||||
icon='eye-off'
|
||||
text='Hide Electron Fiddle'
|
||||
onClick={onClick}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
export function ShowMeApp(): JSX.Element {
|
||||
const [secondsLeft, setSeconds] = React.useState(-1);
|
||||
export function ShowMeApp(_props: any): JSX.Element {
|
||||
const [ secondsLeft, setSeconds ] = React.useState(-1);
|
||||
|
||||
const playFocus = () => {
|
||||
setSeconds(3);
|
||||
@@ -50,7 +47,7 @@ export function ShowMeApp(): JSX.Element {
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const [paths, setPaths] = React.useState('');
|
||||
const [ paths, setPaths ] = React.useState('');
|
||||
const playPaths = () => {
|
||||
const pathsToQuery = [
|
||||
'home',
|
||||
@@ -58,7 +55,7 @@ export function ShowMeApp(): JSX.Element {
|
||||
'userData',
|
||||
'temp',
|
||||
'downloads',
|
||||
'desktop',
|
||||
'desktop'
|
||||
];
|
||||
|
||||
let result = '';
|
||||
@@ -69,71 +66,75 @@ export function ShowMeApp(): JSX.Element {
|
||||
setPaths(result);
|
||||
};
|
||||
|
||||
const [metrics, setMetrics] = React.useState('');
|
||||
const [ metrics, setMetrics ] = React.useState('');
|
||||
const playMetrics = () => {
|
||||
setMetrics(JSON.stringify(remote.app.getAppMetrics(), undefined, 2));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Icon
|
||||
icon="help"
|
||||
iconSize={40}
|
||||
style={{ float: 'left', margin: '0 10px 0 0' }}
|
||||
/>
|
||||
<p className="bp3r-running-text">
|
||||
The <code>app</code> module controls the app's application
|
||||
life-cycle. Most of the events and methods available on this module are
|
||||
responsible for handling how your interacts with the operating system or
|
||||
to set application-wide settings.
|
||||
<Icon icon='help' iconSize={40} style={{ float: 'left', margin: '0 10px 0 0' }} />
|
||||
<p className='bp3r-running-text'>
|
||||
The <code>app</code> module controls the app's application life-cycle. Most of the
|
||||
events and methods available on this module are responsible for handling how your
|
||||
interacts with the operating system or to set application-wide settings.
|
||||
</p>
|
||||
<h3>API Demos</h3>
|
||||
{getSubsetOnly('app')}
|
||||
<Callout title="Hiding, Showing, Focussing" icon="eye-open">
|
||||
<Callout
|
||||
title='Hiding, Showing, Focussing'
|
||||
icon='eye-open'
|
||||
>
|
||||
<p>
|
||||
The app can ask the operating system for window focus. On macOS, it
|
||||
can additionally request that the app be hidden or shown. Give it a
|
||||
try: Click on the button below, focus another app, and wait for two
|
||||
seconds to see Electron Fiddle become the focused app again.
|
||||
The app can ask the operating system for window focus. On macOS, it can additionally
|
||||
request that the app be hidden or shown. Give it a try: Click on the button below,
|
||||
focus another app, and wait for two seconds to see Electron Fiddle become the focused
|
||||
app again.
|
||||
</p>
|
||||
<Button
|
||||
id="focus"
|
||||
icon="lightbulb"
|
||||
text={`Focus Electron Fiddle${
|
||||
secondsLeft > 0 ? ` in ${secondsLeft}s` : ''
|
||||
}`}
|
||||
id='focus'
|
||||
icon='lightbulb'
|
||||
text={`Focus Electron Fiddle${secondsLeft > 0 ? ` in ${secondsLeft}s` : ''}`}
|
||||
onClick={playFocus}
|
||||
/>
|
||||
<ShowHide />
|
||||
</Callout>
|
||||
<Callout title="Paths" icon="folder-open">
|
||||
<Callout
|
||||
title='Paths'
|
||||
icon='folder-open'
|
||||
>
|
||||
<p>
|
||||
Need to query information about various paths in a cross-platform
|
||||
manner? Electron can help. The button queries the operating system for
|
||||
some of them.
|
||||
Need to query information about various paths in a cross-platform manner? Electron
|
||||
can help. The button queries the operating system for some of them.
|
||||
</p>
|
||||
<Button
|
||||
id="special-paths"
|
||||
icon="play"
|
||||
id='special-paths'
|
||||
icon='play'
|
||||
text={`Get special directory paths`}
|
||||
onClick={playPaths}
|
||||
/>
|
||||
<pre id="special-paths-content">{paths}</pre>
|
||||
<pre id='special-paths-content'>
|
||||
{paths}
|
||||
</pre>
|
||||
</Callout>
|
||||
<Callout title="Process & Device Information" icon="pulse">
|
||||
<Callout
|
||||
title='Process & Device Information'
|
||||
icon='pulse'
|
||||
>
|
||||
<p>
|
||||
Need to query information about the process or system? The{' '}
|
||||
<code>app</code> module lets developers query for information about
|
||||
the running app, hardware, and operating system. A good example are
|
||||
process metrics.
|
||||
Need to query information about the process or system? The <code>app</code> module lets
|
||||
developers query for information about the running app, hardware, and operating system.
|
||||
A good example are process metrics.
|
||||
</p>
|
||||
<Button
|
||||
id="process-metrics"
|
||||
icon="play"
|
||||
id='process-metrics'
|
||||
icon='play'
|
||||
text={`Get process metrics`}
|
||||
onClick={playMetrics}
|
||||
/>
|
||||
<pre id="process-metrics-content">{metrics}</pre>
|
||||
<pre id='process-metrics-content'>
|
||||
{metrics}
|
||||
</pre>
|
||||
</Callout>
|
||||
{renderMoreDocumentation()}
|
||||
</>
|
||||
|
||||
@@ -14,33 +14,29 @@ export function ShowMeDefault(props: { appState: AppState }): JSX.Element {
|
||||
<li key={key}>
|
||||
<Button
|
||||
minimal={true}
|
||||
icon="play"
|
||||
icon='play'
|
||||
text={DOCS_DEMO_NAMES[key]}
|
||||
onClick={() =>
|
||||
(props.appState.currentDocsDemoPage = key as DocsDemoPage)
|
||||
}
|
||||
onClick={() => (props.appState.currentDocsDemoPage = key as DocsDemoPage)}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Icon
|
||||
icon="help"
|
||||
iconSize={40}
|
||||
style={{ float: 'left', margin: '0 10px 0 0' }}
|
||||
/>
|
||||
<p className="bp3r-running-text">
|
||||
This panel offers useful information about Electron APIs – and easy way
|
||||
to try some of the methods it offers. Fiddle has an example fiddle for
|
||||
every module available in Electron.
|
||||
<Icon icon='help' iconSize={40} style={{ float: 'left', margin: '0 10px 0 0' }} />
|
||||
<p className='bp3r-running-text'>
|
||||
This panel offers useful information about Electron APIs – and
|
||||
easy way to try some of the methods it offers. Fiddle has an example fiddle
|
||||
for every module available in Electron.
|
||||
</p>
|
||||
<p>
|
||||
Clicking on any of the modules below will open up a fiddle showcasing
|
||||
that particular module.
|
||||
</p>
|
||||
<ul className="show-me-list">{...showMeMenu}</ul>
|
||||
<ul className='show-me-list'>
|
||||
{...showMeMenu}
|
||||
</ul>
|
||||
{renderMoreDocumentation()}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -5,10 +5,10 @@ import { ShowMeDefault } from './default';
|
||||
|
||||
export const DOCS_DEMO_COMPONENTS: Record<DocsDemoPage, any> = {
|
||||
DEFAULT: ShowMeDefault,
|
||||
DEMO_APP: ShowMeApp,
|
||||
DEMO_APP: ShowMeApp
|
||||
};
|
||||
|
||||
export const DOCS_DEMO_NAMES: Record<DocsDemoPage, string> = {
|
||||
DEFAULT: 'Home',
|
||||
DEMO_APP: 'App Demos',
|
||||
DEMO_APP: 'App Demos'
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import { shell } from 'electron';
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function renderMoreDocumentation(
|
||||
url = 'electronjs.org/docs',
|
||||
url: string = 'electronjs.org/docs'
|
||||
): JSX.Element {
|
||||
const fullUrl = `https://${url}`;
|
||||
const gitHubUrl = `https://github.com/electron/fiddle`;
|
||||
@@ -18,21 +18,14 @@ export function renderMoreDocumentation(
|
||||
return (
|
||||
<>
|
||||
<br />
|
||||
<p className="b3-running-text">
|
||||
For more documentation, visit{' '}
|
||||
<a id="open-url" onClick={() => shell.openExternal(fullUrl)}>
|
||||
{url}
|
||||
</a>
|
||||
, where you can find the full documentation for Electron.
|
||||
<p className='b3-running-text'>
|
||||
For more documentation, visit <a id='open-url' onClick={() => shell.openExternal(fullUrl)}>{url}</a>, where
|
||||
you can find the full documentation for Electron.
|
||||
</p>
|
||||
<p className="bp3-text-muted">
|
||||
By the way, Electron Fiddle and the documentation you see here is
|
||||
entirely open source. If you have ideas on how to improve it, we'd
|
||||
love to have your contributions! You can find the repository on{' '}
|
||||
<a id="open-github" onClick={() => shell.openExternal(gitHubUrl)}>
|
||||
GitHub
|
||||
</a>
|
||||
.
|
||||
<p className='bp3-text-muted'>
|
||||
By the way, Electron Fiddle and the documentation you see here is entirely open source. If you
|
||||
have ideas on how to improve it, we'd love to have your contributions! You can find the
|
||||
repository on <a id='open-github' onClick={() => shell.openExternal(gitHubUrl)}>GitHub</a>.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -17,15 +17,10 @@ export function getSubsetOnly(moduleName: string): JSX.Element {
|
||||
const { full, short } = getDocsUrlForModule(moduleName);
|
||||
|
||||
return (
|
||||
<p className="bp3-running-text">
|
||||
The following demos display only a subset of what the{' '}
|
||||
<code>{moduleName}</code>
|
||||
<p className='bp3-running-text'>
|
||||
The following demos display only a subset of what the <code>{moduleName}</code>
|
||||
module is capable of. If you want to see its full abilities, check out the
|
||||
documentation on{' '}
|
||||
<a id="open-url" onClick={() => shell.openExternal(full)}>
|
||||
{short}
|
||||
</a>
|
||||
.
|
||||
documentation on <a id='open-url' onClick={() => shell.openExternal(full)}>{short}</a>.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,17 +28,17 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
content: (
|
||||
<>
|
||||
<p>
|
||||
Electron Fiddle allows you to build little experiments and mini-apps
|
||||
with Electron. Each Fiddle has at least three of these files: A main
|
||||
script, a renderer script, a preload script, and an HTML file.
|
||||
Electron Fiddle allows you to build little experiments and mini-apps with
|
||||
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 it
|
||||
automatically. It will also automatically provide you with
|
||||
autocomplete information for the <code>electron</code> module.
|
||||
If you <code>require()</code> a module, Fiddle will install
|
||||
it automatically. It will also automatically provide you with autocomplete
|
||||
information for the <code>electron</code> module.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'select-versions',
|
||||
@@ -47,21 +47,25 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
content: (
|
||||
<>
|
||||
<p>
|
||||
Electron Fiddle knows about all released Electron versions,
|
||||
downloading your versions automatically in the background.
|
||||
Electron Fiddle knows about all released Electron versions, downloading
|
||||
your versions automatically in the background.
|
||||
</p>
|
||||
<p>
|
||||
Open the preferences to see all available versions and delete those
|
||||
previously downloaded.
|
||||
Open the preferences to see all available versions and delete those previously
|
||||
downloaded.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'button-run',
|
||||
selector: '.button-run',
|
||||
title: '🚀 Run Your Fiddle',
|
||||
content: <p>Hit this button to give your Fiddle a try and start it.</p>,
|
||||
content: (
|
||||
<p>
|
||||
Hit this button to give your Fiddle a try and start it.
|
||||
</p>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'button-publish',
|
||||
@@ -70,17 +74,17 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
content: (
|
||||
<>
|
||||
<p>
|
||||
Like what you've built? You can save your Fiddle as a public
|
||||
GitHub Gist, allowing other users to load it by pasting the URL into
|
||||
the address bar. If they don't have Electron Fiddle, they can
|
||||
see and download your code directly from GitHub.
|
||||
Like what you've built? You can save your Fiddle as a public GitHub Gist,
|
||||
allowing other users to load it by pasting the URL into the address bar.
|
||||
If they don't have Electron Fiddle, they can see and download your code
|
||||
directly from GitHub.
|
||||
</p>
|
||||
<p>
|
||||
You can also package your Fiddle as a standalone binary or as an
|
||||
installer from the "Tasks" menu.
|
||||
You can also package your Fiddle as a standalone binary or as an installer
|
||||
from the "Tasks" menu.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'first-time-electron',
|
||||
@@ -88,25 +92,17 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
title: '👋 Getting Started With Electron?',
|
||||
content: (
|
||||
<p>
|
||||
We've finished our tour of Electron Fiddle, but if this is your
|
||||
We've finished our tour of Electron Fiddle, but if this is your
|
||||
first time using Electron, we could introduce you to its basics.
|
||||
Interested?
|
||||
</p>
|
||||
),
|
||||
getButtons: ({
|
||||
stop,
|
||||
advance,
|
||||
}: TourStepGetButtonParams): Array<JSX.Element> => {
|
||||
getButtons: ({ stop, advance }: TourStepGetButtonParams): Array<JSX.Element> => {
|
||||
return [
|
||||
<Button key="btn-stop" onClick={stop} text="I'm good!" icon="stop" />,
|
||||
<Button
|
||||
key="btn-adv"
|
||||
onClick={advance}
|
||||
text="Electron Basics"
|
||||
icon="help"
|
||||
/>,
|
||||
<Button key='btn-stop' onClick={stop} text="I'm good!" icon='stop' />,
|
||||
<Button key='btn-adv' onClick={advance} text='Electron Basics' icon='help' />
|
||||
];
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'main-editor',
|
||||
@@ -115,24 +111,24 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
content: (
|
||||
<>
|
||||
<p>
|
||||
Every Electron app starts with a main script, very similar to how a
|
||||
Node.js application is started. The main script runs in the
|
||||
"main process". To display a user interface, the main
|
||||
process creates renderer processes – usually in the form of windows,
|
||||
which Electron calls <code>BrowserWindow</code>.
|
||||
Every Electron app starts with a main script, very similar to how
|
||||
a Node.js application is started. The main script runs in the "main
|
||||
process". To display a user interface, the main process creates renderer
|
||||
processes – usually in the form of windows, which Electron calls
|
||||
<code>BrowserWindow</code>.
|
||||
</p>
|
||||
<p>
|
||||
To get started, pretend that the main process is just like a Node.js
|
||||
process. All APIs and features found in Electron are accessible
|
||||
through the <code>electron</code> module, which can be required like
|
||||
any other Node.js module.
|
||||
process. All APIs and features found in Electron are accessible through
|
||||
the <code>electron</code> module, which can be required like any other
|
||||
Node.js module.
|
||||
</p>
|
||||
<p>
|
||||
The default fiddle creates a new <code>BrowserWindow</code> and
|
||||
loads an HTML file.
|
||||
The default fiddle creates a new <code>BrowserWindow</code> and loads
|
||||
an HTML file.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'html-editor',
|
||||
@@ -140,14 +136,14 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
title: '📝 HTML',
|
||||
content: (
|
||||
<p>
|
||||
In the default fiddle, this HTML file is loaded in the
|
||||
<code>BrowserWindow</code>. Any HTML, CSS, or JavaScript that works in
|
||||
a browser will work here, too. In addition, Electron allows you to
|
||||
execute Node.js code. Take a close look at the
|
||||
<code><script /></code> tag and notice how we can call{' '}
|
||||
<code>require()</code> like we would in Node.js.
|
||||
In the default fiddle, this HTML file is loaded in the
|
||||
<code>BrowserWindow</code>. Any HTML, CSS, or JavaScript that works
|
||||
in a browser will work here, too. In addition, Electron allows you
|
||||
to execute Node.js code. Take a close look at the
|
||||
<code><script /></code> tag and notice how we can call <code>
|
||||
require()</code> like we would in Node.js.
|
||||
</p>
|
||||
),
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'renderer-editor',
|
||||
@@ -156,19 +152,17 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
content: (
|
||||
<>
|
||||
<p>
|
||||
This is the script we just required from the HTML file. In here, you
|
||||
can do anything that works in Node.js <i>and</i> anything that works
|
||||
in a browser.
|
||||
This is the script we just required from the HTML file. In here, you can
|
||||
do anything that works in Node.js <i>and</i> anything that works in a browser.
|
||||
</p>
|
||||
<p>
|
||||
By the way: If you want to use an <code>npm</code> module here, just
|
||||
<code>require</code> it. Electron Fiddle will automatically
|
||||
detect that you requested a module and install it as soon as you run
|
||||
your fiddle.
|
||||
<code>require</code> it. Electron Fiddle will automatically detect that you
|
||||
requested a module and install it as soon as you run your fiddle.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -180,10 +174,7 @@ export function getWelcomeTour(): Set<TourScriptStep> {
|
||||
* @extends {React.Component<WelcomeTourProps, WelcomeTourState>}
|
||||
*/
|
||||
@observer
|
||||
export class WelcomeTour extends React.Component<
|
||||
WelcomeTourProps,
|
||||
WelcomeTourState
|
||||
> {
|
||||
export class WelcomeTour extends React.Component<WelcomeTourProps, WelcomeTourState> {
|
||||
constructor(props: WelcomeTourProps) {
|
||||
super(props);
|
||||
|
||||
@@ -191,7 +182,7 @@ export class WelcomeTour extends React.Component<
|
||||
this.startTour = this.startTour.bind(this);
|
||||
|
||||
this.state = {
|
||||
isTourStarted: false,
|
||||
isTourStarted: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -213,16 +204,16 @@ export class WelcomeTour extends React.Component<
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
key="cancel"
|
||||
key='cancel'
|
||||
onClick={this.stopTour}
|
||||
icon="cross"
|
||||
icon='cross'
|
||||
text={`I'll figure it out`}
|
||||
/>
|
||||
<Button
|
||||
key="ok"
|
||||
key='ok'
|
||||
onClick={this.startTour}
|
||||
icon="presentation"
|
||||
text="Show me around"
|
||||
icon='presentation'
|
||||
text='Show me around'
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -236,27 +227,34 @@ export class WelcomeTour extends React.Component<
|
||||
|
||||
if (!isTourStarted) {
|
||||
return (
|
||||
<Dialog key="welcome-tour-dialog" isOpen={true}>
|
||||
<Dialog
|
||||
key='welcome-tour-dialog'
|
||||
isOpen={true}
|
||||
>
|
||||
<div className={Classes.DIALOG_HEADER}>
|
||||
<h4 className={Classes.HEADING}>🙋 Hey There!</h4>
|
||||
</div>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<p>
|
||||
Welcome to Electron Fiddle! If you're new to the app,
|
||||
we'd like to give you a brief tour of its features.
|
||||
Welcome to Electron Fiddle! If you're new to the app,
|
||||
we'd like to give you a brief tour of its features.
|
||||
</p>
|
||||
<p>
|
||||
We won't show this dialog again, but you can always find the
|
||||
tour in the Help menu.
|
||||
We won't show this dialog again, but you can always
|
||||
find the tour in the Help menu.
|
||||
</p>
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>{this.buttons}</div>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
{this.buttons}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
} else {
|
||||
return <Tour tour={getWelcomeTour()} onStop={this.stopTour} />;
|
||||
return (
|
||||
<Tour tour={getWelcomeTour()} onStop={this.stopTour} />
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export class Tour extends React.Component<TourProps, TourState> {
|
||||
this.state = {
|
||||
tour: props.tour.entries(),
|
||||
step: null,
|
||||
i: 0,
|
||||
i: 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,7 +92,11 @@ export class Tour extends React.Component<TourProps, TourState> {
|
||||
|
||||
if (!step) return null;
|
||||
|
||||
return <div className="tour">{this.getStep(step)}</div>;
|
||||
return (
|
||||
<div className='tour'>
|
||||
{this.getStep(step)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,34 +112,36 @@ export class Tour extends React.Component<TourProps, TourState> {
|
||||
if (getButtons) {
|
||||
return getButtons({
|
||||
stop: this.stop,
|
||||
advance: this.advance,
|
||||
advance: this.advance
|
||||
});
|
||||
}
|
||||
|
||||
// No? Fine! Are we at the end of the tour?
|
||||
return this.props.tour.size === this.state.i
|
||||
? [
|
||||
? [(
|
||||
<Button
|
||||
icon='tick-circle'
|
||||
onClick={this.stop}
|
||||
key='btn-stop'
|
||||
text='Finish Tour'
|
||||
/>
|
||||
)] : [
|
||||
(
|
||||
<Button
|
||||
icon="tick-circle"
|
||||
icon='stop'
|
||||
key='btn-stop'
|
||||
onClick={this.stop}
|
||||
key="btn-stop"
|
||||
text="Finish Tour"
|
||||
/>,
|
||||
]
|
||||
: [
|
||||
text='Stop Tour'
|
||||
/>
|
||||
), (
|
||||
<Button
|
||||
icon="stop"
|
||||
key="btn-stop"
|
||||
onClick={this.stop}
|
||||
text="Stop Tour"
|
||||
/>,
|
||||
<Button
|
||||
icon="step-forward"
|
||||
key="btn-adv"
|
||||
icon='step-forward'
|
||||
key='btn-adv'
|
||||
onClick={this.advance}
|
||||
text="Continue"
|
||||
/>,
|
||||
];
|
||||
text='Continue'
|
||||
/>
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,10 +151,7 @@ export class Tour extends React.Component<TourProps, TourState> {
|
||||
* @param {ClientRect} rect
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
private getDialogForStep(
|
||||
step: TourScriptStep,
|
||||
rect: ClientRect,
|
||||
): JSX.Element {
|
||||
private getDialogForStep(step: TourScriptStep, rect: ClientRect): JSX.Element {
|
||||
const buttons = this.getButtons(step);
|
||||
const size = { width: 400, height: 300 };
|
||||
const margin = 10;
|
||||
@@ -167,15 +170,21 @@ export class Tour extends React.Component<TourProps, TourState> {
|
||||
key={step.name}
|
||||
isOpen={true}
|
||||
style={style}
|
||||
portalClassName="tour-portal"
|
||||
backdropProps={{ style: { visibility: 'hidden' } }}
|
||||
portalClassName='tour-portal'
|
||||
backdropProps={{ style: { visibility: 'hidden' }}}
|
||||
>
|
||||
<div className={Classes.DIALOG_HEADER}>
|
||||
<h4 className={Classes.HEADING}>{step.title}</h4>
|
||||
<h4 className={Classes.HEADING}>
|
||||
{step.title}
|
||||
</h4>
|
||||
</div>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
{step.content}
|
||||
</div>
|
||||
<div className={Classes.DIALOG_BODY}>{step.content}</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>{buttons}</div>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -196,38 +205,20 @@ export class Tour extends React.Component<TourProps, TourState> {
|
||||
return (
|
||||
<>
|
||||
{this.getDialogForStep(step, rect)}
|
||||
<svg height="100%" width="100%">
|
||||
<rect
|
||||
fill="rgba(0, 0, 0, 0.65)"
|
||||
x="0"
|
||||
y="0"
|
||||
mask="url(#mask)"
|
||||
height="100%"
|
||||
width="100%"
|
||||
/>
|
||||
<mask
|
||||
id="mask"
|
||||
maskUnits="userSpaceOnUse"
|
||||
maskContentUnits="userSpaceOnUse"
|
||||
>
|
||||
<rect
|
||||
className="bg"
|
||||
x="0"
|
||||
y="0"
|
||||
fill="white"
|
||||
height="100%"
|
||||
width="100%"
|
||||
/>
|
||||
<svg height='100%' width='100%'>
|
||||
<rect fill='rgba(0, 0, 0, 0.65)' x='0' y='0' mask='url(#mask)' height='100%' width='100%'/>
|
||||
<mask id='mask' maskUnits='userSpaceOnUse' maskContentUnits='userSpaceOnUse'>
|
||||
<rect className='bg' x='0' y='0' fill='white' height='100%' width='100%' />
|
||||
<rect
|
||||
x={left}
|
||||
y={top}
|
||||
width={width}
|
||||
height={height}
|
||||
fill="black"
|
||||
rx="5"
|
||||
ry="5"
|
||||
strokeWidth="5"
|
||||
stroke="black"
|
||||
fill='black'
|
||||
rx='5'
|
||||
ry='5'
|
||||
strokeWidth='5'
|
||||
stroke='black'
|
||||
/>
|
||||
</mask>
|
||||
</svg>
|
||||
|
||||
@@ -44,9 +44,7 @@ export function getItemLabel({ source, state, name }: RunnableVersion): string {
|
||||
export function getItemIcon({ state }: RunnableVersion) {
|
||||
return state === 'ready'
|
||||
? 'saved'
|
||||
: state === 'downloading'
|
||||
? 'cloud-download'
|
||||
: 'cloud';
|
||||
: state === 'downloading' ? 'cloud-download' : 'cloud';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,10 +55,7 @@ export function getItemIcon({ state }: RunnableVersion) {
|
||||
* @param {RunnableVersion} { version }
|
||||
* @returns
|
||||
*/
|
||||
export const filterItem: ItemPredicate<RunnableVersion> = (
|
||||
query,
|
||||
{ version },
|
||||
) => {
|
||||
export const filterItem: ItemPredicate<RunnableVersion> = (query, { version }) => {
|
||||
return version.toLowerCase().includes(query.toLowerCase());
|
||||
};
|
||||
|
||||
@@ -72,10 +67,7 @@ export const filterItem: ItemPredicate<RunnableVersion> = (
|
||||
* @param {IItemRendererProps} { handleClick, modifiers, query }
|
||||
* @returns
|
||||
*/
|
||||
export const renderItem: ItemRenderer<RunnableVersion> = (
|
||||
item,
|
||||
{ handleClick, modifiers, query },
|
||||
) => {
|
||||
export const renderItem: ItemRenderer<RunnableVersion> = (item, { handleClick, modifiers, query }) => {
|
||||
if (!modifiers.matchesPredicate) {
|
||||
return null;
|
||||
}
|
||||
@@ -103,9 +95,7 @@ export interface VersionSelectProps {
|
||||
currentVersion: RunnableVersion;
|
||||
onVersionSelect: (version: RunnableVersion) => void;
|
||||
buttonGroupProps?: IButtonGroupProps;
|
||||
itemDisabled?:
|
||||
| keyof RunnableVersion
|
||||
| ((item: RunnableVersion, index: number) => boolean);
|
||||
itemDisabled?: keyof RunnableVersion | ((item: RunnableVersion, index: number) => boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,10 +106,7 @@ export interface VersionSelectProps {
|
||||
* @extends {React.Component<VersionSelectProps, VersionSelectState>}
|
||||
*/
|
||||
@observer
|
||||
export class VersionSelect extends React.Component<
|
||||
VersionSelectProps,
|
||||
VersionSelectState
|
||||
> {
|
||||
export class VersionSelect extends React.Component<VersionSelectProps, VersionSelectState> {
|
||||
public render() {
|
||||
const { currentVersion, itemDisabled } = this.props;
|
||||
const { version } = currentVersion;
|
||||
@@ -132,11 +119,11 @@ export class VersionSelect extends React.Component<
|
||||
itemPredicate={filterItem}
|
||||
itemDisabled={itemDisabled}
|
||||
onItemSelect={this.props.onVersionSelect}
|
||||
noResults={<MenuItem disabled={true} text="No results." />}
|
||||
noResults={<MenuItem disabled={true} text='No results.' />}
|
||||
disabled={!!this.props.disabled}
|
||||
>
|
||||
<Button
|
||||
className="version-chooser"
|
||||
className='version-chooser'
|
||||
text={`Electron v${version}`}
|
||||
icon={getItemIcon(currentVersion)}
|
||||
disabled={!!this.props.disabled}
|
||||
|
||||
@@ -8,10 +8,7 @@ import { EditorBackup, getEditorBackup } from '../utils/editor-backup';
|
||||
// Reminder: When testing, this file is mocked in tests/setup.js
|
||||
|
||||
export const USER_DATA_PATH = remote.app.getPath('userData');
|
||||
export const CONFIG_PATH = path.join(
|
||||
remote.app.getPath('home'),
|
||||
'.electron-fiddle',
|
||||
);
|
||||
export const CONFIG_PATH = path.join(remote.app.getPath('home'), '.electron-fiddle');
|
||||
|
||||
export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
|
||||
direction: 'row',
|
||||
@@ -20,13 +17,10 @@ export const DEFAULT_MOSAIC_ARRANGEMENT: MosaicNode<MosaicId> = {
|
||||
direction: 'column',
|
||||
first: EditorId.renderer,
|
||||
second: EditorId.html,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export const DEFAULT_CLOSED_PANELS: Partial<Record<
|
||||
MosaicId,
|
||||
EditorBackup | true
|
||||
>> = {
|
||||
export const DEFAULT_CLOSED_PANELS: Partial<Record<MosaicId, EditorBackup | true>> = {
|
||||
docsDemo: true,
|
||||
preload: getEditorBackup(EditorId.preload),
|
||||
css: getEditorBackup(EditorId.css),
|
||||
@@ -35,7 +29,7 @@ export const DEFAULT_CLOSED_PANELS: Partial<Record<
|
||||
export const EMPTY_EDITOR_CONTENT = {
|
||||
html: '<!-- Empty -->',
|
||||
js: '// Empty',
|
||||
css: '/* Empty */',
|
||||
css: '/* Empty */'
|
||||
};
|
||||
|
||||
export const ELECTRON_ORG = 'electron';
|
||||
|
||||
@@ -42,18 +42,15 @@ export async function getContent(
|
||||
export async function isContentUnchanged(name: EditorId): Promise<boolean> {
|
||||
if (!window.ElectronFiddle || !window.ElectronFiddle.app) return false;
|
||||
|
||||
const values = await window.ElectronFiddle.app.getEditorValues({
|
||||
include: false,
|
||||
});
|
||||
const values = await window.ElectronFiddle.app.getEditorValues({ include: false });
|
||||
|
||||
// Handle main case, which needs to check both possible versions
|
||||
if (name === EditorId.main) {
|
||||
const isChanged1x =
|
||||
(await getContent(EditorId.main, '1.0')) === values.main;
|
||||
const isChangedOther = (await getContent(EditorId.main)) === values.main;
|
||||
const isChanged1x = await getContent(EditorId.main, '1.0') === values.main;
|
||||
const isChangedOther = await getContent(EditorId.main) === values.main;
|
||||
|
||||
return isChanged1x || isChangedOther;
|
||||
} else {
|
||||
return values[name] === (await getContent(name));
|
||||
return values[name] === await getContent(name);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-28
@@ -56,6 +56,7 @@ export async function removeTypeDefsForVersion(version: string) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the path for offline TypeScript definitions
|
||||
*
|
||||
@@ -72,9 +73,7 @@ export function getOfflineTypeDefinitionPath(version: string): string {
|
||||
* @param {string} version
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export async function getOfflineTypeDefinitions(
|
||||
version: string,
|
||||
): Promise<boolean> {
|
||||
export async function getOfflineTypeDefinitions(version: string): Promise<boolean> {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
return fs.existsSync(getOfflineTypeDefinitionPath(version));
|
||||
}
|
||||
@@ -86,12 +85,10 @@ export async function getOfflineTypeDefinitions(
|
||||
* @param {string} version
|
||||
* @returns {void}
|
||||
*/
|
||||
export async function getDownloadedVersionTypeDefs(
|
||||
version: RunnableVersion,
|
||||
): 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);
|
||||
await fs.mkdirp(definitionPath);
|
||||
const offlinePath = getOfflineTypeDefinitionPath(version.version);
|
||||
|
||||
if (await getOfflineTypeDefinitions(version.version)) {
|
||||
try {
|
||||
@@ -131,10 +128,7 @@ export async function getLocalVersionTypeDefs(version: RunnableVersion) {
|
||||
*
|
||||
* @param {string} version
|
||||
*/
|
||||
export async function updateEditorTypeDefinitions(
|
||||
version: RunnableVersion,
|
||||
i = 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`);
|
||||
@@ -142,23 +136,18 @@ export async function updateEditorTypeDefinitions(
|
||||
}
|
||||
|
||||
console.warn(`Fetch Types: Called too soon, deferring`);
|
||||
return callIn(i * 100 + 200, () =>
|
||||
updateEditorTypeDefinitions(version, i + 1),
|
||||
);
|
||||
return callIn(i * 100 + 200, () => updateEditorTypeDefinitions(version, i + 1));
|
||||
};
|
||||
|
||||
// If this method is called before we're ready, we'll delay this work a bit
|
||||
if (!window.ElectronFiddle.app || !window.ElectronFiddle.app.monaco)
|
||||
return defer();
|
||||
if (!window.ElectronFiddle.app || !window.ElectronFiddle.app.monaco) return defer();
|
||||
|
||||
const { app } = window.ElectronFiddle;
|
||||
const monaco: typeof MonacoType = app.monaco!;
|
||||
const typeDefDisposable: MonacoType.IDisposable = app.typeDefDisposable!;
|
||||
|
||||
const getTypeDefs =
|
||||
version.source === VersionSource.local
|
||||
? getLocalVersionTypeDefs
|
||||
: getDownloadedVersionTypeDefs;
|
||||
const getTypeDefs = (version.source === VersionSource.local) ?
|
||||
getLocalVersionTypeDefs : getDownloadedVersionTypeDefs;
|
||||
|
||||
const typeDefs = await getTypeDefs(version);
|
||||
|
||||
@@ -167,12 +156,8 @@ export async function updateEditorTypeDefinitions(
|
||||
}
|
||||
|
||||
if (typeDefs) {
|
||||
console.log(
|
||||
`Fetch Types: Updating Monaco types with electron.d.ts@${version.version}`,
|
||||
);
|
||||
const disposable = monaco.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
typeDefs,
|
||||
);
|
||||
console.log(`Fetch Types: Updating Monaco types with electron.d.ts@${version.version}`);
|
||||
const disposable = monaco.languages.typescript.javascriptDefaults.addExtraLib(typeDefs);
|
||||
window.ElectronFiddle.app.typeDefDisposable = disposable;
|
||||
} else {
|
||||
console.log(`Fetch Types: No type definitions for ${version.version} 😢`);
|
||||
@@ -187,7 +172,7 @@ export function getLocalTypePathForVersion(version: RunnableVersion) {
|
||||
'electron',
|
||||
'tsc',
|
||||
'typings',
|
||||
'electron.d.ts',
|
||||
'electron.d.ts'
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -3,14 +3,7 @@ import * as path from 'path';
|
||||
|
||||
import { EditorValues, Files, FileTransform } from '../interfaces';
|
||||
import { IpcEvents } from '../ipc-events';
|
||||
import {
|
||||
INDEX_HTML_NAME,
|
||||
MAIN_JS_NAME,
|
||||
PACKAGE_NAME,
|
||||
PRELOAD_JS_NAME,
|
||||
RENDERER_JS_NAME,
|
||||
STYLES_CSS_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';
|
||||
@@ -41,12 +34,9 @@ export class FileManager {
|
||||
this.saveFiddle(filePath, dotfilesTransform);
|
||||
});
|
||||
|
||||
ipcRendererManager.on(
|
||||
IpcEvents.FS_SAVE_FIDDLE_FORGE,
|
||||
(_event, filePath) => {
|
||||
this.saveFiddle(filePath, dotfilesTransform, forgeTransform);
|
||||
},
|
||||
);
|
||||
ipcRendererManager.on(IpcEvents.FS_SAVE_FIDDLE_FORGE, (_event, filePath) => {
|
||||
this.saveFiddle(filePath, dotfilesTransform, forgeTransform);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,9 +47,7 @@ export class FileManager {
|
||||
*/
|
||||
public async openTemplate(templateName: string) {
|
||||
const editorValues = await getTemplateValues(templateName);
|
||||
await window.ElectronFiddle.app.replaceFiddle(editorValues, {
|
||||
templateName,
|
||||
});
|
||||
await window.ElectronFiddle.app.replaceFiddle(editorValues, {templateName});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,10 +68,11 @@ 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)),
|
||||
css: await this.readFile(path.join(filePath, STYLES_CSS_NAME))
|
||||
};
|
||||
|
||||
window.ElectronFiddle.app.replaceFiddle(editorValues, { filePath });
|
||||
|
||||
window.ElectronFiddle.app.replaceFiddle(editorValues, {filePath});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,10 +82,7 @@ export class FileManager {
|
||||
* @param {string} filePath
|
||||
* @memberof FileManager
|
||||
*/
|
||||
public async saveFiddle(
|
||||
filePath?: string,
|
||||
...transforms: Array<FileTransform>
|
||||
) {
|
||||
public async saveFiddle(filePath?: string, ...transforms: Array<FileTransform>) {
|
||||
const { localPath } = this.appState;
|
||||
const pathToSave = filePath || localPath;
|
||||
|
||||
@@ -121,13 +107,13 @@ export class FileManager {
|
||||
|
||||
if (pathToSave !== localPath) {
|
||||
this.appState.localPath = pathToSave;
|
||||
this.appState.gistId = undefined;
|
||||
}
|
||||
|
||||
this.appState.isUnsaved = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get files to save, but with a transform applied
|
||||
*
|
||||
@@ -136,10 +122,7 @@ export class FileManager {
|
||||
* @returns {Promise<Files>}
|
||||
* @memberof FileManager
|
||||
*/
|
||||
public async getFiles(
|
||||
options?: PackageJsonOptions,
|
||||
...transforms: Array<FileTransform>
|
||||
): Promise<Files> {
|
||||
public async getFiles(options?: PackageJsonOptions, ...transforms: Array<FileTransform>): Promise<Files> {
|
||||
const pOptions = typeof options === 'object' ? options : DEFAULT_OPTIONS;
|
||||
const values = await window.ElectronFiddle.app.getEditorValues(pOptions);
|
||||
let output: Files = new Map();
|
||||
@@ -156,16 +139,14 @@ export class FileManager {
|
||||
console.log(`getFiles: Applying ${transform.name}`);
|
||||
output = await transform(output);
|
||||
} catch (error) {
|
||||
console.warn(`getFiles: Failed to apply transform`, {
|
||||
transform,
|
||||
error,
|
||||
});
|
||||
console.warn(`getFiles: Failed to apply transform`, { transform, error });
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to clean a given directory. Used to manually
|
||||
* clean temp directories.
|
||||
@@ -198,8 +179,7 @@ export class FileManager {
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public async saveToTemp(
|
||||
options: PackageJsonOptions,
|
||||
...transforms: Array<FileTransform>
|
||||
options: PackageJsonOptions, ...transforms: Array<FileTransform>
|
||||
): Promise<string> {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const tmp = await import('tmp');
|
||||
|
||||
+37
-60
@@ -1,70 +1,53 @@
|
||||
import { EditorValues } from '../interfaces';
|
||||
import { exec } from '../utils/exec';
|
||||
|
||||
const { builtinModules } = require('module');
|
||||
const { builtinModules } = require('module');
|
||||
|
||||
export type IPackageManager = 'npm' | 'yarn';
|
||||
|
||||
export interface PMOperationOptions {
|
||||
export interface NpmOperationOptions {
|
||||
dir: string;
|
||||
packageManager: IPackageManager;
|
||||
}
|
||||
|
||||
export let isNpmInstalled: boolean | null = null;
|
||||
export let isYarnInstalled: boolean | null = null;
|
||||
export let isInstalled: boolean | null = null;
|
||||
|
||||
/* add other modules to automatically ignore here */
|
||||
/* perhaps we can expose this to the settings module?*/
|
||||
const ignoredModules: Array<string> = [
|
||||
'electron',
|
||||
'original-fs',
|
||||
...builtinModules,
|
||||
...builtinModules
|
||||
];
|
||||
|
||||
/* regular expression to both match and extract module names */
|
||||
const requiregx = /^.*require\(['"](.*?)['"]\)/gm;
|
||||
|
||||
|
||||
/*
|
||||
Quick and dirty filter functions for filtering module names
|
||||
*/
|
||||
const isIgnored = (str: string): boolean => ignoredModules.includes(str);
|
||||
const isLocalModule = (str: string): boolean => /^[,/~\.]/.test(str);
|
||||
const isLocalModule = (str: string): boolean => (/^[,/~\.]/.test(str));
|
||||
const isUnique = (item: any, idx: number, arr: Array<any>): boolean => {
|
||||
return arr.lastIndexOf(item) === idx;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if package manager is installed by checking if a binary
|
||||
* Checks if npm is installed by checking if a binary
|
||||
* with that name can be found.
|
||||
*/
|
||||
export async function getIsPackageManagerInstalled(
|
||||
packageManager: IPackageManager,
|
||||
ignoreCache?: boolean,
|
||||
): Promise<boolean> {
|
||||
if (packageManager === 'npm' && isNpmInstalled !== null && !ignoreCache)
|
||||
return isNpmInstalled;
|
||||
if (packageManager === 'yarn' && isYarnInstalled !== null && !ignoreCache)
|
||||
return isYarnInstalled;
|
||||
export async function getIsNpmInstalled(ignoreCache?: boolean): Promise<boolean> {
|
||||
if (isInstalled !== null && !ignoreCache) return isInstalled;
|
||||
|
||||
const command =
|
||||
process.platform === 'win32'
|
||||
? `where.exe ${packageManager}`
|
||||
: `which ${packageManager}`;
|
||||
const command = process.platform === 'win32'
|
||||
? 'where.exe npm'
|
||||
: 'which npm';
|
||||
|
||||
try {
|
||||
await exec(process.cwd(), command);
|
||||
if (packageManager === 'npm') {
|
||||
return (isNpmInstalled = true);
|
||||
} else {
|
||||
return (isYarnInstalled = true);
|
||||
}
|
||||
return isInstalled = true;
|
||||
} catch (error) {
|
||||
console.warn(`getIsPackageManagerInstalled: "${command}" failed.`, error);
|
||||
if (packageManager === 'npm') {
|
||||
return (isNpmInstalled = false);
|
||||
} else {
|
||||
return (isYarnInstalled = false);
|
||||
}
|
||||
console.warn(`getIsNpmInstalled: "${command}" failed.`, error);
|
||||
return isInstalled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,10 +58,13 @@ export async function getIsPackageManagerInstalled(
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
export function findModulesInEditors(values: EditorValues) {
|
||||
const files = [values.main, values.renderer];
|
||||
const files = [ values.main, values.renderer ];
|
||||
const modules = files.reduce(
|
||||
(agg, file) => [...agg, ...findModules(file)],
|
||||
[],
|
||||
(agg, file) => [
|
||||
...agg,
|
||||
...findModules(file)
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
console.log('Modules Found:', modules);
|
||||
@@ -105,7 +91,8 @@ export function findModules(input: string): Array<string> {
|
||||
let match: RegExpMatchArray | null;
|
||||
|
||||
/* grab all global require matches in the text */
|
||||
while ((match = requiregx.exec(input) || null)) {
|
||||
// tslint:disable-next-line:no-conditional-assignment
|
||||
while (match = (requiregx.exec(input) || null)) {
|
||||
// ensure commented-out requires aren't downloaded
|
||||
if (!match[0].startsWith('//')) {
|
||||
const mod = match[1];
|
||||
@@ -116,7 +103,9 @@ export function findModules(input: string): Array<string> {
|
||||
/* map and reduce */
|
||||
return modules
|
||||
.map((mod) =>
|
||||
mod.includes('/') && !mod.startsWith('@') ? mod.split('/')[0] : mod,
|
||||
mod.includes('/') && !mod.startsWith('@') ?
|
||||
mod.split('/')[0] :
|
||||
mod
|
||||
)
|
||||
.filter((m) => !isIgnored(m))
|
||||
.filter((m) => !isLocalModule(m))
|
||||
@@ -126,37 +115,25 @@ export function findModules(input: string): Array<string> {
|
||||
/**
|
||||
* Installs given modules to a given folder.
|
||||
*
|
||||
* @param {PMOperationOptions} { dir, packageManager }
|
||||
* @param {NpmOperationOptions} { dir }
|
||||
* @param {...Array<string>} names
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function installModules(
|
||||
{ dir, packageManager }: PMOperationOptions,
|
||||
...names: Array<string>
|
||||
): Promise<string> {
|
||||
let nameArgs: Array<string> = [];
|
||||
export async function installModules({ dir }: NpmOperationOptions, ...names: Array<string>): Promise<string> {
|
||||
const nameArgs = names.length > 0
|
||||
? [ '-S', ...names ]
|
||||
: ['--dev --prod'];
|
||||
|
||||
if (packageManager === 'npm') {
|
||||
nameArgs = names.length > 0 ? ['-S', ...names] : ['--dev --prod'];
|
||||
} else {
|
||||
nameArgs = [...names];
|
||||
}
|
||||
|
||||
const installCommand = packageManager === 'npm' ? 'npm install' : 'yarn add';
|
||||
|
||||
return exec(dir, [installCommand].concat(nameArgs).join(' '));
|
||||
return exec(dir, [ `npm install` ].concat(nameArgs).join(' '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an "{packageManager} run" command
|
||||
* Execute an "npm run" command
|
||||
*
|
||||
* @param {PMOperationOptions} { dir, packageManager }
|
||||
* @param {NpmOperationOptions} { dir }
|
||||
* @param {string} command
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function packageRun(
|
||||
{ dir, packageManager }: PMOperationOptions,
|
||||
command: string,
|
||||
): Promise<string> {
|
||||
return exec(dir, `${packageManager} run ${command}`);
|
||||
export function npmRun({ dir }: NpmOperationOptions, command: string): Promise<string> {
|
||||
return exec(dir, `npm run ${command}`);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { when } from 'mobx';
|
||||
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 { 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';
|
||||
@@ -29,16 +23,11 @@ export class RemoteLoader {
|
||||
this.handleLoadingFailed.bind(this);
|
||||
}
|
||||
|
||||
public async loadFiddleFromElectronExample(
|
||||
_: any,
|
||||
exampleInfo: { path: string; ref: string },
|
||||
) {
|
||||
public async loadFiddleFromElectronExample(_: any, exampleInfo: { path: string; ref: string }) {
|
||||
console.log(`Loading fiddle from Electron example`, _, exampleInfo);
|
||||
const { path, ref } = exampleInfo;
|
||||
const prettyName = path.replace('docs/fiddles/', '');
|
||||
const ok = await this.verifyRemoteLoad(
|
||||
`'${prettyName}' example from the Electron docs for version ${ref}`,
|
||||
);
|
||||
const ok = await this.verifyRemoteLoad(`'${prettyName}' example from the Electron docs for version ${ref}`);
|
||||
if (!ok) return;
|
||||
|
||||
this.fetchExampleAndLoad(ref, path);
|
||||
@@ -52,10 +41,7 @@ export class RemoteLoader {
|
||||
this.fetchGistAndLoad(id);
|
||||
}
|
||||
|
||||
public async fetchExampleAndLoad(
|
||||
ref: string,
|
||||
path: string,
|
||||
): Promise<boolean> {
|
||||
public async fetchExampleAndLoad(ref: string, path: string): Promise<boolean> {
|
||||
try {
|
||||
const octo = await getOctokit(this.appState);
|
||||
|
||||
@@ -74,14 +60,12 @@ 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),
|
||||
css: await getContent(EditorId.css, this.appState.version)
|
||||
};
|
||||
|
||||
const loaders: Array<Promise<void>> = [];
|
||||
if (!Array.isArray(folder.data)) {
|
||||
throw new Error(
|
||||
'The example Fiddle tried to launch is not a valid Electron example',
|
||||
);
|
||||
throw new Error('The example Fiddle tried to launch is not a valid Electron example');
|
||||
}
|
||||
|
||||
for (const child of folder.data) {
|
||||
@@ -92,52 +76,32 @@ export class RemoteLoader {
|
||||
|
||||
switch (child.name) {
|
||||
case MAIN_JS_NAME:
|
||||
loaders.push(
|
||||
fetch(child.download_url)
|
||||
.then((r) => r.text())
|
||||
.then((t) => {
|
||||
values.main = t;
|
||||
}),
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.main = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case INDEX_HTML_NAME:
|
||||
loaders.push(
|
||||
fetch(child.download_url)
|
||||
.then((r) => r.text())
|
||||
.then((t) => {
|
||||
values.html = t;
|
||||
}),
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.html = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
case RENDERER_JS_NAME:
|
||||
loaders.push(
|
||||
fetch(child.download_url)
|
||||
.then((r) => r.text())
|
||||
.then((t) => {
|
||||
values.renderer = t;
|
||||
}),
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.renderer = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
case PRELOAD_JS_NAME:
|
||||
loaders.push(
|
||||
fetch(child.download_url)
|
||||
.then((r) => r.text())
|
||||
.then((t) => {
|
||||
values.preload = t;
|
||||
}),
|
||||
loaders.push(fetch(child.download_url)
|
||||
.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;
|
||||
}),
|
||||
loaders.push(fetch(child.download_url)
|
||||
.then((r) => r.text()).then((t) => { values.css = t; })
|
||||
);
|
||||
|
||||
break;
|
||||
@@ -162,10 +126,7 @@ export class RemoteLoader {
|
||||
* @returns {string}
|
||||
* @memberof RemoteLoader
|
||||
*/
|
||||
public getContentOrEmpty(
|
||||
gist: Octokit.Response<Octokit.GistsGetResponse>,
|
||||
name: string,
|
||||
): string {
|
||||
public getContentOrEmpty(gist: Octokit.Response<Octokit.GistsGetResponse>, name: string): string {
|
||||
try {
|
||||
return gist.data.files[name].content;
|
||||
} catch (error) {
|
||||
@@ -184,16 +145,13 @@ export class RemoteLoader {
|
||||
const octo = await getOctokit(this.appState);
|
||||
const gist = await octo.gists.get({ gist_id: gistId });
|
||||
|
||||
return this.handleLoadingSuccess(
|
||||
{
|
||||
html: this.getContentOrEmpty(gist, INDEX_HTML_NAME),
|
||||
main: this.getContentOrEmpty(gist, MAIN_JS_NAME),
|
||||
renderer: this.getContentOrEmpty(gist, RENDERER_JS_NAME),
|
||||
preload: this.getContentOrEmpty(gist, PRELOAD_JS_NAME),
|
||||
css: this.getContentOrEmpty(gist, STYLES_CSS_NAME),
|
||||
},
|
||||
gistId,
|
||||
);
|
||||
return this.handleLoadingSuccess({
|
||||
html: this.getContentOrEmpty(gist, INDEX_HTML_NAME),
|
||||
main: this.getContentOrEmpty(gist, MAIN_JS_NAME),
|
||||
renderer: this.getContentOrEmpty(gist, RENDERER_JS_NAME),
|
||||
preload: this.getContentOrEmpty(gist, PRELOAD_JS_NAME),
|
||||
css: this.getContentOrEmpty(gist, STYLES_CSS_NAME)
|
||||
}, gistId);
|
||||
} catch (error) {
|
||||
return this.handleLoadingFailed(error);
|
||||
}
|
||||
@@ -202,21 +160,14 @@ export class RemoteLoader {
|
||||
public async setElectronVersionWithRef(ref: string): Promise<boolean> {
|
||||
const version = await this.getPackageVersionFromRef(ref);
|
||||
|
||||
const supportedVersions = sortedElectronMap(
|
||||
this.appState.versions,
|
||||
(k) => k,
|
||||
);
|
||||
const supportedVersions = sortedElectronMap(this.appState.versions, (k) => k);
|
||||
if (!supportedVersions.includes(version)) {
|
||||
this.handleLoadingFailed(
|
||||
new Error('Version of Electron in example not supported'),
|
||||
);
|
||||
this.handleLoadingFailed(new Error('Version of Electron in example not supported'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if version is part of release channel
|
||||
const versionReleaseChannel: ElectronReleaseChannel = getReleaseChannel(
|
||||
version,
|
||||
);
|
||||
const versionReleaseChannel: ElectronReleaseChannel = getReleaseChannel(version);
|
||||
|
||||
if (!this.appState.channelsToShow.includes(versionReleaseChannel)) {
|
||||
const ok = await this.verifyReleaseChannelEnabled(versionReleaseChannel);
|
||||
@@ -235,23 +186,17 @@ export class RemoteLoader {
|
||||
owner: ELECTRON_ORG,
|
||||
repo: ELECTRON_REPO,
|
||||
ref,
|
||||
path: 'package.json',
|
||||
path: 'package.json'
|
||||
});
|
||||
|
||||
if (!Array.isArray(packageJsonData) && !!packageJsonData.content) {
|
||||
const packageJsonString = Buffer.from(
|
||||
packageJsonData.content,
|
||||
'base64',
|
||||
).toString('utf8');
|
||||
const packageJsonString = Buffer.from(packageJsonData.content, 'base64').toString('utf8');
|
||||
const { version } = JSON.parse(packageJsonString);
|
||||
return version;
|
||||
} else {
|
||||
console.error(
|
||||
`getPackageVersionFromRef: Received unexpected response from GitHub, could not parse version`,
|
||||
{
|
||||
packageJsonData,
|
||||
},
|
||||
);
|
||||
console.error(`getPackageVersionFromRef: Received unexpected response from GitHub, could not parse version`, {
|
||||
packageJsonData
|
||||
});
|
||||
|
||||
return '0.0.0';
|
||||
}
|
||||
@@ -265,7 +210,7 @@ export class RemoteLoader {
|
||||
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.`,
|
||||
label: `Are you sure you want to load this ${what}? Only load and run it if you trust the source.`
|
||||
});
|
||||
this.appState.isGenericDialogShowing = true;
|
||||
await when(() => !this.appState.isGenericDialogShowing);
|
||||
@@ -278,7 +223,7 @@ export class RemoteLoader {
|
||||
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?`,
|
||||
version of Electron from the example?`
|
||||
});
|
||||
this.appState.isGenericDialogShowing = true;
|
||||
await when(() => !this.appState.isGenericDialogShowing);
|
||||
@@ -293,10 +238,7 @@ export class RemoteLoader {
|
||||
* @param {string} gistId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private async handleLoadingSuccess(
|
||||
values: Partial<EditorValues>,
|
||||
gistId: string,
|
||||
): Promise<boolean> {
|
||||
private async handleLoadingSuccess(values: Partial<EditorValues>, gistId: string): Promise<boolean> {
|
||||
await window.ElectronFiddle.app.replaceFiddle(values, { gistId });
|
||||
return true;
|
||||
}
|
||||
@@ -313,13 +255,13 @@ export class RemoteLoader {
|
||||
this.appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `Loading the fiddle failed: ${error}`,
|
||||
cancel: undefined,
|
||||
cancel: undefined
|
||||
});
|
||||
} else {
|
||||
this.appState.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `Loading the fiddle failed. Your computer seems to be offline. Error: ${error}`,
|
||||
cancel: undefined,
|
||||
cancel: undefined
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+34
-68
@@ -7,18 +7,12 @@ import { PackageJsonOptions } from '../utils/get-package';
|
||||
import { maybePlural } from '../utils/plural-maybe';
|
||||
import { getElectronBinaryPath, getIsDownloaded } from './binary';
|
||||
import { ipcRendererManager } from './ipc';
|
||||
import {
|
||||
findModulesInEditors,
|
||||
getIsPackageManagerInstalled,
|
||||
installModules,
|
||||
packageRun,
|
||||
PMOperationOptions,
|
||||
} from './npm';
|
||||
import { findModulesInEditors, getIsNpmInstalled, installModules, npmRun } from './npm';
|
||||
import { AppState } from './state';
|
||||
|
||||
export enum ForgeCommands {
|
||||
PACKAGE = 'package',
|
||||
MAKE = 'make',
|
||||
MAKE = 'make'
|
||||
}
|
||||
|
||||
export class Runner {
|
||||
@@ -32,6 +26,7 @@ export class Runner {
|
||||
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);
|
||||
@@ -59,12 +54,11 @@ export class Runner {
|
||||
|
||||
const values = await getEditorValues(options);
|
||||
const dir = await this.saveToTemp(options);
|
||||
const packageManager = this.appState.packageManager;
|
||||
|
||||
if (!dir) return false;
|
||||
|
||||
try {
|
||||
await this.installModulesForEditor(values, { dir, packageManager });
|
||||
await this.installModulesForEditor(values, dir);
|
||||
} catch (error) {
|
||||
console.error('Runner: Could not install modules', error);
|
||||
fileManager.cleanup(dir);
|
||||
@@ -108,49 +102,39 @@ export class Runner {
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof Runner
|
||||
*/
|
||||
public async performForgeOperation(
|
||||
operation: ForgeCommands,
|
||||
): Promise<boolean> {
|
||||
public async performForgeOperation(operation: ForgeCommands): Promise<boolean> {
|
||||
const options = { includeDependencies: true, includeElectron: true };
|
||||
const { dotfilesTransform } = await import('./transforms/dotfiles');
|
||||
const { forgeTransform } = await import('./transforms/forge');
|
||||
const { pushError, pushOutput } = this.appState;
|
||||
|
||||
const strings =
|
||||
operation === ForgeCommands.MAKE
|
||||
? ['Creating installers for', 'Binary']
|
||||
: ['Packaging', 'Installers'];
|
||||
const strings = operation === ForgeCommands.MAKE
|
||||
? [ 'Creating installers for', 'Binary' ]
|
||||
: [ 'Packaging', 'Installers' ];
|
||||
|
||||
this.appState.isConsoleShowing = true;
|
||||
pushOutput(`📦 ${strings[0]} current Fiddle...`);
|
||||
|
||||
const packageManager = this.appState.packageManager;
|
||||
const pmInstalled = await getIsPackageManagerInstalled(packageManager);
|
||||
if (!pmInstalled) {
|
||||
let message = `Error: Could not find ${packageManager}. Fiddle requires Node.js and npm or yarn `;
|
||||
if (!(await getIsNpmInstalled())) {
|
||||
let message = `Error: Could not find npm. Fiddle requires Node.js and npm `;
|
||||
message += `to compile packages. Please visit https://nodejs.org to install `;
|
||||
message += `Node.js and npm, or https://classic.yarnpkg.com/lang/en/ `;
|
||||
message += `to install Yarn`;
|
||||
message += `Node.js and npm.`;
|
||||
|
||||
this.appState.pushOutput(message, { isNotPre: true });
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save files to temp
|
||||
const dir = await this.saveToTemp(
|
||||
options,
|
||||
dotfilesTransform,
|
||||
forgeTransform,
|
||||
);
|
||||
const dir = await this.saveToTemp(options, dotfilesTransform, forgeTransform);
|
||||
if (!dir) return false;
|
||||
|
||||
// Files are now saved to temp, let's install Forge and dependencies
|
||||
if (!(await this.packageInstall({ dir, packageManager }))) return false;
|
||||
if (!(await this.npmInstall(dir))) return false;
|
||||
|
||||
// Cool, let's run "package"
|
||||
try {
|
||||
console.log(`Now creating ${strings[1].toLowerCase()}...`);
|
||||
pushOutput(await packageRun({ dir, packageManager }, operation));
|
||||
pushOutput(await npmRun({ dir }, operation));
|
||||
pushOutput(`✅ ${strings[1]} successfully created.`, { isNotPre: true });
|
||||
} catch (error) {
|
||||
pushError(`Creating ${strings[1].toLowerCase()} failed.`, error);
|
||||
@@ -170,36 +154,24 @@ export class Runner {
|
||||
* @param {string} dir
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async installModulesForEditor(
|
||||
values: EditorValues,
|
||||
pmOptions: PMOperationOptions,
|
||||
): Promise<void> {
|
||||
const modules = findModulesInEditors(values);
|
||||
public async installModulesForEditor(values: EditorValues, dir: string): Promise<void> {
|
||||
const modules = await findModulesInEditors(values);
|
||||
const { pushOutput } = this.appState;
|
||||
|
||||
if (modules && modules.length > 0) {
|
||||
const packageManager = pmOptions.packageManager;
|
||||
const pmInstalled = await getIsPackageManagerInstalled(packageManager);
|
||||
if (!pmInstalled) {
|
||||
let message = `The ${maybePlural(`module`, modules)} ${modules.join(
|
||||
', ',
|
||||
)} need to be installed, `;
|
||||
message += `but we could not find ${packageManager}. Fiddle requires Node.js and npm `;
|
||||
if (!(await getIsNpmInstalled())) {
|
||||
let message = `The ${maybePlural(`module`, modules)} ${modules.join(', ')} need to be installed, `;
|
||||
message += `but we could not find npm. Fiddle requires Node.js and npm `;
|
||||
message += `to support the installation of modules not included in `;
|
||||
message += `Electron. Please visit https://nodejs.org to install Node.js `;
|
||||
message += `and npm, or https://classic.yarnpkg.com/lang/en/ to install Yarn`;
|
||||
message += `and npm.`;
|
||||
|
||||
pushOutput(message, { isNotPre: true });
|
||||
return;
|
||||
}
|
||||
|
||||
pushOutput(
|
||||
`Installing node modules using ${
|
||||
pmOptions.packageManager
|
||||
}: ${modules.join(', ')}...`,
|
||||
{ isNotPre: true },
|
||||
);
|
||||
pushOutput(await installModules(pmOptions, ...modules));
|
||||
pushOutput(`Installing npm modules: ${modules.join(', ')}...`, { isNotPre: true });
|
||||
pushOutput(await installModules({ dir }, ...modules));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,21 +201,18 @@ export class Runner {
|
||||
}
|
||||
|
||||
// Add user-specified cli flags if any have been set.
|
||||
const options = [dir, '--inspect'].concat(this.appState.executionFlags);
|
||||
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.`);
|
||||
|
||||
this.child.stdout!.on('data', (data) =>
|
||||
pushOutput(data, { bypassBuffer: false }),
|
||||
);
|
||||
this.child.stderr!.on('data', (data) =>
|
||||
pushOutput(data, { bypassBuffer: false }),
|
||||
);
|
||||
this.child.stdout!.on('data', (data) => pushOutput(data, { bypassBuffer: false }));
|
||||
this.child.stderr!.on('data', (data) => pushOutput(data, { bypassBuffer: false }));
|
||||
this.child.on('close', async (code) => {
|
||||
const withCode =
|
||||
typeof code === 'number' ? ` with code ${code.toString()}.` : `.`;
|
||||
const withCode = typeof code === 'number'
|
||||
? ` with code ${code.toString()}.`
|
||||
: `.`;
|
||||
|
||||
pushOutput(`Electron exited${withCode}`);
|
||||
this.appState.isRunning = false;
|
||||
@@ -264,8 +233,7 @@ export class Runner {
|
||||
* @memberof Runner
|
||||
*/
|
||||
public async saveToTemp(
|
||||
options: PackageJsonOptions,
|
||||
...transforms: Array<FileTransform>
|
||||
options: PackageJsonOptions, ...transforms: Array<FileTransform>
|
||||
): Promise<string | null> {
|
||||
const { fileManager } = window.ElectronFiddle.app;
|
||||
const { pushOutput, pushError } = this.appState;
|
||||
@@ -284,16 +252,16 @@ export class Runner {
|
||||
|
||||
/**
|
||||
* Installs modules in a given directory (we're basically
|
||||
* just running "{packageManager} install")
|
||||
* just running "npm install")
|
||||
*
|
||||
* @param {PMOperationOptions} options
|
||||
* @param {string} dir
|
||||
* @returns
|
||||
* @memberof Runner
|
||||
*/
|
||||
public async packageInstall(options: PMOperationOptions): Promise<boolean> {
|
||||
public async npmInstall(dir: string): Promise<boolean> {
|
||||
try {
|
||||
this.appState.pushOutput(`Now running "npm install..."`);
|
||||
this.appState.pushOutput(await installModules(options));
|
||||
this.appState.pushOutput(await installModules({ dir }));
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.appState.pushError('Failed to run "npm install".', error);
|
||||
@@ -307,9 +275,7 @@ export class Runner {
|
||||
*/
|
||||
private async deleteUserData() {
|
||||
if (this.appState.isKeepingUserDataDirs) {
|
||||
console.log(
|
||||
`Cleanup: Not deleting data dir due to isKeepingUserDataDirs setting`,
|
||||
);
|
||||
console.log(`Cleanup: Not deleting data dir due to isKeepingUserDataDirs setting`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+95
-191
@@ -8,45 +8,32 @@ import {
|
||||
EditorId,
|
||||
GenericDialogOptions,
|
||||
GenericDialogType,
|
||||
GistActionState,
|
||||
MosaicId,
|
||||
OutputEntry,
|
||||
OutputOptions,
|
||||
RunnableVersion,
|
||||
Version,
|
||||
VersionSource,
|
||||
VersionState,
|
||||
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 { 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 {
|
||||
getDownloadedVersions,
|
||||
getDownloadingVersions,
|
||||
removeBinary,
|
||||
setupBinary,
|
||||
} from './binary';
|
||||
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 { getLocalTypePathForVersion, updateEditorTypeDefinitions } from './fetch-types';
|
||||
import { ipcRendererManager } from './ipc';
|
||||
import { activateTheme } from './themes';
|
||||
|
||||
import { waitForEditorsToMount } from '../utils/editor-mounted';
|
||||
import { sortedElectronMap } from '../utils/sorted-electron-map';
|
||||
import { IPackageManager } from './npm';
|
||||
import {
|
||||
addLocalVersion,
|
||||
ElectronReleaseChannel,
|
||||
@@ -54,7 +41,7 @@ import {
|
||||
getElectronVersions,
|
||||
getReleaseChannel,
|
||||
getUpdatedElectronVersions,
|
||||
saveLocalVersions,
|
||||
saveLocalVersions
|
||||
} from './versions';
|
||||
|
||||
const knownVersions = getElectronVersions();
|
||||
@@ -65,16 +52,14 @@ const defaultVersion = getDefaultVersion(knownVersions);
|
||||
* easier, we keep them around in a global object. Don't judge us,
|
||||
* we're really only doing that for the editors.
|
||||
*/
|
||||
window.ElectronFiddle =
|
||||
window.ElectronFiddle ||
|
||||
({
|
||||
editors: {
|
||||
main: null,
|
||||
renderer: null,
|
||||
html: null,
|
||||
},
|
||||
app: null,
|
||||
} as any);
|
||||
window.ElectronFiddle = window.ElectronFiddle || {
|
||||
editors: {
|
||||
main: null,
|
||||
renderer: null,
|
||||
html: null
|
||||
},
|
||||
app: null
|
||||
} as any;
|
||||
|
||||
/**
|
||||
* The application's state. Exported as a singleton below.
|
||||
@@ -86,98 +71,58 @@ export class AppState {
|
||||
// -- Persisted settings ------------------
|
||||
@observable public version: string = defaultVersion;
|
||||
@observable public theme: string | null = localStorage.getItem('theme');
|
||||
@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 = !!this.retrieve(
|
||||
'gitHubPublishAsPublic',
|
||||
);
|
||||
@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 = !!this.retrieve(
|
||||
'isKeepingUserDataDirs',
|
||||
);
|
||||
@observable public isEnablingElectronLogging = !!this.retrieve(
|
||||
'isEnablingElectronLogging',
|
||||
);
|
||||
@observable public isClearingConsoleOnRun = !!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 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 isClearingConsoleOnRun: boolean = !!this.retrieve('isClearingConsoleOnRun');
|
||||
@observable public executionFlags: Array<string> =
|
||||
(this.retrieve('executionFlags') as Array<string>) === null
|
||||
? []
|
||||
: (this.retrieve('executionFlags') as Array<string>);
|
||||
@observable public packageManager: IPackageManager =
|
||||
(localStorage.getItem('packageManager') as IPackageManager) || 'npm';
|
||||
this.retrieve('executionFlags') as Array<string> === null ?
|
||||
[] : this.retrieve('executionFlags') as Array<string>;
|
||||
|
||||
// -- Various session-only state ------------------
|
||||
@observable public gistId: string | undefined;
|
||||
@observable public versions: Record<
|
||||
string,
|
||||
RunnableVersion
|
||||
> = arrayToStringMap(knownVersions);
|
||||
@observable public gistId: string = '';
|
||||
@observable public versions: Record<string, RunnableVersion> = arrayToStringMap(knownVersions);
|
||||
@observable public output: Array<OutputEntry> = [];
|
||||
@observable public localPath: string | undefined;
|
||||
@observable public genericDialogOptions: GenericDialogOptions = {
|
||||
type: GenericDialogType.warning,
|
||||
label: '' as string | JSX.Element,
|
||||
ok: 'Okay',
|
||||
cancel: 'Cancel',
|
||||
wantsInput: false,
|
||||
placeholder: '',
|
||||
};
|
||||
@observable public genericDialogOptions = { type: GenericDialogType.warning, label: '', ok: 'Okay', cancel: 'Cancel' };
|
||||
@observable public genericDialogLastResult: boolean | null = null;
|
||||
@observable public genericDialogLastInput: string | null = null;
|
||||
@observable public mosaicArrangement: MosaicNode<
|
||||
MosaicId
|
||||
> | null = DEFAULT_MOSAIC_ARRANGEMENT;
|
||||
@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 activeGistAction: GistActionState = GistActionState.none;
|
||||
@observable public isRunning = false;
|
||||
@observable public isPublishing: boolean = false;
|
||||
@observable public isRunning: boolean = false;
|
||||
@observable public isUnsaved: boolean;
|
||||
@observable public isUpdatingElectronVersions = false;
|
||||
@observable public isQuitting = false;
|
||||
@observable public isUpdatingElectronVersions: boolean = false;
|
||||
@observable public isQuitting: boolean = false;
|
||||
|
||||
// -- Various "isShowing" settings ------------------
|
||||
@observable public isBisectCommandShowing: boolean;
|
||||
@observable public isConsoleShowing = false;
|
||||
@observable public isTokenDialogShowing = false;
|
||||
@observable public isGenericDialogShowing = false;
|
||||
@observable public isSettingsShowing = false;
|
||||
@observable public isBisectDialogShowing = false;
|
||||
@observable public isAddVersionDialogShowing = false;
|
||||
@observable public isThemeDialogShowing = false;
|
||||
@observable public isTourShowing = !localStorage.getItem('hasShownTour');
|
||||
@observable public isConsoleShowing: boolean = false;
|
||||
@observable public isTokenDialogShowing: boolean = false;
|
||||
@observable public isGenericDialogShowing: boolean = false;
|
||||
@observable public isSettingsShowing: 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>
|
||||
> = DEFAULT_CLOSED_PANELS;
|
||||
@observable public closedPanels: Partial<Record<MosaicId, EditorBackup | true>> = DEFAULT_CLOSED_PANELS;
|
||||
|
||||
private outputBuffer = '';
|
||||
private outputBuffer: string = '';
|
||||
private name: string;
|
||||
public appData: string;
|
||||
|
||||
@@ -208,38 +153,24 @@ export class AppState {
|
||||
ipcRendererManager.on(IpcEvents.OPEN_SETTINGS, this.toggleSettings);
|
||||
ipcRendererManager.on(IpcEvents.SHOW_WELCOME_TOUR, this.showTour);
|
||||
ipcRendererManager.on(IpcEvents.CLEAR_CONSOLE, this.clearConsole);
|
||||
ipcRendererManager.on(
|
||||
IpcEvents.BISECT_COMMANDS_TOGGLE,
|
||||
this.toggleBisectCommands,
|
||||
);
|
||||
ipcRendererManager.on(IpcEvents.BISECT_COMMANDS_TOGGLE, this.toggleBisectCommands);
|
||||
ipcRendererManager.on(IpcEvents.BEFORE_QUIT, this.setIsQuitting);
|
||||
ipcRendererManager.once(IpcEvents.SET_APPDATA_DIR, (_event, dir) => {
|
||||
this.appData = dir;
|
||||
});
|
||||
ipcRendererManager.once(IpcEvents.SET_APPDATA_DIR, (_event, dir) => { this.appData = dir; });
|
||||
|
||||
// Setup auto-runs
|
||||
autorun(() => this.save('theme', this.theme));
|
||||
autorun(() =>
|
||||
this.save('isClearingConsoleOnRun', this.isClearingConsoleOnRun),
|
||||
);
|
||||
autorun(() => this.save('isClearingConsoleOnRun', this.isClearingConsoleOnRun));
|
||||
autorun(() => this.save('gitHubAvatarUrl', this.gitHubAvatarUrl));
|
||||
autorun(() => this.save('gitHubLogin', this.gitHubLogin));
|
||||
autorun(() => this.save('gitHubName', this.gitHubName));
|
||||
autorun(() => this.save('gitHubToken', this.gitHubToken));
|
||||
autorun(() =>
|
||||
this.save('gitHubPublishAsPublic', this.gitHubPublishAsPublic),
|
||||
);
|
||||
autorun(() =>
|
||||
this.save('isKeepingUserDataDirs', this.isKeepingUserDataDirs),
|
||||
);
|
||||
autorun(() =>
|
||||
this.save('isEnablingElectronLogging', this.isEnablingElectronLogging),
|
||||
);
|
||||
autorun(() => this.save('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('channelsToShow', this.channelsToShow));
|
||||
autorun(() => this.save('statesToShow', this.statesToShow));
|
||||
autorun(() => this.save('packageManager', this.packageManager ?? 'npm'));
|
||||
|
||||
autorun(() => {
|
||||
if (typeof this.isUnsaved === 'undefined') return;
|
||||
@@ -250,7 +181,7 @@ export class AppState {
|
||||
this.setGenericDialogOptions({
|
||||
type: GenericDialogType.warning,
|
||||
label: `The current Fiddle is unsaved. Do you want to exit anyway?`,
|
||||
ok: 'Quit',
|
||||
ok: 'Quit'
|
||||
});
|
||||
|
||||
this.isGenericDialogShowing = true;
|
||||
@@ -272,6 +203,7 @@ export class AppState {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// return value doesn't matter, we just want to cancel the event
|
||||
return false;
|
||||
};
|
||||
@@ -313,26 +245,24 @@ export class AppState {
|
||||
* 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;
|
||||
}
|
||||
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 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;
|
||||
}
|
||||
// Check if we want to show the state
|
||||
if (!this.statesToShow.includes(item.state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -405,7 +335,9 @@ export class AppState {
|
||||
@action public toggleSettings() {
|
||||
// We usually don't lose editor focus,
|
||||
// so you can still type. Let's force-blur.
|
||||
(document.activeElement as HTMLInputElement).blur();
|
||||
if ((document.activeElement as HTMLInputElement).blur) {
|
||||
(document.activeElement as HTMLInputElement).blur();
|
||||
}
|
||||
|
||||
this.resetView({ isSettingsShowing: !this.isSettingsShowing });
|
||||
}
|
||||
@@ -431,11 +363,10 @@ export class AppState {
|
||||
|
||||
@action public setGenericDialogOptions(opts: GenericDialogOptions) {
|
||||
this.genericDialogOptions = {
|
||||
type: GenericDialogType.warning,
|
||||
ok: 'Okay',
|
||||
cancel: 'Cancel',
|
||||
wantsInput: false,
|
||||
placeholder: '',
|
||||
...opts,
|
||||
...opts
|
||||
};
|
||||
}
|
||||
|
||||
@@ -471,9 +402,9 @@ export class AppState {
|
||||
if (release && release.source === VersionSource.local) {
|
||||
delete updatedVersions[version];
|
||||
|
||||
const versionsAsArray = Object.keys(updatedVersions).map(
|
||||
(k) => updatedVersions[k],
|
||||
);
|
||||
const versionsAsArray = Object
|
||||
.keys(updatedVersions)
|
||||
.map((k) => updatedVersions[k]);
|
||||
|
||||
saveLocalVersions(versionsAsArray);
|
||||
} else {
|
||||
@@ -509,9 +440,7 @@ export class AppState {
|
||||
await setupBinary(this, version);
|
||||
this.updateDownloadedVersionState();
|
||||
} else {
|
||||
console.log(
|
||||
`State: Version ${version} already downloaded, doing nothing.`,
|
||||
);
|
||||
console.log(`State: Version ${version} already downloaded, doing nothing.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,9 +454,7 @@ export class AppState {
|
||||
const version = normalizeVersion(input);
|
||||
|
||||
if (!this.versions[version]) {
|
||||
console.warn(
|
||||
`State: Called setVersion() with ${version}, which does not exist.`,
|
||||
);
|
||||
console.warn(`State: Called setVersion() with ${version}, which does not exist.`);
|
||||
this.setVersion(knownVersions[0].version);
|
||||
|
||||
return;
|
||||
@@ -549,14 +476,10 @@ export class AppState {
|
||||
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}`,
|
||||
);
|
||||
console.info(`TypeDefs: Watching file for local version ${version} at path ${typePath}`);
|
||||
try {
|
||||
this.localTypeWatcher = fs.watch(typePath!, async () => {
|
||||
console.info(
|
||||
`TypeDefs: Noticed file change at ${typePath}. Updating editor typedefs.`,
|
||||
);
|
||||
console.info(`TypeDefs: Noticed file change at ${typePath}. Updating editor typedefs.`);
|
||||
await updateEditorTypeDefinitions(versionObject);
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -564,9 +487,7 @@ export class AppState {
|
||||
}
|
||||
} else {
|
||||
if (!!this.localTypeWatcher) {
|
||||
console.info(
|
||||
`TypeDefs: Switched to downloaded version ${version}. Unwatching local typedefs.`,
|
||||
);
|
||||
console.info(`TypeDefs: Switched to downloaded version ${version}. Unwatching local typedefs.`);
|
||||
this.localTypeWatcher.close();
|
||||
this.localTypeWatcher = undefined;
|
||||
}
|
||||
@@ -624,8 +545,7 @@ export class AppState {
|
||||
* @param {(string | Buffer)} data
|
||||
*/
|
||||
@action public pushOutput(
|
||||
data: string | Buffer,
|
||||
options: OutputOptions = { isNotPre: false, bypassBuffer: true },
|
||||
data: string | Buffer, options: OutputOptions = { isNotPre: false, bypassBuffer: true }
|
||||
) {
|
||||
let strData = data.toString();
|
||||
const { isNotPre, bypassBuffer } = options;
|
||||
@@ -654,7 +574,7 @@ export class AppState {
|
||||
this.output.push({
|
||||
timestamp: Date.now(),
|
||||
text: strData.trim(),
|
||||
isNotPre,
|
||||
isNotPre
|
||||
});
|
||||
}
|
||||
|
||||
@@ -676,9 +596,7 @@ export class AppState {
|
||||
*
|
||||
* @param {EditorId} id
|
||||
*/
|
||||
@action public getAndRemoveEditorValueBackup(
|
||||
id: EditorId,
|
||||
): EditorBackup | null {
|
||||
@action public getAndRemoveEditorValueBackup(id: EditorId): EditorBackup | null {
|
||||
const value = this.closedPanels[id];
|
||||
|
||||
if (isEditorBackup(value)) {
|
||||
@@ -694,7 +612,9 @@ export class AppState {
|
||||
|
||||
for (const id of ALL_MOSAICS) {
|
||||
if (!visible.includes(id) && currentlyVisible.includes(id)) {
|
||||
this.closedPanels[id] = isEditorId(id) ? getEditorBackup(id) : true;
|
||||
this.closedPanels[id] = isEditorId(id)
|
||||
? getEditorBackup(id)
|
||||
: true;
|
||||
|
||||
// if we have backup, remove active editor
|
||||
delete window.ElectronFiddle.editors[id];
|
||||
@@ -702,21 +622,13 @@ export class AppState {
|
||||
|
||||
// Remove the backup for panels now. Editors will remove their
|
||||
// backup once the data has been loaded.
|
||||
if (
|
||||
isPanelId(id) &&
|
||||
visible.includes(id) &&
|
||||
!currentlyVisible.includes(id)
|
||||
) {
|
||||
if (isPanelId(id) && visible.includes(id) && !currentlyVisible.includes(id)) {
|
||||
delete this.closedPanels[id];
|
||||
}
|
||||
}
|
||||
|
||||
const updatedArrangement = createMosaicArrangement(visible);
|
||||
console.log(
|
||||
`State: Setting visible mosaic panels`,
|
||||
visible,
|
||||
updatedArrangement,
|
||||
);
|
||||
console.log(`State: Setting visible mosaic panels`, visible, updatedArrangement);
|
||||
|
||||
this.mosaicArrangement = updatedArrangement;
|
||||
|
||||
@@ -804,19 +716,11 @@ export class AppState {
|
||||
* @param {string} key
|
||||
* @param {(string | number | object)} [value]
|
||||
*/
|
||||
private save(
|
||||
key: string,
|
||||
value?:
|
||||
| string
|
||||
| number
|
||||
| Array<any>
|
||||
| Record<string, unknown>
|
||||
| null
|
||||
| boolean,
|
||||
) {
|
||||
private save(key: string, value?: string | number | object | null | boolean) {
|
||||
if (value) {
|
||||
const _value =
|
||||
typeof value === 'object' ? JSON.stringify(value) : value.toString();
|
||||
const _value = typeof value === 'object'
|
||||
? JSON.stringify(value)
|
||||
: value.toString();
|
||||
|
||||
localStorage.setItem(key, _value);
|
||||
} else {
|
||||
|
||||
@@ -2,13 +2,7 @@ import * as fsExtraType from 'fs-extra';
|
||||
import * as pathType from 'path';
|
||||
|
||||
import { EditorValues } from '../interfaces';
|
||||
import {
|
||||
INDEX_HTML_NAME,
|
||||
MAIN_JS_NAME,
|
||||
PRELOAD_JS_NAME,
|
||||
RENDERER_JS_NAME,
|
||||
STYLES_CSS_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';
|
||||
|
||||
/**
|
||||
@@ -34,10 +28,7 @@ export async function getTemplateValues(name: string): Promise<EditorValues> {
|
||||
|
||||
if (fs.existsSync(templatesPath)) {
|
||||
const contents = fs.readdirSync(templatesPath);
|
||||
console.log(
|
||||
`getTemplateValues(): ${templatesPath} contents:`,
|
||||
contents,
|
||||
);
|
||||
console.log(`getTemplateValues(): ${templatesPath} contents:`, contents);
|
||||
} else {
|
||||
console.log(`getTemplateValues(): ${templatesPath} does not exist`);
|
||||
}
|
||||
@@ -51,6 +42,6 @@ export async function getTemplateValues(name: string): Promise<EditorValues> {
|
||||
main: await getFile(MAIN_JS_NAME),
|
||||
html: await getFile(INDEX_HTML_NAME),
|
||||
preload: await getFile(PRELOAD_JS_NAME),
|
||||
css: await getFile(STYLES_CSS_NAME),
|
||||
css: await getFile(STYLES_CSS_NAME)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,16 +53,17 @@ export const defaultDark: LoadedFiddleTheme = {
|
||||
'text-color-2': '#1e2527',
|
||||
'text-color-3': '#dcdcdc',
|
||||
'error-color': '#df3434',
|
||||
'fonts-common': `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
'fonts-common': `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`
|
||||
},
|
||||
editor: {
|
||||
base: 'vs-dark',
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: {
|
||||
'editor.background': '#2f3241',
|
||||
},
|
||||
},
|
||||
'editor.background': '#2f3241'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultLight: LoadedFiddleTheme = {
|
||||
@@ -85,17 +86,18 @@ export const defaultLight: LoadedFiddleTheme = {
|
||||
'text-color-2': '#1e2527',
|
||||
'text-color-3': '#0e0e0e',
|
||||
'error-color': '#df3434',
|
||||
'fonts-common': `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
'fonts-common': `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`
|
||||
},
|
||||
editor: {
|
||||
base: 'vs',
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: {},
|
||||
},
|
||||
colors: {}
|
||||
}
|
||||
};
|
||||
|
||||
export enum DefaultThemes {
|
||||
DARK = 'defaultDark',
|
||||
LIGHT = 'defaultLight',
|
||||
LIGHT = 'defaultLight'
|
||||
}
|
||||
|
||||
+11
-23
@@ -4,13 +4,7 @@ import * as path from 'path';
|
||||
|
||||
import { fancyImport } from '../utils/import';
|
||||
import { CONFIG_PATH } from './constants';
|
||||
import {
|
||||
defaultDark,
|
||||
defaultLight,
|
||||
DefaultThemes,
|
||||
FiddleTheme,
|
||||
LoadedFiddleTheme,
|
||||
} from './themes-defaults';
|
||||
import { defaultDark, defaultLight, DefaultThemes, FiddleTheme, LoadedFiddleTheme } from './themes-defaults';
|
||||
|
||||
export const THEMES_PATH = path.join(CONFIG_PATH, 'themes');
|
||||
|
||||
@@ -21,12 +15,10 @@ export const THEMES_PATH = path.join(CONFIG_PATH, 'themes');
|
||||
* @param {LoadedFiddleTheme} [theme]
|
||||
*/
|
||||
export async function activateTheme(
|
||||
monaco?: typeof MonacoType,
|
||||
theme?: LoadedFiddleTheme,
|
||||
name?: string | null,
|
||||
monaco?: typeof MonacoType, theme?: LoadedFiddleTheme, name?: string | null
|
||||
) {
|
||||
const _monaco = monaco || window.ElectronFiddle.app.monaco;
|
||||
const _theme = theme || (await getTheme(name));
|
||||
const _theme = theme || await getTheme(name);
|
||||
|
||||
if (!_monaco || !_monaco.editor) return;
|
||||
|
||||
@@ -34,6 +26,7 @@ export async function activateTheme(
|
||||
_monaco.editor.setTheme('main');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read in a theme file.
|
||||
*
|
||||
@@ -41,9 +34,7 @@ export async function activateTheme(
|
||||
* @param {string} [name]
|
||||
* @returns {Promise<FiddleTheme>}
|
||||
*/
|
||||
export async function readThemeFile(
|
||||
name?: string,
|
||||
): Promise<LoadedFiddleTheme | null> {
|
||||
export async function readThemeFile(name?: string): Promise<LoadedFiddleTheme | null> {
|
||||
if (!name || name === DefaultThemes.DARK) return defaultDark as any;
|
||||
if (name === DefaultThemes.LIGHT) return defaultLight as any;
|
||||
|
||||
@@ -56,7 +47,7 @@ export async function readThemeFile(
|
||||
return {
|
||||
...theme,
|
||||
name: theme.name || name.replace('.json', ''),
|
||||
file,
|
||||
file
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn(`Themes: Loading theme ${name} failed`, error);
|
||||
@@ -64,6 +55,7 @@ export async function readThemeFile(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads and then returns all available themes.
|
||||
*
|
||||
@@ -73,7 +65,7 @@ export async function getAvailableThemes(): Promise<Array<LoadedFiddleTheme>> {
|
||||
const fs = await fancyImport<typeof fsType>('fs-extra');
|
||||
const themes: Array<LoadedFiddleTheme> = [
|
||||
defaultDark as any,
|
||||
defaultLight as any,
|
||||
defaultLight as any
|
||||
];
|
||||
|
||||
if (!fs.existsSync(THEMES_PATH)) {
|
||||
@@ -105,11 +97,9 @@ export async function getAvailableThemes(): Promise<Array<LoadedFiddleTheme>> {
|
||||
* @param {string} [name]
|
||||
* @returns {Promise<FiddleTheme>}
|
||||
*/
|
||||
export async function getTheme(
|
||||
name?: string | null,
|
||||
): Promise<LoadedFiddleTheme> {
|
||||
export async function getTheme(name?: string | null): Promise<LoadedFiddleTheme> {
|
||||
console.log(`Themes: getTheme() loading ${name || 'default'}`);
|
||||
const theme = (await readThemeFile(name || undefined)) || defaultDark;
|
||||
const theme = await readThemeFile(name || undefined) || defaultDark;
|
||||
|
||||
return { ...theme, css: await getCssStringForTheme(theme) };
|
||||
}
|
||||
@@ -120,9 +110,7 @@ export async function getTheme(
|
||||
* @param {FiddleTheme} theme
|
||||
* @returns {string}
|
||||
*/
|
||||
export async function getCssStringForTheme(
|
||||
theme: FiddleTheme,
|
||||
): Promise<string> {
|
||||
export async function getCssStringForTheme(theme: FiddleTheme): Promise<string> {
|
||||
let cssContent = '';
|
||||
|
||||
Object.keys(theme.common).forEach((key) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
remote,
|
||||
TouchBarButtonConstructorOptions,
|
||||
TouchBarScrubberConstructorOptions,
|
||||
TouchBarScrubberConstructorOptions
|
||||
} from 'electron';
|
||||
import { autorun } from 'mobx';
|
||||
|
||||
@@ -15,7 +15,7 @@ const {
|
||||
TouchBarButton,
|
||||
TouchBarScrubber,
|
||||
TouchBarSpacer,
|
||||
TouchBarLabel,
|
||||
TouchBarLabel
|
||||
} = TouchBar;
|
||||
|
||||
/**
|
||||
@@ -26,9 +26,11 @@ const {
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getItemIcon(
|
||||
{ state }: Partial<RunnableVersion> = { state: VersionState.unknown },
|
||||
{ state }: Partial<RunnableVersion> = { state: VersionState.unknown }
|
||||
) {
|
||||
return state === 'ready' ? '💾' : state === 'downloading' ? '⏬' : '☁';
|
||||
return state === 'ready'
|
||||
? '💾'
|
||||
: state === 'downloading' ? '⏬' : '☁';
|
||||
}
|
||||
|
||||
export class TouchBarManager {
|
||||
@@ -38,28 +40,31 @@ export class TouchBarManager {
|
||||
public touchBar: Electron.TouchBar;
|
||||
public versionSelector: Electron.TouchBarScrubber;
|
||||
public versionSelectorBtn: Electron.TouchBarButton;
|
||||
public selectedVersion = '';
|
||||
public selectedVersion: string = '';
|
||||
public versionSelectorSelectBtn: Electron.TouchBarButton;
|
||||
// Lol have mercy, TS
|
||||
public items: Array<Electron.TouchBarButton
|
||||
| Electron.TouchBarColorPicker
|
||||
| Electron.TouchBarGroup
|
||||
| Electron.TouchBarLabel
|
||||
| Electron.TouchBarPopover
|
||||
| Electron.TouchBarScrubber
|
||||
| Electron.TouchBarSegmentedControl
|
||||
| Electron.TouchBarSlider
|
||||
| Electron.TouchBarSpacer>
|
||||
| undefined;
|
||||
|
||||
constructor(public readonly appState: AppState) {
|
||||
this.selectVersion = this.selectVersion.bind(this);
|
||||
this.updateStartStopBtn = this.updateStartStopBtn.bind(this);
|
||||
this.updateVersionSelectorItems = this.updateVersionSelectorItems.bind(
|
||||
this,
|
||||
);
|
||||
this.updateVersionSelectorItems = this.updateVersionSelectorItems.bind(this);
|
||||
this.updateVersionButton = this.updateVersionButton.bind(this);
|
||||
|
||||
this.startStopBtn = new TouchBarButton(this.getStartStopButtonOptions());
|
||||
this.consoleBtn = new TouchBarButton(this.getConsoleButtonOptions());
|
||||
this.versionSelectorBtn = new TouchBarButton(
|
||||
this.getVersionButtonOptions(),
|
||||
);
|
||||
this.versionSelectorSelectBtn = new TouchBarButton(
|
||||
this.getVersionSelectorSelectButtonOptions(),
|
||||
);
|
||||
this.versionSelector = new TouchBarScrubber(
|
||||
this.getVersionSelectorOptions(),
|
||||
);
|
||||
this.versionSelectorBtn = new TouchBarButton(this.getVersionButtonOptions());
|
||||
this.versionSelectorSelectBtn = new TouchBarButton(this.getVersionSelectorSelectButtonOptions());
|
||||
this.versionSelector = new TouchBarScrubber(this.getVersionSelectorOptions());
|
||||
|
||||
this.setupTouchBar();
|
||||
}
|
||||
@@ -120,9 +125,8 @@ export class TouchBarManager {
|
||||
*/
|
||||
public setTouchBar(options: Partial<Electron.TouchBarConstructorOptions>) {
|
||||
try {
|
||||
this.touchBar = new TouchBar(
|
||||
options as Electron.TouchBarConstructorOptions,
|
||||
);
|
||||
this.items = options.items;
|
||||
this.touchBar = new TouchBar(options as Electron.TouchBarConstructorOptions);
|
||||
this.browserWindow = this.browserWindow || remote.getCurrentWindow();
|
||||
this.browserWindow.setTouchBar(this.touchBar);
|
||||
} catch (error) {
|
||||
@@ -138,8 +142,8 @@ export class TouchBarManager {
|
||||
items: [
|
||||
new TouchBarSpacer({ size: 'flexible' }),
|
||||
new TouchBarLabel({ label: getNiceGreeting() }),
|
||||
new TouchBarSpacer({ size: 'flexible' }),
|
||||
],
|
||||
new TouchBarSpacer({ size: 'flexible' })
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,8 +157,8 @@ export class TouchBarManager {
|
||||
this.consoleBtn,
|
||||
new TouchBarSpacer({ size: 'flexible' }),
|
||||
this.versionSelectorBtn,
|
||||
new TouchBarSpacer({ size: 'flexible' }),
|
||||
],
|
||||
new TouchBarSpacer({ size: 'flexible' })
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -163,8 +167,13 @@ export class TouchBarManager {
|
||||
*/
|
||||
public setVersionSelectorItems() {
|
||||
this.setTouchBar({
|
||||
items: [this.versionSelector, this.versionSelectorSelectBtn],
|
||||
escapeItem: new TouchBarButton(this.getVersionSelectorEscButtonOptions()),
|
||||
items: [
|
||||
this.versionSelector,
|
||||
this.versionSelectorSelectBtn,
|
||||
],
|
||||
escapeItem: new TouchBarButton(
|
||||
this.getVersionSelectorEscButtonOptions()
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -177,7 +186,7 @@ export class TouchBarManager {
|
||||
label: `Back`,
|
||||
click: () => {
|
||||
this.setDefaultItems();
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -194,7 +203,7 @@ export class TouchBarManager {
|
||||
}
|
||||
|
||||
this.setDefaultItems();
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -205,12 +214,13 @@ export class TouchBarManager {
|
||||
* @returns {TouchBarButtonConstructorOptions}
|
||||
*/
|
||||
public getStartStopButtonOptions(): TouchBarButtonConstructorOptions {
|
||||
const label = this.appState.isRunning ? '🛑 Stop' : '🚀 Run';
|
||||
const label = this.appState.isRunning
|
||||
? '🛑 Stop'
|
||||
: '🚀 Run';
|
||||
|
||||
const click = () =>
|
||||
this.appState.isRunning
|
||||
? window.ElectronFiddle.app.runner.stop()
|
||||
: window.ElectronFiddle.app.runner.run();
|
||||
const click = () => this.appState.isRunning
|
||||
? window.ElectronFiddle.app.runner.stop()
|
||||
: window.ElectronFiddle.app.runner.run();
|
||||
|
||||
return { label, click };
|
||||
}
|
||||
@@ -245,7 +255,8 @@ export class TouchBarManager {
|
||||
*
|
||||
* @returns {TouchBarScrubberConstructorOptions}
|
||||
*/
|
||||
public getVersionSelectorOptions(): TouchBarScrubberConstructorOptions {
|
||||
public getVersionSelectorOptions(
|
||||
): TouchBarScrubberConstructorOptions {
|
||||
return {
|
||||
select: this.selectVersion,
|
||||
highlight: this.selectVersion,
|
||||
@@ -254,7 +265,7 @@ export class TouchBarManager {
|
||||
overlayStyle: 'outline',
|
||||
mode: 'free',
|
||||
continuous: true,
|
||||
showArrowButtons: false,
|
||||
showArrowButtons: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Files } from '../../interfaces';
|
||||
* @returns {Promise<Files>}
|
||||
*/
|
||||
export async function dotfilesTransform(files: Files): Promise<Files> {
|
||||
files.set('.gitignore', 'node_modules\nout');
|
||||
files.set('.gitignore', 'node_modules\nout');
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
@@ -15,12 +15,11 @@ export async function forgeTransform(files: Files): Promise<Files> {
|
||||
|
||||
// devDependencies
|
||||
parsed.devDependencies = parsed.devDependencies || {};
|
||||
parsed.devDependencies['@electron-forge/cli'] = '6.0.0-beta.52';
|
||||
parsed.devDependencies['@electron-forge/maker-deb'] = '6.0.0-beta.52';
|
||||
parsed.devDependencies['@electron-forge/maker-rpm'] = '6.0.0-beta.52';
|
||||
parsed.devDependencies['@electron-forge/maker-squirrel'] =
|
||||
'6.0.0-beta.52';
|
||||
parsed.devDependencies['@electron-forge/maker-zip'] = '6.0.0-beta.52';
|
||||
parsed.devDependencies['@electron-forge/cli'] = '6.0.0-beta.34';
|
||||
parsed.devDependencies['@electron-forge/maker-deb'] = '6.0.0-beta.34';
|
||||
parsed.devDependencies['@electron-forge/maker-rpm'] = '6.0.0-beta.34';
|
||||
parsed.devDependencies['@electron-forge/maker-squirrel'] = '6.0.0-beta.34';
|
||||
parsed.devDependencies['@electron-forge/maker-zip'] = '6.0.0-beta.34';
|
||||
|
||||
// Scripts
|
||||
parsed.scripts = parsed.scripts || {};
|
||||
@@ -42,16 +41,18 @@ export async function forgeTransform(files: Files): Promise<Files> {
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-zip',
|
||||
platforms: ['darwin'],
|
||||
platforms: [
|
||||
'darwin'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-deb',
|
||||
config: {},
|
||||
config: {}
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-rpm',
|
||||
config: {},
|
||||
},
|
||||
config: {}
|
||||
}
|
||||
];
|
||||
|
||||
files.set(PACKAGE_NAME, JSON.stringify(parsed, undefined, 2));
|
||||
|
||||
+19
-41
@@ -1,17 +1,12 @@
|
||||
import semver from 'semver';
|
||||
import {
|
||||
RunnableVersion,
|
||||
Version,
|
||||
VersionSource,
|
||||
VersionState,
|
||||
} from '../interfaces';
|
||||
import { RunnableVersion, Version, VersionSource, VersionState } from '../interfaces';
|
||||
import { normalizeVersion } from '../utils/normalize-version';
|
||||
|
||||
export const enum ElectronReleaseChannel {
|
||||
stable = 'Stable',
|
||||
beta = 'Beta',
|
||||
nightly = 'Nightly',
|
||||
unsupported = 'Unsupported',
|
||||
unsupported = 'Unsupported'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,25 +16,18 @@ export const enum ElectronReleaseChannel {
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDefaultVersion(
|
||||
knownVersions: Array<RunnableVersion> = [],
|
||||
knownVersions: Array<RunnableVersion> = []
|
||||
): string {
|
||||
const ls = localStorage.getItem('version');
|
||||
|
||||
if (
|
||||
ls &&
|
||||
knownVersions &&
|
||||
knownVersions.find(({ version }) => version === ls)
|
||||
) {
|
||||
if (ls && knownVersions && knownVersions.find(({ version }) => version === ls)) {
|
||||
return ls;
|
||||
}
|
||||
|
||||
// Self-heal: Version not formated correctly
|
||||
const normalized = ls && normalizeVersion(ls);
|
||||
if (normalized) {
|
||||
if (
|
||||
knownVersions &&
|
||||
knownVersions.find(({ version }) => version === normalized)
|
||||
) {
|
||||
if (knownVersions && knownVersions.find(({ version }) => version === normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -62,9 +50,9 @@ export function getDefaultVersion(
|
||||
* @returns {ElectronReleaseChannel}
|
||||
*/
|
||||
export function getReleaseChannel(
|
||||
input: Version | string,
|
||||
input: Version | string
|
||||
): ElectronReleaseChannel {
|
||||
const tag = typeof input === 'string' ? input : input.version || '';
|
||||
const tag = (typeof input === 'string') ? input : (input.version || '');
|
||||
|
||||
if (tag.includes('beta')) {
|
||||
return ElectronReleaseChannel.beta;
|
||||
@@ -84,7 +72,7 @@ export function getReleaseChannel(
|
||||
|
||||
export const enum VersionKeys {
|
||||
local = 'local-electron-versions',
|
||||
known = 'known-electron-versions',
|
||||
known = 'known-electron-versions'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,8 +83,7 @@ export const enum VersionKeys {
|
||||
* @returns {Array<Version>}
|
||||
*/
|
||||
function getVersions(
|
||||
key: VersionKeys,
|
||||
fallbackMethod: () => Array<Version>,
|
||||
key: VersionKeys, fallbackMethod: () => Array<Version>
|
||||
): Array<Version> {
|
||||
const fromLs = window.localStorage.getItem(key);
|
||||
|
||||
@@ -107,9 +94,7 @@ function getVersions(
|
||||
if (!isExpectedFormat(result)) {
|
||||
// Known versions can just be downloaded again.
|
||||
if (key === VersionKeys.known) {
|
||||
throw new Error(
|
||||
`Electron versions in LS does not match expected format`,
|
||||
);
|
||||
throw new Error(`Electron versions in LS does not match expected format`);
|
||||
}
|
||||
|
||||
// Local versions are a bit more tricky and might be in an old format (pre 0.5)
|
||||
@@ -119,9 +104,7 @@ function getVersions(
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Parsing local Electron versions failed, returning fallback method.`,
|
||||
);
|
||||
console.warn(`Parsing local Electron versions failed, returning fallback method.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +132,7 @@ export function getElectronVersions(): Array<RunnableVersion> {
|
||||
return {
|
||||
...version,
|
||||
source: VersionSource.remote,
|
||||
state: VersionState.unknown,
|
||||
state: VersionState.unknown
|
||||
};
|
||||
});
|
||||
|
||||
@@ -157,7 +140,7 @@ export function getElectronVersions(): Array<RunnableVersion> {
|
||||
return {
|
||||
...version,
|
||||
source: VersionSource.local,
|
||||
state: VersionState.ready,
|
||||
state: VersionState.ready
|
||||
};
|
||||
});
|
||||
|
||||
@@ -217,9 +200,7 @@ export function saveLocalVersions(versions: Array<Version | RunnableVersion>) {
|
||||
* @returns {Array<Version>}
|
||||
*/
|
||||
export function getKnownVersions(): Array<Version> {
|
||||
return getVersions(VersionKeys.known, () =>
|
||||
require('../../static/releases.json'),
|
||||
);
|
||||
return getVersions(VersionKeys.known, () => require('../../static/releases.json'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,9 +219,8 @@ export function saveKnownVersions(versions: Array<Version>) {
|
||||
* @export
|
||||
* @returns {Promise<Array<RunnableVersion>>}
|
||||
*/
|
||||
export async function getUpdatedElectronVersions(): Promise<
|
||||
Array<RunnableVersion>
|
||||
> {
|
||||
export async function getUpdatedElectronVersions(
|
||||
): Promise<Array<RunnableVersion>> {
|
||||
try {
|
||||
await fetchVersions();
|
||||
} catch (error) {
|
||||
@@ -256,9 +236,7 @@ export async function getUpdatedElectronVersions(): Promise<
|
||||
* @returns {Promise<Array<Version>>}
|
||||
*/
|
||||
export async function fetchVersions() {
|
||||
const response = await window.fetch(
|
||||
'https://unpkg.com/electron-releases/lite.json',
|
||||
);
|
||||
const response = await window.fetch('https://unpkg.com/electron-releases/lite.json');
|
||||
const data = await response.json();
|
||||
|
||||
// pre-0.24.0 versions were technically 'atom-shell' and cannot
|
||||
@@ -304,14 +282,14 @@ export function migrateVersions(input: Array<any> = []): Array<Version> {
|
||||
return {
|
||||
version: tag_name,
|
||||
name,
|
||||
localPath: url,
|
||||
localPath: url
|
||||
};
|
||||
})
|
||||
.filter((item) => !!item) as Array<Version>;
|
||||
}
|
||||
|
||||
export function isElectronVersion(
|
||||
input: Version | RunnableVersion,
|
||||
input: Version | RunnableVersion
|
||||
): input is RunnableVersion {
|
||||
return (input as RunnableVersion).source !== undefined;
|
||||
}
|
||||
|
||||
+2
-5
@@ -1,10 +1,7 @@
|
||||
import * as Sentry from '@sentry/electron';
|
||||
import { isDevMode } from './utils/devmode';
|
||||
|
||||
export function initSentry() {
|
||||
if (!(global as any).__JEST__ && !isDevMode()) {
|
||||
Sentry.init({
|
||||
dsn: 'https://966a5b01ac8d4941b81e4ebd0ab4c991@sentry.io/1882540',
|
||||
});
|
||||
if (!(global as any).__JEST__) {
|
||||
Sentry.init({dsn: 'https://966a5b01ac8d4941b81e4ebd0ab4c991@sentry.io/1882540'});
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -29,6 +29,6 @@ export const SHOW_ME_TEMPLATES: Templates = {
|
||||
TouchBar: 'TouchBar',
|
||||
Tray: 'Tray',
|
||||
WebContents: 'WebContents',
|
||||
WebFrame: 'WebFrame',
|
||||
},
|
||||
WebFrame: 'WebFrame'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import { normalizeVersion } from './normalize-version';
|
||||
* @returns {Record<string, RunnableVersion>}
|
||||
*/
|
||||
export function arrayToStringMap(
|
||||
input: Array<RunnableVersion>,
|
||||
input: Array<RunnableVersion>
|
||||
): Record<string, RunnableVersion> {
|
||||
const output = {};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
/**
|
||||
* Call this method in a certain number of milliseconds. Returns
|
||||
* a promise that resolves with the passed function.
|
||||
@@ -7,10 +8,7 @@
|
||||
* @param {(...args: Array<any>) => T} fn
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
export function callIn<T>(
|
||||
ms: number,
|
||||
fn: (...args: Array<any>) => T,
|
||||
): Promise<T> {
|
||||
export function callIn<T>(ms: number, fn: (...args: Array<any>) => T): Promise<T> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(fn()), ms);
|
||||
});
|
||||
|
||||
@@ -8,6 +8,6 @@ export function getDocsUrlForModule(moduleName: string) {
|
||||
return {
|
||||
full: `https://electronjs.org/docs/api/${moduleName}`,
|
||||
repo: `https://github.com/electron/electron/blob/master/docs/api/${moduleName}.md`,
|
||||
short: `electronjs.org/docs/api/${moduleName}`,
|
||||
short: `electronjs.org/docs/api/${moduleName}`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ export function getEditorBackup(id: EditorId): EditorBackup {
|
||||
return {
|
||||
value: getEditorValue(id),
|
||||
model: getEditorModel(id),
|
||||
viewState: getEditorViewState(id),
|
||||
viewState: getEditorViewState(id)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { EditorId } from '../interfaces';
|
||||
* @param {EditorId} id
|
||||
* @returns {editor.ITextModel | null}
|
||||
*/
|
||||
export function getEditorModel(id: EditorId): editor.ITextModel | null {
|
||||
export function getEditorModel(id: EditorId): editor.ITextModel | null {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
|
||||
@@ -10,17 +10,13 @@ export function waitForEditorsToMount(editors: Array<MosaicId>) {
|
||||
const interval = 100;
|
||||
return new Promise((resolve, reject) => {
|
||||
(function checkMountedEditors() {
|
||||
const allMounted = editors.every(
|
||||
(id) => !!window.ElectronFiddle.editors[id],
|
||||
);
|
||||
const allMounted = editors.every((id) => !!window.ElectronFiddle.editors[id]);
|
||||
if (allMounted) {
|
||||
return resolve();
|
||||
}
|
||||
time += interval;
|
||||
if (time > maxTime) {
|
||||
return reject(
|
||||
`Timed out after ${maxTime}ms: can't mount editors onto mosaics.`,
|
||||
);
|
||||
return reject(`Timed out after ${maxTime}ms: can't mount editors onto mosaics.`);
|
||||
}
|
||||
setTimeout(checkMountedEditors, 100);
|
||||
})();
|
||||
|
||||
@@ -9,9 +9,7 @@ import { EditorId } from '../interfaces';
|
||||
* @param {EditorId} id
|
||||
* @returns {(editor.ICodeEditorViewState | null)}
|
||||
*/
|
||||
export function getEditorViewState(
|
||||
id: EditorId,
|
||||
): editor.ICodeEditorViewState | null {
|
||||
export function getEditorViewState(id: EditorId): editor.ICodeEditorViewState | null {
|
||||
const { ElectronFiddle: fiddle } = window;
|
||||
|
||||
if (!fiddle) {
|
||||
|
||||
@@ -10,21 +10,20 @@ import { MosaicId } from '../interfaces';
|
||||
* @returns {MosaicNode<MosaicId>}
|
||||
*/
|
||||
export function createMosaicArrangement(
|
||||
input: Array<MosaicId>,
|
||||
direction: MosaicDirection = 'row',
|
||||
input: Array<MosaicId>, direction: MosaicDirection = 'row'
|
||||
): MosaicNode<MosaicId> {
|
||||
if (input.length === 1) {
|
||||
return input[0];
|
||||
}
|
||||
|
||||
// This cuts out the first half of input. Input becomes the second half.
|
||||
const secondHalf = [...input];
|
||||
const secondHalf = [ ...input ];
|
||||
const firstHalf = secondHalf.splice(0, Math.floor(secondHalf.length / 2));
|
||||
|
||||
return {
|
||||
direction,
|
||||
first: createMosaicArrangement(firstHalf, 'column'),
|
||||
second: createMosaicArrangement(secondHalf, 'column'),
|
||||
second: createMosaicArrangement(secondHalf, 'column')
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,20 +34,18 @@ export function createMosaicArrangement(
|
||||
* @param {MosaicNode<MosaicId> | null} input
|
||||
* @returns {Array<MosaicId>}
|
||||
*/
|
||||
export function getVisibleMosaics(
|
||||
input: MosaicNode<MosaicId> | null,
|
||||
): Array<MosaicId> {
|
||||
export function getVisibleMosaics(input: MosaicNode<MosaicId> | null): Array<MosaicId> {
|
||||
// Handle the unlikely null case
|
||||
if (!input) return [];
|
||||
|
||||
// Handle the case where only one editor is visible
|
||||
if (typeof input === 'string') {
|
||||
return [input];
|
||||
return [ input ];
|
||||
}
|
||||
|
||||
// Handle the other cases (2 - 4)
|
||||
const result = [];
|
||||
for (const node of [input.first, input.second]) {
|
||||
for (const node of [ input.first, input.second ]) {
|
||||
if (typeof node === 'string') {
|
||||
result.push(node);
|
||||
} else {
|
||||
|
||||
+9
-13
@@ -14,20 +14,16 @@ export async function exec(dir: string, cliArgs: string): Promise<string> {
|
||||
|
||||
const { exec: cpExec } = await import('child_process');
|
||||
|
||||
cpExec(
|
||||
cliArgs,
|
||||
{
|
||||
cwd: dir,
|
||||
maxBuffer: 200 * 1024 * 100, // 100 times the default
|
||||
},
|
||||
(error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
cpExec(cliArgs, {
|
||||
cwd: dir,
|
||||
maxBuffer: 200 * 1024 * 100 // 100 times the default
|
||||
}, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
|
||||
resolve(typeof result === 'string' ? result : `${result}`);
|
||||
},
|
||||
);
|
||||
resolve(typeof result === 'string' ? result : `${result}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,15 @@ import * as MonacoType from 'monaco-editor';
|
||||
*
|
||||
* @returns {(MonacoType.editor.IStandaloneCodeEditor | null)}
|
||||
*/
|
||||
export function getFocusedEditor(): MonacoType.editor.IStandaloneCodeEditor | null {
|
||||
export function getFocusedEditor(
|
||||
): MonacoType.editor.IStandaloneCodeEditor | null {
|
||||
const focusedKey = Object.keys(window.ElectronFiddle.editors).find((key) => {
|
||||
const editor: MonacoType.editor.IStandaloneCodeEditor =
|
||||
window.ElectronFiddle.editors[key];
|
||||
const editor: MonacoType.editor.IStandaloneCodeEditor
|
||||
= window.ElectronFiddle.editors[key];
|
||||
return editor && editor.hasTextFocus && editor.hasTextFocus();
|
||||
});
|
||||
|
||||
return focusedKey ? window.ElectronFiddle.editors[focusedKey] : null;
|
||||
return focusedKey
|
||||
? window.ElectronFiddle.editors[focusedKey]
|
||||
: null;
|
||||
}
|
||||
|
||||
+15
-21
@@ -11,7 +11,7 @@ export interface PackageJsonOptions {
|
||||
|
||||
export const DEFAULT_OPTIONS = {
|
||||
includeElectron: true,
|
||||
includeDependencies: true,
|
||||
includeDependencies: true
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -23,9 +23,7 @@ export const DEFAULT_OPTIONS = {
|
||||
* @returns {string}
|
||||
*/
|
||||
export async function getPackageJson(
|
||||
appState: AppState,
|
||||
values?: EditorValues,
|
||||
options?: PackageJsonOptions,
|
||||
appState: AppState, values?: EditorValues, options?: PackageJsonOptions
|
||||
): Promise<string> {
|
||||
const { includeElectron, includeDependencies } = options || DEFAULT_OPTIONS;
|
||||
const name = await appState.getName();
|
||||
@@ -44,22 +42,18 @@ export async function getPackageJson(
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify(
|
||||
{
|
||||
name,
|
||||
productName: name,
|
||||
description: 'My Electron application description',
|
||||
keywords: [],
|
||||
main: './main.js',
|
||||
version: '1.0.0',
|
||||
author: getUsername(),
|
||||
scripts: {
|
||||
start: 'electron .',
|
||||
},
|
||||
dependencies,
|
||||
devDependencies,
|
||||
return JSON.stringify({
|
||||
name,
|
||||
productName: name,
|
||||
description: 'My Electron application description',
|
||||
keywords: [],
|
||||
main: './main.js',
|
||||
version: '1.0.0',
|
||||
author: getUsername(),
|
||||
scripts: {
|
||||
start: 'electron .'
|
||||
},
|
||||
undefined,
|
||||
2,
|
||||
);
|
||||
dependencies,
|
||||
devDependencies
|
||||
}, undefined, 2);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as os from 'os';
|
||||
|
||||
let username = '';
|
||||
let username: string = '';
|
||||
|
||||
/**
|
||||
* Returns the curren username
|
||||
@@ -9,5 +9,5 @@ let username = '';
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getUsername(): string {
|
||||
return (username = username || os.userInfo().username);
|
||||
return username = username || os.userInfo().username;
|
||||
}
|
||||
|
||||
+2
-2
@@ -21,6 +21,6 @@ export function idFromUrl(input: string): string | null {
|
||||
* @param {string} input
|
||||
* @returns {string}
|
||||
*/
|
||||
export function urlFromId(input?: string): string {
|
||||
return input ? `https://gist.github.com/${input}` : '';
|
||||
export function urlFromId(input: string): string {
|
||||
return `https://gist.github.com/${input}`;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,7 @@ import * as React from 'react';
|
||||
* @param {string} query
|
||||
* @returns
|
||||
*/
|
||||
export function highlightText(
|
||||
text: string,
|
||||
query: string,
|
||||
): Array<React.ReactNode | string> | null {
|
||||
export function highlightText(text: string, query: string): Array<React.ReactNode | string> | null {
|
||||
let lastIndex = 0;
|
||||
|
||||
const words = query
|
||||
@@ -23,7 +20,7 @@ export function highlightText(
|
||||
.filter((word) => word.length > 0)
|
||||
.map((s) => s.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'));
|
||||
|
||||
if (words.length === 0) return [text];
|
||||
if (words.length === 0) return [ text ];
|
||||
|
||||
const regexp = new RegExp(words.join('|'), 'gi');
|
||||
const tokens: Array<React.ReactNode> = [];
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
* @returns {any}
|
||||
*/
|
||||
export function getAtPath(input: string, obj: any): any {
|
||||
return input.split('.').reduce((o, s) => o[s], obj);
|
||||
return input
|
||||
.split('.')
|
||||
.reduce((o, s) => o[s], obj);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @param {string} version
|
||||
* @returns {string}
|
||||
*/
|
||||
export function normalizeVersion(version = ''): string {
|
||||
export function normalizeVersion(version: string = ''): string {
|
||||
if (version.startsWith('v')) {
|
||||
return version.slice(1);
|
||||
} else {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user